From 81798b4c3b6e7ddc5ded2a6ebead359d6251f690 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Wed, 11 Jun 2025 21:10:33 -0500 Subject: [PATCH 1/5] feat: Open Manage tags on click tag count in section/subsection page --- src/generic/tag-count/index.tsx | 10 ++++++-- src/library-authoring/data/apiHooks.ts | 5 +++- .../LibraryContainerChildren.tsx | 24 +++++++++++++++++-- .../LibrarySectionSubsectionPage.test.tsx | 16 +++++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/generic/tag-count/index.tsx b/src/generic/tag-count/index.tsx index d8787bb2bf..34ba6c526e 100644 --- a/src/generic/tag-count/index.tsx +++ b/src/generic/tag-count/index.tsx @@ -6,10 +6,16 @@ type TagCountProps = { count: number; onClick?: () => void; size?: Parameters[0]['size']; + dataTestId?: string; }; // eslint-disable-next-line react/prop-types -const TagCount: React.FC = ({ count, onClick, size }) => { +const TagCount: React.FC = ({ + count, + onClick, + size, + dataTestId, +}: TagCountProps) => { const renderContent = () => ( @@ -23,7 +29,7 @@ const TagCount: React.FC = ({ count, onClick, size }) => { } > { onClick ? ( - ) diff --git a/src/library-authoring/data/apiHooks.ts b/src/library-authoring/data/apiHooks.ts index 3098e37560..f8a9974400 100644 --- a/src/library-authoring/data/apiHooks.ts +++ b/src/library-authoring/data/apiHooks.ts @@ -678,7 +678,10 @@ export const useContainerChildren = (containerId?: string, published: boolean = enabled: !!containerId, queryKey: libraryAuthoringQueryKeys.containerChildren(containerId!), queryFn: () => api.getLibraryContainerChildren(containerId!, published), - structuralSharing: (oldData: api.LibraryBlockMetadata[], newData: api.LibraryBlockMetadata[]) => { + structuralSharing: ( + oldData: api.LibraryBlockMetadata[] | api.Container[], + newData: api.LibraryBlockMetadata[] | api.Container[], + ) => { // This just sets `isNew` flag to new children components if (oldData) { const oldDataIds = oldData.map((obj) => obj.id); diff --git a/src/library-authoring/section-subsections/LibraryContainerChildren.tsx b/src/library-authoring/section-subsections/LibraryContainerChildren.tsx index 70913afd68..e0041caf45 100644 --- a/src/library-authoring/section-subsections/LibraryContainerChildren.tsx +++ b/src/library-authoring/section-subsections/LibraryContainerChildren.tsx @@ -23,7 +23,8 @@ import { ToastContext } from '../../generic/toast-context'; import TagCount from '../../generic/tag-count'; import { ContainerMenu } from '../components/ContainerCard'; import { useLibraryRoutes } from '../routes'; -import { useSidebarContext } from '../common/context/SidebarContext'; +import { SidebarActions, useSidebarContext } from '../common/context/SidebarContext'; +import { useRunOnNextRender } from '../../utils'; interface LibraryContainerChildrenProps { containerKey: string; @@ -44,6 +45,8 @@ const ContainerRow = ({ container, readOnly }: ContainerRowProps) => { const { showToast } = useContext(ToastContext); const updateMutation = useUpdateContainer(container.originalId); const { showOnlyPublished } = useLibraryContext(); + const { navigateTo } = useLibraryRoutes(); + const { setSidebarAction } = useSidebarContext(); const handleSaveDisplayName = async (newDisplayName: string) => { try { @@ -56,6 +59,18 @@ const ContainerRow = ({ container, readOnly }: ContainerRowProps) => { } }; + /* istanbul ignore next */ + const scheduleJumpToTags = useRunOnNextRender(() => { + // TODO: Ugly hack to make sure sidebar shows manage tags section + // This needs to run after all changes to url takes place to avoid conflicts. + setTimeout(() => setSidebarAction(SidebarActions.JumpToManageTags), 250); + }); + + const jumpToManageTags = () => { + navigateTo({ selectedItemId: container.originalId }); + scheduleJumpToTags(); + }; + return ( <> { )} - + {!readOnly && ( ', () => { expect((await screen.findAllByText(new RegExp(`Test ${childType}`, 'i')))[0]).toBeInTheDocument(); expect(await screen.findByRole('button', { name: new RegExp(`${childType} Info`, 'i') })).toBeInTheDocument(); }); + + it(`should open manage tags on click tag count in ${cType} page`, async () => { + const cId = cType === ContainerType.Section + ? mockGetContainerMetadata.sectionId + : mockGetContainerMetadata.subsectionId; + renderLibrarySectionPage(cId, undefined, cType); + // check all children components are rendered. + expect((await screen.findAllByText(`${childType} block 0`))[0]).toBeInTheDocument(); + expect((await screen.findAllByText(`${childType} block 1`))[0]).toBeInTheDocument(); + expect((await screen.findAllByText(`${childType} block 2`))[0]).toBeInTheDocument(); + + const tagCountButton = screen.getByTestId(`tag-count-lb:org1:Demo_course:${childType}:${childType}-0----0`); + fireEvent.click(tagCountButton); + + expect(await screen.findByTestId('library-sidebar')).toBeInTheDocument(); + }); }); }); From 4070ab18ab57fd59a32d9e2603969019dc14a565 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 12 Jun 2025 17:06:54 -0500 Subject: [PATCH 2/5] fix: Invalidate children query when update child tags --- .../ContentTagsDrawer.test.jsx | 48 +++++++++++++++++-- src/content-tags-drawer/data/api.mocks.ts | 2 +- src/content-tags-drawer/data/apiHooks.jsx | 13 ++--- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/content-tags-drawer/ContentTagsDrawer.test.jsx b/src/content-tags-drawer/ContentTagsDrawer.test.jsx index 5035b2fe43..90fb145683 100644 --- a/src/content-tags-drawer/ContentTagsDrawer.test.jsx +++ b/src/content-tags-drawer/ContentTagsDrawer.test.jsx @@ -34,6 +34,7 @@ const { languageWithoutTagsId, largeTagsId, emptyTagsId, + containerTagsId, } = mockContentTaxonomyTagsData; jest.mock('react-router-dom', () => ({ @@ -46,14 +47,15 @@ jest.mock('../library-authoring/common/context/SidebarContext', () => ({ useSidebarContext: () => ({ sidebarAction: mockSidebarAction() }), })); -const renderDrawer = (contentId, drawerParams = {}) => ( - render( +const renderDrawer = (contentId, drawerParams = {}, renderPath = path, containerId = '') => { + const params = { contentId, containerId }; + return render( , - { path, params: { contentId } }, - ) -); + { path: renderPath, params }, + ); +}; describe('', () => { beforeEach(async () => { @@ -692,6 +694,42 @@ describe('', () => { await waitFor(() => expect(axiosMock.history.put[0].url).toEqual(url)); }); + [ + 'lct:org:lib:unit:1', + 'lib-collection:org:lib:1', + 'lb:org:lib:html:1', + ].forEach((containerId) => { + it(`should invalidate children query when update child tag when containerId is ${containerId}`, async () => { + const newPath = '/container/:containerId/'; + const { axiosMock, queryClient } = initializeMocks(); + const mockInvalidateQueries = jest.spyOn(queryClient, 'invalidateQueries'); + const url = getContentTaxonomyTagsApiUrl(containerTagsId); + axiosMock.onPut(url).reply(200); + renderDrawer(containerTagsId, { id: containerTagsId }, newPath, containerId); + expect(await screen.findByText('Taxonomy 1')).toBeInTheDocument(); + const editTagsButton = screen.getByRole('button', { + name: /edit tags/i, + }); + fireEvent.click(editTagsButton); + + const saveButton = screen.getByRole('button', { + name: /save/i, + }); + fireEvent.click(saveButton); + + await waitFor(() => expect(axiosMock.history.put[0].url).toEqual(url)); + expect(mockInvalidateQueries).toHaveBeenCalledTimes(5); + expect(mockInvalidateQueries).toHaveBeenNthCalledWith(5, [ + 'contentLibrary', + 'lib:org:lib', + 'content', + 'container', + containerId, + 'children', + ]); + }); + }); + it('should taxonomies must be ordered', async () => { renderDrawer(largeTagsId); expect(await screen.findByText('Taxonomy 1')).toBeInTheDocument(); diff --git a/src/content-tags-drawer/data/api.mocks.ts b/src/content-tags-drawer/data/api.mocks.ts index 2f7360be8f..932ae7311e 100644 --- a/src/content-tags-drawer/data/api.mocks.ts +++ b/src/content-tags-drawer/data/api.mocks.ts @@ -205,7 +205,7 @@ mockContentTaxonomyTagsData.emptyTagsId = 'block-v1:EmptyTagsOrg+STC1+2023_1+typ mockContentTaxonomyTagsData.emptyTags = { taxonomies: [], }; -mockContentTaxonomyTagsData.containerTagsId = 'lct:org:lib:unit:container_tags'; +mockContentTaxonomyTagsData.containerTagsId = 'lct:StagedTagsOrg:lib:unit:container_tags'; mockContentTaxonomyTagsData.applyMock = () => jest.spyOn(api, 'getContentTaxonomyTagsData').mockImplementation(mockContentTaxonomyTagsData); /** diff --git a/src/content-tags-drawer/data/apiHooks.jsx b/src/content-tags-drawer/data/apiHooks.jsx index 01ef3a3a7b..fcd8da92b2 100644 --- a/src/content-tags-drawer/data/apiHooks.jsx +++ b/src/content-tags-drawer/data/apiHooks.jsx @@ -112,7 +112,7 @@ export const useContentTaxonomyTagsData = (contentId) => ( /** * Builds the query to get meta data about the content object - * @param {string} contentId The id of the content object (unit/component) + * @param {string} contentId The id of the content object * @param {boolean} enabled Flag to enable/disable the query */ export const useContentData = (contentId, enabled) => ( @@ -130,7 +130,7 @@ export const useContentData = (contentId, enabled) => ( export const useContentTaxonomyTagsUpdater = (contentId) => { const queryClient = useQueryClient(); const unitIframe = window.frames['xblock-iframe']; - const { unitId } = useParams(); + const { containerId } = useParams(); return useMutation({ /** @@ -143,7 +143,7 @@ export const useContentTaxonomyTagsUpdater = (contentId) => { * >} */ mutationFn: ({ tagsData }) => updateContentTaxonomyTags(contentId, tagsData), - onSettled: /* istanbul ignore next */ () => { + onSettled: () => { queryClient.invalidateQueries({ queryKey: ['contentTaxonomyTags', contentId] }); /// Invalidate query with pattern on course outline let contentPattern; @@ -160,9 +160,10 @@ export const useContentTaxonomyTagsUpdater = (contentId) => { queryClient.invalidateQueries(xblockQueryKeys.componentMetadata(contentId)); // Invalidate content search to update tags count queryClient.invalidateQueries(['content_search'], { predicate: (query) => libraryQueryPredicate(query, libraryId) }); - // If the tags for a compoent were edited from Unit page, invalidate children query to fetch count again. - if (unitId) { - queryClient.invalidateQueries(libraryAuthoringQueryKeys.containerChildren(unitId)); + // If the tags for an item were edited from a container page (Unit, Subsection, Section), + // invalidate children query to fetch count again. + if (containerId) { + queryClient.invalidateQueries(libraryAuthoringQueryKeys.containerChildren(containerId)); } } }, From b94f2fa3b4689f16bda13af49f5237d2a8f0f79b Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 19 Jun 2025 17:16:04 -0500 Subject: [PATCH 3/5] test: Adding some code on tests --- src/generic/tag-count/index.tsx | 10 ++-------- .../section-subsections/LibraryContainerChildren.tsx | 1 - .../LibrarySectionSubsectionPage.test.tsx | 3 ++- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/generic/tag-count/index.tsx b/src/generic/tag-count/index.tsx index 34ba6c526e..9d31639186 100644 --- a/src/generic/tag-count/index.tsx +++ b/src/generic/tag-count/index.tsx @@ -6,16 +6,10 @@ type TagCountProps = { count: number; onClick?: () => void; size?: Parameters[0]['size']; - dataTestId?: string; }; // eslint-disable-next-line react/prop-types -const TagCount: React.FC = ({ - count, - onClick, - size, - dataTestId, -}: TagCountProps) => { +const TagCount: React.FC = ({ count, onClick, size }: TagCountProps) => { const renderContent = () => ( @@ -29,7 +23,7 @@ const TagCount: React.FC = ({ } > { onClick ? ( - ) diff --git a/src/library-authoring/section-subsections/LibraryContainerChildren.tsx b/src/library-authoring/section-subsections/LibraryContainerChildren.tsx index 09e887f3bb..81f9e78c1d 100644 --- a/src/library-authoring/section-subsections/LibraryContainerChildren.tsx +++ b/src/library-authoring/section-subsections/LibraryContainerChildren.tsx @@ -103,7 +103,6 @@ const ContainerRow = ({ container, readOnly }: ContainerRowProps) => { size="sm" count={container.tagsCount} onClick={readOnly ? undefined : jumpToManageTags} - dataTestId={`tag-count-${container.id}`} /> {!readOnly && ( ', () => { expect((await screen.findAllByText(`${childType} block 1`))[0]).toBeInTheDocument(); expect((await screen.findAllByText(`${childType} block 2`))[0]).toBeInTheDocument(); - const tagCountButton = screen.getByTestId(`tag-count-lb:org1:Demo_course:${childType}:${childType}-0----0`); + const tagCountButton = screen.getAllByRole('button', { name: '0' })[0]; fireEvent.click(tagCountButton); expect(await screen.findByTestId('library-sidebar')).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: /manage/i })).toHaveClass('active'); }); }); }); From 54e02bbddba5dc8f2ed1719e53e541304d442374 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 19 Jun 2025 17:51:57 -0500 Subject: [PATCH 4/5] fix: Broken tests --- .../section-subsections/LibrarySectionSubsectionPage.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx b/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx index 8415b5d6e2..fc97a07f1f 100644 --- a/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx +++ b/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx @@ -392,7 +392,7 @@ describe('', () => { fireEvent.click(tagCountButton); expect(await screen.findByTestId('library-sidebar')).toBeInTheDocument(); - expect(screen.getByRole('tab', { name: /manage/i })).toHaveClass('active'); + expect(await screen.findByRole('tab', { name: /manage/i })).toHaveClass('active'); }); }); }); From bfa8b12363bf071bcc9ced222349ffdbc6635aad Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 19 Jun 2025 19:25:21 -0500 Subject: [PATCH 5/5] fix: Broken tests --- .../LibrarySectionSubsectionPage.test.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx b/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx index 76af75781d..a24ba92768 100644 --- a/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx +++ b/src/library-authoring/section-subsections/LibrarySectionSubsectionPage.test.tsx @@ -404,7 +404,10 @@ describe('', () => { fireEvent.click(tagCountButton); expect(await screen.findByTestId('library-sidebar')).toBeInTheDocument(); - expect(await screen.findByRole('tab', { name: /manage/i })).toHaveClass('active'); + await waitFor( + () => expect(screen.getByRole('tab', { name: /manage/i })).toHaveClass('active'), + { timeout: 300 }, + ); }); }); });