/* * Does every seeded template's `fields` actually reach the screen? * * node scripts/motion-fields-check.mjs # PIG_WEB_URL=http://127.0.0.1:8975 * * Three of the twelve starter templates shipped with `fields` shapes no renderer * in `FieldsView` read — `decisions`, `blockingSet`, `checks`, `steps` and the * rest — so about forty authored records rendered as no DOM at all, in the * library and again on the engagement that instantiated them. Nothing failed: * each renderer returned null for a key set it did not recognise, and a template * page that is header-plus-body looks like a template that was written that way. * * So the check is not "does the page load". It samples the actual authored * strings out of the seed JSON and asserts they are present in the rendered * text, which is the only claim that distinguishes a rendered field from a * dropped one. */ import { chromium } from 'playwright'; import { readdirSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; const BASE = process.env.PIG_WEB_URL ?? 'http://127.0.0.1:8975'; const SEEDS = 'packages/db/src/seed/motion'; /** Every string of real prose in a fields tree, longest first. */ function strings(node, out = []) { if (typeof node === 'string') out.push(node); else if (Array.isArray(node)) node.forEach((n) => strings(n, out)); else if (node && typeof node === 'object') Object.values(node).forEach((n) => strings(n, out)); return out; } const templates = readdirSync(SEEDS) .filter((f) => f.endsWith('.json')) .map((f) => JSON.parse(readFileSync(join(SEEDS, f), 'utf8'))) .filter((t) => t.fields && Object.keys(t.fields).length > 0); const live = await fetch(`${BASE}/api/motion/templates?all=1`).then((r) => r.json()); const bySlug = new Map(live.templates.map((t) => [t.slug, t])); const browser = await chromium.launch({ channel: 'chrome' }); const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } }); let failures = 0; for (const template of templates) { const row = bySlug.get(template.slug); if (!row) { console.log(`SKIP ${template.slug}: not in the seeded library`); continue; } // Long strings only: a short one ("Owner", "Yes") can appear in the chrome by // coincidence and would make this pass on a page that dropped every field. const sample = strings(template.fields) .filter((s) => s.length > 45) .sort((a, b) => b.length - a.length) .slice(0, 6); if (sample.length === 0) { console.log(`SKIP ${template.slug}: no prose long enough to sample`); continue; } const page = await ctx.newPage(); await page.goto(`${BASE}/motion/library/${row.id}`, { waitUntil: 'networkidle' }); await page.waitForTimeout(900); const text = await page.evaluate(() => document.body.innerText); // The markdown body is rendered too, so a string that also appears there // would not prove the FIELDS reached the screen. Those are excluded. const fieldsOnly = sample.filter((s) => !template.body.includes(s)); const checking = fieldsOnly.length ? fieldsOnly : sample; const missing = checking.filter((s) => !text.includes(s)); if (missing.length) { failures++; console.log(`FAIL ${template.slug} (${template.kind}): ${missing.length}/${checking.length} authored strings absent`); console.log(` e.g. "${missing[0].slice(0, 90)}…"`); } else { console.log(`ok ${template.slug} (${template.kind}): ${checking.length} authored strings on screen`); } await page.close(); } await browser.close(); console.log(failures === 0 ? '\nevery seeded fields tree reaches the screen' : `\n${failures} template(s) render authored content nowhere`); process.exit(failures === 0 ? 0 : 1);