diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index d9dbf2f84..ee2047ac6 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -9,17 +9,21 @@ on: jobs: tests: runs-on: ubuntu-latest + strategy: + matrix: + node: [20, 24] + continue-on-error: ${{ matrix.node == 24 }} steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 with: - node-version-file: '.nvmrc' + node-version: ${{ matrix.node }} - run: make validate.ci - name: Archive code coverage results uses: actions/upload-artifact@v4 with: - name: code-coverage-report + name: code-coverage-report-${{ matrix.node }} path: coverage/*.* coverage: runs-on: ubuntu-latest @@ -29,7 +33,9 @@ jobs: - name: Download code coverage results uses: actions/download-artifact@v5 with: - name: code-coverage-report + pattern: code-coverage-report-* + path: coverage + merge-multiple: true - name: Upload coverage uses: codecov/codecov-action@v5 with: diff --git a/src/library-authoring/component-info/ComponentManagement.test.tsx b/src/library-authoring/component-info/ComponentManagement.test.tsx index e8fe425af..3b7e00305 100644 --- a/src/library-authoring/component-info/ComponentManagement.test.tsx +++ b/src/library-authoring/component-info/ComponentManagement.test.tsx @@ -34,18 +34,22 @@ mockLibraryBlockMetadata.applyMock(); mockContentTaxonomyTagsData.applyMock(); /* - * This function is used to get the inner text of an element. - * https://stackoverflow.com/questions/47902335/innertext-is-undefined-in-jest-test + * Utility to get the inner text of an element safely. */ -const getInnerText = (element: Element) => element?.textContent - ?.split('\n') - .filter((text) => text && !text.match(/^\s+$/)) - .map((text) => text.trim()) - .join(' '); +const getInnerText = (element: Element | null): string => { + if (!element) { + return ''; + } + return (element.textContent ?? '') + .split('\n') + .filter((text) => text && !/^\s+$/.test(text)) + .map((text) => text.trim()) + .join(' '); +}; -const matchInnerText = (nodeName: string, textToMatch: string) => (_: string, element: Element) => ( - element.nodeName === nodeName && getInnerText(element) === textToMatch -); +const matchInnerText = (nodeName: string, textToMatch: string) => (_: string, element: Element | null) => !!element + && element.nodeName === nodeName + && getInnerText(element) === textToMatch; const render = (usageKey: string, libraryId?: string) => baseRender(, { extraWrapper: ({ children }) => ( @@ -76,7 +80,11 @@ describe('', () => { render(mockLibraryBlockMetadata.usageKeyNeverPublished); expect(await screen.findByText('Draft')).toBeInTheDocument(); expect(await screen.findByText('(Never Published)')).toBeInTheDocument(); - expect(screen.getByText(matchInnerText('SPAN', 'Draft saved on June 20, 2024 at 13:54.'))).toBeInTheDocument(); + expect( + screen.getByText( + matchInnerText('SPAN', 'Draft saved on June 20, 2024 at 13:54.'), + ), + ).toBeInTheDocument(); }); it('should render published status', async () => { @@ -84,7 +92,13 @@ describe('', () => { expect(await screen.findByText('Published')).toBeInTheDocument(); expect(screen.getByText('Published')).toBeInTheDocument(); expect( - screen.getByText(matchInnerText('SPAN', 'Last published on June 22, 2024 at 24:00 by Luke.')), + screen.getByText( + (_: string, element: Element | null) => !!element + && element.nodeName === 'SPAN' + && /Last published on June 22, 2024 at (00:00|24:00) by Luke\./.test( + getInnerText(element), + ), + ), ).toBeInTheDocument(); }); @@ -106,7 +120,9 @@ describe('', () => { }); render(mockLibraryBlockMetadata.usageKeyForTags, libraryId); await waitFor(() => { - expect(screen.getByText(`Mocked ${expected} ContentTagsDrawer`)).toBeInTheDocument(); + expect( + screen.getByText(`Mocked ${expected} ContentTagsDrawer`), + ).toBeInTheDocument(); }); }, ); @@ -143,10 +159,14 @@ describe('', () => { mockSearchParam.mockReturnValue([SidebarActions.JumpToManageCollections]); render(mockLibraryBlockMetadata.usageKeyWithCollections); expect(await screen.findByText('Collections (1)')).toBeInTheDocument(); - expect(screen.queryByRole('button', { name: 'Manage tags' })).not.toBeInTheDocument(); + expect( + screen.queryByRole('button', { name: 'Manage tags' }), + ).not.toBeInTheDocument(); const tagsSection = await screen.findByRole('button', { name: 'Tags (0)' }); expect(tagsSection).toHaveAttribute('aria-expanded', 'false'); - const collectionsSection = await screen.findByRole('button', { name: 'Collections (1)' }); + const collectionsSection = await screen.findByRole('button', { + name: 'Collections (1)', + }); expect(collectionsSection).toHaveAttribute('aria-expanded', 'true'); }); @@ -158,10 +178,14 @@ describe('', () => { mockSearchParam.mockReturnValue([SidebarActions.JumpToManageTags]); render(mockLibraryBlockMetadata.usageKeyForTags); expect(await screen.findByText('Collections (0)')).toBeInTheDocument(); - expect(screen.queryByRole('button', { name: 'Manage tags' })).not.toBeInTheDocument(); + expect( + screen.queryByRole('button', { name: 'Manage tags' }), + ).not.toBeInTheDocument(); const tagsSection = await screen.findByRole('button', { name: 'Tags (6)' }); expect(tagsSection).toHaveAttribute('aria-expanded', 'true'); - const collectionsSection = await screen.findByRole('button', { name: 'Collections (0)' }); + const collectionsSection = await screen.findByRole('button', { + name: 'Collections (0)', + }); expect(collectionsSection).toHaveAttribute('aria-expanded', 'false'); }); });