* fix(logs): declare the compressed length so a log download finishes The gzip middleware compressed a text body but kept the handler's declared Content-Length (the uncompressed size), so a browser waited for bytes that never came: downloading any log over 1 KB hung, in the workspace and on the admin page. The middleware now drops a declared length before compressing. Part of #329 * feat(system): show Diagnostics & logs natively and retire the hand-off panel System › Diagnostics & logs is native (D109, D110): the concept's Diagnostics surface with the live database, source, Viewer.js and renderer lines, Run checks (the shared Health report), the diagnostic report, Inspect and the confirmed test link; and the Logs surface with the minimum-level viewer, Lines 1-500, statistics, the log download, the runtime level and console setting saved by Save preferences, the log files with their downloads, Review log cleanup (the rotated_logs maintenance plan, no backup box, Listed files) and a confirmed Clear all logs. D94's hand-off panel, SECTION_HANDOFFS and the undelivered runtime adapter are gone. The browser server logs into its own temporary root. Part of #329 * test(system): compare Diagnostics & logs with the concept System › Diagnostics & logs and its log cleanup review are compared with the concept (app.js 694–695 and maintenancePlan("rotated logs")) in both themes at 1600x1100 and 390x844, with D109 and D110 (and D100 for the review) as the only mapped differences, a 320-wide select check and a mutation check. The fixture gains a concept seed mode (the concept's four log lines, a 1.4 MB active log with three rotated copies, console logging off), and the plan dialog's measureReview and mapPlanConcept move unchanged into concept-inventory.mjs for Maintenance and this review. The comparison found that the Database line read a report key that does not exist (database_info.size_bytes) and so always showed "0 bytes"; it now reads database_size_bytes, the key get_database_info() returns. Part of #329 * test(system): prove every legacy System capability has an owner TESTING.md gains "Legacy System capability coverage": every System capability of the legacy admin and gallery pages with its anchor on main, owner, status (covered, kept in legacy or approved removal), native control and the exact title of the test that proves it. tests/vendor/legacy-coverage.test.mjs (with tools/vendor/legacy-coverage.mjs) fails on a row with no owner, an unknown status, a malformed native control, a control on a row that is not covered, or a test title its file does not declare; a title only in a comment does not count. workspace-system.spec's "every covered legacy System capability has its native control" opens each covered row's section and finds the control by role and accessible name. Versus the design's table: the anchors are re-checked against main, "Run health checks" is on Maintenance (D123), not Overview, Diagnostics run is proven by "Run checks opens the shared Health report", and the export rows repeat their test title instead of "(same as above)". Part of #329 * fix(system): confirm Clear all logs on a fresh file list and record coverage Review fixes for the Diagnostics & logs section: - Clear all logs reads the log files again before it confirms, so the dialog names the copies it deletes now (a list it cannot read gives the general sentence, never an old count); Refresh reads the files too. - Runtime values that cannot be read stay disabled and say so. - Create test link holds its button until the write answered, so a second press cannot write a second record. - The Lines field names its range. - The browser server builds its logger in its own root, so no log file is ever opened in another folder; the fixture closes every directory scan and replaces a link instead of writing through it. - system.mjs drops the unused section fallback; frontend coverage floors recorded with coverage:update (system-diagnostics.mjs 97.5% lines). Part of #329 * fix(system): read the database size the report sends The Database line read database_info.size_bytes, a key the report does not have (get_database_info answers database_size_bytes), so it always read "Readable · 0 bytes · N images". The browser test now requires a non-zero size. Found by PR 3's concept comparison. Part of #329 * test(system): require a running test and a control for every covered capability Review fixes for the legacy coverage proof: - A title declared only with test.skip, .fixme or .fail no longer proves a row (.only and .slow still do). - Every covered row names its native control, except the rows in CONTROL_EXEMPTIONS, each with its reason (today only the gallery per-image auto-tag row, owned by the Images selection bar). The browser test asserts that the covered rows without a control are exactly the exemptions. - JavaScript comments are stripped across the whole source, wherever a block comment starts, skipping strings, template literals and regular expressions. A Python def test_ counts only at function or method indentation and outside comments and docstrings. - A control name several rows share ("Review plan", "Review options", "Refresh") is looked up inside the row the table names (· in "<row>"). - The diagnostics comparison records each select's chosen option, so a wrong default level is caught. Part of #329
117 lines
7.2 KiB
JavaScript
117 lines
7.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { COLUMNS, CONTROL_EXEMPTIONS, STATUSES, declaredTitles, parseControl, parseCoverageTable, readCoverageTable,
|
|
rowProblems } from '../../tools/vendor/legacy-coverage.mjs';
|
|
|
|
// TESTING.md "Legacy System capability coverage" (#249 System S12, #329): every legacy System capability names an
|
|
// owner, a known status, a well-formed native control and a test its file really declares.
|
|
|
|
const ROW = Object.freeze({ Capability: 'Clear/truncate logs', 'Legacy anchor': '`admin.html:915`', Owner: 'S12 Logs',
|
|
Status: 'covered', 'Native control': '`diagnostics` · button · "Clear all logs…"', Test: 'clears the logs (clear.spec.mjs)' });
|
|
const declaring = source => ({ locate: file => `tests/browser/${file}`, read: () => source });
|
|
|
|
test('every coverage row has an owner, a known status and a declared test', () => {
|
|
const rows = readCoverageTable();
|
|
assert.ok(rows.length >= 54, `the table lists ${rows.length} capabilities`);
|
|
for (const row of rows) {
|
|
assert.deepEqual(rowProblems(row), [], `TESTING.md:${row.line} ${row.Capability}`);
|
|
assert.ok(row.Owner.trim());
|
|
assert.ok(STATUSES.includes(row.Status));
|
|
}
|
|
assert.ok(rows.some(row => row.Status === 'kept in legacy') && rows.some(row => row.Status === 'approved removal'));
|
|
// A broken row fails: an empty owner, an unknown status, a test its file does not declare.
|
|
const [first] = rows;
|
|
assert.deepEqual(rowProblems({ ...first, Owner: '' }), ['no owner']);
|
|
assert.ok(rowProblems({ ...first, Status: 'done' }).includes('unknown status "done"'));
|
|
assert.match(rowProblems({ ...first, Test: first.Test.replace(/^(.+) \(/, '$1 again (') }).join(), /declares no test/);
|
|
assert.match(rowProblems({ ...first, Test: 'some title (missing.spec.mjs)' }).join(), /no test file missing\.spec\.mjs/);
|
|
});
|
|
|
|
test('a title that appears only in a comment does not count', () => {
|
|
const commented = [
|
|
"// test('clears the logs', async () => {});",
|
|
"/* test('clears the logs', async () => {}); */",
|
|
'/**',
|
|
" * it('clears the logs')",
|
|
' */',
|
|
"const title = 'clears the logs';",
|
|
"test.describe('clears the logs', () => {});",
|
|
].join('\n');
|
|
assert.equal(declaredTitles(commented, 'clear.spec.mjs').has('clears the logs'), false);
|
|
assert.deepEqual(rowProblems(ROW, declaring(commented)), ['clear.spec.mjs declares no test "clears the logs"']);
|
|
const declared = `${commented}\nfor (const theme of THEMES) {\n test('clears the logs', async ({ page }) => {});\n}\n`;
|
|
assert.deepEqual(rowProblems(ROW, declaring(declared)), []);
|
|
// Escaped quotes, template literals, modifiers and a title on the next line are declarations too.
|
|
const titles = declaredTitles("test('a gate\\'s refusal', () => {});\ntest.only(`b`, () => {});\nit(\n 'c', () => {});",
|
|
'x.test.js');
|
|
assert.deepEqual([...titles], ["a gate's refusal", 'b', 'c']);
|
|
// A block comment that starts after code on a line, and a `//` or `/*` inside a string or a regular expression.
|
|
const trailing = [
|
|
"const url = 'http://host/*'; /* test('clears the logs', () => {});",
|
|
"test('clears the logs', () => {}); */",
|
|
"const pattern = /['\"`]\\/\\*/;",
|
|
"test('after the pattern', () => {});",
|
|
].join('\n');
|
|
assert.deepEqual([...declaredTitles(trailing, 'x.spec.mjs')], ['after the pattern']);
|
|
// Python: only a real `def test_…` counts, never a commented one.
|
|
const python = ['# def test_report_lists_config_only_keys(self):', 'class T:', ' \"\"\"Example:', '',
|
|
' def test_in_a_docstring(self):', ' \"\"\"', ' def test_other(self):', ' def test_nested():',
|
|
' pass', 'def test_module():', ' pass', ''].join('\n');
|
|
assert.deepEqual([...declaredTitles(python, 'test_x.py')], ['test_other', 'test_module']);
|
|
});
|
|
|
|
test('a malformed native control cell fails', () => {
|
|
assert.deepEqual(parseControl('`diagnostics` · button · "Clear all logs…"'),
|
|
{ section: 'diagnostics', role: 'button', name: 'Clear all logs…', row: null });
|
|
assert.deepEqual(parseControl('`maintenance` · button · "Review plan" · in "Duplicate prompt text"'),
|
|
{ section: 'maintenance', role: 'button', name: 'Review plan', row: 'Duplicate prompt text' });
|
|
assert.equal(parseControl('—'), null);
|
|
for (const [cell, message] of [
|
|
['diagnostics · button · "Clear all logs…"', /malformed/],
|
|
['`diagnostics` · button · Clear all logs…', /malformed/],
|
|
['`diagnostics` - button - "Clear all logs…"', /malformed/],
|
|
['', /malformed/],
|
|
['`logs` · button · "Clear all logs…"', /unknown System section "logs"/],
|
|
['`diagnostics` · link · "Clear all logs…"', /unknown role "link"/],
|
|
]) {
|
|
assert.throws(() => parseControl(cell), message, cell);
|
|
assert.match(rowProblems({ ...ROW, 'Native control': cell }, declaring("test('clears the logs', () => {});")).join(), message);
|
|
}
|
|
// Only a covered row names a control.
|
|
assert.deepEqual(rowProblems({ ...ROW, Status: 'kept in legacy' }, declaring("test('clears the logs', () => {});")),
|
|
['a kept in legacy row names a native control']);
|
|
// The table itself must keep its six columns.
|
|
assert.throws(() => parseCoverageTable(`### Legacy System capability coverage\n\n| Capability | Owner |\n| --- | --- |\n`), /columns/);
|
|
assert.deepEqual(COLUMNS, ['Capability', 'Legacy anchor', 'Owner', 'Status', 'Native control', 'Test']);
|
|
});
|
|
|
|
test('a skipped test does not prove a capability', () => {
|
|
for (const modifier of ['skip', 'fixme', 'fail']) {
|
|
assert.deepEqual(rowProblems(ROW, declaring(`test.${modifier}('clears the logs', async () => {});`)),
|
|
['clear.spec.mjs only skips "clears the logs"'], modifier);
|
|
}
|
|
for (const modifier of ['only', 'slow']) {
|
|
assert.deepEqual(rowProblems(ROW, declaring(`test.${modifier}('clears the logs', async () => {});`)), [], modifier);
|
|
}
|
|
// A running declaration beside a skipped one counts.
|
|
assert.deepEqual(rowProblems(ROW, declaring("test.skip('clears the logs', () => {});\ntest('clears the logs', () => {});")), []);
|
|
});
|
|
|
|
test('a covered row names its native control unless it is exempt', () => {
|
|
const source = declaring("test('clears the logs', () => {});");
|
|
assert.deepEqual(rowProblems({ ...ROW, 'Native control': '—' }, source), ['a covered row names no native control']);
|
|
for (const status of ['kept in legacy', 'approved removal']) {
|
|
assert.deepEqual(rowProblems({ ...ROW, Status: status, 'Native control': '—' }, source), [], status);
|
|
}
|
|
const [exempt] = Object.keys(CONTROL_EXEMPTIONS);
|
|
assert.deepEqual(rowProblems({ ...ROW, Capability: exempt, 'Native control': '—' }, source), []);
|
|
// Every exemption has a reason and names a covered row of the table that has no control.
|
|
const rows = readCoverageTable();
|
|
for (const [capability, reason] of Object.entries(CONTROL_EXEMPTIONS)) {
|
|
assert.ok(reason.length > 20, capability);
|
|
const row = rows.find(item => item.Capability === capability);
|
|
assert.ok(row, `${capability} is in the table`);
|
|
assert.deepEqual([row.Status, row['Native control']], ['covered', '—']);
|
|
}
|
|
});
|