@ricsam/isolate-fs 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.test.ts DELETED
@@ -1,882 +0,0 @@
1
- import { test, describe, beforeEach, afterEach } from "node:test";
2
- import assert from "node:assert";
3
- import ivm from "isolated-vm";
4
- import { setupFs, clearAllInstanceState, type FileSystemHandler } from "./index.ts";
5
-
6
- // ============================================================================
7
- // MockFileSystem - In-memory file system for testing
8
- // ============================================================================
9
-
10
- class MockFileSystem implements FileSystemHandler {
11
- files = new Map<string, { data: Uint8Array; lastModified: number; type: string }>();
12
- directories = new Set<string>(["/"]); // Root always exists
13
-
14
- async getFileHandle(path: string, options?: { create?: boolean }): Promise<void> {
15
- const exists = this.files.has(path);
16
- if (!exists && !options?.create) {
17
- throw new Error("[NotFoundError]File not found: " + path);
18
- }
19
- if (this.directories.has(path)) {
20
- throw new Error("[TypeMismatchError]Path is a directory: " + path);
21
- }
22
- if (!exists && options?.create) {
23
- this.files.set(path, { data: new Uint8Array(0), lastModified: Date.now(), type: "" });
24
- }
25
- }
26
-
27
- async getDirectoryHandle(path: string, options?: { create?: boolean }): Promise<void> {
28
- const exists = this.directories.has(path);
29
- if (!exists && !options?.create) {
30
- throw new Error("[NotFoundError]Directory not found: " + path);
31
- }
32
- if (this.files.has(path)) {
33
- throw new Error("[TypeMismatchError]Path is a file: " + path);
34
- }
35
- if (!exists && options?.create) {
36
- this.directories.add(path);
37
- }
38
- }
39
-
40
- async removeEntry(path: string, options?: { recursive?: boolean }): Promise<void> {
41
- if (this.files.has(path)) {
42
- this.files.delete(path);
43
- return;
44
- }
45
-
46
- if (this.directories.has(path)) {
47
- // Check for children
48
- const prefix = path === "/" ? "/" : path + "/";
49
- const hasChildren =
50
- [...this.files.keys()].some((p) => p.startsWith(prefix)) ||
51
- [...this.directories].some((p) => p !== path && p.startsWith(prefix));
52
-
53
- if (hasChildren && !options?.recursive) {
54
- throw new Error("[InvalidModificationError]Directory not empty: " + path);
55
- }
56
-
57
- // Remove directory and all descendants
58
- for (const p of this.files.keys()) {
59
- if (p.startsWith(prefix)) {
60
- this.files.delete(p);
61
- }
62
- }
63
- for (const p of this.directories) {
64
- if (p.startsWith(prefix) || p === path) {
65
- this.directories.delete(p);
66
- }
67
- }
68
- return;
69
- }
70
-
71
- throw new Error("[NotFoundError]Entry not found: " + path);
72
- }
73
-
74
- async readDirectory(path: string): Promise<Array<{ name: string; kind: "file" | "directory" }>> {
75
- if (!this.directories.has(path)) {
76
- throw new Error("[NotFoundError]Directory not found: " + path);
77
- }
78
-
79
- const prefix = path === "/" ? "/" : path + "/";
80
- const entries: Array<{ name: string; kind: "file" | "directory" }> = [];
81
- const seen = new Set<string>();
82
-
83
- // Find files directly in this directory
84
- for (const p of this.files.keys()) {
85
- if (p.startsWith(prefix)) {
86
- const rest = p.slice(prefix.length);
87
- const name = rest.split("/")[0];
88
- if (name && !rest.includes("/") && !seen.has(name)) {
89
- seen.add(name);
90
- entries.push({ name, kind: "file" });
91
- }
92
- }
93
- }
94
-
95
- // Find subdirectories
96
- for (const p of this.directories) {
97
- if (p !== path && p.startsWith(prefix)) {
98
- const rest = p.slice(prefix.length);
99
- const name = rest.split("/")[0];
100
- if (name && !rest.includes("/") && !seen.has(name)) {
101
- seen.add(name);
102
- entries.push({ name, kind: "directory" });
103
- }
104
- }
105
- }
106
-
107
- return entries;
108
- }
109
-
110
- async readFile(
111
- path: string
112
- ): Promise<{ data: Uint8Array; size: number; lastModified: number; type: string }> {
113
- const file = this.files.get(path);
114
- if (!file) {
115
- throw new Error("[NotFoundError]File not found: " + path);
116
- }
117
- return {
118
- data: file.data,
119
- size: file.data.length,
120
- lastModified: file.lastModified,
121
- type: file.type,
122
- };
123
- }
124
-
125
- async writeFile(path: string, data: Uint8Array, position?: number): Promise<void> {
126
- const existing = this.files.get(path);
127
- if (!existing) {
128
- throw new Error("[NotFoundError]File not found: " + path);
129
- }
130
-
131
- if (position !== undefined && position > 0) {
132
- // Write at position
133
- const newSize = Math.max(existing.data.length, position + data.length);
134
- const newData = new Uint8Array(newSize);
135
- newData.set(existing.data);
136
- newData.set(data, position);
137
- existing.data = newData;
138
- } else if (position === 0) {
139
- // Overwrite from beginning
140
- const newSize = Math.max(existing.data.length, data.length);
141
- const newData = new Uint8Array(newSize);
142
- newData.set(existing.data);
143
- newData.set(data, 0);
144
- existing.data = newData;
145
- } else {
146
- // Append (no position specified means append to current content)
147
- // For simplicity, we treat undefined position as overwrite
148
- existing.data = data;
149
- }
150
- existing.lastModified = Date.now();
151
- }
152
-
153
- async truncateFile(path: string, size: number): Promise<void> {
154
- const file = this.files.get(path);
155
- if (!file) {
156
- throw new Error("[NotFoundError]File not found: " + path);
157
- }
158
- if (size < file.data.length) {
159
- file.data = file.data.slice(0, size);
160
- } else if (size > file.data.length) {
161
- const newData = new Uint8Array(size);
162
- newData.set(file.data);
163
- file.data = newData;
164
- }
165
- file.lastModified = Date.now();
166
- }
167
-
168
- async getFileMetadata(
169
- path: string
170
- ): Promise<{ size: number; lastModified: number; type: string }> {
171
- const file = this.files.get(path);
172
- if (!file) {
173
- throw new Error("[NotFoundError]File not found: " + path);
174
- }
175
- return {
176
- size: file.data.length,
177
- lastModified: file.lastModified,
178
- type: file.type,
179
- };
180
- }
181
- }
182
-
183
- // ============================================================================
184
- // Tests
185
- // ============================================================================
186
-
187
- describe("@ricsam/isolate-fs", () => {
188
- let isolate: ivm.Isolate;
189
- let context: ivm.Context;
190
- let mockFs: MockFileSystem;
191
-
192
- beforeEach(async () => {
193
- isolate = new ivm.Isolate();
194
- context = await isolate.createContext();
195
- mockFs = new MockFileSystem();
196
- clearAllInstanceState();
197
- });
198
-
199
- afterEach(() => {
200
- context.release();
201
- isolate.dispose();
202
- });
203
-
204
- describe("FileSystemDirectoryHandle", () => {
205
- test("getFileHandle creates new file", async () => {
206
- await setupFs(context, { getDirectory: async () => mockFs });
207
-
208
- const result = await context.eval(
209
- `
210
- (async () => {
211
- const root = await getDirectory("/");
212
- const fileHandle = root.getFileHandle("test.txt", { create: true });
213
- return JSON.stringify({ kind: fileHandle.kind, name: fileHandle.name });
214
- })();
215
- `,
216
- { promise: true }
217
- );
218
-
219
- const data = JSON.parse(result as string);
220
- assert.strictEqual(data.kind, "file");
221
- assert.strictEqual(data.name, "test.txt");
222
- assert.ok(mockFs.files.has("/test.txt"));
223
- });
224
-
225
- test("getFileHandle opens existing file", async () => {
226
- // Pre-create file
227
- mockFs.files.set("/existing.txt", {
228
- data: new TextEncoder().encode("hello"),
229
- lastModified: Date.now(),
230
- type: "text/plain",
231
- });
232
-
233
- await setupFs(context, { getDirectory: async () => mockFs });
234
-
235
- const result = await context.eval(
236
- `
237
- (async () => {
238
- const root = await getDirectory("/");
239
- const fileHandle = root.getFileHandle("existing.txt");
240
- const file = fileHandle.getFile();
241
- const text = file.text();
242
- return text;
243
- })();
244
- `,
245
- { promise: true }
246
- );
247
-
248
- assert.strictEqual(result, "hello");
249
- });
250
-
251
- test("getDirectoryHandle creates new directory", async () => {
252
- await setupFs(context, { getDirectory: async () => mockFs });
253
-
254
- const result = await context.eval(
255
- `
256
- (async () => {
257
- const root = await getDirectory("/");
258
- const dirHandle = root.getDirectoryHandle("subdir", { create: true });
259
- return JSON.stringify({ kind: dirHandle.kind, name: dirHandle.name });
260
- })();
261
- `,
262
- { promise: true }
263
- );
264
-
265
- const data = JSON.parse(result as string);
266
- assert.strictEqual(data.kind, "directory");
267
- assert.strictEqual(data.name, "subdir");
268
- assert.ok(mockFs.directories.has("/subdir"));
269
- });
270
-
271
- test("removeEntry removes file", async () => {
272
- mockFs.files.set("/to-delete.txt", {
273
- data: new Uint8Array(),
274
- lastModified: Date.now(),
275
- type: "",
276
- });
277
-
278
- await setupFs(context, { getDirectory: async () => mockFs });
279
-
280
- await context.eval(
281
- `
282
- (async () => {
283
- const root = await getDirectory("/");
284
- root.removeEntry("to-delete.txt");
285
- })();
286
- `,
287
- { promise: true }
288
- );
289
-
290
- assert.ok(!mockFs.files.has("/to-delete.txt"));
291
- });
292
-
293
- test("removeEntry removes directory recursively", async () => {
294
- mockFs.directories.add("/parent");
295
- mockFs.directories.add("/parent/child");
296
- mockFs.files.set("/parent/file.txt", {
297
- data: new Uint8Array(),
298
- lastModified: Date.now(),
299
- type: "",
300
- });
301
-
302
- await setupFs(context, { getDirectory: async () => mockFs });
303
-
304
- await context.eval(
305
- `
306
- (async () => {
307
- const root = await getDirectory("/");
308
- root.removeEntry("parent", { recursive: true });
309
- })();
310
- `,
311
- { promise: true }
312
- );
313
-
314
- assert.ok(!mockFs.directories.has("/parent"));
315
- assert.ok(!mockFs.directories.has("/parent/child"));
316
- assert.ok(!mockFs.files.has("/parent/file.txt"));
317
- });
318
-
319
- test("entries() iterates directory contents", async () => {
320
- mockFs.files.set("/file1.txt", {
321
- data: new Uint8Array(),
322
- lastModified: Date.now(),
323
- type: "",
324
- });
325
- mockFs.files.set("/file2.txt", {
326
- data: new Uint8Array(),
327
- lastModified: Date.now(),
328
- type: "",
329
- });
330
- mockFs.directories.add("/subdir");
331
-
332
- await setupFs(context, { getDirectory: async () => mockFs });
333
-
334
- const result = await context.eval(
335
- `
336
- (async () => {
337
- const root = await getDirectory("/");
338
- const entries = [];
339
- for await (const [name, handle] of root.entries()) {
340
- entries.push({ name, kind: handle.kind });
341
- }
342
- return JSON.stringify(entries.sort((a, b) => a.name.localeCompare(b.name)));
343
- })();
344
- `,
345
- { promise: true }
346
- );
347
-
348
- const entries = JSON.parse(result as string);
349
- assert.strictEqual(entries.length, 3);
350
- assert.deepStrictEqual(entries[0], { name: "file1.txt", kind: "file" });
351
- assert.deepStrictEqual(entries[1], { name: "file2.txt", kind: "file" });
352
- assert.deepStrictEqual(entries[2], { name: "subdir", kind: "directory" });
353
- });
354
-
355
- test("keys() returns file/directory names", async () => {
356
- mockFs.files.set("/a.txt", { data: new Uint8Array(), lastModified: Date.now(), type: "" });
357
- mockFs.files.set("/b.txt", { data: new Uint8Array(), lastModified: Date.now(), type: "" });
358
-
359
- await setupFs(context, { getDirectory: async () => mockFs });
360
-
361
- const result = await context.eval(
362
- `
363
- (async () => {
364
- const root = await getDirectory("/");
365
- const keys = [];
366
- for await (const name of root.keys()) {
367
- keys.push(name);
368
- }
369
- return JSON.stringify(keys.sort());
370
- })();
371
- `,
372
- { promise: true }
373
- );
374
-
375
- const keys = JSON.parse(result as string);
376
- assert.deepStrictEqual(keys, ["a.txt", "b.txt"]);
377
- });
378
-
379
- test("values() returns handles", async () => {
380
- mockFs.files.set("/test.txt", { data: new Uint8Array(), lastModified: Date.now(), type: "" });
381
-
382
- await setupFs(context, { getDirectory: async () => mockFs });
383
-
384
- const result = await context.eval(
385
- `
386
- (async () => {
387
- const root = await getDirectory("/");
388
- const handles = [];
389
- for await (const handle of root.values()) {
390
- handles.push({ name: handle.name, kind: handle.kind });
391
- }
392
- return JSON.stringify(handles);
393
- })();
394
- `,
395
- { promise: true }
396
- );
397
-
398
- const handles = JSON.parse(result as string);
399
- assert.strictEqual(handles.length, 1);
400
- assert.deepStrictEqual(handles[0], { name: "test.txt", kind: "file" });
401
- });
402
- });
403
-
404
- describe("FileSystemFileHandle", () => {
405
- test("getFile returns File object", async () => {
406
- const content = "file content here";
407
- mockFs.files.set("/myfile.txt", {
408
- data: new TextEncoder().encode(content),
409
- lastModified: 1234567890,
410
- type: "text/plain",
411
- });
412
-
413
- await setupFs(context, { getDirectory: async () => mockFs });
414
-
415
- const result = await context.eval(
416
- `
417
- (async () => {
418
- const root = await getDirectory("/");
419
- const fileHandle = root.getFileHandle("myfile.txt");
420
- const file = fileHandle.getFile();
421
- const text = await file.text();
422
- return JSON.stringify({
423
- name: file.name,
424
- size: file.size,
425
- type: file.type,
426
- lastModified: file.lastModified,
427
- text: text
428
- });
429
- })();
430
- `,
431
- { promise: true }
432
- );
433
-
434
- const data = JSON.parse(result as string);
435
- assert.strictEqual(data.name, "myfile.txt");
436
- assert.strictEqual(data.size, content.length);
437
- assert.strictEqual(data.type, "text/plain");
438
- assert.strictEqual(data.lastModified, 1234567890);
439
- assert.strictEqual(data.text, content);
440
- });
441
-
442
- test("createWritable returns WritableStream", async () => {
443
- mockFs.files.set("/writable.txt", {
444
- data: new Uint8Array(),
445
- lastModified: Date.now(),
446
- type: "",
447
- });
448
-
449
- await setupFs(context, { getDirectory: async () => mockFs });
450
-
451
- const result = await context.eval(
452
- `
453
- (async () => {
454
- const root = await getDirectory("/");
455
- const fileHandle = root.getFileHandle("writable.txt");
456
- const writable = fileHandle.createWritable();
457
- return typeof writable.write === 'function' && typeof writable.close === 'function';
458
- })();
459
- `,
460
- { promise: true }
461
- );
462
-
463
- assert.strictEqual(result, true);
464
- });
465
- });
466
-
467
- describe("FileSystemWritableFileStream", () => {
468
- test("write string data", async () => {
469
- mockFs.files.set("/test.txt", {
470
- data: new Uint8Array(),
471
- lastModified: Date.now(),
472
- type: "",
473
- });
474
-
475
- await setupFs(context, { getDirectory: async () => mockFs });
476
-
477
- await context.eval(
478
- `
479
- (async () => {
480
- const root = await getDirectory("/");
481
- const fileHandle = root.getFileHandle("test.txt");
482
- const writable = fileHandle.createWritable();
483
- writable.write("hello world");
484
- writable.close();
485
- })();
486
- `,
487
- { promise: true }
488
- );
489
-
490
- const file = mockFs.files.get("/test.txt");
491
- const text = new TextDecoder().decode(file!.data);
492
- assert.strictEqual(text, "hello world");
493
- });
494
-
495
- test("write ArrayBuffer data", async () => {
496
- mockFs.files.set("/binary.dat", {
497
- data: new Uint8Array(),
498
- lastModified: Date.now(),
499
- type: "",
500
- });
501
-
502
- await setupFs(context, { getDirectory: async () => mockFs });
503
-
504
- await context.eval(
505
- `
506
- (async () => {
507
- const root = await getDirectory("/");
508
- const fileHandle = root.getFileHandle("binary.dat");
509
- const writable = fileHandle.createWritable();
510
- const buffer = new Uint8Array([1, 2, 3, 4, 5]);
511
- writable.write(buffer);
512
- writable.close();
513
- })();
514
- `,
515
- { promise: true }
516
- );
517
-
518
- const file = mockFs.files.get("/binary.dat");
519
- assert.deepStrictEqual(Array.from(file!.data), [1, 2, 3, 4, 5]);
520
- });
521
-
522
- test("write at specific position", async () => {
523
- const initialData = new TextEncoder().encode("hello world");
524
- mockFs.files.set("/test.txt", {
525
- data: initialData,
526
- lastModified: Date.now(),
527
- type: "",
528
- });
529
-
530
- await setupFs(context, { getDirectory: async () => mockFs });
531
-
532
- await context.eval(
533
- `
534
- (async () => {
535
- const root = await getDirectory("/");
536
- const fileHandle = root.getFileHandle("test.txt");
537
- const writable = fileHandle.createWritable();
538
- writable.write({ type: 'write', data: 'XXXXX', position: 6 });
539
- writable.close();
540
- })();
541
- `,
542
- { promise: true }
543
- );
544
-
545
- const file = mockFs.files.get("/test.txt");
546
- const text = new TextDecoder().decode(file!.data);
547
- assert.strictEqual(text, "hello XXXXX");
548
- });
549
-
550
- test("seek changes position", async () => {
551
- mockFs.files.set("/test.txt", {
552
- data: new Uint8Array(20),
553
- lastModified: Date.now(),
554
- type: "",
555
- });
556
-
557
- await setupFs(context, { getDirectory: async () => mockFs });
558
-
559
- await context.eval(
560
- `
561
- (async () => {
562
- const root = await getDirectory("/");
563
- const fileHandle = root.getFileHandle("test.txt");
564
- const writable = fileHandle.createWritable();
565
- writable.write("start");
566
- writable.seek(10);
567
- writable.write("middle");
568
- writable.close();
569
- })();
570
- `,
571
- { promise: true }
572
- );
573
-
574
- const file = mockFs.files.get("/test.txt");
575
- const text = new TextDecoder().decode(file!.data);
576
- // "start" written at 0, then seek to 10, then "middle" written at 10
577
- assert.ok(text.startsWith("start"));
578
- assert.ok(text.slice(10).startsWith("middle"));
579
- });
580
-
581
- test("truncate changes file size", async () => {
582
- mockFs.files.set("/test.txt", {
583
- data: new TextEncoder().encode("hello world!"),
584
- lastModified: Date.now(),
585
- type: "",
586
- });
587
-
588
- await setupFs(context, { getDirectory: async () => mockFs });
589
-
590
- await context.eval(
591
- `
592
- (async () => {
593
- const root = await getDirectory("/");
594
- const fileHandle = root.getFileHandle("test.txt");
595
- const writable = fileHandle.createWritable();
596
- writable.truncate(5);
597
- writable.close();
598
- })();
599
- `,
600
- { promise: true }
601
- );
602
-
603
- const file = mockFs.files.get("/test.txt");
604
- assert.strictEqual(file!.data.length, 5);
605
- assert.strictEqual(new TextDecoder().decode(file!.data), "hello");
606
- });
607
-
608
- test("close finalizes write", async () => {
609
- mockFs.files.set("/test.txt", {
610
- data: new Uint8Array(),
611
- lastModified: Date.now(),
612
- type: "",
613
- });
614
-
615
- await setupFs(context, { getDirectory: async () => mockFs });
616
-
617
- // Test that writing after close throws
618
- const result = await context.eval(
619
- `
620
- (async () => {
621
- const root = await getDirectory("/");
622
- const fileHandle = root.getFileHandle("test.txt");
623
- const writable = fileHandle.createWritable();
624
- writable.write("initial");
625
- writable.close();
626
-
627
- try {
628
- writable.write("should fail");
629
- return "no error";
630
- } catch (e) {
631
- return e.name;
632
- }
633
- })();
634
- `,
635
- { promise: true }
636
- );
637
-
638
- assert.strictEqual(result, "InvalidStateError");
639
- });
640
- });
641
-
642
- describe("streaming", () => {
643
- test("stream() returns ReadableStream", async () => {
644
- const content = "streaming content test";
645
- mockFs.files.set("/stream.txt", {
646
- data: new TextEncoder().encode(content),
647
- lastModified: Date.now(),
648
- type: "text/plain",
649
- });
650
-
651
- await setupFs(context, { getDirectory: async () => mockFs });
652
-
653
- const result = await context.eval(
654
- `
655
- (async () => {
656
- const root = await getDirectory("/");
657
- const fileHandle = root.getFileHandle("stream.txt");
658
- const file = fileHandle.getFile();
659
- const stream = file.stream();
660
- return stream instanceof ReadableStream;
661
- })();
662
- `,
663
- { promise: true }
664
- );
665
-
666
- assert.strictEqual(result, true);
667
- });
668
-
669
- test("can read file in chunks", async () => {
670
- const content = "chunk1chunk2chunk3";
671
- mockFs.files.set("/chunks.txt", {
672
- data: new TextEncoder().encode(content),
673
- lastModified: Date.now(),
674
- type: "text/plain",
675
- });
676
-
677
- await setupFs(context, { getDirectory: async () => mockFs });
678
-
679
- const result = await context.eval(
680
- `
681
- (async () => {
682
- const root = await getDirectory("/");
683
- const fileHandle = root.getFileHandle("chunks.txt");
684
- const file = fileHandle.getFile();
685
- const stream = file.stream();
686
- const reader = stream.getReader();
687
-
688
- let fullContent = '';
689
- const decoder = new TextDecoder();
690
-
691
- while (true) {
692
- const { done, value } = await reader.read();
693
- if (done) break;
694
- fullContent += decoder.decode(value, { stream: true });
695
- }
696
-
697
- return fullContent;
698
- })();
699
- `,
700
- { promise: true }
701
- );
702
-
703
- assert.strictEqual(result, content);
704
- });
705
- });
706
-
707
- describe("error handling", () => {
708
- test("getFileHandle throws NotFoundError for missing file", async () => {
709
- await setupFs(context, { getDirectory: async () => mockFs });
710
-
711
- const result = await context.eval(
712
- `
713
- (async () => {
714
- try {
715
- const root = await getDirectory("/");
716
- root.getFileHandle("nonexistent.txt");
717
- return "no error";
718
- } catch (e) {
719
- return e.name;
720
- }
721
- })();
722
- `,
723
- { promise: true }
724
- );
725
-
726
- assert.strictEqual(result, "NotFoundError");
727
- });
728
-
729
- test("getDirectoryHandle throws NotFoundError for missing directory", async () => {
730
- await setupFs(context, { getDirectory: async () => mockFs });
731
-
732
- const result = await context.eval(
733
- `
734
- (async () => {
735
- try {
736
- const root = await getDirectory("/");
737
- root.getDirectoryHandle("nonexistent");
738
- return "no error";
739
- } catch (e) {
740
- return e.name;
741
- }
742
- })();
743
- `,
744
- { promise: true }
745
- );
746
-
747
- assert.strictEqual(result, "NotFoundError");
748
- });
749
-
750
- test("removeEntry throws InvalidModificationError for non-empty directory", async () => {
751
- mockFs.directories.add("/nonempty");
752
- mockFs.files.set("/nonempty/file.txt", {
753
- data: new Uint8Array(),
754
- lastModified: Date.now(),
755
- type: "",
756
- });
757
-
758
- await setupFs(context, { getDirectory: async () => mockFs });
759
-
760
- const result = await context.eval(
761
- `
762
- (async () => {
763
- try {
764
- const root = await getDirectory("/");
765
- root.removeEntry("nonempty");
766
- return "no error";
767
- } catch (e) {
768
- return e.name;
769
- }
770
- })();
771
- `,
772
- { promise: true }
773
- );
774
-
775
- assert.strictEqual(result, "InvalidModificationError");
776
- });
777
- });
778
-
779
- describe("isSameEntry", () => {
780
- test("returns true for same file", async () => {
781
- mockFs.files.set("/same.txt", { data: new Uint8Array(), lastModified: Date.now(), type: "" });
782
-
783
- await setupFs(context, { getDirectory: async () => mockFs });
784
-
785
- const result = await context.eval(
786
- `
787
- (async () => {
788
- const root = await getDirectory("/");
789
- const handle1 = root.getFileHandle("same.txt");
790
- const handle2 = root.getFileHandle("same.txt");
791
- return handle1.isSameEntry(handle2);
792
- })();
793
- `,
794
- { promise: true }
795
- );
796
-
797
- assert.strictEqual(result, true);
798
- });
799
-
800
- test("returns false for different files", async () => {
801
- mockFs.files.set("/file1.txt", { data: new Uint8Array(), lastModified: Date.now(), type: "" });
802
- mockFs.files.set("/file2.txt", { data: new Uint8Array(), lastModified: Date.now(), type: "" });
803
-
804
- await setupFs(context, { getDirectory: async () => mockFs });
805
-
806
- const result = await context.eval(
807
- `
808
- (async () => {
809
- const root = await getDirectory("/");
810
- const handle1 = root.getFileHandle("file1.txt");
811
- const handle2 = root.getFileHandle("file2.txt");
812
- return handle1.isSameEntry(handle2);
813
- })();
814
- `,
815
- { promise: true }
816
- );
817
-
818
- assert.strictEqual(result, false);
819
- });
820
- });
821
-
822
- describe("resolve", () => {
823
- test("returns path components for descendant", async () => {
824
- mockFs.directories.add("/parent");
825
- mockFs.directories.add("/parent/child");
826
- mockFs.files.set("/parent/child/file.txt", {
827
- data: new Uint8Array(),
828
- lastModified: Date.now(),
829
- type: "",
830
- });
831
-
832
- await setupFs(context, { getDirectory: async () => mockFs });
833
-
834
- const result = await context.eval(
835
- `
836
- (async () => {
837
- const root = await getDirectory("/");
838
- const parent = root.getDirectoryHandle("parent");
839
- const child = parent.getDirectoryHandle("child");
840
- const file = child.getFileHandle("file.txt");
841
-
842
- const pathFromRoot = root.resolve(file);
843
- return JSON.stringify(pathFromRoot);
844
- })();
845
- `,
846
- { promise: true }
847
- );
848
-
849
- const path = JSON.parse(result as string);
850
- assert.deepStrictEqual(path, ["parent", "child", "file.txt"]);
851
- });
852
-
853
- test("returns null for non-descendant", async () => {
854
- mockFs.directories.add("/dir1");
855
- mockFs.directories.add("/dir2");
856
- mockFs.files.set("/dir2/file.txt", {
857
- data: new Uint8Array(),
858
- lastModified: Date.now(),
859
- type: "",
860
- });
861
-
862
- await setupFs(context, { getDirectory: async () => mockFs });
863
-
864
- const result = await context.eval(
865
- `
866
- (async () => {
867
- const root = await getDirectory("/");
868
- const dir1 = root.getDirectoryHandle("dir1");
869
- const dir2 = root.getDirectoryHandle("dir2");
870
- const file = dir2.getFileHandle("file.txt");
871
-
872
- const path = dir1.resolve(file);
873
- return path;
874
- })();
875
- `,
876
- { promise: true }
877
- );
878
-
879
- assert.strictEqual(result, null);
880
- });
881
- });
882
- });