Skip to content

Commit 6a532e6

Browse files
Prithvi-Webclaude
andcommitted
test(compare, guard, a11y, snapshots): the review's notes on the CI fixtures: paths read as spelled, drive roots, the Linux Trash answer in any order, every view's errors caught
An independent review of 981add4, 28e6098 and b788ae9 found nothing at high or medium. Its notes: - compareUnlisted read each path through path.relative, which reads a doubled separator away, so a store that spelled "//Mail" passed. It now reads the path exactly as spelled, and the fixtures also root a whole drive's scan ("/" and "C:\") and a share's ("\\srv\share\"), whose roots already end in their separator: with the store's join for such a root broken, the whole-drive spelling now fails. The comment says four tests failed on Windows, not every one. - realMachineGuard named the home Trash folder only as the first failure. On a Linux machine with another drive whose Trash folder cannot be looked at, that drive is named first. The test now asks that the home folder is among the failures, and still that gio is not. - hiddenBoxesSaidAloud took "the registry caught nothing" as proof the Treemap view's unmount ran to its end. It now counts the unmount's last step (the mode pills' rings turned off) once per visit away; with the unmount made to return early, the test fails. And the check that the registry caught nothing moves from that one test into the harness every view test here shares (viewsPage): each switch of view and each frame fails, naming the error, if the registry logged one from a view's mount, unmount or onScanChange. No other view had one; with a name nobody gave put into Disk City's unmount or the Dashboard's mount, their tests now fail. - snapshotDiffCutoff rooted its fixture at path.join(path.sep, 'snap-root'), drive-less on Windows, the spelling that failed compareUnlisted there. It is rooted on its drive now; on a Mac and on Linux the path is the same as before. Checked on default Node, Node 20 with a 2 GB heap and pt-BR; the Linux branch of the Trash test with the child told it runs on Linux, red when gio's entry is added back or the home folder's is dropped; each mutant restored by SHA-256, the mutants of the built page among them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 92b03b9 commit 6a532e6

4 files changed

Lines changed: 53 additions & 16 deletions

File tree

‎tests/compareUnlisted.test.ts‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,17 @@ import { waitFor } from './fixtures/waitFor';
3838
* off the machine — the store takes it from the root's own spelling (separatorOf). A root spelled `\cmp-root`,
3939
* with no drive, as `path.join(path.sep, …)` spells it on Windows, is no Windows scan's root: separatorOf reads it
4040
* as a POSIX path, the store joins its children with "/", and no child spelled with "\" is found in it (the
41-
* Windows leg of 25 Oct 2026 failed every test here with "PackedScanStore: no node -1").
41+
* Windows leg of 25 Oct 2026 failed each test here that looks a path up, with "PackedScanStore: no node -1").
42+
* A scan of a whole drive, or of a Windows share, is rooted at a path that already ends in its separator, which the
43+
* store joins without adding another: those roots are spelled here too.
4244
*/
4345
interface Spelling { name: string; p: path.PlatformPath; root: string }
4446
const SPELLINGS: readonly Spelling[] = [
4547
{ name: 'spelled as a Mac or Linux scan spells it', p: path.posix, root: '/cmp-root' },
4648
{ name: 'spelled as a Windows scan spells it', p: path.win32, root: 'C:\\cmp-root' },
49+
{ name: 'a Mac or Linux scan of the whole drive', p: path.posix, root: '/' },
50+
{ name: 'a Windows scan of a whole drive', p: path.win32, root: 'C:\\' },
51+
{ name: 'a Windows scan of a share', p: path.win32, root: '\\\\srv\\share\\' },
4752
];
4853

4954
/** A complete scan of the spelling's root holding `tree` (relative path → bytes, a trailing / for a folder). */
@@ -73,8 +78,14 @@ function scanOf(s: Spelling, tree: Record<string, number>, unlisted: string[] =
7378

7479
const MAIL = { 'Mail/': 0, 'Mail/msg-0.emlx': 4000, 'Mail/msg-1.emlx': 4000, 'Mail/msg-2.emlx': 4000 };
7580
/** Each entry as "<path under the root, with / between names> <change>", whatever separator the scan spelled it with. */
76-
const rows = (s: Spelling, r: { entries: { path: string; change: string }[] }): string[] =>
77-
r.entries.map((e) => `${s.p.relative(s.root, e.path).split(s.p.sep).join('/')} ${e.change}`).sort();
81+
const rows = (s: Spelling, r: { entries: { path: string; change: string }[] }): string[] => {
82+
// Read off each path exactly as the store spelled it: path.relative would read a doubled separator away.
83+
const base = s.root.endsWith(s.p.sep) ? s.root : s.root + s.p.sep;
84+
return r.entries.map((e) => {
85+
assert.ok(e.path.startsWith(base), `${s.name}: ${e.path} is spelled under ${base}`);
86+
return `${e.path.slice(base.length).split(s.p.sep).join('/')} ${e.change}`;
87+
}).sort();
88+
};
7889

7990
test('files in a folder the newer scan could not list are not called removed, and the folder is counted', () => {
8091
for (const s of SPELLINGS) {

‎tests/hiddenBoxesSaidAloud.test.ts‎

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,7 +2078,10 @@ const noop = () => {};
20782078
* `real`, each one's own object lifted, its mount and unmount with it — and `standIns`, which mount nothing and have
20792079
* no unmount. Each view's section holds its live regions as the markup has them; every other live region stands in
20802080
* the page's main part, on screen. Every view starts hidden. `decls`, `given` and `missing` as page() takes them; a
2081-
* frame as waitingPage runs one.
2081+
* frame as waitingPage runs one. Every switch and every frame checks that the registry caught nothing from a view's
2082+
* own mount, unmount or onScanChange: it logs such an error and goes on (020), so a name the harness did not give
2083+
* threw there unseen and the rest of that view's code never ran (the Treemap view's unmount, until 25 Oct 2026,
2084+
* stopped at `tmLoadSeq++` on every leg of CI).
20822085
*/
20832086
function viewsPage(real: string[], standIns: string[], decls: string[] = [], given: Record<string, unknown> = {}, missing: string[] = []) {
20842087
const ro = resizeObservers();
@@ -2091,12 +2094,31 @@ function viewsPage(real: string[], standIns: string[], decls: string[] = [], giv
20912094
...real.map((id) => `VIEW_BY_ID.set('${id}', ${viewSrc(id)});`),
20922095
...standIns.map((id) => `VIEW_BY_ID.set('${id}', { id: '${id}', label: '${id}', icon: 'x', needsScan: false, mount() {} });`),
20932096
];
2097+
const caught: string[] = [];
2098+
const pageConsole = {
2099+
...console,
2100+
error: (...args: unknown[]) => {
2101+
const line = args.map(String).join(' ');
2102+
if (/^\[treemap\] (?:unmount of|mount of|onScanChange for) "/.test(line)) caught.push(line);
2103+
else console.error(...args);
2104+
},
2105+
};
20942106
const p = page([...WAIT, ...SWITCH, ...decls], {
20952107
ResizeObserver: ro.ResizeObserver, MutationObserver: mo.MutationObserver,
20962108
document: { body, activeElement: null, querySelectorAll: (sel: string) => (sel === '.view' ? sections : []) },
20972109
tabs: [], hideTooltip: noop, hideCtxMenu: noop, selectionBarFollowView: noop, renderEmptyState: noop,
2098-
fxEmptyCtaSync: noop, fxScanHeroSync: noop, renderCapabilityNotice: noop, fxViewEnter: noop, toast: noop, ...given,
2110+
fxEmptyCtaSync: noop, fxScanHeroSync: noop, renderCapabilityNotice: noop, fxViewEnter: noop, toast: noop,
2111+
console: pageConsole, ...given,
20992112
}, missing, run);
2113+
const nothingCaught = (when: string): void => {
2114+
assert.deepEqual(caught.splice(0), [], `control, ${when}: the registry caught nothing from a view's own code`);
2115+
};
2116+
const switchView = p.fns.switchView;
2117+
p.fns.switchView = (...args: unknown[]) => {
2118+
const out = switchView(...args);
2119+
nothingCaught(`switching to ${String(args[0])}`);
2120+
return out;
2121+
};
21002122
for (const id of [...real, ...standIns]) {
21012123
const section = p.$(`view-${id}`);
21022124
section.id = `view-${id}`;
@@ -2112,7 +2134,7 @@ function viewsPage(real: string[], standIns: string[], decls: string[] = [], giv
21122134
if (!live.parent) Object.assign(live, { id: r, parent: main });
21132135
p.fns.aloudKnow(live);
21142136
}
2115-
const frame = () => { mo.microtasks(); p.frame(mo.microtasks); ro.layout(); mo.microtasks(); };
2137+
const frame = () => { mo.microtasks(); p.frame(mo.microtasks); ro.layout(); mo.microtasks(); nothingCaught('in a frame'); };
21162138
frame();
21172139
return { ...p, frame };
21182140
}
@@ -2560,20 +2582,21 @@ test('the notes over and under the map are taken back as the Treemap view goes
25602582
treemap: { rootPath: '/r', altNote: '', altZoomRaf: 0, altZoom: null as unknown, cells: [] as unknown[] },
25612583
};
25622584
const map: { draw: () => void } = { draw: noop };
2563-
// What the registry caught from the view's own mount or unmount (020: it logs the error and goes on). A name the
2564-
// harness did not give threw there unseen: the unmount stopped at `tmLoadSeq++`, and the rest of its teardown never ran.
2565-
const caught: string[] = [];
2585+
// The unmount's last step turns the mode pills' rings off (fxTmPillBeamsSync(false)): one more of those per visit away
2586+
// is the unmount reaching its end, counted, not inferred from the registry catching nothing (viewsPage checks that).
2587+
let pillsOff = 0;
25662588
const p = viewsPage(['treemap'], ['elsewhere'], ['function tmSetEmpty(', 'function renderAltNote(', 'function altRefiningNote('], {
25672589
state: st, tmAnimFrame: 0, tmResizeDeb: 0, tmLoadSeq: 0, treeScanId: () => 's1',
2568-
console: { ...console, error: (...args: unknown[]) => { caught.push(args.map(String).join(' ')); } },
25692590
// Its unmount's teardown, and its mount's loads, the map's own landing (draw, below) among them.
25702591
tmCancelQueries: noop, tmCloseHints: noop, tmClearHits: noop, nlClose: noop, exitCartPreview: noop, tmSetQueryMessage: noop, disableLive: noop,
2571-
altRefineCancel: noop, lapseStop: noop, lassoCancel: noop, fxTmPillBeamsSync: noop, loadSavedViews: noop, refreshDock: noop, tmRejudgeQuery: noop,
2592+
altRefineCancel: noop, lapseStop: noop, lassoCancel: noop, loadSavedViews: noop, refreshDock: noop, tmRejudgeQuery: noop,
2593+
fxTmPillBeamsSync: (on: boolean) => { if (on === false) pillsOff += 1; },
25722594
tmStopMorph: noop, loadTreemap: () => map.draw(),
25732595
});
25742596
const leave = (what: string): void => {
2597+
const before = pillsOff;
25752598
p.fns.switchView('elsewhere');
2576-
assert.deepEqual(caught, [], `${what}: control, the Treemap view's own unmount ran to its end — the registry caught nothing from it`);
2599+
assert.equal(pillsOff, before + 1, `${what}: control, the Treemap view's own unmount ran to its last step, once`);
25772600
};
25782601
const empty = 'This folder is empty.';
25792602
const cap = 'The map holds at most 250,000 items, and this folder reached that: 1.2 GB has no rectangle here — drill in, or open the Grid view, to see the rest.';

‎tests/realMachineGuard.test.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -630,10 +630,11 @@ test("outside a test runner, emptying the Trash goes ahead: to the machine's own
630630
return;
631631
}
632632
// On Linux, with gio not run, the freedesktop fallback empties the Trash folders itself, and the disarmed machine
633-
// refuses it the first of them, the home folder's. The answer names that folder, and only what the fallback could
634-
// not clear: once the fallback ran, gio's absence is no reason the Trash stayed full (runTrashEmptiers).
633+
// refuses it the home folder's. The answer names that folder, and only what the fallback could not clear: once the
634+
// fallback ran, gio's absence is no reason the Trash stayed full (runTrashEmptiers). Not necessarily first: a Trash
635+
// folder on another drive the fallback could not look at would be named before it (clearFreedesktopTrash).
635636
const homeTrash = path.join(child.home, '.local', 'share', 'Trash', 'files');
636-
assert.equal(result.failed[0]?.location, homeTrash, `and the answer names the Trash folder the fallback could not clear: ${JSON.stringify(value)}`);
637+
assert.ok(result.failed.some((f) => f.location === homeTrash), `and the answer names the Trash folder the fallback could not clear: ${JSON.stringify(value)}`);
637638
assert.ok(!result.failed.some((f) => f.location === cmd), `and not gio, which the fallback stood in for: ${JSON.stringify(value)}`);
638639
});
639640

‎tests/snapshotDiffCutoff.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import type { FileNode, Snapshot } from '../src/models/types';
2222
* one missing from a list that holds every item is removed or new, as before.
2323
*/
2424

25-
const ROOT = path.join(path.sep, 'snap-root');
25+
// On its drive, as every Windows scan's root is: path.join(path.sep, …) gives Windows the drive-less "\snap-root",
26+
// which no Windows scan has (the spelling compareUnlisted's fixtures failed with there).
27+
const ROOT = path.resolve(path.sep, 'snap-root');
2628

2729
function scanWith(children: { name: string; size: number; dir?: boolean }[]) {
2830
const nodes: FileNode[] = children.map((c) => ({

0 commit comments

Comments
 (0)