import React from 'react'; import { initializeMockApp } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import MockAdapter from 'axios-mock-adapter'; import { getCommitLibraryChangesUrl, getCreateLibraryBlockUrl, getLibraryCollectionItemsApiUrl, getLibraryCollectionsApiUrl, getLibraryCollectionApiUrl, getBlockTypesMetaDataUrl, getLibraryContainerApiUrl, getLibraryContainerRestoreApiUrl, getLibraryContainerChildrenApiUrl, getLibraryContainerPublishApiUrl, } from './api'; import { useCommitLibraryChanges, useCreateLibraryBlock, useCreateLibraryCollection, useRevertLibraryChanges, useAddItemsToCollection, useCollection, useBlockTypesMetadata, useContainer, useDeleteContainer, useRestoreContainer, useContainerChildren, useAddItemsToContainer, useUpdateContainerChildren, useRemoveContainerChildren, usePublishContainer, } from './apiHooks'; let axiosMock; const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }); const wrapper = ({ children }) => ( {children} ); describe('library api hooks', () => { beforeEach(() => { initializeMockApp({ authenticatedUser: { userId: 3, username: 'abc123', administrator: true, roles: [], }, }); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); }); it('should create library block', async () => { const libraryId = 'lib:org:1'; const url = getCreateLibraryBlockUrl(libraryId); axiosMock.onPost(url).reply(200); const { result } = renderHook(() => useCreateLibraryBlock(), { wrapper }); await result.current.mutateAsync({ libraryId, blockType: 'html', definitionId: '1', }); expect(axiosMock.history.post[0].url).toEqual(url); }); it('should commit library changes', async () => { const libraryId = 'lib:org:1'; const url = getCommitLibraryChangesUrl(libraryId); axiosMock.onPost(url).reply(200); const { result } = renderHook(() => useCommitLibraryChanges(), { wrapper }); await result.current.mutateAsync(libraryId); expect(axiosMock.history.post[0].url).toEqual(url); }); it('should revert library changes', async () => { const libraryId = 'lib:org:1'; const url = getCommitLibraryChangesUrl(libraryId); axiosMock.onDelete(url).reply(200); const { result } = renderHook(() => useRevertLibraryChanges(), { wrapper }); await result.current.mutateAsync(libraryId); expect(axiosMock.history.delete[0].url).toEqual(url); }); it('should create collection', async () => { const libraryId = 'lib:org:1'; const url = getLibraryCollectionsApiUrl(libraryId); axiosMock.onPost(url).reply(200); const { result } = renderHook(() => useCreateLibraryCollection(libraryId), { wrapper }); await result.current.mutateAsync({ title: 'This is a test', description: 'This is only a test', }); expect(axiosMock.history.post[0].url).toEqual(url); }); it('should add components to collection', async () => { const libraryId = 'lib:org:1'; const collectionId = 'my-first-collection'; const url = getLibraryCollectionItemsApiUrl(libraryId, collectionId); axiosMock.onPatch(url).reply(200); const { result } = renderHook(() => useAddItemsToCollection(libraryId, collectionId), { wrapper }); await result.current.mutateAsync(['some-usage-key']); expect(axiosMock.history.patch[0].url).toEqual(url); }); it('should get collection metadata', async () => { const libraryId = 'lib:org:1'; const collectionId = 'my-first-collection'; const url = getLibraryCollectionApiUrl(libraryId, collectionId); axiosMock.onGet(url).reply(200, { 'test-data': 'test-value' }); const { result } = renderHook(() => useCollection(libraryId, collectionId), { wrapper }); await waitFor(() => { expect(result.current.isLoading).toBeFalsy(); }); expect(result.current.data).toEqual({ testData: 'test-value' }); expect(axiosMock.history.get[0].url).toEqual(url); }); it('should get block types metadata', async () => { const libraryId = 'lib:org:1'; const url = getBlockTypesMetaDataUrl(libraryId); axiosMock.onGet(url).reply(200, { 'test-data': 'test-value' }); const { result } = renderHook(() => useBlockTypesMetadata(libraryId), { wrapper }); await waitFor(() => { expect(result.current.isLoading).toBeFalsy(); }); expect(result.current.data).toEqual({ testData: 'test-value' }); expect(axiosMock.history.get[0].url).toEqual(url); }); it('should get container metadata', async () => { const containerId = 'lct:lib:org:unit:unit1'; const url = getLibraryContainerApiUrl(containerId); axiosMock.onGet(url).reply(200, { 'test-data': 'test-value' }); const { result } = renderHook(() => useContainer(containerId), { wrapper }); await waitFor(() => { expect(result.current.isLoading).toBeFalsy(); }); expect(result.current.data).toEqual({ testData: 'test-value' }); expect(axiosMock.history.get[0].url).toEqual(url); }); it('should delete a container', async () => { const containerId = 'lct:org:lib:unit:unit1'; const url = getLibraryContainerApiUrl(containerId); axiosMock.onDelete(url).reply(200); const { result } = renderHook(() => useDeleteContainer(containerId), { wrapper }); await result.current.mutateAsync(); await waitFor(() => { expect(axiosMock.history.delete[0].url).toEqual(url); }); }); it('should restore a container', async () => { const containerId = 'lct:org:lib:unit:unit1'; const url = getLibraryContainerRestoreApiUrl(containerId); axiosMock.onPost(url).reply(200); const { result } = renderHook(() => useRestoreContainer(containerId), { wrapper }); await result.current.mutateAsync(); await waitFor(() => { expect(axiosMock.history.post[0].url).toEqual(url); }); }); it('should get container children', async () => { const containerId = 'lct:lib:org:unit:unit1'; const url = getLibraryContainerChildrenApiUrl(containerId); axiosMock.onGet(url).reply(200, [ { id: 'lb:org1:Demo_course:html:text', block_type: 'html', display_name: 'text block', last_published: null, published_by: null, last_draft_created: null, last_draft_created_by: null, has_unpublished_changes: false, created: null, modified: null, tags_count: 0, collections: ['col1', 'col2'], }, { id: 'lb:org1:Demo_course:video:video1', block_type: 'video', display_name: 'video block', last_published: null, published_by: null, last_draft_created: null, last_draft_created_by: null, has_unpublished_changes: false, created: null, modified: null, tags_count: 0, collections: ['col2'], }, ]); const { result } = renderHook(() => useContainerChildren(containerId), { wrapper }); await waitFor(() => { expect(result.current.isLoading).toBeFalsy(); }); expect(result.current.data).toEqual([ { id: 'lb:org1:Demo_course:html:text', blockType: 'html', displayName: 'text block', lastPublished: null, publishedBy: null, lastDraftCreated: null, lastDraftCreatedBy: null, hasUnpublishedChanges: false, created: null, modified: null, tagsCount: 0, collections: ['col1', 'col2'], }, { id: 'lb:org1:Demo_course:video:video1', blockType: 'video', displayName: 'video block', lastPublished: null, publishedBy: null, lastDraftCreated: null, lastDraftCreatedBy: null, hasUnpublishedChanges: false, created: null, modified: null, tagsCount: 0, collections: ['col2'], }, ]); expect(axiosMock.history.get[0].url).toEqual(url); }); it('should add components to container', async () => { const componentId = 'lb:org:lib:html:1'; const containerId = 'lct:org:lib:unit:1'; const url = getLibraryContainerChildrenApiUrl(containerId); axiosMock.onPost(url).reply(200); const { result } = renderHook(() => useAddItemsToContainer(containerId), { wrapper }); await result.current.mutateAsync([componentId]); expect(axiosMock.history.post[0].url).toEqual(url); }); it('should update container children', async () => { const containerId = 'lct:org:lib:unit:unit-1'; const url = getLibraryContainerChildrenApiUrl(containerId); axiosMock.onPatch(url).reply(200); const { result } = renderHook(() => useUpdateContainerChildren(containerId), { wrapper }); await result.current.mutateAsync([]); await waitFor(() => { expect(axiosMock.history.patch[0].url).toEqual(url); }); }); it('should not attempt request if containerId is not defined', async () => { const { result } = renderHook(() => useUpdateContainerChildren(), { wrapper }); await result.current.mutateAsync([]); await waitFor(() => { expect(axiosMock.history.patch.length).toEqual(0); }); }); it('should remove container children', async () => { const containerId = 'lct:org:lib:unit:unit-1'; const url = getLibraryContainerChildrenApiUrl(containerId); axiosMock.onDelete(url).reply(200); const { result } = renderHook(() => useRemoveContainerChildren(containerId), { wrapper }); await result.current.mutateAsync([]); await waitFor(() => { expect(axiosMock.history.delete[0].url).toEqual(url); }); }); it('should not attempt request if containerId is not defined in remove children from container', async () => { const { result } = renderHook(() => useRemoveContainerChildren(), { wrapper }); await result.current.mutateAsync([]); await waitFor(() => { expect(axiosMock.history.patch.length).toEqual(0); }); }); it('should invalidate subsection when added to section', async () => { const spy = jest.spyOn(queryClient, 'invalidateQueries'); const subsectionId1 = 'lct:org:lib:subsection:1'; const subsectionId2 = 'lct:org:lib:subsection:2'; const sectionId = 'lct:org:lib:section:1'; const url = getLibraryContainerChildrenApiUrl(sectionId); axiosMock.onPost(url).reply(200); const { result } = renderHook(() => useAddItemsToContainer(sectionId), { wrapper }); await result.current.mutateAsync([subsectionId1, subsectionId2]); expect(axiosMock.history.post[0].url).toEqual(url); // Keys should be invalidated: // 1. library // 2. containerChildren // 3. container // 4. containerHierarchy // 5 & 6. subsections // 7 all hierarchies expect(spy).toHaveBeenCalledTimes(7); }); describe('publishContainer', () => { it('should publish a container', async () => { const containerId = 'lct:org:lib:unit:1'; const url = getLibraryContainerPublishApiUrl(containerId); axiosMock.onPost(url).reply(200); const { result } = renderHook(() => usePublishContainer(containerId), { wrapper }); await result.current.mutateAsync(); expect(axiosMock.history.post[0].url).toEqual(url); }); }); });