ZDDC/tests/build-label.spec.js
ZDDC 90a31020db fix: clear the 14 stale Playwright baseline failures
Four root causes, each affecting one or more pre-existing
failures. All resolved without weakening any assertion.

1. build-label.spec.js (×4 — archive/transmittal/classifier/browse)
   The regex accepted v<X.Y.Z>-alpha|beta channel labels but not the
   -dev label modern dev builds emit. CLAUDE.md describes
   v<X.Y.Z>-dev as the canonical dev-build form. Added |dev to the
   channel alternation; tests now pass on dev builds and remain
   tight on stable cuts.

2. landing.spec.js (×8)
   SAMPLE_PROJECTS fixture pre-dated the post-reshape listing JSON
   contract. The landing's loader now filters projects on
   `is_dir: true`; the fixture didn't set it, so every entry was
   filtered out and every "renders a project table" test failed at
   the `.project-table` wait. Added `is_dir: true` (and trailing
   slash on names, matching the live server's shape) to the three
   fixture entries.

3. browse.spec.js (×1 — Download (zip))
   The #downloadZipBtn toolbar button was retired in the SPA
   overhaul (94b2e29) — Download ZIP moved to the right-click
   context menu. Test still poked the dead toolbar button. The
   picked-root folder no longer renders as a row (only its
   contents do), so the test now scopes the assertion to
   downloading a sub-folder (sub/) via right-click → Download ZIP;
   verifies the zip's entries, magic bytes, and filename.

4. tables.spec.js (×1 — Phase 3 row-blur fires PUT)
   Real bug, not a test issue. The editor's commit path tears down
   its input element (clearing focus to body) before refocusing
   the owning cell. main.js's focusout-on-#table-root handler ran
   synchronously, saw `relatedTarget=null`, treated it as "user
   left the grid", and fired flushAll() — racing the
   selection-change save that fires from the subsequent
   setSelected(r+1, c) inside the Enter handler. Net effect: two
   identical PUTs per row-blur. Deferred the focusout check to
   next tick via setTimeout(0); the cell.focus() inside the
   editor's tearDown has time to settle, and the deferred check
   sees document.activeElement still inside #table-root → skips
   the redundant flush.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 11:24:30 -05:00

75 lines
3.6 KiB
JavaScript

/**
* build-label.spec.js
*
* Verifies that the {{BUILD_LABEL}} placeholder is correctly substituted in all
* four tool dist and website files:
*
* - No placeholder text leaks through
* - The .build-timestamp element is present and visible in the browser
* - The label content is either a timestamp ("Built: 20...") or a version ("v...")
* - website/ files (when present) always show the version format
*
* Note: dist/ files may show either format depending on whether the last build
* was a dev build (timestamp) or a release build (version). Both are valid.
*/
import { test, expect } from '@playwright/test';
import * as path from 'path';
import * as fs from 'fs';
const tools = ['archive', 'transmittal', 'classifier', 'browse'];
for (const tool of tools) {
const distPath = path.resolve(`${tool}/dist/${tool}.html`);
const websitePath = path.resolve(`website/${tool}.html`);
test.describe(`${tool}: build-label`, () => {
test(`dist file: no placeholder text leaks through`, async () => {
const html = fs.readFileSync(distPath, 'utf8');
expect(html).not.toContain('{{BUILD_LABEL}}');
expect(html).not.toContain('{{BUILD_TIMESTAMP}}');
});
test(`dist file: .build-timestamp element is visible in browser`, async ({ page }) => {
// browse may load Toast UI lazily; wait for full load.
const waitUntil = tool === 'browse' ? 'load' : 'domcontentloaded';
await page.goto(`file://${distPath}`, { waitUntil });
const el = page.locator('.build-timestamp');
await expect(el).toBeVisible({ timeout: 10000 });
});
test(`dist file: label is a valid channel or version string`, async () => {
const html = fs.readFileSync(distPath, 'utf8');
// Channel labels (alpha/beta) are wrapped in an inner <span style="color:red...">;
// stable version labels are bare text.
const match = html.match(/class="build-timestamp">(?:<span[^>]*>)?([^<]+?)(?:<\/span>)?</);
expect(match, 'build-timestamp element must have text content').toBeTruthy();
const label = match[1];
// Plain dev builds, ./build beta, and the retired
// ./build alpha all share one label shape — full UTC
// timestamp + short source SHA (with optional -dirty
// marker when the tree is uncommitted):
// "v0.0.21-dev · 2026-05-21 16:11:12 · 736f422"
// "v0.0.21-dev · 2026-05-21 16:11:12 · 736f422-dirty"
// "v0.0.17-beta · 2026-05-13 15:29:05 · e7f6334"
// "v0.0.17-alpha · 2026-04-29 00:50:17 · 714faf6"
// Stable cuts emit a bare version: "v0.0.17"
const isChannel = /^v\d+\.\d+\.\d+-(?:alpha|beta|dev) · 20\d\d-\d\d-\d\d \d\d:\d\d:\d\d · [0-9a-f]+(?:-dirty)?$/.test(label);
const isVersion = /^v\d+\.\d+\.\d+$/.test(label);
expect(isChannel || isVersion,
`Expected channel or version label, got: "${label}"`
).toBe(true);
});
test(`website file: label shows version format (release build)`, async () => {
if (!fs.existsSync(websitePath)) {
test.skip(true, `website/${tool}.html not present — run sh ${tool}/build.sh --release first`);
return;
}
const html = fs.readFileSync(websitePath, 'utf8');
expect(html).not.toContain('{{BUILD_LABEL}}');
expect(html).not.toContain('{{BUILD_TIMESTAMP}}');
expect(html).toMatch(/class="build-timestamp">(?:<span[^>]*>)?v\d+\.\d+\.\d+( BETA)?(?:<\/span>)?</);
});
});
}