1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
// node modules/test/test-virtual-fs-require-leaf.js
// Proves the 2026-11-09 virtual-fs patch: phantom deletes are refused (_requireLeaf) and
// checkpoints stop cloning _mods / .semantic_index (while rollback keeps the live copies).
// Offline: no browser, no real localStorage, no IndexedDB, no timers.
const fsMod = require('fs'), path = require('path');
const ROOT = path.join(__dirname, '..', 'core');
// ── persistence stub: in-memory localStorage, IndexedDB that refuses to exist ──
const ls = {};
global.localStorage = { getItem: k => (k in ls ? ls[k] : null), setItem: (k, v) => { ls[k] = String(v); }, removeItem: k => { delete ls[k]; } };
global.indexedDB = { open() { throw new Error('no idb in tests'); }, deleteDatabase() { throw new Error('no idb in tests'); } };
global.window = global;
// The file ends with `window.VirtualFileSystem = VirtualFileSystem;` — so the class is a
// window global *and* is in scope at the bottom of the script. Return the real class object.
function load(file, cls) {
const src = fsMod.readFileSync(path.join(ROOT, file), 'utf8');
return new Function(src + '\nreturn ' + cls + ';')();
}
const VFS = load('virtual-fs.js', 'VirtualFileSystem');
let fails = 0, passes = 0;
function ok(name, cond, detail) {
if (cond) { passes++; return; }
fails++;
console.log('FAIL ' + name + (detail ? ' — ' + detail : ''));
}
function mk() {
const vfs = new VFS();
vfs.saveToStorage = () => true; // keep the 2s setTimeout saver out of the test
vfs.root = { type: 'dir', content: {} };
vfs.currentPath = [];
if (vfs._ready && typeof vfs._ready.catch === 'function') vfs._ready.catch(() => {});
return vfs;
}
const keysOf = node => Object.keys(node.content).sort();
// ── 0. export shape ────────────────────────────────────────────────────────────
ok('0 export shape: window.VirtualFileSystem is the class', typeof VFS === 'function' && global.window.VirtualFileSystem === VFS);
ok('0 constructor name', VFS.name === 'VirtualFileSystem', VFS.name);
ok('0 has _requireLeaf guard', typeof VFS.prototype._requireLeaf === 'function');
// ── A. deleteFile of a missing leaf must be refused, sibling untouched ─────────
{
const vfs = mk();
vfs.mkdirRecursive('/docs');
vfs.writeFile('/docs/real.txt', 'keep me');
ok('A1 deleteFile("/docs/ghost.txt") returns false', vfs.deleteFile('/docs/ghost.txt') === false);
ok('A2 sibling survived', vfs.readFile('/docs/real.txt') === 'keep me');
ok('A3 nothing was trashed', !vfs.root.content['.trash'] || keysOf(vfs.root.content['.trash']).length === 0);
ok('A4 deleteFile("/no/such/dir/x.txt") returns false', vfs.deleteFile('/no/such/dir/x.txt') === false);
// ── B. deleteFile of a real file removes exactly that file ────────────────
vfs.writeFile('/docs/other.txt', 'other');
ok('B1 deleteFile of a real file returns true', vfs.deleteFile('/docs/real.txt') === true);
ok('B2 the file is gone', vfs.readFile('/docs/real.txt') === null);
ok('B3 sibling intact', vfs.readFile('/docs/other.txt') === 'other');
ok('B4 only that entry was removed', JSON.stringify(keysOf(vfs.root.content.docs)) === '["other.txt"]', JSON.stringify(keysOf(vfs.root.content.docs)));
}
// ── C. deleteCheckpoint of a nonexistent checkpoint is refused ────────────────
{
const vfs = mk();
vfs.mkdirRecursive('/_snapshots');
vfs.writeFile('/_snapshots/checkpoint-keep.json', JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z' }));
ok('C1 deleteCheckpoint("nope") is falsy', !vfs.deleteCheckpoint('nope'));
ok('C2 _snapshots unchanged', JSON.stringify(keysOf(vfs.root.content['_snapshots'])) === '["checkpoint-keep.json"]', JSON.stringify(keysOf(vfs.root.content['_snapshots'])));
ok('C3 existing checkpoint still readable', !!vfs.fsReadJSON('/_snapshots/checkpoint-keep.json'));
ok('C4 real checkpoint delete still returns true', vfs.deleteCheckpoint('keep') === true);
ok('C5 it was actually removed', keysOf(vfs.root.content['_snapshots']).length === 0);
}
// ── D. a checkpoint must not carry _mods or .semantic_index ───────────────────
{
const vfs = mk();
vfs.mkdirRecursive('/_mods'); vfs.writeFile('/_mods/mod-a.js', 'MOD-ORIGINAL');
vfs.mkdirRecursive('/.semantic_index'); vfs.writeFile('/.semantic_index/idx.json', '{"nodes":1}');
vfs.writeFile('/notes.txt', 'plain');
const name = vfs.checkpoint('excl');
const snap = vfs.fsReadJSON('/_snapshots/checkpoint-' + name + '.json');
ok('D1 checkpoint written', !!snap && !!snap.root);
ok('D2 _mods absent from the snapshot root', !('_mods' in snap.root.content), JSON.stringify(Object.keys(snap.root.content)));
ok('D3 .semantic_index absent from the snapshot root', !('.semantic_index' in snap.root.content));
ok('D4 ordinary files are still snapshotted', snap.root.content['notes.txt'] && snap.root.content['notes.txt'].content === 'plain');
ok('D5 scaffolding still excluded (_snapshots/_sessions/.trash)',
!('_snapshots' in snap.root.content) && !('_sessions' in snap.root.content) && !('.trash' in snap.root.content));
// ── E. rollback keeps the LIVE _mods / .semantic_index ────────────────────
vfs.writeFile('/notes.txt', 'edited after checkpoint');
ok('E0 pre-rollback edit in place', vfs.readFile('/notes.txt') === 'edited after checkpoint');
ok('E1 rollback returns true', vfs.rollback(name) === true);
ok('E2 live _mods survives rollback', vfs.readFile('/_mods/mod-a.js') === 'MOD-ORIGINAL', String(vfs.readFile('/_mods/mod-a.js')));
ok('E3 live .semantic_index survives rollback', vfs.readFile('/.semantic_index/idx.json') === '{"nodes":1}');
ok('E4 ordinary file rolled back to the snapshot content', vfs.readFile('/notes.txt') === 'plain', String(vfs.readFile('/notes.txt')));
ok('E5 snapshot bookkeeping survived rollback', keysOf(vfs.root.content['_snapshots']).indexOf('checkpoint-' + name + '.json') !== -1);
}
// ── F. a legacy checkpoint that *does* embed _mods must not clobber the live one ──
{
const vfs = mk();
vfs.mkdirRecursive('/_mods'); vfs.writeFile('/_mods/live.js', 'LIVE');
vfs.mkdirRecursive('/.semantic_index'); vfs.writeFile('/.semantic_index/live.json', '{"live":true}');
vfs.writeFile('/f.txt', 'v1');
const name = vfs.checkpoint('legacy');
const p = '/_snapshots/checkpoint-' + name + '.json';
const snap = vfs.fsReadJSON(p); // simulate a pre-patch 4MB snapshot
snap.root.content['_mods'] = { type: 'dir', content: { 'stale.js': { type: 'file', content: 'STALE' } } };
snap.root.content['.semantic_index'] = { type: 'dir', content: { 'stale.json': { type: 'file', content: 'STALE' } } };
vfs.writeFile(p, JSON.stringify(snap));
ok('F1 rollback of a legacy snapshot succeeds', vfs.rollback(name) === true);
ok('F2 live _mods wins over the stale copy', vfs.readFile('/_mods/live.js') === 'LIVE' && vfs.readFile('/_mods/stale.js') === null);
ok('F3 live .semantic_index wins over the stale copy',
vfs.readFile('/.semantic_index/live.json') === '{"live":true}' && vfs.readFile('/.semantic_index/stale.json') === null);
}
// ── G. plain checkpoint/restore round trip is unbroken ───────────────────────
{
const vfs = mk();
vfs.mkdirRecursive('/work');
vfs.writeFile('/work/a.txt', 'version-1');
vfs.writeFile('/work/b.txt', 'b-1');
const name = vfs.checkpoint('roundtrip');
vfs.writeFile('/work/a.txt', 'version-2');
vfs.deleteFile('/work/b.txt');
vfs.writeFile('/work/new.txt', 'added-later');
ok('G1 rollback returns true', vfs.rollback(name) === true);
ok('G2 modified file restored', vfs.readFile('/work/a.txt') === 'version-1', String(vfs.readFile('/work/a.txt')));
ok('G3 deleted file restored', vfs.readFile('/work/b.txt') === 'b-1');
ok('G4 post-checkpoint file removed by the rollback', vfs.readFile('/work/new.txt') === null);
}
// ── H. measured saving on a realistic 2 MB _mods ────────────────────────────
{
const vfs = mk();
vfs.mkdirRecursive('/_mods');
vfs.writeFile('/_mods/bundle.js', 'x'.repeat(2 * 1024 * 1024)); // 2 MiB module originals
vfs.mkdirRecursive('/.semantic_index');
vfs.writeFile('/.semantic_index/idx.json', JSON.stringify({ nodes: 'y'.repeat(4096) }));
vfs.writeFile('/doc.txt', 'hello');
const name = vfs.checkpoint('big');
const raw = vfs.readFile('/_snapshots/checkpoint-' + name + '.json');
const after = raw.length;
// what the old code produced: the same snapshot with the two dirs cloned back in
const snap = JSON.parse(raw);
snap.root.content['_mods'] = JSON.parse(JSON.stringify(vfs.root.content['_mods']));
snap.root.content['.semantic_index'] = JSON.parse(JSON.stringify(vfs.root.content['.semantic_index']));
const before = JSON.stringify(snap).length;
console.log('MEASURE checkpoint bytes before exclusion: ' + before.toLocaleString('en-US'));
console.log('MEASURE checkpoint bytes after exclusion: ' + after.toLocaleString('en-US'));
console.log('MEASURE saved: ' + (before - after).toLocaleString('en-US') + ' bytes (' + (100 * (before - after) / before).toFixed(1) + '%)');
ok('H1 excluded snapshot stays small (< 200 KB)', after < 200 * 1024, after + ' bytes');
ok('H2 the old shape would have carried the 2 MiB _mods', before > 2 * 1024 * 1024, before + ' bytes');
}
if (fails === 0) {
console.log('test-virtual-fs-require-leaf: ' + passes + ' passed, 0 failed');
} else {
console.log('test-virtual-fs-require-leaf: ' + passes + ' passed, ' + fails + ' FAILED');
process.exit(1);
}
|