@scelar/nodepod 1.0.2 → 1.0.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/dist/__tests__/bench/integration.bench.d.ts +1 -0
- package/dist/__tests__/bench/memory-volume.bench.d.ts +1 -0
- package/dist/__tests__/bench/polyfills.bench.d.ts +1 -0
- package/dist/__tests__/bench/script-engine.bench.d.ts +1 -0
- package/dist/__tests__/bench/shell.bench.d.ts +1 -0
- package/dist/__tests__/bench/syntax-transforms.bench.d.ts +1 -0
- package/dist/__tests__/bench/version-resolver.bench.d.ts +1 -0
- package/dist/__tests__/buffer.test.d.ts +1 -0
- package/dist/__tests__/byte-encoding.test.d.ts +1 -0
- package/dist/__tests__/digest.test.d.ts +1 -0
- package/dist/__tests__/events.test.d.ts +1 -0
- package/dist/__tests__/memory-volume.test.d.ts +1 -0
- package/dist/__tests__/path.test.d.ts +1 -0
- package/dist/__tests__/process.test.d.ts +1 -0
- package/dist/__tests__/script-engine.test.d.ts +1 -0
- package/dist/__tests__/shell-builtins.test.d.ts +1 -0
- package/dist/__tests__/shell-interpreter.test.d.ts +1 -0
- package/dist/__tests__/shell-parser.test.d.ts +1 -0
- package/dist/__tests__/stream.test.d.ts +1 -0
- package/dist/__tests__/syntax-transforms.test.d.ts +1 -0
- package/dist/__tests__/version-resolver.test.d.ts +1 -0
- package/dist/{child_process-Dopvyd-E.js → child_process-D6oDN2MX.js} +4 -4
- package/dist/{child_process-Dopvyd-E.js.map → child_process-D6oDN2MX.js.map} +1 -1
- package/dist/{child_process-B38qoN6R.cjs → child_process-hmVqFcF7.cjs} +5 -5
- package/dist/{child_process-B38qoN6R.cjs.map → child_process-hmVqFcF7.cjs.map} +1 -1
- package/dist/{index--Qr8LVpQ.js → index-Ale2oba_.js} +240 -136
- package/dist/index-Ale2oba_.js.map +1 -0
- package/dist/{index-cnitc68U.cjs → index-BO1i013L.cjs} +236 -191
- package/dist/index-BO1i013L.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/script-engine.d.ts +2 -0
- package/dist/syntax-transforms.d.ts +1 -0
- package/package.json +97 -95
- package/src/__tests__/bench/integration.bench.ts +117 -0
- package/src/__tests__/bench/memory-volume.bench.ts +115 -0
- package/src/__tests__/bench/polyfills.bench.ts +147 -0
- package/src/__tests__/bench/script-engine.bench.ts +104 -0
- package/src/__tests__/bench/shell.bench.ts +101 -0
- package/src/__tests__/bench/syntax-transforms.bench.ts +82 -0
- package/src/__tests__/bench/version-resolver.bench.ts +95 -0
- package/src/__tests__/buffer.test.ts +273 -0
- package/src/__tests__/byte-encoding.test.ts +98 -0
- package/src/__tests__/digest.test.ts +44 -0
- package/src/__tests__/events.test.ts +245 -0
- package/src/__tests__/memory-volume.test.ts +443 -0
- package/src/__tests__/path.test.ts +181 -0
- package/src/__tests__/process.test.ts +129 -0
- package/src/__tests__/script-engine.test.ts +229 -0
- package/src/__tests__/shell-builtins.test.ts +357 -0
- package/src/__tests__/shell-interpreter.test.ts +157 -0
- package/src/__tests__/shell-parser.test.ts +204 -0
- package/src/__tests__/stream.test.ts +142 -0
- package/src/__tests__/syntax-transforms.test.ts +158 -0
- package/src/__tests__/version-resolver.test.ts +184 -0
- package/src/constants/cdn-urls.ts +18 -18
- package/src/helpers/byte-encoding.ts +51 -39
- package/src/memory-volume.ts +962 -941
- package/src/module-transformer.ts +368 -368
- package/src/packages/installer.ts +396 -396
- package/src/polyfills/buffer.ts +633 -628
- package/src/polyfills/esbuild.ts +854 -854
- package/src/polyfills/events.ts +282 -276
- package/src/polyfills/process.ts +695 -690
- package/src/polyfills/readline.ts +692 -692
- package/src/polyfills/tty.ts +71 -71
- package/src/script-engine.ts +3396 -3375
- package/src/syntax-transforms.ts +543 -561
- package/dist/index--Qr8LVpQ.js.map +0 -1
- package/dist/index-cnitc68U.cjs.map +0 -1
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { MemoryVolume } from "../memory-volume";
|
|
3
|
+
|
|
4
|
+
describe("MemoryVolume", () => {
|
|
5
|
+
describe("writeFileSync / readFileSync", () => {
|
|
6
|
+
it("writes and reads a string file", () => {
|
|
7
|
+
const vol = new MemoryVolume();
|
|
8
|
+
vol.writeFileSync("/test.txt", "hello");
|
|
9
|
+
expect(vol.readFileSync("/test.txt", "utf8")).toBe("hello");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("writes and reads binary data (Uint8Array)", () => {
|
|
13
|
+
const vol = new MemoryVolume();
|
|
14
|
+
const data = new Uint8Array([1, 2, 3, 4]);
|
|
15
|
+
vol.writeFileSync("/bin", data);
|
|
16
|
+
const result = vol.readFileSync("/bin");
|
|
17
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
18
|
+
expect(result).toEqual(data);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("overwrites existing file content", () => {
|
|
22
|
+
const vol = new MemoryVolume();
|
|
23
|
+
vol.writeFileSync("/f.txt", "first");
|
|
24
|
+
vol.writeFileSync("/f.txt", "second");
|
|
25
|
+
expect(vol.readFileSync("/f.txt", "utf8")).toBe("second");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("auto-creates parent directories", () => {
|
|
29
|
+
const vol = new MemoryVolume();
|
|
30
|
+
vol.writeFileSync("/a/b/c.txt", "deep");
|
|
31
|
+
expect(vol.readFileSync("/a/b/c.txt", "utf8")).toBe("deep");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns Uint8Array when no encoding specified", () => {
|
|
35
|
+
const vol = new MemoryVolume();
|
|
36
|
+
vol.writeFileSync("/f", "data");
|
|
37
|
+
expect(vol.readFileSync("/f")).toBeInstanceOf(Uint8Array);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("returns string with utf8 encoding", () => {
|
|
41
|
+
const vol = new MemoryVolume();
|
|
42
|
+
vol.writeFileSync("/f", "text");
|
|
43
|
+
expect(typeof vol.readFileSync("/f", "utf8")).toBe("string");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("throws ENOENT on read of nonexistent file", () => {
|
|
47
|
+
const vol = new MemoryVolume();
|
|
48
|
+
expect(() => vol.readFileSync("/nope")).toThrow();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("handles empty file content", () => {
|
|
52
|
+
const vol = new MemoryVolume();
|
|
53
|
+
vol.writeFileSync("/empty", "");
|
|
54
|
+
expect(vol.readFileSync("/empty", "utf8")).toBe("");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("handles file at root level", () => {
|
|
58
|
+
const vol = new MemoryVolume();
|
|
59
|
+
vol.writeFileSync("/root.txt", "root");
|
|
60
|
+
expect(vol.readFileSync("/root.txt", "utf8")).toBe("root");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("mkdirSync", () => {
|
|
65
|
+
it("creates a directory", () => {
|
|
66
|
+
const vol = new MemoryVolume();
|
|
67
|
+
vol.mkdirSync("/dir");
|
|
68
|
+
expect(vol.statSync("/dir").isDirectory()).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("creates nested directories with recursive:true", () => {
|
|
72
|
+
const vol = new MemoryVolume();
|
|
73
|
+
vol.mkdirSync("/a/b/c/d", { recursive: true });
|
|
74
|
+
expect(vol.statSync("/a/b/c/d").isDirectory()).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("does not throw when directory exists with recursive:true", () => {
|
|
78
|
+
const vol = new MemoryVolume();
|
|
79
|
+
vol.mkdirSync("/dir", { recursive: true });
|
|
80
|
+
expect(() =>
|
|
81
|
+
vol.mkdirSync("/dir", { recursive: true }),
|
|
82
|
+
).not.toThrow();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("readdirSync", () => {
|
|
87
|
+
it("lists files and directories", () => {
|
|
88
|
+
const vol = new MemoryVolume();
|
|
89
|
+
vol.mkdirSync("/project", { recursive: true });
|
|
90
|
+
vol.writeFileSync("/project/a.txt", "a");
|
|
91
|
+
vol.writeFileSync("/project/b.txt", "b");
|
|
92
|
+
vol.mkdirSync("/project/sub");
|
|
93
|
+
const entries = vol.readdirSync("/project");
|
|
94
|
+
expect(entries).toContain("a.txt");
|
|
95
|
+
expect(entries).toContain("b.txt");
|
|
96
|
+
expect(entries).toContain("sub");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("returns empty array for empty directory", () => {
|
|
100
|
+
const vol = new MemoryVolume();
|
|
101
|
+
vol.mkdirSync("/empty");
|
|
102
|
+
expect(vol.readdirSync("/empty")).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("throws ENOENT for nonexistent directory", () => {
|
|
106
|
+
const vol = new MemoryVolume();
|
|
107
|
+
expect(() => vol.readdirSync("/nope")).toThrow();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("existsSync", () => {
|
|
112
|
+
it("returns true for existing file", () => {
|
|
113
|
+
const vol = new MemoryVolume();
|
|
114
|
+
vol.writeFileSync("/f", "data");
|
|
115
|
+
expect(vol.existsSync("/f")).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("returns true for existing directory", () => {
|
|
119
|
+
const vol = new MemoryVolume();
|
|
120
|
+
vol.mkdirSync("/dir");
|
|
121
|
+
expect(vol.existsSync("/dir")).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("returns false for nonexistent path", () => {
|
|
125
|
+
const vol = new MemoryVolume();
|
|
126
|
+
expect(vol.existsSync("/nope")).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns true for root /", () => {
|
|
130
|
+
const vol = new MemoryVolume();
|
|
131
|
+
expect(vol.existsSync("/")).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("statSync", () => {
|
|
136
|
+
it("returns correct stat for a file", () => {
|
|
137
|
+
const vol = new MemoryVolume();
|
|
138
|
+
vol.writeFileSync("/f.txt", "hello");
|
|
139
|
+
const stat = vol.statSync("/f.txt");
|
|
140
|
+
expect(stat.isFile()).toBe(true);
|
|
141
|
+
expect(stat.isDirectory()).toBe(false);
|
|
142
|
+
expect(stat.size).toBe(5);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("returns correct stat for a directory", () => {
|
|
146
|
+
const vol = new MemoryVolume();
|
|
147
|
+
vol.mkdirSync("/dir");
|
|
148
|
+
const stat = vol.statSync("/dir");
|
|
149
|
+
expect(stat.isDirectory()).toBe(true);
|
|
150
|
+
expect(stat.isFile()).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("has numeric mtimeMs and size fields", () => {
|
|
154
|
+
const vol = new MemoryVolume();
|
|
155
|
+
vol.writeFileSync("/f", "data");
|
|
156
|
+
const stat = vol.statSync("/f");
|
|
157
|
+
expect(typeof stat.mtimeMs).toBe("number");
|
|
158
|
+
expect(typeof stat.size).toBe("number");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("throws ENOENT for nonexistent path", () => {
|
|
162
|
+
const vol = new MemoryVolume();
|
|
163
|
+
expect(() => vol.statSync("/nope")).toThrow();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("unlinkSync", () => {
|
|
168
|
+
it("removes a file", () => {
|
|
169
|
+
const vol = new MemoryVolume();
|
|
170
|
+
vol.writeFileSync("/f", "data");
|
|
171
|
+
vol.unlinkSync("/f");
|
|
172
|
+
expect(vol.existsSync("/f")).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("throws ENOENT for nonexistent file", () => {
|
|
176
|
+
const vol = new MemoryVolume();
|
|
177
|
+
expect(() => vol.unlinkSync("/nope")).toThrow();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("rmdirSync", () => {
|
|
182
|
+
it("removes an empty directory", () => {
|
|
183
|
+
const vol = new MemoryVolume();
|
|
184
|
+
vol.mkdirSync("/dir");
|
|
185
|
+
vol.rmdirSync("/dir");
|
|
186
|
+
expect(vol.existsSync("/dir")).toBe(false);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("throws ENOTEMPTY for non-empty directory", () => {
|
|
190
|
+
const vol = new MemoryVolume();
|
|
191
|
+
vol.mkdirSync("/dir");
|
|
192
|
+
vol.writeFileSync("/dir/f.txt", "data");
|
|
193
|
+
expect(() => vol.rmdirSync("/dir")).toThrow();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("throws ENOENT for nonexistent directory", () => {
|
|
197
|
+
const vol = new MemoryVolume();
|
|
198
|
+
expect(() => vol.rmdirSync("/nope")).toThrow();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("renameSync", () => {
|
|
203
|
+
it("moves a file from one path to another", () => {
|
|
204
|
+
const vol = new MemoryVolume();
|
|
205
|
+
vol.writeFileSync("/old.txt", "data");
|
|
206
|
+
vol.renameSync("/old.txt", "/new.txt");
|
|
207
|
+
expect(vol.readFileSync("/new.txt", "utf8")).toBe("data");
|
|
208
|
+
expect(vol.existsSync("/old.txt")).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("throws ENOENT when source does not exist", () => {
|
|
212
|
+
const vol = new MemoryVolume();
|
|
213
|
+
expect(() => vol.renameSync("/nope", "/dest")).toThrow();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("copyFileSync", () => {
|
|
218
|
+
it("copies file content to new path", () => {
|
|
219
|
+
const vol = new MemoryVolume();
|
|
220
|
+
vol.writeFileSync("/src.txt", "content");
|
|
221
|
+
vol.copyFileSync("/src.txt", "/dst.txt");
|
|
222
|
+
expect(vol.readFileSync("/dst.txt", "utf8")).toBe("content");
|
|
223
|
+
expect(vol.readFileSync("/src.txt", "utf8")).toBe("content");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("throws ENOENT when source does not exist", () => {
|
|
227
|
+
const vol = new MemoryVolume();
|
|
228
|
+
expect(() => vol.copyFileSync("/nope", "/dst")).toThrow();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("appendFileSync", () => {
|
|
233
|
+
it("appends to existing file", () => {
|
|
234
|
+
const vol = new MemoryVolume();
|
|
235
|
+
vol.writeFileSync("/f", "hello");
|
|
236
|
+
vol.appendFileSync("/f", " world");
|
|
237
|
+
expect(vol.readFileSync("/f", "utf8")).toBe("hello world");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("creates file if it does not exist", () => {
|
|
241
|
+
const vol = new MemoryVolume();
|
|
242
|
+
vol.appendFileSync("/new.txt", "fresh");
|
|
243
|
+
expect(vol.readFileSync("/new.txt", "utf8")).toBe("fresh");
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("truncateSync", () => {
|
|
248
|
+
it("truncates file to specified length", () => {
|
|
249
|
+
const vol = new MemoryVolume();
|
|
250
|
+
vol.writeFileSync("/f", "hello world");
|
|
251
|
+
vol.truncateSync("/f", 5);
|
|
252
|
+
expect(vol.readFileSync("/f", "utf8")).toBe("hello");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("throws ENOENT for nonexistent file", () => {
|
|
256
|
+
const vol = new MemoryVolume();
|
|
257
|
+
expect(() => vol.truncateSync("/nope", 5)).toThrow();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe("symlinks", () => {
|
|
262
|
+
it("symlinkSync creates a symlink, readlinkSync reads target", () => {
|
|
263
|
+
const vol = new MemoryVolume();
|
|
264
|
+
vol.writeFileSync("/target", "data");
|
|
265
|
+
vol.symlinkSync("/target", "/link");
|
|
266
|
+
expect(vol.readlinkSync("/link")).toBe("/target");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("reading a symlinked file follows the symlink", () => {
|
|
270
|
+
const vol = new MemoryVolume();
|
|
271
|
+
vol.writeFileSync("/target", "data");
|
|
272
|
+
vol.symlinkSync("/target", "/link");
|
|
273
|
+
expect(vol.readFileSync("/link", "utf8")).toBe("data");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("realpathSync resolves symlink to real path", () => {
|
|
277
|
+
const vol = new MemoryVolume();
|
|
278
|
+
vol.writeFileSync("/real", "data");
|
|
279
|
+
vol.symlinkSync("/real", "/sym");
|
|
280
|
+
expect(vol.realpathSync("/sym")).toBe("/real");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("lstatSync reports symlink for symlink node", () => {
|
|
284
|
+
const vol = new MemoryVolume();
|
|
285
|
+
vol.writeFileSync("/target", "data");
|
|
286
|
+
vol.symlinkSync("/target", "/link");
|
|
287
|
+
expect(vol.lstatSync("/link").isSymbolicLink()).toBe(true);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe("path normalization", () => {
|
|
292
|
+
it("normalizes paths with ..", () => {
|
|
293
|
+
const vol = new MemoryVolume();
|
|
294
|
+
vol.mkdirSync("/a", { recursive: true });
|
|
295
|
+
vol.writeFileSync("/a/b/../c.txt", "data");
|
|
296
|
+
expect(vol.readFileSync("/a/c.txt", "utf8")).toBe("data");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("normalizes paths with .", () => {
|
|
300
|
+
const vol = new MemoryVolume();
|
|
301
|
+
vol.writeFileSync("/./test.txt", "data");
|
|
302
|
+
expect(vol.readFileSync("/test.txt", "utf8")).toBe("data");
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("handles paths without leading /", () => {
|
|
306
|
+
const vol = new MemoryVolume();
|
|
307
|
+
vol.writeFileSync("foo.txt", "data");
|
|
308
|
+
expect(vol.readFileSync("/foo.txt", "utf8")).toBe("data");
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe("toSnapshot / fromSnapshot", () => {
|
|
313
|
+
it("round-trips empty volume", () => {
|
|
314
|
+
const vol = new MemoryVolume();
|
|
315
|
+
const snap = vol.toSnapshot();
|
|
316
|
+
const vol2 = MemoryVolume.fromSnapshot(snap);
|
|
317
|
+
expect(vol2.readdirSync("/")).toEqual([]);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("round-trips volume with files and directories", () => {
|
|
321
|
+
const vol = new MemoryVolume();
|
|
322
|
+
vol.mkdirSync("/project/src", { recursive: true });
|
|
323
|
+
vol.writeFileSync("/project/src/index.ts", "export default 1;");
|
|
324
|
+
vol.writeFileSync("/project/readme.md", "# Hello");
|
|
325
|
+
|
|
326
|
+
const snap = vol.toSnapshot();
|
|
327
|
+
const vol2 = MemoryVolume.fromSnapshot(snap);
|
|
328
|
+
|
|
329
|
+
expect(vol2.readFileSync("/project/src/index.ts", "utf8")).toBe(
|
|
330
|
+
"export default 1;",
|
|
331
|
+
);
|
|
332
|
+
expect(vol2.readFileSync("/project/readme.md", "utf8")).toBe("# Hello");
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("round-trips binary file content", () => {
|
|
336
|
+
const vol = new MemoryVolume();
|
|
337
|
+
const data = new Uint8Array([0, 128, 255]);
|
|
338
|
+
vol.writeFileSync("/bin", data);
|
|
339
|
+
const snap = vol.toSnapshot();
|
|
340
|
+
const vol2 = MemoryVolume.fromSnapshot(snap);
|
|
341
|
+
expect(vol2.readFileSync("/bin")).toEqual(data);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
describe("watchers", () => {
|
|
346
|
+
it("watch() fires callback on file write", () => {
|
|
347
|
+
const vol = new MemoryVolume();
|
|
348
|
+
const events: string[] = [];
|
|
349
|
+
vol.watch("/", {}, (event, filename) => {
|
|
350
|
+
events.push(`${event}:${filename}`);
|
|
351
|
+
});
|
|
352
|
+
vol.writeFileSync("/test.txt", "data");
|
|
353
|
+
expect(events.length).toBeGreaterThan(0);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("watch.close() stops receiving events", () => {
|
|
357
|
+
const vol = new MemoryVolume();
|
|
358
|
+
const events: string[] = [];
|
|
359
|
+
const handle = vol.watch("/", {}, (event, filename) => {
|
|
360
|
+
events.push(`${event}:${filename}`);
|
|
361
|
+
});
|
|
362
|
+
handle.close();
|
|
363
|
+
vol.writeFileSync("/test.txt", "data");
|
|
364
|
+
expect(events.length).toBe(0);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
describe("createReadStream", () => {
|
|
369
|
+
it("emits data and end events", async () => {
|
|
370
|
+
const vol = new MemoryVolume();
|
|
371
|
+
vol.writeFileSync("/f.txt", "stream content");
|
|
372
|
+
const rs = vol.createReadStream("/f.txt");
|
|
373
|
+
|
|
374
|
+
const chunks: Uint8Array[] = [];
|
|
375
|
+
await new Promise<void>((resolve) => {
|
|
376
|
+
rs.on("data", (chunk: any) => chunks.push(chunk));
|
|
377
|
+
rs.on("end", () => resolve());
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const text = new TextDecoder().decode(chunks[0]);
|
|
381
|
+
expect(text).toBe("stream content");
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
describe("createWriteStream", () => {
|
|
386
|
+
it("write + end flushes data to volume", async () => {
|
|
387
|
+
const vol = new MemoryVolume();
|
|
388
|
+
const ws = vol.createWriteStream("/out.txt");
|
|
389
|
+
ws.write("hello ");
|
|
390
|
+
ws.end("world");
|
|
391
|
+
|
|
392
|
+
// wait for async flush
|
|
393
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
394
|
+
expect(vol.readFileSync("/out.txt", "utf8")).toBe("hello world");
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
describe("event subscription", () => {
|
|
399
|
+
it("on('change') fires when file is written", () => {
|
|
400
|
+
const vol = new MemoryVolume();
|
|
401
|
+
const changes: string[] = [];
|
|
402
|
+
vol.on("change", (path: string) => changes.push(path));
|
|
403
|
+
vol.writeFileSync("/f.txt", "data");
|
|
404
|
+
expect(changes).toContain("/f.txt");
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("off() unsubscribes handler", () => {
|
|
408
|
+
const vol = new MemoryVolume();
|
|
409
|
+
const changes: string[] = [];
|
|
410
|
+
const handler = (path: string) => changes.push(path);
|
|
411
|
+
vol.on("change", handler);
|
|
412
|
+
vol.off("change", handler);
|
|
413
|
+
vol.writeFileSync("/f.txt", "data");
|
|
414
|
+
expect(changes).toEqual([]);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
describe("async methods", () => {
|
|
419
|
+
it("readFile with callback returns data", async () => {
|
|
420
|
+
const vol = new MemoryVolume();
|
|
421
|
+
vol.writeFileSync("/f.txt", "async data");
|
|
422
|
+
const data = await new Promise<any>((resolve, reject) => {
|
|
423
|
+
vol.readFile("/f.txt", { encoding: "utf8" }, (err: any, d: any) => {
|
|
424
|
+
if (err) reject(err);
|
|
425
|
+
else resolve(d);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
expect(data).toBe("async data");
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it("stat with callback returns stats", async () => {
|
|
432
|
+
const vol = new MemoryVolume();
|
|
433
|
+
vol.writeFileSync("/f.txt", "data");
|
|
434
|
+
const stats = await new Promise<any>((resolve, reject) => {
|
|
435
|
+
vol.stat("/f.txt", (err: any, s: any) => {
|
|
436
|
+
if (err) reject(err);
|
|
437
|
+
else resolve(s);
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
expect(stats.isFile()).toBe(true);
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import * as path from "../polyfills/path";
|
|
3
|
+
|
|
4
|
+
describe("path.normalize", () => {
|
|
5
|
+
it("resolves . and ..", () => {
|
|
6
|
+
expect(path.normalize("/foo/bar/../baz")).toBe("/foo/baz");
|
|
7
|
+
expect(path.normalize("/foo/./bar")).toBe("/foo/bar");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("removes duplicate slashes", () => {
|
|
11
|
+
expect(path.normalize("/foo//bar///baz")).toBe("/foo/bar/baz");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('returns "." for empty input', () => {
|
|
15
|
+
expect(path.normalize("")).toBe(".");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("handles relative paths with ..", () => {
|
|
19
|
+
expect(path.normalize("a/b/../../c")).toBe("c");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("does not go above root", () => {
|
|
23
|
+
expect(path.normalize("/../../foo")).toBe("/foo");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("path.join", () => {
|
|
28
|
+
it("joins segments with /", () => {
|
|
29
|
+
expect(path.join("/foo", "bar", "baz")).toBe("/foo/bar/baz");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("normalizes result", () => {
|
|
33
|
+
expect(path.join("/foo", "../bar")).toBe("/bar");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('returns "." for no args', () => {
|
|
37
|
+
expect(path.join()).toBe(".");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("handles empty strings", () => {
|
|
41
|
+
expect(path.join("", "foo", "")).toBe("foo");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("path.resolve", () => {
|
|
46
|
+
it("resolves relative to absolute base", () => {
|
|
47
|
+
expect(path.resolve("/foo", "bar")).toBe("/foo/bar");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("right-to-left resolution stops at absolute path", () => {
|
|
51
|
+
expect(path.resolve("a", "/b", "c")).toBe("/b/c");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("normalizes the result", () => {
|
|
55
|
+
expect(path.resolve("/foo", "bar", "..", "baz")).toBe("/foo/baz");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("path.dirname", () => {
|
|
60
|
+
it("returns parent directory", () => {
|
|
61
|
+
expect(path.dirname("/foo/bar.txt")).toBe("/foo");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('returns "/" for root-level file', () => {
|
|
65
|
+
expect(path.dirname("/file.txt")).toBe("/");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns "." for bare filename', () => {
|
|
69
|
+
expect(path.dirname("file.txt")).toBe(".");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("path.basename", () => {
|
|
74
|
+
it("returns filename from path", () => {
|
|
75
|
+
expect(path.basename("/foo/bar.txt")).toBe("bar.txt");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("strips suffix when provided", () => {
|
|
79
|
+
expect(path.basename("/foo/bar.txt", ".txt")).toBe("bar");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns empty for empty string", () => {
|
|
83
|
+
expect(path.basename("")).toBe("");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("returns last segment for directory path", () => {
|
|
87
|
+
expect(path.basename("/foo/bar/")).toBe("bar");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("path.extname", () => {
|
|
92
|
+
it("returns extension with dot", () => {
|
|
93
|
+
expect(path.extname("file.txt")).toBe(".txt");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("returns empty for no extension", () => {
|
|
97
|
+
expect(path.extname("Makefile")).toBe("");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("returns last extension for multiple dots", () => {
|
|
101
|
+
expect(path.extname("file.test.js")).toBe(".js");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("returns empty for leading dot only", () => {
|
|
105
|
+
expect(path.extname(".gitignore")).toBe("");
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("path.relative", () => {
|
|
110
|
+
it("computes relative path between two absolute paths", () => {
|
|
111
|
+
expect(path.relative("/a/b", "/a/c")).toBe("../c");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("returns empty string for same path", () => {
|
|
115
|
+
expect(path.relative("/a/b", "/a/b")).toBe("");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("handles deeply nested paths", () => {
|
|
119
|
+
expect(path.relative("/a/b/c", "/a/b/c/d/e")).toBe("d/e");
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("path.isAbsolute", () => {
|
|
124
|
+
it("returns true for paths starting with /", () => {
|
|
125
|
+
expect(path.isAbsolute("/foo")).toBe(true);
|
|
126
|
+
expect(path.isAbsolute("/")).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns false for relative paths", () => {
|
|
130
|
+
expect(path.isAbsolute("foo")).toBe(false);
|
|
131
|
+
expect(path.isAbsolute("./foo")).toBe(false);
|
|
132
|
+
expect(path.isAbsolute("../foo")).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("path.parse", () => {
|
|
137
|
+
it("decomposes path into parts", () => {
|
|
138
|
+
const result = path.parse("/home/user/file.txt");
|
|
139
|
+
expect(result.root).toBe("/");
|
|
140
|
+
expect(result.dir).toBe("/home/user");
|
|
141
|
+
expect(result.base).toBe("file.txt");
|
|
142
|
+
expect(result.ext).toBe(".txt");
|
|
143
|
+
expect(result.name).toBe("file");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("handles root path", () => {
|
|
147
|
+
const result = path.parse("/");
|
|
148
|
+
expect(result.root).toBe("/");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("handles relative path", () => {
|
|
152
|
+
const result = path.parse("foo/bar.js");
|
|
153
|
+
expect(result.root).toBe("");
|
|
154
|
+
expect(result.dir).toBe("foo");
|
|
155
|
+
expect(result.base).toBe("bar.js");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("path.format", () => {
|
|
160
|
+
it("reconstructs path from components", () => {
|
|
161
|
+
expect(path.format({ dir: "/home/user", base: "file.txt" })).toBe(
|
|
162
|
+
"/home/user/file.txt",
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("uses name + ext when base is missing", () => {
|
|
167
|
+
expect(path.format({ dir: "/home", name: "file", ext: ".txt" })).toBe(
|
|
168
|
+
"/home/file.txt",
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("path constants", () => {
|
|
174
|
+
it('sep is "/"', () => {
|
|
175
|
+
expect(path.sep).toBe("/");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('delimiter is ":"', () => {
|
|
179
|
+
expect(path.delimiter).toBe(":");
|
|
180
|
+
});
|
|
181
|
+
});
|