oxedyne/daimond/dev/verify_crystalfull.mjs
4.8 KiB, 1 run
created by r2519314175:327, 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 | // verify_crystalfull.mjs — the crystal face's full-screen toggle (piece b). |
| 2 | // |
| 3 | // The lane that wrote the toggle died mid-verification, so for a while nothing |
| 4 | // drove it. What its own comments promise, and what is held to here: |
| 5 | // |
| 6 | // * the control appears on the CRYSTAL face and nowhere else; |
| 7 | // * pressing it sets `data-cfull`, which takes the top bar, the rail and the |
| 8 | // dock away; |
| 9 | // * Escape leaves it, and the control is still on screen to leave it with; |
| 10 | // * leaving the crystal face puts the mode down; |
| 11 | // * it does not survive a reload. |
| 12 | // |
| 13 | // node dev/verify_crystalfull.mjs |
| 14 | // |
| 15 | // It writes no path down: `harness.mjs` is imported relative to this file, and the one |
| 16 | // screenshot goes to the harness scratch root — never into `www/`, whose bytes ship. |
| 17 | import { open, scratch } from './harness.mjs'; |
| 18 | |
| 19 | let failures = 0; |
| 20 | const check = (cond, msg, detail) => { |
| 21 | console.log((cond ? ' ok ' : ' FAIL ') + msg + (detail != null ? ' — ' + detail : '')); |
| 22 | if (!cond) failures++; |
| 23 | }; |
| 24 | |
| 25 | const state = (p) => p.evaluate(() => { |
| 26 | const drawn = (sel) => { const e = document.querySelector(sel); |
| 27 | return !!(e && e.getClientRects().length); }; |
| 28 | const b = document.getElementById('crystal-full-btn'); |
| 29 | return { |
| 30 | attr: document.documentElement.getAttribute('data-cfull'), |
| 31 | btn: b ? { drawn: !!b.getClientRects().length, pressed: b.getAttribute('aria-pressed'), |
| 32 | label: b.getAttribute('aria-label'), word: (document.getElementById('crystal-full-txt') || {}).textContent } : null, |
| 33 | topbar: drawn('.topbar'), |
| 34 | rail: drawn('#panel-rail'), |
| 35 | dock: drawn('.dock'), |
| 36 | bar: drawn('.panel.ai .crystal-bar'), |
| 37 | frame: drawn('#crystal-frame-wrap'), |
| 38 | body: drawn('#crystal-body'), |
| 39 | }; |
| 40 | }); |
| 41 | |
| 42 | const s = await open({ name: 'cfull', connect: false }); |
| 43 | const p = s.page; |
| 44 | await p.waitForTimeout(1500); |
| 45 | |
| 46 | try { |
| 47 | // A seeded default Diamond; the face it opens on is the crystal. |
| 48 | await p.evaluate(() => { |
| 49 | const box = document.querySelector('.diamond-box'); |
| 50 | if (box) box.click(); |
| 51 | }); |
| 52 | await p.waitForTimeout(1800); |
| 53 | |
| 54 | const rest = await state(p); |
| 55 | console.log(' rest ' + JSON.stringify(rest)); |
| 56 | check(!!rest.btn && rest.btn.drawn, 'the control is on the crystal face'); |
| 57 | check(rest.btn && rest.btn.pressed === 'false', 'and says it is not pressed'); |
| 58 | check(rest.btn && !rest.btn.word, 'with no word beside the glyph while the mode is off', |
| 59 | JSON.stringify(rest.btn && rest.btn.word)); |
| 60 | check(rest.attr === null, 'and nothing is covering the app'); |
| 61 | |
| 62 | await p.click('#crystal-full-btn'); |
| 63 | await p.waitForTimeout(500); |
| 64 | const on = await state(p); |
| 65 | console.log(' on ' + JSON.stringify(on)); |
| 66 | check(on.attr === '1', 'pressing it sets data-cfull'); |
| 67 | check(!on.topbar && !on.rail && !on.dock, 'the top bar, the rail and the dock go', |
| 68 | JSON.stringify({ topbar: on.topbar, rail: on.rail, dock: on.dock })); |
| 69 | check(on.btn && on.btn.drawn, 'AND THE WAY OUT IS STILL ON SCREEN'); |
| 70 | check(on.btn && on.btn.pressed === 'true' && /exit/i.test(on.btn.label || ''), |
| 71 | 'saying what pressing it will do', JSON.stringify(on.btn)); |
| 72 | check(!!(on.btn && on.btn.word), 'with the word beside the glyph', JSON.stringify(on.btn && on.btn.word)); |
| 73 | check(on.body, 'and the crystal is still drawn (reflow did not tear it down)'); |
| 74 | await p.screenshot({ path: scratch('cfull-on.png') }); |
| 75 | |
| 76 | // Escape, from the app's own focus. |
| 77 | await p.keyboard.press('Escape'); |
| 78 | await p.waitForTimeout(400); |
| 79 | const esc = await state(p); |
| 80 | check(esc.attr === null && esc.topbar && esc.rail, 'Escape leaves it', JSON.stringify(esc.attr)); |
| 81 | |
| 82 | // Leaving the crystal face puts the mode down rather than leaving a covered |
| 83 | // app with no subject. |
| 84 | await p.click('#crystal-full-btn'); |
| 85 | await p.waitForTimeout(400); |
| 86 | check((await state(p)).attr === '1', 'back in'); |
| 87 | await p.evaluate(() => { const b = document.getElementById('dview-chat'); if (b) b.click(); }); |
| 88 | await p.waitForTimeout(800); |
| 89 | const face = await state(p); |
| 90 | check(face.attr === null, 'switching to the chat face puts the mode down'); |
| 91 | check(!(face.btn && face.btn.drawn), 'and the control goes with the face it belongs to'); |
| 92 | |
| 93 | // It does not survive a reload. |
| 94 | await p.evaluate(() => { const b = document.getElementById('dview-crystal'); if (b) b.click(); }); |
| 95 | await p.waitForTimeout(700); |
| 96 | await p.click('#crystal-full-btn'); |
| 97 | await p.waitForTimeout(400); |
| 98 | check((await state(p)).attr === '1', 'in again, before the reload'); |
| 99 | await p.reload({ waitUntil: 'domcontentloaded' }); |
| 100 | await p.waitForTimeout(1500); |
| 101 | check((await state(p)).attr === null, 'a reload does not come back inside the mode'); |
| 102 | } catch (e) { |
| 103 | console.log(' FAIL threw — ' + (e && e.message)); |
| 104 | failures++; |
| 105 | } finally { |
| 106 | const errs = s.errs.filter(e => !/favicon|manifest|502|Bad Gateway|gateway/i.test(e)); |
| 107 | if (errs.length) console.log(' console errors: ' + errs.slice(0, 6).join(' | ')); |
| 108 | await s.close(); |
| 109 | } |
| 110 | console.log(failures ? failures + ' failure(s)' : 'all checks passed'); |
| 111 | process.exit(failures ? 1 : 0); |