Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_identityfold.mjs

6.5 KiB, 1 run

created by r2519314175:483, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1// Can a first-time visitor SEE the button they came here to press?
2//
3// The question is asked the way a person meets the screen: the card at the top
4// of its own scroll, nothing scrolled into view first, and `elementFromPoint`
5// over the middle of the button rather than its bounding box. A box is returned
6// for an element nobody can see, which is how the identity card came to hold a
7// "Create account" button 25px below its own bottom edge on 2026-08-14 with
8// every check still green.
9//
10// It is NOT enough to sample near the button's top edge. A card that overflows
11// by less than the button's height leaves the top of it showing, and a check
12// that photographs a few pixels there passes on a button whose label is cut in
13// half. `verify_contrast_ui` samples at `top + 4` and scrolls the button into
14// view before it does -- it is asking "is this pixel really the button", which
15// is a fair question about a photograph and a poor one about a fold. Hence this.
16//
17// The header used to say "L11 scratch ... Not part of the suite. `node
18// dev/l11_fold.mjs`". It is `dev/verify_identityfold.mjs`, `run_all.sh` takes
19// every `dev/verify_*.mjs` there is, and it has been in the suite since it was
20// renamed. It counted its own reds and then exited 0, so the count reached
21// nobody. It exits on the count now.
22//
23// PROVED AGAINST A CARD THAT CANNOT SHOW ITS OWN FOOT FIRST. `--break shortcard`
24// caps the card's height, which puts the primary button under the fold at every
25// size, and the run is expected to FAIL.
26//
27// node dev/verify_identityfold.mjs --break shortcard # expected to FAIL
28// node dev/verify_identityfold.mjs # and then, clean
29import { open } from './harness.mjs';
30
31const ok = [], bad = [];
32const check = (name, pass, detail) => {
33 (pass ? ok : bad).push(name + (detail ? ' — ' + detail : ''));
34 console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : ''));
35 return pass;
36};
37
38const BREAK = (() => {
39 const i = process.argv.indexOf('--break');
40 return i > 0 ? String(process.argv[i + 1] || '') : '';
41})();
42if (BREAK && BREAK !== 'shortcard') {
43 console.error(`unknown break '${BREAK}'; known: shortcard`);
44 process.exit(2);
45}
46
47/// The break is injected on EVERY load, because this file reloads the page
48/// half way through to meet the returning visitor.
49const breakInto = (page) => page.addInitScript(() => {
50 const put = () => {
51 const st = document.createElement('style');
52 st.textContent = '#identity-modal .modal-card { max-height: 300px !important; overflow-y: auto !important; }';
53 document.head.appendChild(st);
54 };
55 if (document.head) put(); else document.addEventListener('DOMContentLoaded', put);
56});
57
58/// Where the button is, and whether the button is what is painted over its
59/// middle -- with the card put back to the top first, because a fold is about
60/// what greets somebody, not about what they can reach.
61const look = (page) => page.evaluate(() => {
62 const card = document.querySelector('#identity-modal .modal-card');
63 const btn = document.getElementById('id-primary');
64 if (!card || !btn) return null;
65 card.scrollTop = 0;
66 const cr = card.getBoundingClientRect();
67 const br = btn.getBoundingClientRect();
68 const hit = document.elementFromPoint(br.left + br.width / 2, br.top + br.height / 2);
69 return {
70 card: { top: +cr.top.toFixed(1), bottom: +cr.bottom.toFixed(1) },
71 btn: { top: +br.top.toFixed(1), bottom: +br.bottom.toFixed(1), label: (btn.textContent || '').trim() },
72 over: card.scrollHeight - card.clientHeight,
73 seen: !!(hit && (hit === btn || btn.contains(hit))),
74 by: hit ? hit.tagName.toLowerCase() + (hit.id ? '#' + hit.id : '') : 'nothing',
75 };
76});
77
78const at = async (page, skin, w, h, what) => {
79 await page.evaluate((sk) => document.documentElement.setAttribute('data-skin', sk), skin);
80 await page.setViewportSize({ width: w, height: h });
81 await page.waitForTimeout(350);
82 const m = await look(page);
83 if (!m) return check(`${what}, ${skin} at ${w}x${h}: the card is on screen`, false, 'no card and no #id-primary');
84 return check(
85 `${what}, ${skin} at ${w}x${h}: "${m.btn.label}" is on screen unscrolled`,
86 m.seen,
87 `button ${m.btn.top}–${m.btn.bottom}, card edge ${m.card.bottom}, ${m.over}px under the fold`
88 + (m.seen ? '' : `, ${m.by} is drawn over it`));
89};
90
91const s = await open({ name: 'identityfold', signIn: false, defaults: false, route: BREAK ? breakInto : null });
92const { page } = s;
93if (BREAK) console.log(`\n*** RUNNING UNDER --break ${BREAK}: failures below are the point ***\n`);
94await page.waitForSelector('#id-primary', { timeout: 15000 });
95await page.waitForTimeout(600);
96
97// ── A stranger, who is shown the front door and the tallest form ──
98check('the stranger strip is on the screen being measured',
99 await page.evaluate(() => !!document.getElementById('id-doors').getClientRects().length), '#id-doors');
100for (const skin of ['sharp', 'warm']) {
101 await at(page, skin, 1500, 950, 'create'); // the laptop of the report
102 await at(page, skin, 1280, 800, 'create'); // a smaller one
103 await at(page, skin, 375, 812, 'create'); // a phone
104 await at(page, skin, 375, 667, 'create'); // the shortest phone still current
105}
106
107// ── And somebody coming back, who must be no worse off ────────────
108await page.evaluate(() => document.documentElement.setAttribute('data-skin', 'warm'));
109await page.setViewportSize({ width: 1500, height: 950 });
110await page.fill('#id-name', 'Returning');
111await page.evaluate(() => { const w = document.getElementById('id-wrote'); if (w && !w.checked) w.click(); });
112await page.waitForTimeout(150);
113await page.locator('#id-primary').click();
114await page.waitForTimeout(4000);
115await page.keyboard.press('Escape');
116await page.waitForTimeout(300);
117await page.reload();
118await page.waitForSelector('#id-primary', { timeout: 15000 });
119await page.waitForTimeout(2000);
120const mode = await page.evaluate(() => document.getElementById('identity-modal').dataset.mode);
121check('the return visit is an unlock screen', mode === 'unlock', `mode=${mode}`);
122for (const skin of ['sharp', 'warm']) {
123 await at(page, skin, 1500, 950, 'unlock');
124 await at(page, skin, 375, 667, 'unlock');
125}
126// The unlock card is short enough not to scroll at all, which is what makes the
127// sticky foot inert there rather than merely harmless.
128const un = await look(page);
129check('and it does not scroll, so the foot never leaves its place',
130 !!un && un.over === 0, un ? `${un.over}px under the fold` : 'no card');
131
132await s.close();
133console.log(`\n${ok.length} passed, ${bad.length} failed`);
134if (bad.length) { bad.forEach(x => console.log(' FAILED: ' + x)); process.exit(1); }