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');
});
});