aboutsummaryrefslogtreecommitdiff
path: root/tests/gallery-probes/probe-fams.mjs
blob: a2333fbfe6e9d781b9efca881340bfdccfe18d9a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Verify screen-family chips: presence, default fallbacks intact, live recolor on click.
import { spawn } from 'node:child_process';
import { writeFileSync } from 'node:fs';
const PORT = 9335;
const URL = 'file:///home/cjennings/code/archsetup/docs/prototypes/panel-widget-gallery.html';
const chrome = spawn('google-chrome-stable', ['--headless=new', `--remote-debugging-port=${PORT}`, '--no-first-run', '--window-size=1600,1200', URL], { stdio: 'ignore' });
const sleep = ms => new Promise(r => setTimeout(r, ms));
let ws, id = 0; const pending = new Map(); const events = [];
for (let i = 0; i < 40; i++) { try { const l = await (await fetch(`http://127.0.0.1:${PORT}/json`)).json(); const p = l.find(t => t.type === 'page' && t.url.startsWith('file')); if (p) { ws = new WebSocket(p.webSocketDebuggerUrl); break; } } catch {} await sleep(250); }
await new Promise(r => ws.onopen = r);
ws.onmessage = m => { const d = JSON.parse(m.data); if (d.id && pending.has(d.id)) { pending.get(d.id)(d); pending.delete(d.id); } else if (d.method) events.push(d); };
const send = (method, params = {}) => new Promise(res => { const i = ++id; pending.set(i, res); ws.send(JSON.stringify({ id: i, method, params })); });
const evl = async e => { const r = await send('Runtime.evaluate', { expression: e, returnByValue: true }); if (r.result.exceptionDetails) throw new Error(JSON.stringify(r.result.exceptionDetails)); return r.result.result.value; };
const fails = [];
const ok = (n, c, d = '') => { console.log(`${c ? 'PASS' : 'FAIL'} ${n}${d ? ' — ' + d : ''}`); if (!c) fails.push(n); };
await send('Runtime.enable'); await sleep(1500);

/* Everything below runs inside try/finally so a failing check cannot orphan the
   browser. It could before, and the consequence was worse than a leak: the next
   run connected to the survivor on this same port and reported results from a
   STALE page — old widgets.js, chips already clicked. A probe that answers from
   the previous run is worse than one that crashes. */
try {
  ok('no exceptions on load', events.filter(e => e.method === 'Runtime.exceptionThrown').length === 0);
  // count screen-family rows only — card option groups (e.g. card 01) reuse .famchips
  const famRows = await evl(`[...document.querySelectorAll('.famchips .lab')].filter(l=>l.textContent==='screen').length`);
  ok('6 screen chip rows', famRows === 6, `got ${famRows}`);

  // default fallback: dmx ink computes to gold-hi rgb(255,190,84)
  const dmxFill0 = await evl(`getComputedStyle(document.querySelector('#card-R10 svg text')).fill`);
  ok('R10 default ink = gold-hi', dmxFill0 === 'rgb(255, 190, 84)', dmxFill0);

  // click green chip on R10 -> ink becomes phos rgb(127,224,160)
  await evl(`document.querySelector('#card-R10').querySelector('.fc[title="green"]').click()`);
  const dmxFill1 = await evl(`getComputedStyle(document.querySelector('#card-R10 svg text')).fill`);
  ok('R10 green chip recolors ink', dmxFill1 === 'rgb(127, 224, 160)', dmxFill1);

  // radar: default sweep line = gold-hi; click green -> phos
  const swSel = `document.querySelector('#card-R31')`;
  const line0 = await evl(`getComputedStyle(document.querySelectorAll('#card-R31 svg line')[0]).stroke`);
  await evl(`${swSel}.querySelector('.fc[title="green"]').click()`);
  const line1 = await evl(`getComputedStyle(document.querySelectorAll('#card-R31 svg line')[0]).stroke`);
  ok('R31 green chip recolors furniture', line0 !== line1 && line1 === 'rgb(88, 184, 126)', `${line0} -> ${line1}`);

  // scope (CSS instrument): trace stroke changes on amber chip
  const tr0 = await evl(`getComputedStyle(document.querySelector('.dupre-scope polyline')).stroke`);
  await evl(`document.querySelector('.dupre-scope').closest('.card').querySelector('.fc[title="amber"]').click()`);
  const tr1 = await evl(`getComputedStyle(document.querySelector('.dupre-scope polyline')).stroke`);
  ok('N11 amber chip recolors trace', tr0 !== tr1 && tr1 === 'rgb(255, 190, 84)', `${tr0} -> ${tr1}`);

  // R19: drag still works after family switch (click vfd, then check the handle's set() writes var-based fills)
  await evl(`document.querySelector('#card-R19 .fc[title="vfd"]').click()`);
  await evl(`document.getElementById('card-R19').dupre.set(30,70)`);
  const barFill = await evl(`(()=>{const b=document.querySelectorAll('#card-R19 svg rect')[10];return b.getAttribute('fill');})()`);
  ok('R19 dynamic fills use vars', barFill.startsWith('var(--scr-'), barFill);

  // R17: face gradient stop resolves to amber face after chip
  await evl(`document.querySelector('#card-R17 .fc[title="amber"]').click()`);
  const face = await evl(`getComputedStyle(document.querySelector('#card-R17 svg radialGradient stop')).stopColor`);
  ok('R17 amber chip retints face', face === 'rgb(216, 203, 166)', face);

  // R57's window is a screen like any other, and it offers ALL six families
  // rather than the five its siblings carry — including vfd, the marquee cyan.
  const kpFams = await evl(`(()=>{
    const chips = [...document.querySelectorAll('#card-R57 .fc')].map(c => c.title);
    const want = ['amber','green','red','blue','vfd','white'];
    return want.every(f => chips.includes(f)) ? 'ok' : 'got ' + JSON.stringify(chips);
  })()`);
  ok('R57 offers all six screen families', kpFams === 'ok', kpFams);

  // Default is the shipped gold, unchanged until a chip is clicked. It reads the
  // same as the amber family's --scr-hi because --gold-hi IS #ffbe54 — which is
  // what makes the amber default pixel-identical rather than a near-miss.
  const kpInk0 = await evl(`getComputedStyle(document.querySelector('#card-R57 .dupre-kp-pad text')).fill`);
  ok('R57 default ink = gold-hi', kpInk0 === 'rgb(255, 190, 84)', kpInk0);

  // vfd recolors both the ink and the window behind it — a screen is not just its text
  await evl(`document.querySelector('#card-R57 .fc[title="vfd"]').click()`);
  const kpInk1 = await evl(`getComputedStyle(document.querySelector('#card-R57 .dupre-kp-pad text')).fill`);
  ok('R57 vfd chip recolors the ink to marquee cyan', kpInk1 === 'rgb(99, 230, 200)', kpInk1);
  /* .dupre-kp-win, not the first rect on the pad — that one is the faceplate, and
     reading it made this check report the plate's colour and fail for the wrong
     reason. */
  const kpBg = await evl(`getComputedStyle(document.querySelector('#card-R57 .dupre-kp-win')).fill`);
  ok('R57 vfd chip recolors the window too', kpBg === 'rgb(6, 16, 13)', kpBg);

  ok('no exceptions after chip clicks', events.filter(e => e.method === 'Runtime.exceptionThrown').length === 0);

  // screenshot the recolored screens region (scroll R31 into view)
  await evl(`document.querySelector('#card-R31 svg').scrollIntoView({block:'center'})`);
  await sleep(400);
  const shot = await send('Page.captureScreenshot', { format: 'png' });
  writeFileSync('fams.png', Buffer.from(shot.result.data, 'base64'));
  console.log('shot: fams.png');
} catch (e) {
  console.error('PROBE ERROR: ' + e.message);
  fails.push('probe-error');
} finally {
  chrome.kill();
}
process.exit(fails.length ? 1 : 0);