]*>/.exec(html)[0], /aria-hidden/,
- 'aria-hidden would make the region useless');
+ assert.doesNotMatch(
+ /
]*>/.exec(html)[0],
+ /aria-hidden/,
+ 'aria-hidden would make the region useless',
+ );
});
test('visually-hidden keeps the element rendered', () => {
@@ -55,7 +58,10 @@ test('visually-hidden keeps the element rendered', () => {
/* ── what gets announced ──────────────────────────────────────────────────── */
test('the announcement is made where the page changes', () => {
- const goTo = dashboard.slice(dashboard.indexOf('function goTo('), dashboard.indexOf('\n}', dashboard.indexOf('function goTo(')));
+ const goTo = dashboard.slice(
+ dashboard.indexOf('function goTo('),
+ dashboard.indexOf('\n}', dashboard.indexOf('function goTo(')),
+ );
assert.match(goTo, /announcePage\(pg, total\)/);
});
@@ -70,7 +76,10 @@ test('every way of changing page goes through goTo', () => {
/* Repeating the same page would otherwise announce it again on every click. */
test('nothing is announced when the page did not change', () => {
- const goTo = dashboard.slice(dashboard.indexOf('function goTo('), dashboard.indexOf('\n}', dashboard.indexOf('function goTo(')));
+ const goTo = dashboard.slice(
+ dashboard.indexOf('function goTo('),
+ dashboard.indexOf('\n}', dashboard.indexOf('function goTo(')),
+ );
assert.match(goTo, /const was = pg/);
assert.match(goTo, /if \(pg !== was\)/);
});
@@ -106,6 +115,9 @@ test('mobile no longer builds dots into a hidden container', () => {
const ui = read('js/ui.js');
assert.doesNotMatch(ui, /const de = \[\]/, 'unreachable markup that still had to be kept in step');
/* Two statements, whether the formatter puts them on one line or two. */
- assert.match(ui, /dw\.style\.cssText\s*=\s*'display:none';\s*dw\.innerHTML\s*=\s*''/,
- 'the container is still cleared');
+ assert.match(
+ ui,
+ /dw\.style\.cssText\s*=\s*'display:none';\s*dw\.innerHTML\s*=\s*''/,
+ 'the container is still cleared',
+ );
});
diff --git a/ui/test/manifest.test.mjs b/ui/test/manifest.test.mjs
index 99e2fcef..80d590db 100644
--- a/ui/test/manifest.test.mjs
+++ b/ui/test/manifest.test.mjs
@@ -40,14 +40,20 @@ test('every page declares a theme colour', () => {
colour above the content. */
test('the manifest and every page agree on the theme colour', () => {
for (const page of ['index.html', 'admin/index.html']) {
- assert.equal(themeColorOf(read(page)), manifest.theme_color,
- `${page} and the manifest disagree, so the browser chrome will not match the page`);
+ assert.equal(
+ themeColorOf(read(page)),
+ manifest.theme_color,
+ `${page} and the manifest disagree, so the browser chrome will not match the page`,
+ );
}
});
test('the theme colour matches the dashboard background', () => {
- assert.equal(manifest.theme_color, manifest.background_color,
- 'the splash background and the chrome should be the same colour');
+ assert.equal(
+ manifest.theme_color,
+ manifest.background_color,
+ 'the splash background and the chrome should be the same colour',
+ );
});
/* ── the icons ────────────────────────────────────────────────────────────── */
@@ -55,8 +61,11 @@ test('the theme colour matches the dashboard background', () => {
test('every icon declares exactly one purpose', () => {
for (const icon of manifest.icons) {
assert.ok(icon.purpose, `${icon.src} declares no purpose`);
- assert.equal(icon.purpose.trim().split(/\s+/).length, 1,
- `${icon.src} claims "${icon.purpose}"; one image cannot be both, and Android will crop it`);
+ assert.equal(
+ icon.purpose.trim().split(/\s+/).length,
+ 1,
+ `${icon.src} claims "${icon.purpose}"; one image cannot be both, and Android will crop it`,
+ );
}
});
diff --git a/ui/test/motion-preferences.test.mjs b/ui/test/motion-preferences.test.mjs
index ffc13a0f..aea11dba 100644
--- a/ui/test/motion-preferences.test.mjs
+++ b/ui/test/motion-preferences.test.mjs
@@ -41,8 +41,10 @@ test('both pages load tokens.css', () => {
});
test('only the dashboard loads dashboard.css', () => {
- assert.ok(!stylesheetsOf('admin/index.html').includes('dashboard.css'),
- 'if Admin loaded it, none of this would have been necessary');
+ assert.ok(
+ !stylesheetsOf('admin/index.html').includes('dashboard.css'),
+ 'if Admin loaded it, none of this would have been necessary',
+ );
});
/* ── every page honours motion and transparency ───────────────────────────── */
@@ -66,7 +68,9 @@ test('reduced motion neutralises rather than removes transitions', () => {
test('every page ends up honouring all three preferences', () => {
const forPage = page => {
- const sheets = stylesheetsOf(page).map(f => read(`css/${f}`)).join('\n');
+ const sheets = stylesheetsOf(page)
+ .map(f => read(`css/${f}`))
+ .join('\n');
return {
motion: /@media \(prefers-reduced-motion: reduce\)/.test(sheets),
transparency: /@media \(prefers-reduced-transparency: reduce\)/.test(sheets),
@@ -74,8 +78,11 @@ test('every page ends up honouring all three preferences', () => {
};
};
for (const page of ['index.html', 'admin/index.html']) {
- assert.deepEqual(forPage(page), { motion: true, transparency: true, contrast: true },
- `${page} does not honour every preference`);
+ assert.deepEqual(
+ forPage(page),
+ { motion: true, transparency: true, contrast: true },
+ `${page} does not honour every preference`,
+ );
}
});
@@ -121,7 +128,10 @@ test('admin raises its own tokens for increased contrast', () => {
/* Measured against the page's own background rather than chosen by eye. WCAG
asks 4.5 for body text and 3.0 for a UI border. */
test('the raised colours meet the contrast they are raised for', () => {
- const lin = c => { const v = c / 255; return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; };
+ const lin = c => {
+ const v = c / 255;
+ return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
+ };
const lum = hex => {
const n = parseInt(hex.slice(1), 16);
return 0.2126 * lin((n >> 16) & 255) + 0.7152 * lin((n >> 8) & 255) + 0.0722 * lin(n & 255);
@@ -164,7 +174,10 @@ test('the raised colours meet the contrast they are raised for', () => {
const pane = resolve('--pane');
assert.ok(pane, 'the panel background does not resolve to a hex');
- for (const [token, min, what] of [['--dm', 4.5, 'body text'], ['--bd', 3, 'a UI border']]) {
+ for (const [token, min, what] of [
+ ['--dm', 4.5, 'body text'],
+ ['--bd', 3, 'a UI border'],
+ ]) {
const value = resolve(token);
assert.ok(value, `${token} does not resolve to a hex`);
const got = ratio(value, pane);
diff --git a/ui/test/pagination.test.mjs b/ui/test/pagination.test.mjs
index 8b53cd91..bae443a9 100644
--- a/ui/test/pagination.test.mjs
+++ b/ui/test/pagination.test.mjs
@@ -55,8 +55,7 @@ test('the current page is marked for a screen reader', () => {
test('both places that build dots use the one helper', () => {
const calls = dashboard.match(/mkDot\(/g) || [];
assert.ok(calls.length >= 3, `expected the definition and two call sites, found ${calls.length}`);
- assert.doesNotMatch(dashboard, /const d = mk\('div'\); d\.className = 'dot'/,
- 'a call site still builds its own div');
+ assert.doesNotMatch(dashboard, /const d = mk\('div'\); d\.className = 'dot'/, 'a call site still builds its own div');
});
/* Paging updates the dots in place rather than rebuilding them, so the
diff --git a/ui/test/password-strength.test.mjs b/ui/test/password-strength.test.mjs
index 551d83d6..51786a90 100644
--- a/ui/test/password-strength.test.mjs
+++ b/ui/test/password-strength.test.mjs
@@ -34,9 +34,9 @@ test('the minimum length is the boundary it claims to be', () => {
});
test('score rises with length and character variety', () => {
- const weak = pwStrength('aaaaaaaa'); /* 8 chars, one class */
- const better = pwStrength('aaaaAAAA1111'); /* 12 chars, three classes */
- const best = pwStrength('aaaaAAAA1111!!!!'); /* 16 chars, four classes */
+ const weak = pwStrength('aaaaaaaa'); /* 8 chars, one class */
+ const better = pwStrength('aaaaAAAA1111'); /* 12 chars, three classes */
+ const best = pwStrength('aaaaAAAA1111!!!!'); /* 16 chars, four classes */
assert.ok(weak.score < better.score, 'variety and length must count');
assert.ok(better.score <= best.score);
assert.equal(best.labelKey, 'pwStrength.strong');
@@ -59,8 +59,16 @@ test('the strongest password still gets a label and a colour', () => {
/* Nothing in the range may fall off the end of either array. */
test('every reachable score yields a label and a colour', () => {
const seen = new Set();
- for (const pw of ['aaaaaaaa', 'aaaaaaaaaaaa', 'aaaaAAAAaaaa', 'aaaaAAAA1111',
- 'aaaaAAAA1111!!!!', 'aA1!aA1!', 'aA1!aA1!aA1!', 'Aa1!'.repeat(8)]) {
+ for (const pw of [
+ 'aaaaaaaa',
+ 'aaaaaaaaaaaa',
+ 'aaaaAAAAaaaa',
+ 'aaaaAAAA1111',
+ 'aaaaAAAA1111!!!!',
+ 'aA1!aA1!',
+ 'aA1!aA1!aA1!',
+ 'Aa1!'.repeat(8),
+ ]) {
const r = pwStrength(pw);
seen.add(r.score);
assert.equal(typeof r.labelKey, 'string', pw);
diff --git a/ui/test/postmessage-origin.test.mjs b/ui/test/postmessage-origin.test.mjs
index e6cfbb72..281f4448 100644
--- a/ui/test/postmessage-origin.test.mjs
+++ b/ui/test/postmessage-origin.test.mjs
@@ -49,8 +49,10 @@ const MESSAGE_LISTENER = /addEventListener\(\s*'message'\s*,\s*(?:async\s*)?(\w+
test('the scan finds the widget pages', () => {
assert.ok(pages.length > 10, `only ${pages.length} widget pages found`);
- assert.ok(pages.includes('widgets/stats/disk-health.html'),
- 'the widget that implements the protocol should be in the scan');
+ assert.ok(
+ pages.includes('widgets/stats/disk-health.html'),
+ 'the widget that implements the protocol should be in the scan',
+ );
});
/* ── the sending direction ────────────────────────────────────────────────── */
@@ -61,8 +63,11 @@ test('the dashboard listens for widget-active, and checks the origin', () => {
const listeners = [...src.matchAll(MESSAGE_LISTENER)];
assert.ok(listeners.length > 0);
for (const m of listeners) {
- assert.match(src.slice(m.index, m.index + 400), /e\.origin !== window\.location\.origin/,
- 'a message listener in ui.js does not check e.origin');
+ assert.match(
+ src.slice(m.index, m.index + 400),
+ /e\.origin !== window\.location\.origin/,
+ 'a message listener in ui.js does not check e.origin',
+ );
}
});
@@ -72,24 +77,25 @@ test('a widget that posts, posts widget-active to its own origin', () => {
for (const p of senders) {
const src = read(p);
assert.doesNotMatch(src, /postMessage\([^)]*,\s*['"]\*['"]\s*\)/, `${p} posts to '*'`);
- assert.match(src, /postMessage\(\{ type:'widget-active' \}, window\.location\.origin\)/,
- `${p} posts something other than widget-active to its own origin`);
+ assert.match(
+ src,
+ /postMessage\(\{ type:'widget-active' \}, window\.location\.origin\)/,
+ `${p} posts something other than widget-active to its own origin`,
+ );
}
});
/* ── the resetting direction, which is a call and not a message ───────────── */
test('the dashboard resets a widget by calling its __clearActive', () => {
- assert.match(read(UI), /__clearActive\(\)/,
- 'the dashboard no longer calls into the widget to reset it');
+ assert.match(read(UI), /__clearActive\(\)/, 'the dashboard no longer calls into the widget to reset it');
});
test('a widget that can go active exposes __clearActive', () => {
for (const p of pages) {
const src = read(p);
if (!/postMessage\(\{ type:'widget-active' \}/.test(src)) continue;
- assert.match(src, /window\.__clearActive\s*=/,
- `${p} announces itself active but cannot be reset`);
+ assert.match(src, /window\.__clearActive\s*=/, `${p} announces itself active but cannot be reset`);
}
});
@@ -100,7 +106,10 @@ test('no widget listens for a message the dashboard never sends', () => {
const offenders = [];
for (const p of pages) {
const src = read(p);
- if (!MESSAGE_LISTENER.test(src)) { MESSAGE_LISTENER.lastIndex = 0; continue; }
+ if (!MESSAGE_LISTENER.test(src)) {
+ MESSAGE_LISTENER.lastIndex = 0;
+ continue;
+ }
MESSAGE_LISTENER.lastIndex = 0;
for (const m of src.matchAll(/e\.data\.type === '([\w-]+)'/g)) {
if (!ui.includes(`type: '${m[1]}'`) && !ui.includes(`type:'${m[1]}'`)) {
@@ -108,16 +117,22 @@ test('no widget listens for a message the dashboard never sends', () => {
}
}
}
- assert.deepEqual(offenders, [],
- `Nothing in ui.js posts these. Remove the listener, or add the sender:\n ${offenders.join('\n ')}`);
+ assert.deepEqual(
+ offenders,
+ [],
+ `Nothing in ui.js posts these. Remove the listener, or add the sender:\n ${offenders.join('\n ')}`,
+ );
});
test('a widget listener, if one is added back, checks the origin', () => {
for (const p of pages) {
const src = read(p);
for (const m of src.matchAll(MESSAGE_LISTENER)) {
- assert.match(src.slice(m.index, m.index + 400), /e\.origin !== window\.location\.origin/,
- `a message listener in ${p} does not check e.origin`);
+ assert.match(
+ src.slice(m.index, m.index + 400),
+ /e\.origin !== window\.location\.origin/,
+ `a message listener in ${p} does not check e.origin`,
+ );
}
}
});
diff --git a/ui/test/refused-widgets.test.mjs b/ui/test/refused-widgets.test.mjs
index 0ae39a4e..0d83ef01 100644
--- a/ui/test/refused-widgets.test.mjs
+++ b/ui/test/refused-widgets.test.mjs
@@ -46,12 +46,14 @@ test('the picker says how many widgets were refused', () => {
/* The notice belongs above the size and config sections, next to the list the
widget is missing from, not at the bottom of the form. */
test('the notice renders next to the type list', () => {
- const atType = form.indexOf("id=\"f-wtype\"");
+ const atType = form.indexOf('id="f-wtype"');
const atNotice = form.indexOf('refusedNoticeKey(');
const atSize = form.search(/sizeHdr\.textContent\s*=\s*'Size'/);
assert.ok(atType !== -1 && atNotice !== -1 && atSize !== -1, 'the form no longer has these parts');
- assert.ok(atType < atNotice && atNotice < atSize,
- 'the refusal notice should sit between the type list and the size section');
+ assert.ok(
+ atType < atNotice && atNotice < atSize,
+ 'the refusal notice should sit between the type list and the size section',
+ );
});
/* A validator message is built from names taken out of the manifest, so it is
diff --git a/ui/test/rtl-logical-properties.test.mjs b/ui/test/rtl-logical-properties.test.mjs
index d4676ea5..6a949ae1 100644
--- a/ui/test/rtl-logical-properties.test.mjs
+++ b/ui/test/rtl-logical-properties.test.mjs
@@ -47,8 +47,11 @@ function widgetPages() {
/* Only the