Files
ComfyAssets-ComfyUI_PromptM…/tests/vendor/concept-css.test.mjs
T
Vito Sansevero 950cca1181 refactor(workspace): drop the dead embedded caption and tighten the review guards
- images.mjs: an embedded collection never has a caption (D47), so the
  caption option, its "Linked images" heading, the embedded count string
  and the heading arm of the focus fallback are removed; callers no
  longer pass caption:false. Behavior is unchanged.
- workspace.css: delete the never-applied .recorded-run-dialog::backdrop
  rule (the concept's .ca-tools dialog::backdrop always won) and note that
  the D3 row's min-inline-size: 0 is load-bearing for the concept's
  <=700px 1fr track.
- concept-css test: the waiting-rule guard is case-insensitive and is
  proven against the exact retired comments and the kept provenance labels.
- history-concept spec: function replacement for the singular run label,
  and the DOM mutation removes Compare negatives by role.
- D47 markers sit where the caption trim happens.
2026-10-21 04:44:07 -07:00

151 lines
9.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { spawnSync } from 'node:child_process';
import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, copyFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
import { lintOverlay } from '../../tools/vendor/sync-concept-css.mjs';
const root = join(dirname(fileURLToPath(import.meta.url)), '../..');
const script = join(root, 'tools/vendor/sync-concept-css.mjs');
const concept = join(root, 'docs/concepts/workspace/style.css');
const copy = join(root, 'web/js/workspace/concept.css');
const sha = path => createHash('sha256').update(readFileSync(path)).digest('hex');
function walk(dir) {
return readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
const path = join(dir, entry.name);
return entry.isDirectory() ? walk(path) : [path];
});
}
test('concept copy is byte-identical', () => {
assert.ok(existsSync(copy), 'web/js/workspace/concept.css is missing; run sync-concept-css.mjs --write');
assert.equal(sha(copy), sha(concept));
const result = spawnSync(process.execPath, [script, '--check'], { cwd: root, encoding: 'utf8' });
assert.equal(result.status, 0, result.stderr);
});
test('check fails on a one-byte drift', () => {
const temp = mkdtempSync(join(tmpdir(), 'concept-css-'));
try {
mkdirSync(join(temp, 'docs/concepts/workspace'), { recursive: true });
mkdirSync(join(temp, 'web/js/workspace'), { recursive: true });
copyFileSync(concept, join(temp, 'docs/concepts/workspace/style.css'));
writeFileSync(join(temp, 'web/js/workspace/concept.css'), Buffer.concat([readFileSync(concept), Buffer.from(' ')]));
const run = () => spawnSync(process.execPath, [script, '--check', '--root', temp], { encoding: 'utf8' });
const drifted = run();
assert.equal(drifted.status, 1);
assert.match(drifted.stderr, /drift/i);
assert.equal(spawnSync(process.execPath, [script, '--write', '--root', temp]).status, 0);
assert.equal(run().status, 0);
} finally { rmSync(temp, { recursive: true, force: true }); }
});
test('overlay lint requires a decision marker on concept selectors', () => {
const conceptCss = '.card, .row { color: red; }\n@media (max-width: 700px) { .card .title { margin: 0; } }\n';
assert.deepEqual(lintOverlay(conceptCss, '.error-row { color: red; }').violations, []);
assert.equal(lintOverlay(conceptCss, '.x, .card { color: blue; }').violations.length, 1);
assert.equal(lintOverlay(conceptCss, '@media (max-width: 700px) { .card .title { margin: 1px; } }').violations.length, 1);
assert.deepEqual(lintOverlay(conceptCss, '/* D12 */\n.card { color: blue; }').violations, []);
assert.deepEqual(lintOverlay(conceptCss, '.card { /* D3 */ color: blue; }').violations, []);
assert.equal(lintOverlay(conceptCss, '/* tweak */ .card { color: blue; }').violations.length, 1);
assert.equal(lintOverlay(conceptCss, '/* D1 */ .card { color: blue; }\n.row { gap: 1px; }').violations.length, 1);
});
test('overlay lint accepts only this spec\'s (#249) decision numbers', () => {
const conceptCss = '.card { color: red; }\n';
assert.deepEqual(lintOverlay(conceptCss, '/* D12 of #249 */ .card { color: blue; }').violations, []);
assert.deepEqual(lintOverlay(conceptCss, '/* #249 D3: reason */ .card { color: blue; }').violations, []);
// Another issue's decision numbers are not #249 rulings, so they never mark a concept override.
assert.equal(lintOverlay(conceptCss, '/* D5 (#245) */ .card { color: blue; }').violations.length, 1);
assert.equal(lintOverlay(conceptCss, '/* D2 of #240 and #249 */ .card { color: blue; }').violations.length, 1);
assert.equal(lintOverlay(conceptCss, '/* #245 decision 5 */ .card { color: blue; }').violations.length, 1);
assert.equal(lintOverlay(conceptCss, '/* AD5 */ .card { color: blue; }').violations.length, 1);
});
test('overlay rules on concept selectors carry a decision marker', () => {
const overlay = join(root, 'web/js/workspace/workspace.css');
assert.ok(existsSync(overlay), 'web/js/workspace/workspace.css is missing');
const { violations } = lintOverlay(readFileSync(concept, 'utf8'), readFileSync(overlay, 'utf8'));
assert.deepEqual(violations, []);
});
// #249 PR F-b Task 3: every page is rebuilt, so no rule waits for a page PR any more and the history page's
// production-only rules live in the overlay in the concept's tokens (D48). A comment that opens with a page PR
// ("PR F: ...", "PR E/F: ...") or defers to one ("until ... PR", "retired in PR F") is a waiting rule; a provenance
// label inside a heading ("(#249 PR E)") is not.
const WAITING_RULE = [
/\/\*\s*PR [A-F](\/[A-F]|-[ab])?\b/i,
/\buntil\b[^*]*\b(PR [A-F]?|rebuild)/i,
/\bretired in PR\b/i,
/\binterim page guards\b/i,
];
const waitingRules = css => WAITING_RULE.filter(pattern => pattern.test(css)).map(String);
test('the waiting-rule guard catches the retired comments and spares provenance labels', () => {
// The exact comments this PR retired (workspace.css and library.css at 10a4847).
for (const retired of [
'/* Interim page guards: concept element rules (`.ca-tools dialog`, `pre`) now reach production dialogs and prompt text',
' that keep their own design until their page PR rebuilds them in concept markup. */',
'.ca-tools dialog.prompt-sender::backdrop { background: #000b; } /* PR D */',
'.ca-tools dialog.recorded-run-dialog { padding: 16px; } /* PR F */',
' .ca-tools .recorded-history .history-toolbar button { min-height: 44px; } /* PR F: concept coarse buttons are 40px */',
'/* Until their pages rebuild (PR F history): the history page\'s and the recorded run\'s prompt blocks keep their',
'/* PR E/F: the Archive dialog and the frozen field provenance keep `pre.prompt-text`, guarded against the concept `pre` rule. */',
" keeps the concept's) and are retired in PR F (history). */",
]) assert.notDeepEqual(waitingRules(retired), [], retired);
for (const kept of [
"/* Prompt library cards (#249 PR E): the concept's `article.prompt-card` (s653–708) with real prompts, titles and media. */",
'/* History variants panel (#249 PR F). */',
]) assert.deepEqual(waitingRules(kept), [], kept);
});
test('the overlay keeps no rule waiting for a page rebuild', () => {
const css = readFileSync(join(root, 'web/js/workspace/workspace.css'), 'utf8');
assert.deepEqual(waitingRules(css), []);
for (const retired of ['.library-chip', '--library-positive', '--library-negative', '--prompt-role']) {
assert.equal(css.includes(retired), false, `${retired} is retired`);
}
});
// #249 C8 and D3: the Images page and the inspector render the concept DOM, so the replaced production rules stay
// retired; the few production-only rules still consumed live in the overlay.
test('the overlay keeps no retired Images page or inspector rules', () => {
const css = readFileSync(join(root, 'web/js/workspace/workspace.css'), 'utf8');
// `.image-grid` and `.image-card-actions` are concept selectors now, so only the retired production names are listed.
for (const retired of ['.image-facts', '.image-filters', '.image-toolbar', '.image-preview', '.image-collection', '.image-continuation',
'.image-details', '.file-cover', '.file-inspector-inner', '.file-title', '.file-chip', '.file-facts', '.generation-facts', '.file-inspector-footer']) {
assert.equal(css.includes(retired), false, `${retired} is retired`);
}
});
test('only workspace.html loads workspace CSS', () => {
const offenders = walk(join(root, 'web'))
.filter(path => /\.(html|js|mjs)$/.test(path) && !path.includes('/lib/') && !path.includes('/shared/'))
.filter(path => /\b(concept|workspace)\.css\b/.test(readFileSync(path, 'utf8')))
.map(path => path.slice(root.length + 1));
assert.ok(offenders.every(path => path === 'web/workspace.html'), `unexpected consumers: ${offenders.join(', ')}`);
});
// Task A2 order: the concept's own order (toolkit, Viewer.js, concept), then the production overlay last; the page
// stylesheets that waited for their PRs are all retired (PR F-b deleted the last, library.css).
test('workspace.html loads controls, viewer, concept and the overlay in order', () => {
const html = readFileSync(join(root, 'web/workspace.html'), 'utf8');
const hrefs = [...html.matchAll(/<link[^>]+rel="stylesheet"[^>]+href="([^"]+)"/g)].map(match => match[1]);
assert.deepEqual(hrefs, [
'js/shared/toolkit/controls.css',
'lib/viewerjs/viewer.min.css',
'js/workspace/concept.css',
'js/workspace/workspace.css',
]);
});
test('the retired shell, vocabulary, editor, Images, inspector and library stylesheets stay deleted', () => {
for (const name of ['style.css', 'vocabulary.css', 'library-editor.css', 'images.css', 'inspector.css', 'library.css']) {
assert.equal(existsSync(join(root, 'web/js/workspace', name)), false, `web/js/workspace/${name} must not come back`);
}
});