oxedyne/daimond/dev/probe_toolspanel.mjs
4.5 KiB, 1 run
created by r2519314175:89, 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 | // probe_toolspanel.mjs — look at the Tools panel, in the states a reader meets it. |
| 2 | // |
| 3 | // Not a check: a set of screenshots for ink-level QC. Three states (as shipped, with a |
| 4 | // pack for sale, with one bought) and a narrow stage seat, because the panel is a stage |
| 5 | // guest and the stage can be half a phone wide. |
| 6 | // |
| 7 | // eval "$(bash dev/world.sh 5 --env)" |
| 8 | // node dev/probe_toolspanel.mjs |
| 9 | import { open, shot, scratch } from './harness.mjs'; |
| 10 | import fs from 'node:fs'; |
| 11 | |
| 12 | const PROFILE = scratch('pw', 'toolsprobe'); |
| 13 | fs.rmSync(PROFILE, { recursive: true, force: true }); |
| 14 | |
| 15 | const CORS = { 'access-control-allow-origin': '*', 'access-control-allow-headers': '*' }; |
| 16 | const json = (b) => ({ status: 200, contentType: 'application/json', headers: CORS, body: JSON.stringify(b) }); |
| 17 | |
| 18 | let catalogue = []; |
| 19 | |
| 20 | const s = await open({ name: 'toolsprobe', profile: PROFILE, signIn: false, connect: false }); |
| 21 | const p = s.page; |
| 22 | |
| 23 | await p.route('**/api/account', r => r.fulfill(json({ ok: true }))); |
| 24 | await p.route('**/api/auth/challenge', r => r.fulfill(json({ ok: true, challenge: 'c', challenge_id: 'i' }))); |
| 25 | await p.route('**/api/auth/verify', r => r.fulfill(json({ ok: true }))); |
| 26 | await p.route('**/api/balance', r => r.fulfill(json({ ok: true, credits_minor: 5000, currency: 'usd', entries: [] }))); |
| 27 | await p.route('**/api/licence', r => r.fulfill(json({ ok: true, licence: true, held: true, currency: 'usd' }))); |
| 28 | await p.route('**/api/tools', r => r.fulfill(json({ ok: true, credits_minor: 5000, tools: catalogue }))); |
| 29 | |
| 30 | await p.goto(process.env.DAIMOND_APP || 'http://localhost:8777', { waitUntil: 'domcontentloaded' }); |
| 31 | const { signInAs } = await import('./harness.mjs'); |
| 32 | await signInAs(s, 'toolsprobe'); |
| 33 | await p.waitForTimeout(2500); |
| 34 | |
| 35 | const draw = async (label, toShelf) => { |
| 36 | await p.evaluate(() => { window.DaimondPanels.show('tools'); window.DaimondTools.reload(); }); |
| 37 | await p.waitForTimeout(1000); |
| 38 | if (toShelf) { |
| 39 | await p.evaluate(() => { |
| 40 | const secs = [...document.querySelectorAll('#tools-body > .tools-sec')]; |
| 41 | if (secs.length > 1) secs[secs.length - 1].scrollIntoView({ block: 'start' }); |
| 42 | }); |
| 43 | await p.waitForTimeout(300); |
| 44 | } |
| 45 | // The rail row, and the rail row after `DaimondAdmin.status()` has been asked again by |
| 46 | // hand. If the second differs from the first the row is not being redrawn on its own, |
| 47 | // which is a defect in daimond.js and not in this panel — `status()` returns early |
| 48 | // when the money rows draw, and `proRow()`, `tools()` and `storage()` sit after it. |
| 49 | const rail = await p.evaluate(() => { |
| 50 | const r = document.getElementById('astat-tools'); |
| 51 | return r ? r.innerText.replace(/\s+/g, ' ').trim() : '(no row)'; |
| 52 | }); |
| 53 | const forced = await p.evaluate(() => { |
| 54 | if (window.DaimondAdmin && DaimondAdmin.status) DaimondAdmin.status(); |
| 55 | const r = document.getElementById('astat-tools'); |
| 56 | return r ? r.innerText.replace(/\s+/g, ' ').trim() : '(no row)'; |
| 57 | }); |
| 58 | if (forced !== rail) console.log(' ! the rail is stale:', rail, '→', forced, 'once status() is re-asked'); |
| 59 | console.log(label, '— rail:', rail, '— counts:', |
| 60 | JSON.stringify(await p.evaluate(() => window.DaimondTools.counts()))); |
| 61 | await shot(s, label); |
| 62 | }; |
| 63 | |
| 64 | try { |
| 65 | await draw('toolspanel-shipped'); |
| 66 | |
| 67 | // Opened, which is the state the disclosure exists for. |
| 68 | await p.evaluate(() => { |
| 69 | for (const c of document.querySelectorAll('#tools-body .cap')) { |
| 70 | const b = c.querySelector('.cap-more'); |
| 71 | if (b && c.getAttribute('data-cap') === 'browsing') b.click(); |
| 72 | } |
| 73 | }); |
| 74 | await p.waitForTimeout(300); |
| 75 | await shot(s, 'toolspanel-open'); |
| 76 | |
| 77 | catalogue = [ |
| 78 | { tool: 'typst', name: 'Typesetting', blurb: 'Daimond typesets a document into a finished PDF.', price_minor: 2500, unlocked: false, currency: 'usd' }, |
| 79 | { tool: 'research', name: 'Research', blurb: 'Daimond runs a line of enquiry: searching, reading, and keeping what it found where you can check it.', price_minor: 4500, unlocked: false, currency: 'usd' }, |
| 80 | ]; |
| 81 | await draw('toolspanel-shelf', true); |
| 82 | |
| 83 | catalogue[0].unlocked = true; |
| 84 | await draw('toolspanel-owned', true); |
| 85 | |
| 86 | // A narrow stage seat, which is where a long capability name and a Buy button fight. |
| 87 | await p.setViewportSize({ width: 720, height: 950 }); |
| 88 | await p.waitForTimeout(600); |
| 89 | await draw('toolspanel-narrow', true); |
| 90 | |
| 91 | // Overflow: nothing on this panel may push the body sideways. |
| 92 | const over = await p.evaluate(() => { |
| 93 | const b = document.getElementById('tools-body'); |
| 94 | return { scroll: b.scrollWidth, client: b.clientWidth }; |
| 95 | }); |
| 96 | console.log('tools-body overflow:', JSON.stringify(over)); |
| 97 | } finally { |
| 98 | await s.close(); |
| 99 | } |