test(ui): qualify career evidence read and recovery states

This commit is contained in:
cesnimda
2026-11-04 09:06:47 +01:00
parent 505b0c01b9
commit b5b9069d1e
228 changed files with 3883 additions and 7 deletions
+26 -4
View File
@@ -8,12 +8,14 @@ import path from 'node:path';
const compiled = ts.transpileModule(fs.readFileSync(new URL('../src/i18n/translations.ts', import.meta.url), 'utf8'), { compilerOptions: { module: ts.ModuleKind.ESNext } }).outputText;
const { translations } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`);
const visualMatrix = process.env.A3_VISUAL_MATRIX === '1';
const out = path.resolve(import.meta.dirname, visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/career' : '../../docs/ui-modernisation/evidence/career/browser');
const evidenceOnly = process.env.A3_EVIDENCE_REVIEW === '1';
assert.ok(!evidenceOnly || !process.env.A3_BOUNDARY, 'Evidence review does not repeat editor boundary qualification.');
const out = path.resolve(import.meta.dirname, evidenceOnly ? '../../docs/ui-modernisation/visual-correction/evidence/career-evidence' : visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/career' : '../../docs/ui-modernisation/evidence/career/browser');
fs.mkdirSync(out, { recursive: true });
const report = { screens: [], journeys: [], errors: [], unexpectedWrites: [], mockedWrites: [], renderReads: 0, unavailableReads: [], blockedExternal: [] };
const browser = await chromium.launch();
try {
const configurations = process.env.A3_BOUNDARY ? [320, 390, 768, 1000, 1001, 1440].map(width => [width, 'en', 'light']) : process.env.A3_PILOT ? [[320, 'en', 'light']] : (visualMatrix ? [320, 360, 390, 430, 768, 1024, 1280, 1440, 1920] : [320, 390, 768, 1440]).flatMap(width => ['en', 'nb'].flatMap(language => ['light', 'dark'].map(mode => [width, language, mode])));
const configurations = process.env.A3_BOUNDARY ? [320, 390, 768, 1000, 1001, 1440].map(width => [width, 'en', 'light']) : process.env.A3_PILOT ? (evidenceOnly ? [[320, 'en', 'light'], [1440, 'en', 'light']] : [[320, 'en', 'light']]) : (visualMatrix || evidenceOnly ? [320, 360, 390, 430, 768, 1024, 1280, 1440, 1920] : [320, 390, 768, 1440]).flatMap(width => ['en', 'nb'].flatMap(language => ['light', 'dark'].map(mode => [width, language, mode])));
for (const [width, language, mode] of configurations) {
console.log(`Checking ${width}/${language}/${mode}`);
const t = key => { assert.ok(translations[language][key], key); return translations[language][key]; };
@@ -29,12 +31,17 @@ try {
let variant = { id: 3, name: 'Teacher CV', version: 4, settings: { themeId: 'modern', showPhoto: false, showPageNumbers: false, showIcons: true, sections: [], overrides: {}, customSections: [] }, isPublic: false, publicSlug: 'synthetic', jobApplicationId: null, updatedAtUtc: '2026-11-03T10:00:00Z' };
let letter = { revision: 7, text: 'Dear team\n\nRecorded teaching experience.', currentVersion: 2, versions: [{ version: 2, source: 'manual', length: 45, createdAtUtc: '2026-11-03T10:00:00Z', isCurrent: true }], aiSuggestionCount: 0 };
const theme = { id: 'modern', name: 'Modern', category: 'Professional', description: 'Synthetic template', layout: 'header-band', accent: '#3157d5', photoShape: 'none', supportsIcons: false, atsFriendly: true, requiresPro: false, available: true, swatches: ['#3157d5'] };
let evidenceState = 'populated';
const evidenceItems = [{ id: 1, category: 'achievement', title: 'Recorded nurse mentoring', statement: 'Mentored new colleagues in a synthetic training scenario.', tags: ['Training', 'Communication'], sourceReference: 'Synthetic review' }, { id: 2, category: 'project', title: 'Recorded electrician project', statement: 'Documented a synthetic installation handover.', tags: ['Safety'], sourceReference: 'Synthetic project notes' }];
await page.route('**/*', route => {
const req = route.request(), url = new URL(req.url()), p = url.pathname;
if (evidenceOnly && url.origin !== 'http://localhost:4338' && !(url.origin === 'http://localhost:5202' && p.startsWith('/api/'))) { report.blockedExternal.push(url.origin); return route.abort(); }
if (!['localhost', '127.0.0.1'].includes(url.hostname)) { report.blockedExternal.push(url.origin); return route.abort(); }
if (!p.startsWith('/api/')) return route.continue();
const answer = data => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(data) });
const note = action => report.mockedWrites.push({ width, language, mode, action });
if (evidenceOnly && req.method() !== 'GET') { report.unexpectedWrites.push(p); return route.abort(); }
if (evidenceOnly && p === '/api/career/evidence') return evidenceState === 'error' ? route.fulfill({ status: 503, contentType: 'application/json', body: '{}' }) : answer(evidenceState === 'empty' ? [] : evidenceItems);
if (req.method() === 'PUT' && p === '/api/career/profile') {
const body = req.postDataJSON(); assert.equal(body.expectedVersion, career.revision); assert.equal(body.cvText, career.cvText);
assert.equal(body.profile.projects[0].id, 'project-one'); assert.equal(body.profile.projects[0].name, 'Updated community project');
@@ -79,6 +86,21 @@ try {
if (width >= 600) await page.getByRole('tab', { name: t(key), exact: true }).click();
else { await page.getByRole('combobox', { name: t('careerWorkspaceNavigation'), exact: true }).click(); await page.getByRole('option', { name: t(key), exact: true }).click(); }
};
if (evidenceOnly) {
const openEvidence = async () => { await page.goto('http://localhost:4338/career'); await careerSection('careerWorkspaceEvidenceTab'); };
await openEvidence();
const title = page.getByRole('textbox', { name: new RegExp(t('careerEvidenceItemTitle')) });
await expect(page.getByRole('article', { name: evidenceItems[0].title, exact: true })).toBeVisible();
await title.fill('Unsaved evidence draft'); await scan('form-draft', title);
await careerSection('careerWorkspaceOverviewTab'); await careerSection('careerWorkspaceEvidenceTab'); await expect(title).toHaveValue('Unsaved evidence draft');
const article = page.getByRole('article', { name: evidenceItems[0].title, exact: true }); await scan('populated', article);
await article.getByRole('button').click(); const dialog = page.getByRole('dialog'); await expect(dialog).toContainText(evidenceItems[0].title); await scan('delete-confirmation', dialog); await dialog.getByRole('button', { name: t('cancel'), exact: true }).click(); await expect(article).toBeVisible();
evidenceState = 'empty'; await openEvidence(); await expect(page.getByText(t('careerEvidenceEmpty'), { exact: true })).toBeVisible(); await scan('empty');
evidenceState = 'error'; await openEvidence(); await expect(page.getByRole('main').getByRole('alert')).toContainText(t('careerEvidenceLoadFailed'));
await title.fill('Retained failure draft'); await page.getByRole('textbox', { name: new RegExp(t('careerEvidenceStatement')) }).fill('Synthetic statement retained during retry.'); await expect(page.getByRole('button', { name: t('careerEvidenceSave'), exact: true })).toBeDisabled(); await scan('error');
evidenceState = 'populated'; await page.getByRole('button', { name: t('retry'), exact: true }).click(); await expect(page.getByRole('article', { name: evidenceItems[0].title, exact: true })).toBeVisible(); await expect(title).toHaveValue('Retained failure draft'); await expect(page.getByRole('button', { name: t('careerEvidenceSave'), exact: true })).toBeEnabled(); await scan('retry', page.getByRole('article', { name: evidenceItems[0].title, exact: true }));
report.journeys.push({ width, language, mode, evidenceDraftAcrossSections: 'PASS', deletionCancelled: 'PASS', emptyErrorRetry: 'PASS', evidenceSaveDelete: 'NOT RUN', editorImportAndExport: 'NOT RUN' }); await context.close(); continue;
}
await page.goto('http://localhost:4338/career');
await expect(page.locator('[data-edit-career-section]')).toHaveCount(14); await scan('overview');
for (const key of ['contact', 'summary', 'skills', 'interests', 'awards', 'publications', 'organisations', 'references', 'languages', 'jobs', 'education', 'certifications', 'projects', 'otherSections']) {
@@ -124,10 +146,10 @@ try {
report.journeys.push({ width, language, mode, all14KeyboardEditTargets: 'PASS', canonicalSaveStableIds: 'PASS', legacyImportComparisonSelectionNoApply: 'PASS', libraryKeyboardOpen: 'PASS', cvResponsiveRename: 'PASS', letterResponsiveManualSave: 'PASS' });
await context.close();
}
assert.equal(report.screens.length, configurations.length * 6); assert.equal(report.journeys.length, configurations.length); assert.equal(report.mockedWrites.length, configurations.length * 3);
assert.equal(report.screens.length, configurations.length * 6); assert.equal(report.journeys.length, configurations.length); assert.equal(report.mockedWrites.length, evidenceOnly ? 0 : configurations.length * 3);
assert.deepEqual(report.errors, []); assert.deepEqual(report.unexpectedWrites, []); assert.ok(report.blockedExternal.every(origin => origin === 'https://fonts.googleapis.com'));
console.log(`PASS:${report.journeys.length} controlled journeys,${report.screens.length} axe scans/captures,${report.mockedWrites.length} fixture mutations.`);
} finally {
fs.writeFileSync(path.join(out, process.env.A3_BOUNDARY ? 'BOUNDARY.json' : process.env.A3_PILOT ? 'PILOT.json' : 'BROWSER.json'), JSON.stringify({ ...report, limits: ['All API and preview responses are controlled fixtures; not backend end-to-end or physical PDF qualification.', 'Legacy import comparison and selection only; no parser, model, import acceptance or source mutation.', 'No native device, screen reader, provider, integrated security, deployment or release acceptance.'] }, null, 2));
fs.writeFileSync(path.join(out, process.env.A3_BOUNDARY ? 'BOUNDARY.json' : process.env.A3_PILOT ? 'PILOT.json' : 'BROWSER.json'), JSON.stringify({ ...report, limits: ['All API and preview responses are controlled fixtures; not backend end-to-end or physical PDF qualification.', evidenceOnly ? 'Evidence-only scope: delete confirmation cancelled, all mutations blocked; profile/editor/import/export workflows NOT RUN.' : 'Legacy import comparison and selection only; no parser, model, import acceptance or source mutation.', 'No native device, screen reader, provider, integrated security, deployment or release acceptance.'] }, null, 2));
await browser.close();
}