[object Object]

← back to Wallco Ai

add theme smoke-test selftest (meta-test) — proves the harness passes healthy themes AND catches a broken v11 toggle

6516c8d459e59d72289f46b41f4ac67797154137 · 2026-06-01 17:01:10 -0700 · Steve Abrams

Files touched

Diff

commit 6516c8d459e59d72289f46b41f4ac67797154137
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 1 17:01:10 2026 -0700

    add theme smoke-test selftest (meta-test) — proves the harness passes healthy themes AND catches a broken v11 toggle
---
 scripts/smoke-test-themes-selftest.js | 121 ++++++++++++++++++++++++++++++++++
 1 file changed, 121 insertions(+)

diff --git a/scripts/smoke-test-themes-selftest.js b/scripts/smoke-test-themes-selftest.js
new file mode 100644
index 0000000..4d6e0ff
--- /dev/null
+++ b/scripts/smoke-test-themes-selftest.js
@@ -0,0 +1,121 @@
+#!/usr/bin/env node
+/**
+ * smoke-test-themes-selftest.js — meta-test for scripts/smoke-test-themes.js.
+ *
+ * A smoke test is only worth anything if it actually FAILS when the thing it
+ * guards is broken. This proves the PDP theme smoke harness discriminates:
+ *
+ *   Phase A (positive control): serve the REAL theme files + a healthy v11
+ *     toggle → the smoke test must PASS (exit 0).
+ *   Phase B (negative): serve the same files but with the v11 toggle click
+ *     handler wired to nothing (the silent dead-wire regression Steve's rule
+ *     targets) → the smoke test must FAIL (exit 1).
+ *
+ * Self-contained: serves real public/theme-variants/*.html with an inline
+ * mock /api/design payload — no network, no /tmp, no live site needed.
+ * Exit 0 only if A passed AND B failed; otherwise exit 1 (the guard is blind).
+ *
+ * Usage: node scripts/smoke-test-themes-selftest.js
+ */
+'use strict';
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+const { spawn } = require('child_process');
+
+const TV = path.join(__dirname, '..', 'public', 'theme-variants');
+const SMOKE = path.join(__dirname, 'smoke-test-themes.js');
+const PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', 'base64');
+
+const FIXTURE_ID = 55340;
+// Faithful /api/design payload (shape: {ok, design}) so the real theme JS renders.
+const DESIGN = JSON.stringify({ ok: true, design: {
+  id: FIXTURE_ID, kind: 'seamless_tile', category: 'la-toile', dominant_hex: '#e9dfc6',
+  saturation: 0.443, title: 'Chamois Atelier No.55340', handle: 'wallco-55340',
+  image_url: '/designs/img/fixture.png', filename: 'fixture.png', generator: 'wallco.ai',
+  seed: 1343415501, is_published: true, created_at: '2026-06-01T22:18:07.421393+00:00',
+  motifs: [], tags: ['curator-loved'], user_removed: false, room_mockups: [],
+}});
+
+const SLUG_FILE = {
+  compact:'v1-compact.html', bento:'v2-bento.html', tabs:'v3-tabs.html',
+  hero:'v4-hero.html', vertical:'v5-vertical.html', drawer:'v6-drawer.html',
+  editorial:'v7-editorial.html', spec:'v8-spec.html', config:'v9-config.html',
+  stack:'v10-stack.html', best:'v11-best.html',
+  roomsplit:'v12-roomsplit.html', roomdrawer:'v13-roomdrawer.html',
+  roomzine:'v14-roomzine.html', roomsticky:'v15-roomsticky.html',
+  roommin:'v16-roommin.html', roomshow:'v17-roomshow.html',
+};
+
+const HEALTHY_BEST = fs.readFileSync(path.join(TV, 'v11-best.html'), 'utf8');
+function makeBrokenBest() {
+  const before = `    mode = (t.dataset.mode === 'mural') ? 'mural' : 'wallpaper';
+    applyMode();`;
+  const after = `    /* BROKEN TOGGLE (selftest): click wired to nothing */`;
+  if (!HEALTHY_BEST.includes(before)) {
+    console.error('SELFTEST ERROR: toggle handler text not found in v11-best.html — update the fixture');
+    process.exit(2);
+  }
+  return HEALTHY_BEST.replace(before, after);
+}
+const BROKEN_BEST = makeBrokenBest();
+
+function startServer(bestHtml) {
+  return new Promise((resolve) => {
+    const server = http.createServer((req, res) => {
+      const u = req.url.split('?')[0];
+      const m = u.match(/^\/design\/(\d+)\/([a-z]+)$/);
+      if (m) {
+        const slug = m[2];
+        res.setHeader('content-type', 'text/html');
+        if (slug === 'best') return res.end(bestHtml);
+        const f = SLUG_FILE[slug];
+        if (f) return res.end(fs.readFileSync(path.join(TV, f), 'utf8'));
+        res.statusCode = 404; return res.end('no slug');
+      }
+      if (u === '/api/designs') { res.setHeader('content-type','application/json'); return res.end(JSON.stringify({ designs: [{ id: FIXTURE_ID, kind:'seamless_tile', category:'la-toile' }] })); }
+      if (u.startsWith('/api/design/')) { res.setHeader('content-type','application/json'); return res.end(DESIGN); }
+      if (u.startsWith('/designs/img/') || u.startsWith('/elements/img/') || u.endsWith('.png') || u.endsWith('.jpg')) { res.setHeader('content-type','image/png'); return res.end(PNG); }
+      if (u.endsWith('.js') || u.endsWith('.css') || u.startsWith('/js/')) { res.setHeader('content-type','application/javascript'); return res.end(''); }
+      res.statusCode = 404; res.end('nope');
+    });
+    server.listen(0, () => resolve({ server, port: server.address().port }));
+  });
+}
+
+// IMPORTANT: spawn async, NOT spawnSync. The mock HTTP server runs in THIS
+// process; spawnSync would block the event loop and the browser's requests
+// would all time out. Async spawn keeps the server responsive.
+function runSmoke(port) {
+  return new Promise((resolve) => {
+    const child = spawn('node', [SMOKE, String(FIXTURE_ID), `http://127.0.0.1:${port}`], { stdio: 'inherit' });
+    child.on('close', code => resolve(code));
+  });
+}
+
+(async () => {
+  console.log('\n=== SELFTEST: prove the PDP theme smoke harness discriminates ===');
+
+  console.log('\n[Phase A] healthy themes → smoke MUST pass (exit 0)');
+  const a = await startServer(HEALTHY_BEST);
+  const rcA = await runSmoke(a.port);
+  a.server.close();
+  console.log(`[Phase A] smoke exit = ${rcA} (want 0)`);
+
+  console.log('\n[Phase B] broken v11 toggle → smoke MUST fail (exit 1)');
+  const b = await startServer(BROKEN_BEST);
+  const rcB = await runSmoke(b.port);
+  b.server.close();
+  console.log(`[Phase B] smoke exit = ${rcB} (want 1)`);
+
+  const pass = (rcA === 0) && (rcB === 1);
+  console.log('\n──────────────────────────────────────────');
+  if (pass) {
+    console.log('SELFTEST PASS — harness passes healthy themes AND catches a broken toggle.');
+    process.exit(0);
+  }
+  console.log('SELFTEST FAIL — harness did not discriminate:');
+  if (rcA !== 0) console.log('  • Phase A: healthy themes were NOT passed (rc=' + rcA + ') — false negative.');
+  if (rcB !== 1) console.log('  • Phase B: broken toggle was NOT caught (rc=' + rcB + ') — the guard is BLIND.');
+  process.exit(1);
+})().catch(e => { console.error('SELFTEST HARNESS ERROR:', e); process.exit(2); });

← 0288f88 render-time color-name consistency on /api/design/:id + 404  ·  back to Wallco Ai  ·  Replicate generator: optional tileable SDXL cog (pwntus/mate 8ea8731 →