The overlay lint accepted any D-number, so another issue's ruling such as "D5 (#245)" could mark a concept override. A marker now has to be a D-number with no other issue cited; the vocabulary rule that cited #245 reads "#245 decision 5". TESTING.md now states that the lint covers workspace.css only, lists the unmarked overrides still in images.css, and says PRs C and D run the lint over each page sheet they keep.
105 lines
5.9 KiB
JavaScript
105 lines
5.9 KiB
JavaScript
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, []);
|
|
});
|
|
|
|
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) with the page stylesheets that their own PRs
|
|
// retire, then the production overlay last.
|
|
test('workspace.html loads controls, viewer, concept, page CSS 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/images.css',
|
|
'js/workspace/library.css',
|
|
'js/workspace/inspector.css',
|
|
'js/workspace/workspace.css',
|
|
]);
|
|
});
|
|
|
|
test('the retired shell, vocabulary and editor stylesheets stay deleted', () => {
|
|
for (const name of ['style.css', 'vocabulary.css', 'library-editor.css']) {
|
|
assert.equal(existsSync(join(root, 'web/js/workspace', name)), false, `web/js/workspace/${name} must not come back`);
|
|
}
|
|
});
|