chore: clear tech debt — green the suite + delete dead code

The full Playwright suite had 5 pre-existing failures (stale assertions for
since-reworked behavior) and the classifier carried dead code from removed flows.

Stale tests refreshed to current behavior:
- toast.spec (×3): toast.js now STACKS (sticky/dismissible) rather than showing
  one at a time — assert stacking + the "Clear all" control, read the message
  from .zddc-toast__msg (the toast also holds a × button), and dismiss via the ×
  (clicking the body no longer dismisses, by design).
- browse.spec: "New folder/New file" moved from the toolbar into the context
  menu — drop the #newFolderBtn/#newFileBtn assertions (Sort + Show-hidden stay).
- tokens.spec XSS guard: rewritten to the current apiActions modal flow
  (#api-create-btn → .api-modal → #table-root) instead of the long-gone inline
  #desc form. The escaping assertion now actually runs and confirms it holds.

Dead code removed:
- classifier .mdl-overlay* CSS (orphaned when the "MDL from archive" instantiate
  flow moved to the tables tool).
- classify.js filesInNode() — defined + exported but called nowhere.
- "From a list" naming: refreshed a stale "catalog" comment and renamed the 3
  remaining "By existing:" test titles.

Full suite now 340 passed / 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-06-13 12:02:51 -05:00
parent 7c0b66590c
commit 921713d0a4
7 changed files with 46 additions and 56 deletions

View file

@ -628,17 +628,6 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o
.scratch-match__tn { font-family: var(--mono, monospace); } .scratch-match__tn { font-family: var(--mono, monospace); }
.scratch-match__conf { color: var(--text-muted); font-size: 0.72rem; width: 3rem; text-align: right; } .scratch-match__conf { color: var(--text-muted); font-size: 0.72rem; width: 3rem; text-align: right; }
/* ── MDL-from-archive overlay ───────────────────────────────────────────── */
.mdl-overlay { position: fixed; inset: 0; z-index: 1100; background: rgba(0,0,0,0.45); display: flex; align-items: center; justify-content: center; padding: 2rem 1rem; }
.mdl-overlay__box { background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 10px 40px rgba(0,0,0,0.3); width: 100%; max-width: 1000px; height: 80vh; display: flex; flex-direction: column; }
.mdl-overlay__head { display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1rem; border-bottom: 1px solid var(--border); }
.mdl-overlay__head h2 { margin: 0; font-size: 1.1rem; }
.mdl-overlay__close { background: none; border: none; font-size: 1.6rem; line-height: 1; color: var(--text-muted); cursor: pointer; padding: 0 0.4rem; }
.mdl-overlay__close:hover { color: var(--text); }
.mdl-overlay__status { padding: 0.4rem 1rem; color: var(--text-muted); font-size: 0.82rem; border-bottom: 1px solid var(--border); }
.mdl-overlay__table { flex: 1; min-height: 0; }
.mdl-overlay__foot { display: flex; justify-content: flex-end; gap: 0.5rem; padding: 0.75rem 1rem; border-top: 1px solid var(--border); }
/* ── Shared selectable + autofilter table (seltable) ────────────────────── */ /* ── Shared selectable + autofilter table (seltable) ────────────────────── */
.seltable { display: flex; flex-direction: column; min-height: 0; height: 100%; } .seltable { display: flex; flex-direction: column; min-height: 0; height: 100%; }
.seltable__bar { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.5rem; border-bottom: 1px solid var(--border); flex: 0 0 auto; } .seltable__bar { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.5rem; border-bottom: 1px solid var(--border); flex: 0 0 auto; }

View file

@ -378,15 +378,6 @@
return out; return out;
} }
// Files currently placed in a node (reverse lookup over all source files).
function filesInNode(nodeId, axis, allFiles) {
var field = axis === 'transmittal' ? 'transmittalNodeId' : 'trackingNodeId';
return (allFiles || []).filter(function (f) {
var a = state.assignments[srcKeyForFile(f)];
return a && a[field] === nodeId;
});
}
// Per-file classification state for the left-tree markers. // Per-file classification state for the left-tree markers.
// 'excluded' | 'done' | 'tracking' | 'transmittal' | 'partial' | 'none' // 'excluded' | 'done' | 'tracking' | 'transmittal' | 'partial' | 'none'
function fileState(file) { function fileState(file) {
@ -858,7 +849,7 @@
getNode: getNode, getTrackingTree: function () { return state.trackingTree; }, getNode: getNode, getTrackingTree: function () { return state.trackingTree; },
getTransmittalTree: function () { return state.transmittalTree; }, getTransmittalTree: function () { return state.transmittalTree; },
// derive + reverse // derive + reverse
deriveTarget: deriveTarget, filesInNode: filesInNode, deriveTarget: deriveTarget,
fileState: fileState, stats: stats, fileState: fileState, stats: stats,
// persistence // persistence
serialize: serialize, load: load, reset: reset, serialize: serialize, load: load, reset: reset,

View file

@ -601,11 +601,11 @@
}); });
} }
// Load the catalog: "Load…" opens a multi-select directory tree (scoped to // "From a list" loader: "Load…" opens a multi-select directory tree (scoped
// the served context); every ticked directory is walked recursively into the // to the served context); every ticked directory is walked recursively into
// union of existing files + MDL deliverables, deduped by tracking number to // the union of existing files + MDL deliverables, deduped by tracking number
// one row at the latest revision. Writes/alters nothing — the revision cell // to one row at the latest revision. Writes/alters nothing — the revision
// is classifier-local and starts blank. // cell is classifier-local and starts blank.
function isRowYaml(nm) { return /\.yaml$/i.test(nm) && nm !== 'table.yaml' && nm !== 'form.yaml'; } function isRowYaml(nm) { return /\.yaml$/i.test(nm) && nm !== 'table.yaml' && nm !== 'form.yaml'; }
// The newest combined "<rev> (<status>)" string in a set, by revision token. // The newest combined "<rev> (<status>)" string in a set, by revision token.

View file

@ -271,11 +271,10 @@ test.describe('Browse menu — context & tiers', () => {
expect(res.rwd).toContain('Delete…'); expect(res.rwd).toContain('Delete…');
}); });
test('toolbar Sort and Show-hidden drive state; New buttons present', async ({ page }) => { // New folder / New file are not toolbar buttons — they live in the
// row/pane context menu (see the "keyboard menu key and kebab" test).
test('toolbar Sort and Show-hidden drive state', async ({ page }) => {
await openWithTree(page); await openWithTree(page);
await expect(page.locator('#newFolderBtn')).toBeVisible();
await expect(page.locator('#newFileBtn')).toBeVisible();
await page.locator('#sortSelect').selectOption('date:-1'); await page.locator('#sortSelect').selectOption('date:-1');
expect(await page.evaluate(() => window.app.state.sort)).toEqual({ key: 'date', dir: -1 }); expect(await page.evaluate(() => window.app.state.sort)).toEqual({ key: 'date', dir: -1 });

View file

@ -1408,7 +1408,7 @@ test('proposeMatches finds a row whose tracking number is in the filename', asyn
expect(r[1].conf).toBeCloseTo(0.8); expect(r[1].conf).toBeCloseTo(0.8);
}); });
test('By existing: walkDirInto unions files + mdl deliverables, deduped to the latest revision', async ({ page }) => { test('From a list: walkDirInto unions files + mdl deliverables, deduped to the latest revision', async ({ page }) => {
await page.click('#modeClassifyBtn'); await page.click('#modeClassifyBtn');
const r = await page.evaluate(async () => { const r = await page.evaluate(async () => {
const tt = window.app.modules.targetTree; const tt = window.app.modules.targetTree;
@ -1459,7 +1459,7 @@ test('By existing: walkDirInto unions files + mdl deliverables, deduped to the l
expect(r.latestModifierWins).toBe('A+B1 (IFC)'); // A < A+B1 expect(r.latestModifierWins).toBe('A+B1 (IFC)'); // A < A+B1
}); });
test('By existing: _detectScope routes by URL/protocol', async ({ page }) => { test('From a list: _detectScope routes by URL/protocol', async ({ page }) => {
const r = await page.evaluate(() => { const r = await page.evaluate(() => {
const tt = window.app.modules.targetTree; const tt = window.app.modules.targetTree;
return { return {
@ -1475,7 +1475,7 @@ test('By existing: _detectScope routes by URL/protocol', async ({ page }) => {
expect(r.offlineHttp).toBe('local'); expect(r.offlineHttp).toBe('local');
}); });
test('By existing: dir-picker resolves the topmost ticked directories only', async ({ page }) => { test('From a list: dir-picker resolves the topmost ticked directories only', async ({ page }) => {
const r = await page.evaluate(() => { const r = await page.evaluate(() => {
const dp = window.app.modules.dirPicker; const dp = window.app.modules.dirPicker;
const childOfA = { handle: 'A/x', checked: true, children: [] }; const childOfA = { handle: 'A/x', checked: true, children: [] };

View file

@ -22,12 +22,13 @@ test.describe('shared/toast.js', () => {
expect(exposed).toBe(true); expect(exposed).toBe(true);
}); });
test('renders a single toast with the level class and ARIA role', async ({ page }) => { test('renders a toast with the level class and ARIA role', async ({ page }) => {
const after = await page.evaluate(() => { const after = await page.evaluate(() => {
window.zddc.toast('Saved.', 'success'); window.zddc.toast('Saved.', 'success');
const el = document.querySelector('.zddc-toast'); const el = document.querySelector('.zddc-toast');
return el && { return el && {
text: el.textContent, // The message lives in its own span (the toast also holds a × button).
text: el.querySelector('.zddc-toast__msg').textContent,
level: [...el.classList].find(c => c.startsWith('zddc-toast--')), level: [...el.classList].find(c => c.startsWith('zddc-toast--')),
role: el.getAttribute('role'), role: el.getAttribute('role'),
live: el.getAttribute('aria-live'), live: el.getAttribute('aria-live'),
@ -50,18 +51,24 @@ test.describe('shared/toast.js', () => {
expect(probe).toEqual({ role: 'alert', live: 'assertive' }); expect(probe).toEqual({ role: 'alert', live: 'assertive' });
}); });
test('a second toast replaces the first (single-toast policy)', async ({ page }) => { test('toasts stack, and a "Clear all" control appears at 2+', async ({ page }) => {
const count = await page.evaluate(() => { const r = await page.evaluate(() => {
window.zddc.toast('one', 'info'); window.zddc.toast('one', 'error'); // sticky so it stays for the count
window.zddc.toast('two', 'info'); window.zddc.toast('two', 'error');
return document.querySelectorAll('.zddc-toast').length; return {
count: document.querySelectorAll('.zddc-toast').length,
clearAll: !!document.querySelector('.zddc-toasts__clear'),
};
}); });
expect(count).toBe(1); expect(r.count).toBe(2); // stack, not replace
expect(r.clearAll).toBe(true); // "Clear all" surfaces when 2+ are stacked
}); });
test('clicking dismisses immediately', async ({ page }) => { test('the × button dismisses a toast; clicking the body does not', async ({ page }) => {
await page.evaluate(() => window.zddc.toast('click me', 'info')); await page.evaluate(() => window.zddc.toast('keep me', 'error')); // sticky
await page.locator('.zddc-toast').click(); await page.locator('.zddc-toast .zddc-toast__msg').click(); // selecting text ≠ dismiss
await expect(page.locator('.zddc-toast')).toHaveCount(1);
await page.locator('.zddc-toast .zddc-toast__close').click(); // × dismisses
await expect(page.locator('.zddc-toast')).toHaveCount(0); await expect(page.locator('.zddc-toast')).toHaveCount(0);
}); });
}); });

View file

@ -162,19 +162,23 @@ test.describe('/.tokens self-service token UI', () => {
}); });
test('XSS guard: description with HTML special chars is escaped on render', async ({ page }) => { test('XSS guard: description with HTML special chars is escaped on render', async ({ page }) => {
page.on('dialog', d => d.accept());
const xssDesc = `<img src=x onerror="window.__xss=1">`; const xssDesc = `<img src=x onerror="window.__xss=1">`;
await page.goto(`${server.baseURL}/.tokens`); await page.goto(`${server.baseURL}/.tokens`);
await page.fill('#desc', xssDesc); // Create via the apiActions modal (the inline #desc form is long gone).
await page.click('button[type="submit"]'); await page.locator('#api-create-btn').click();
// Wait for the row to appear in the table. await expect(page.locator('.api-modal')).toBeVisible();
await expect(page.locator('#tokens tbody')).toContainText('<img'); await page.locator('.api-modal input').first().fill(xssDesc);
// The literal <img> tag should NOT have been parsed as HTML — await page.locator('.api-modal button[type="submit"]').click();
// window.__xss must remain undefined. await expect(page.locator('.api-modal__secret')).toBeVisible();
const xssFired = await page.evaluate(() => window.__xss === 1); await page.locator('.api-modal button:has-text("Done")').click();
expect(xssFired).toBe(false); await page.waitForLoadState('networkidle');
// And the on-disk text content of the cell should contain the // The description renders as a row — as TEXT, not parsed HTML.
// literal angle brackets, proving they were escaped. const row = page.locator('#table-root tbody tr', { hasText: 'img src' });
const rowText = await page.locator('#tokens tbody tr', { hasText: 'img src' }).textContent(); await expect(row).toBeVisible();
expect(rowText).toContain('<img'); // The <img> must NOT have been parsed (its onerror never fires)…
expect(await page.evaluate(() => window.__xss === 1)).toBe(false);
// …and the literal angle brackets survive in the cell text.
expect(await row.textContent()).toContain('<img');
}); });
}); });