style(ui): simplify account sections and prioritise recorded role titles

81 account and 16 layout tests pass. Settled export built; fresh rendered matrices and final visual acceptance remain pending.
This commit is contained in:
cesnimda
2026-11-04 01:13:24 +01:00
parent 1c88cacb4d
commit b6d830f279
14 changed files with 47 additions and 18 deletions
@@ -7,13 +7,15 @@ import fs from 'node:fs';
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 out = path.resolve(import.meta.dirname, '../../docs/ui-modernisation/evidence/account-admin/browser');
const accountOnly = Boolean(process.env.A3_ACCOUNT_FLAT);
const out = path.resolve(import.meta.dirname, accountOnly ? '../../docs/ui-modernisation/visual-correction/evidence/account-flat' : '../../docs/ui-modernisation/evidence/account-admin/browser');
fs.mkdirSync(out, { recursive: true });
const report = { screens: [], journeys: [], errors: [], unexpectedWrites: [], exportDenials: [], unavailableReads: [], blockedExternal: [] };
const browser = await chromium.launch();
const processingOnly = Boolean(process.env.A3_PROCESSING_ONLY);
assert.ok(!(processingOnly && accountOnly), 'Account and processing qualification are distinct scopes.');
try {
const configurations = process.env.A3_PILOT ? [[320, 'en', 'light']] : [320, 390, 768, 1440].flatMap(width => ['en', 'nb'].flatMap(language => ['light', 'dark'].map(mode => [width, language, mode])));
const configurations = process.env.A3_PILOT ? [[320, 'en', 'light']] : (accountOnly ? [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]; };
@@ -25,7 +27,7 @@ try {
const listing = { items: [operation], total: 1, counts: { failed: 1 }, workers: [{ name: 'CvDocumentImports', flagEnabled: false, individualOperationsRecorded: true }] };
await page.route('**/*', route => {
const req = route.request(), url = new URL(req.url()), p = url.pathname;
if (!['localhost', '127.0.0.1'].includes(url.hostname)) { report.blockedExternal.push(url.origin); return route.abort(); }
if (url.origin !== 'http://localhost:4338' && !(url.origin === 'http://localhost:5202' && p.startsWith('/api/'))) { 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) });
if (req.method() === 'POST' && p === '/api/export/account') {
@@ -48,11 +50,11 @@ try {
if (Object.hasOwn(fixtures, p)) return answer(fixtures[p]);
report.unavailableReads.push(p); return route.fulfill({ status: 503, contentType: 'application/json', body: JSON.stringify({ detail: 'Controlled offline read unavailable.' }) });
});
const scan = async state => {
const scan = async (state, keepScroll = false) => {
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth + 1), false, `overflow:${state}`);
const result = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']).analyze();
report.screens.push({ width, language, mode, state, violations: result.violations }); assert.deepEqual(result.violations, [], `${state} accessibility`);
await page.evaluate(() => window.scrollTo({ top: 0, behavior: 'instant' })); await expect.poll(() => page.evaluate(() => window.scrollY)).toBe(0);
if (!keepScroll) { await page.evaluate(() => window.scrollTo({ top: 0, behavior: 'instant' })); await expect.poll(() => page.evaluate(() => window.scrollY)).toBe(0); }
await page.screenshot({ path: path.join(out, `${state}-${language}-${mode}-${width}.png`), fullPage: width >= 768 });
};
if (!processingOnly) {
@@ -61,6 +63,7 @@ try {
const nav = page.getByRole('navigation', { name: t('accountWorkspaceNavigation'), exact: true });
for (const [id, key] of [['account-details', 'profileAccountSection'], ['account-connections', 'accountWorkspaceConnections'], ['account-security', 'accountWorkspaceSecurity']]) {
await nav.getByRole('button', { name: t(key), exact: true }).press('Enter'); await expect(page.locator(`#${id}`)).toBeFocused(); await expect(name).toHaveValue('Unsaved account name');
if (accountOnly) await scan(id, true);
}
await scan('account');
await page.getByRole('button', { name: t('sessionsRevokeOthers'), exact: true }).click(); const confirmation = page.getByRole('dialog');
@@ -69,10 +72,13 @@ try {
await page.getByRole('button', { name: t('accountExportDownload'), exact: true }).click(); await expect(page.getByText(t('accountExportFailed'), { exact: true })).toBeVisible();
await page.getByRole('button', { name: t('accountDeleteButton'), exact: true }).click(); const deletion = page.getByRole('dialog'); await expect(deletion).toContainText('DELETE alex@example.test'); await scan('data-confirmation'); await deletion.getByRole('button', { name: t('cancel'), exact: true }).click();
await page.goto('http://localhost:4338/settings/connected-accounts'); await expect(page.getByRole('heading', { name: t('connectedAccounts'), exact: true })).toBeVisible(); await scan('connections');
if (!accountOnly) {
await page.goto('http://localhost:4338/admin/users'); await expect(page.getByRole('button', { name: t('adminUsersRemoveAdmin'), exact: true })).toBeDisabled(); await expect(page.getByRole('button', { name: t('adminUsersDelete'), exact: true })).toBeDisabled(); await scan('last-admin');
await page.goto('http://localhost:4338/admin/audit'); await expect(page.getByText(t('adminAuditNoEvents'), { exact: true })).toBeVisible(); await scan('audit-empty');
await page.goto('http://localhost:4338/admin/system'); await expect(page.getByText(t('adminSystemLoadFailed'), { exact: true })).toBeVisible(); await scan('system-unavailable');
}
}
if (!accountOnly) {
await page.goto('http://localhost:4338/admin/processing'); await expect(page.getByRole('button', { name: t('processingViewDetails').replace('{id}', operation.id), exact: true })).toBeVisible();
for (const key of ['processingStatus', 'processingTask', 'processingWorkerFilter', 'processingFailureCode']) {
const control = page.getByRole('combobox', { name: t(key), exact: true });
@@ -84,9 +90,10 @@ try {
await page.getByRole('button', { name: t('processingReturnFilters'), exact: true }).click(); await expect(page.getByRole('form', { name: t('processingFilters'), exact: true })).toBeFocused();
await page.getByRole('button', { name: t('processingViewDetails').replace('{id}', operation.id), exact: true }).click(); await expect(page.getByRole('heading', { name: t('processingDetails'), exact: true })).toBeFocused();
await expect(page.getByRole('link', { name: operation.job.title, exact: true })).toHaveCount(0); await scan('processing-detail');
report.journeys.push({ width, language, mode, draftPreservedAcrossKeyboardSections: processingOnly ? 'NOT RUN' : 'PASS', revocationAndDeletionCancelled: processingOnly ? 'NOT RUN' : 'PASS', recentExportDenial: processingOnly ? 'NOT RUN' : 'PASS', lastAdminActionsDisabled: processingOnly ? 'NOT RUN' : 'PASS', processingKeyboardAndOwnerLink: 'PASS', processingFilterLabelsAndDurationGeometry: 'PASS' }); await context.close();
}
report.journeys.push({ width, language, mode, draftPreservedAcrossKeyboardSections: processingOnly ? 'NOT RUN' : 'PASS', revocationAndDeletionCancelled: processingOnly ? 'NOT RUN' : 'PASS', recentExportDenial: processingOnly ? 'NOT RUN' : 'PASS', lastAdminActionsDisabled: processingOnly || accountOnly ? 'NOT RUN' : 'PASS', processingKeyboardAndOwnerLink: accountOnly ? 'NOT RUN' : 'PASS', processingFilterLabelsAndDurationGeometry: accountOnly ? 'NOT RUN' : 'PASS' }); await context.close();
}
assert.equal(report.screens.length, configurations.length * (processingOnly ? 1 : 8)); assert.equal(report.journeys.length, configurations.length); assert.equal(report.exportDenials.length, processingOnly ? 0 : configurations.length);
assert.equal(report.screens.length, configurations.length * (accountOnly ? 7 : processingOnly ? 1 : 8)); assert.equal(report.journeys.length, configurations.length); assert.equal(report.exportDenials.length, processingOnly ? 0 : configurations.length);
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; no mutation requests.`);
} finally {
@@ -99,6 +99,9 @@ try {
assert.ok(width > 1000 ? side.x > main.x + main.width - 2 && Math.abs(side.y - main.y) < 2 : side.y >= main.y + main.height - 2);
await scan('detail');
if (flatWorkspace) {
const pageTitleSize = await page.getByRole('heading', { level: 1 }).evaluate(element => parseFloat(getComputedStyle(element).fontSize));
const roleTitleSize = await page.getByRole('heading', { name: 'Community nurse', level: 2, exact: true }).evaluate(element => parseFloat(getComputedStyle(element).fontSize));
assert.ok(roleTitleSize > pageTitleSize, 'Recorded role title has stronger visual hierarchy than generic route label');
await page.getByRole('button', { name: language === 'en' ? 'Job details' : 'Stillingsdetaljer', exact: true }).click();
await expect(page.getByText('Recorded source advertisement.', { exact: true })).toBeVisible();
await scan('detail-expanded');