Skip to content

Commit 32e6729

Browse files
authored
Optimise core UI test scope (#238)
* Optimise core UI test scope * Harden async UI test settling * Tighten core UI test fakes * Split unit and workspace CI jobs * Harden CI checkout credentials
1 parent 5168a6f commit 32e6729

10 files changed

Lines changed: 1276 additions & 1218 deletions

.github/workflows/node.js.yml

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
steps:
2020
- name: Check out repository
2121
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
22+
with:
23+
persist-credentials: false
2224

2325
- name: Set up pnpm
2426
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
@@ -50,14 +52,16 @@ jobs:
5052
pnpm run lint:docker
5153
5254
unit-tests:
53-
name: Unit and Workspace Tests
55+
name: Unit Tests
5456
runs-on: ubuntu-latest
5557
env:
5658
NODE_ENV: development
5759

5860
steps:
5961
- name: Check out repository
6062
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
63+
with:
64+
persist-credentials: false
6165

6266
- name: Set up pnpm
6367
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
@@ -80,10 +84,44 @@ jobs:
8084
node -e "require.resolve('jest')"
8185
node -e "require.resolve('jest-environment-jsdom')"
8286
83-
- name: Run root and workspace tests
87+
- name: Run unit tests
88+
run: pnpm test
89+
90+
workspace-tests:
91+
name: Workspace Tests
92+
runs-on: ubuntu-latest
93+
env:
94+
NODE_ENV: development
95+
96+
steps:
97+
- name: Check out repository
98+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
99+
with:
100+
persist-credentials: false
101+
102+
- name: Set up pnpm
103+
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
104+
with:
105+
version: 11.1.1
106+
107+
- name: Use Node.js 24.16.0
108+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
109+
with:
110+
node-version: 24.16.0
111+
cache: pnpm
112+
113+
- name: Install dependencies
114+
run: pnpm install --frozen-lockfile
115+
116+
- name: Verify test toolchain dependencies
117+
shell: bash
84118
run: |
85-
pnpm test
86-
pnpm run test:workspaces
119+
set -euo pipefail
120+
node -e "require.resolve('jest')"
121+
node -e "require.resolve('jest-environment-jsdom')"
122+
123+
- name: Run workspace tests
124+
run: pnpm run test:workspaces
87125

88126
coverage:
89127
name: Coverage
@@ -95,6 +133,8 @@ jobs:
95133
steps:
96134
- name: Check out repository
97135
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
136+
with:
137+
persist-credentials: false
98138

99139
- name: Set up pnpm
100140
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
@@ -141,6 +181,8 @@ jobs:
141181
steps:
142182
- name: Check out repository
143183
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
184+
with:
185+
persist-credentials: false
144186

145187
- name: Set up pnpm
146188
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
@@ -186,6 +228,8 @@ jobs:
186228
steps:
187229
- name: Check out repository
188230
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
231+
with:
232+
persist-credentials: false
189233

190234
- name: Set up pnpm
191235
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
@@ -216,6 +260,8 @@ jobs:
216260
steps:
217261
- name: Check out repository
218262
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
263+
with:
264+
persist-credentials: false
219265

220266
- name: Set up pnpm
221267
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
@@ -241,6 +287,7 @@ jobs:
241287
needs:
242288
- checks
243289
- unit-tests
290+
- workspace-tests
244291
- storybook
245292
- browser
246293
- api-tests
@@ -255,6 +302,7 @@ jobs:
255302
needs:
256303
- checks
257304
- unit-tests
305+
- workspace-tests
258306
- coverage
259307
- storybook
260308
- browser
@@ -276,6 +324,8 @@ jobs:
276324
steps:
277325
- name: Check out repository
278326
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
327+
with:
328+
persist-credentials: false
279329

280330
- name: Set up pnpm
281331
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
@@ -389,6 +439,8 @@ jobs:
389439
steps:
390440
- name: Check out repository
391441
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
442+
with:
443+
persist-credentials: false
392444

393445
- name: Docker smoke test (API)
394446
run: |

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313
"apps/*"
1414
],
1515
"scripts": {
16-
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./jest.config.cjs",
17-
"testverbose": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./jest.config.cjs --verbose",
18-
"testcoverage": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./jest.config.cjs --coverage",
16+
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./jest.config.cjs --maxWorkers=75%",
17+
"testverbose": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./jest.config.cjs --maxWorkers=75% --verbose",
18+
"testcoverage": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./jest.config.cjs --maxWorkers=75% --coverage",
1919
"storybook": "storybook dev -p 6006",
2020
"build-storybook": "node ./scripts/run-build-storybook.mjs",
2121
"test:storybook": "node ./scripts/run-storybook-tests.mjs",
22-
"test:ui:jest": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./jest.config.cjs --testPathPatterns packages/core-ui/src/tests apps/web/src/tests/jest",
22+
"test:ui:jest": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./jest.config.cjs --maxWorkers=75% --testPathPatterns packages/core-ui/src/tests apps/web/src/tests/jest",
2323
"testenv:create": "node ./scripts/create-testenv.mjs",
2424
"test:browser": "node ./node_modules/@playwright/test/cli.js test",
2525
"test:browser:headed": "node ./node_modules/@playwright/test/cli.js test --headed",
@@ -40,7 +40,6 @@
4040
"build:web": "vite build --config ./apps/web/vite.config.mjs",
4141
"preview:web": "vite preview --config ./apps/web/vite.config.mjs",
4242
"serve:web": "pnpm run dev:web",
43-
"benchmark:tabulator": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config ./jest.config.cjs --runTestsByPath packages/core-ui/src/tests/grid/tabulator-performance.benchmark.test.js --runInBand",
4443
"anywaydata:mac": "pnpm --dir docs-src run anywaydata && pnpm run build:web && node ./scripts/merge-web-build-into-docs-build.js && cp -R ./build/ ../../anywaydata-com",
4544
"anywaydata:win": "pnpm --dir docs-src run anywaydata && pnpm run build:web && node .\\scripts\\merge-web-build-into-docs-build.js && echo cleaning anywaydata for deployment && ..\\anywaydata-com\\make-ready-for-prod.bat",
4645
"previewdocs": "pnpm --dir docs-src run start",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as importExportWorkspaceExports from '../../../js/gui_components/app/import-export-workspace/index.js';
2+
import { createImportExportWorkspaceComponent } from '../../../js/gui_components/app/import-export-workspace/index.js';
3+
4+
describe('ImportExportWorkspace contract', () => {
5+
test('public barrel is component-factory-only', () => {
6+
expect(importExportWorkspaceExports.createImportExportWorkspaceComponent).toBe(
7+
createImportExportWorkspaceComponent
8+
);
9+
expect(importExportWorkspaceExports.ImportExportWorkspaceController).toBeUndefined();
10+
expect(importExportWorkspaceExports.ImportExportWorkspaceView).toBeUndefined();
11+
});
12+
});

packages/core-ui/src/tests/app/import-export-workspace.test.js

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { GenericDataTable } from '@anywaydata/core/data_formats/generic-data-table.js';
22
import { jest } from '@jest/globals';
3-
import { fireEvent, waitFor } from '@testing-library/dom';
3+
import { fireEvent } from '@testing-library/dom';
44
import { JSDOM } from 'jsdom';
5-
import * as importExportWorkspaceExports from '../../../js/gui_components/app/import-export-workspace/index.js';
65
import { createImportExportWorkspaceComponent } from '../../../js/gui_components/app/import-export-workspace/index.js';
6+
import { waitForMicrotaskAssertions } from '../helpers/wait-for-microtasks.js';
77

88
function createTable(rowCount = 3) {
99
const dataTable = new GenericDataTable();
@@ -103,14 +103,6 @@ describe('ImportExportWorkspace', () => {
103103
delete global.window;
104104
});
105105

106-
test('public barrel is component-factory-only', () => {
107-
expect(importExportWorkspaceExports.createImportExportWorkspaceComponent).toBe(
108-
createImportExportWorkspaceComponent
109-
);
110-
expect(importExportWorkspaceExports.ImportExportWorkspaceController).toBeUndefined();
111-
expect(importExportWorkspaceExports.ImportExportWorkspaceView).toBeUndefined();
112-
});
113-
114106
test('mounts real controls, normalizes preview row count, and renders format previews without legacy controls', () => {
115107
const { component, documentObj, dom, exporter } = createHarness({
116108
props: { previewRowLimit: 0 },
@@ -216,11 +208,13 @@ describe('ImportExportWorkspace', () => {
216208

217209
fireEvent.click(importButton);
218210

219-
await waitFor(() => expect(importer.setGridFromGenericDataTable).toHaveBeenCalledTimes(1));
220-
expect(getPreviewTextArea(documentObj).value).toBe('csv:rows:2');
221-
expect(documentObj.querySelector('#import-progress-status').textContent).toBe('Import complete.');
222-
expect(component.getState().previewTextDirty).toBe(false);
223-
expect(importButton.disabled).toBe(true);
211+
await waitForMicrotaskAssertions(() => {
212+
expect(importer.setGridFromGenericDataTable).toHaveBeenCalledTimes(1);
213+
expect(getPreviewTextArea(documentObj).value).toBe('csv:rows:2');
214+
expect(documentObj.querySelector('#import-progress-status').textContent).toBe('Import complete.');
215+
expect(component.getState().previewTextDirty).toBe(false);
216+
expect(importButton.disabled).toBe(true);
217+
});
224218

225219
component.destroy();
226220
dom.window.close();
@@ -258,15 +252,15 @@ describe('ImportExportWorkspace', () => {
258252

259253
fireEvent.click(documentObj.querySelector('#filedownload'));
260254

261-
await waitFor(() =>
255+
await waitForMicrotaskAssertions(() => {
262256
expect(downloadService.downloadText).toHaveBeenCalledWith('export.csv', 'full:csv', {
263257
exportEncodingSettings: {
264258
lineEnding: 'lf',
265259
includeBom: true,
266260
},
267-
})
268-
);
269-
expect(documentObj.querySelector('#export-progress-status').textContent).toBe('Download started.');
261+
});
262+
expect(documentObj.querySelector('#export-progress-status').textContent).toBe('Download started.');
263+
});
270264

271265
component.destroy();
272266
dom.window.close();
@@ -291,14 +285,16 @@ describe('ImportExportWorkspace', () => {
291285
});
292286
fireEvent.change(fileInput);
293287

294-
await waitFor(() => expect(fileReadService.readText).toHaveBeenCalledTimes(1));
295-
expect(importer.setImportSettings).toHaveBeenCalledWith({
296-
trimInput: false,
297-
trimInputFieldsCsv: '',
288+
await waitForMicrotaskAssertions(() => {
289+
expect(fileReadService.readText).toHaveBeenCalledTimes(1);
290+
expect(importer.setImportSettings).toHaveBeenCalledWith({
291+
trimInput: false,
292+
trimInputFieldsCsv: '',
293+
});
294+
expect(importer.setGridFromGenericDataTable).toHaveBeenCalledTimes(1);
295+
expect(getPreviewTextArea(documentObj).value).toBe('csv:rows:1');
296+
expect(documentObj.querySelector('#import-progress-status').textContent).toBe('Import complete.');
298297
});
299-
await waitFor(() => expect(importer.setGridFromGenericDataTable).toHaveBeenCalledTimes(1));
300-
expect(getPreviewTextArea(documentObj).value).toBe('csv:rows:1');
301-
expect(documentObj.querySelector('#import-progress-status').textContent).toBe('Import complete.');
302298

303299
component.destroy();
304300
dom.window.close();
@@ -317,16 +313,16 @@ describe('ImportExportWorkspace', () => {
317313

318314
fireEvent.click(documentObj.querySelector('[data-role="clipboard-import-button"]'));
319315

320-
await waitFor(() => expect(clipboardService.readText).toHaveBeenCalledTimes(1));
321-
expect(importer.setImportSettings).toHaveBeenCalledWith({
322-
trimInput: false,
323-
trimInputFieldsCsv: '',
316+
await waitForMicrotaskAssertions(() => {
317+
expect(clipboardService.readText).toHaveBeenCalledTimes(1);
318+
expect(importer.setImportSettings).toHaveBeenCalledWith({
319+
trimInput: false,
320+
trimInputFieldsCsv: '',
321+
});
322+
expect(importer.importText).toHaveBeenCalledWith('csv', 'Name,Role\nAda,Engineer');
323+
expect(getPreviewTextArea(documentObj).value).toBe('csv:rows:1');
324+
expect(documentObj.querySelector('#import-progress-status').textContent).toBe('Import complete.');
324325
});
325-
await waitFor(() => expect(importer.importText).toHaveBeenCalledWith('csv', 'Name,Role\nAda,Engineer'));
326-
await waitFor(() => expect(getPreviewTextArea(documentObj).value).toBe('csv:rows:1'));
327-
await waitFor(() =>
328-
expect(documentObj.querySelector('#import-progress-status').textContent).toBe('Import complete.')
329-
);
330326

331327
component.destroy();
332328
dom.window.close();
@@ -403,9 +399,11 @@ describe('ImportExportWorkspace', () => {
403399
readerInstance.emit('progress', { loaded: 5, total: 10, lengthComputable: true });
404400
readerInstance.emit('load', { target: { result: 'Name,Role\nAda,Engineer' } });
405401

406-
await waitFor(() => expect(importer.setGridFromGenericDataTable).toHaveBeenCalledTimes(1));
407-
expect(getPreviewTextArea(documentObj).value).toBe('csv:rows:1');
408-
expect(documentObj.querySelector('#import-progress-status').textContent).toBe('Import complete.');
402+
await waitForMicrotaskAssertions(() => {
403+
expect(importer.setGridFromGenericDataTable).toHaveBeenCalledTimes(1);
404+
expect(getPreviewTextArea(documentObj).value).toBe('csv:rows:1');
405+
expect(documentObj.querySelector('#import-progress-status').textContent).toBe('Import complete.');
406+
});
409407
} finally {
410408
if (typeof originalFileReader === 'undefined') {
411409
delete global.FileReader;
@@ -442,19 +440,21 @@ describe('ImportExportWorkspace', () => {
442440
trimInput: false,
443441
trimInputFieldsCsv: '',
444442
});
445-
await waitFor(() => expect(importer.importText).toHaveBeenCalledWith('csv', textArea.value));
446-
expect(component.getState().importBusy).toBe(false);
447-
expect(documentObj.querySelector('#import-progress-status').textContent).toBe('Import complete.');
443+
await waitForMicrotaskAssertions(() => {
444+
expect(importer.importText).toHaveBeenCalledWith('csv', textArea.value);
445+
expect(component.getState().importBusy).toBe(false);
446+
expect(documentObj.querySelector('#import-progress-status').textContent).toBe('Import complete.');
447+
});
448448

449449
importer.importText.mockRejectedValueOnce(new Error('boom'));
450450
fireEvent.click(importButton);
451451

452-
await waitFor(() =>
452+
await waitForMicrotaskAssertions(() => {
453453
expect(documentObj.querySelector('#import-progress-status').textContent).toBe(
454454
'Import failed. Check file format/options.'
455-
)
456-
);
457-
expect(component.getState().importBusy).toBe(false);
455+
);
456+
expect(component.getState().importBusy).toBe(false);
457+
});
458458

459459
component.destroy();
460460
dom.window.close();
@@ -534,12 +534,12 @@ describe('ImportExportWorkspace', () => {
534534
});
535535

536536
fireEvent.click(documentObj.querySelector('[data-role="clipboard-import-button"]'));
537-
await waitFor(() =>
537+
await waitForMicrotaskAssertions(() => {
538538
expect(importer.setImportSettings).toHaveBeenCalledWith({
539539
trimInput: true,
540540
trimInputFieldsCsv: 'Role',
541-
})
542-
);
541+
});
542+
});
543543

544544
const fileInput = documentObj.querySelector('#csvinput');
545545
Object.defineProperty(fileInput, 'files', {
@@ -548,12 +548,12 @@ describe('ImportExportWorkspace', () => {
548548
});
549549
fireEvent.change(fileInput);
550550

551-
await waitFor(() =>
551+
await waitForMicrotaskAssertions(() => {
552552
expect(importer.setImportSettings).toHaveBeenLastCalledWith({
553553
trimInput: true,
554554
trimInputFieldsCsv: 'Role',
555-
})
556-
);
555+
});
556+
});
557557

558558
component.destroy();
559559
dom.window.close();

0 commit comments

Comments
 (0)