@scelar/nodepod 1.0.2 → 1.0.4
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/__sw__.js +642 -642
- 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-53fMkug_.js} +4 -4
- package/dist/child_process-53fMkug_.js.map +1 -0
- package/dist/{child_process-B38qoN6R.cjs → child_process-lxSKECHq.cjs} +5 -5
- package/dist/child_process-lxSKECHq.cjs.map +1 -0
- package/dist/{index--Qr8LVpQ.js → index-B8lyh_ti.js} +1316 -559
- package/dist/index-B8lyh_ti.js.map +1 -0
- package/dist/{index-cnitc68U.cjs → index-C-TQIrdG.cjs} +1422 -612
- package/dist/index-C-TQIrdG.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/memory-volume.d.ts +1 -1
- package/dist/polyfills/wasi.d.ts +45 -4
- package/dist/script-engine.d.ts +2 -0
- package/dist/sdk/nodepod.d.ts +4 -3
- package/dist/sdk/types.d.ts +6 -0
- package/dist/syntax-transforms.d.ts +1 -0
- package/dist/threading/process-manager.d.ts +1 -1
- package/dist/threading/worker-protocol.d.ts +1 -1
- package/package.json +5 -3
- 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/index.ts +192 -192
- package/src/memory-volume.ts +968 -941
- package/src/module-transformer.ts +368 -368
- package/src/packages/installer.ts +396 -396
- package/src/packages/version-resolver.ts +12 -2
- package/src/polyfills/buffer.ts +633 -628
- package/src/polyfills/child_process.ts +2288 -2288
- package/src/polyfills/esbuild.ts +854 -854
- package/src/polyfills/events.ts +282 -276
- package/src/polyfills/fs.ts +2888 -2888
- package/src/polyfills/http.ts +1450 -1449
- package/src/polyfills/process.ts +721 -690
- package/src/polyfills/readline.ts +692 -692
- package/src/polyfills/stream.ts +1620 -1620
- package/src/polyfills/tty.ts +71 -71
- package/src/polyfills/wasi.ts +1284 -22
- package/src/request-proxy.ts +716 -716
- package/src/script-engine.ts +465 -146
- package/src/sdk/nodepod.ts +525 -509
- package/src/sdk/types.ts +7 -0
- package/src/syntax-transforms.ts +543 -561
- package/src/threading/offload-worker.ts +383 -383
- package/src/threading/offload.ts +271 -271
- package/src/threading/process-manager.ts +956 -956
- package/src/threading/process-worker-entry.ts +858 -854
- package/src/threading/worker-protocol.ts +1 -1
- package/dist/child_process-B38qoN6R.cjs.map +0 -1
- package/dist/child_process-Dopvyd-E.js.map +0 -1
- package/dist/index--Qr8LVpQ.js.map +0 -1
- package/dist/index-cnitc68U.cjs.map +0 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import eventModule, { EventEmitter } from "../polyfills/events";
|
|
3
|
+
|
|
4
|
+
describe("EventEmitter", () => {
|
|
5
|
+
describe("on/emit", () => {
|
|
6
|
+
it("emits event and calls listener", () => {
|
|
7
|
+
const ee = new EventEmitter();
|
|
8
|
+
const fn = vi.fn();
|
|
9
|
+
ee.on("test", fn);
|
|
10
|
+
ee.emit("test");
|
|
11
|
+
expect(fn).toHaveBeenCalledOnce();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("passes arguments to listener", () => {
|
|
15
|
+
const ee = new EventEmitter();
|
|
16
|
+
const fn = vi.fn();
|
|
17
|
+
ee.on("data", fn);
|
|
18
|
+
ee.emit("data", 1, "two", { three: 3 });
|
|
19
|
+
expect(fn).toHaveBeenCalledWith(1, "two", { three: 3 });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("handles multiple listeners on same event", () => {
|
|
23
|
+
const ee = new EventEmitter();
|
|
24
|
+
const fn1 = vi.fn();
|
|
25
|
+
const fn2 = vi.fn();
|
|
26
|
+
ee.on("x", fn1);
|
|
27
|
+
ee.on("x", fn2);
|
|
28
|
+
ee.emit("x");
|
|
29
|
+
expect(fn1).toHaveBeenCalled();
|
|
30
|
+
expect(fn2).toHaveBeenCalled();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns true when listeners exist, false otherwise", () => {
|
|
34
|
+
const ee = new EventEmitter();
|
|
35
|
+
expect(ee.emit("nope")).toBe(false);
|
|
36
|
+
ee.on("yep", () => {});
|
|
37
|
+
expect(ee.emit("yep")).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("once", () => {
|
|
42
|
+
it("listener fires only once", () => {
|
|
43
|
+
const ee = new EventEmitter();
|
|
44
|
+
const fn = vi.fn();
|
|
45
|
+
ee.once("one", fn);
|
|
46
|
+
ee.emit("one");
|
|
47
|
+
ee.emit("one");
|
|
48
|
+
expect(fn).toHaveBeenCalledOnce();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("is removed after first emit", () => {
|
|
52
|
+
const ee = new EventEmitter();
|
|
53
|
+
ee.once("x", () => {});
|
|
54
|
+
expect(ee.listenerCount("x")).toBe(1);
|
|
55
|
+
ee.emit("x");
|
|
56
|
+
expect(ee.listenerCount("x")).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("removeListener / off", () => {
|
|
61
|
+
it("removes specific listener", () => {
|
|
62
|
+
const ee = new EventEmitter();
|
|
63
|
+
const fn = vi.fn();
|
|
64
|
+
ee.on("x", fn);
|
|
65
|
+
ee.removeListener("x", fn);
|
|
66
|
+
ee.emit("x");
|
|
67
|
+
expect(fn).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("does not remove other listeners on same event", () => {
|
|
71
|
+
const ee = new EventEmitter();
|
|
72
|
+
const fn1 = vi.fn();
|
|
73
|
+
const fn2 = vi.fn();
|
|
74
|
+
ee.on("x", fn1);
|
|
75
|
+
ee.on("x", fn2);
|
|
76
|
+
ee.off("x", fn1);
|
|
77
|
+
ee.emit("x");
|
|
78
|
+
expect(fn1).not.toHaveBeenCalled();
|
|
79
|
+
expect(fn2).toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("removeAllListeners", () => {
|
|
84
|
+
it("removes all listeners for a specific event", () => {
|
|
85
|
+
const ee = new EventEmitter();
|
|
86
|
+
ee.on("a", () => {});
|
|
87
|
+
ee.on("a", () => {});
|
|
88
|
+
ee.on("b", () => {});
|
|
89
|
+
ee.removeAllListeners("a");
|
|
90
|
+
expect(ee.listenerCount("a")).toBe(0);
|
|
91
|
+
expect(ee.listenerCount("b")).toBe(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("removes all listeners for all events when no arg", () => {
|
|
95
|
+
const ee = new EventEmitter();
|
|
96
|
+
ee.on("a", () => {});
|
|
97
|
+
ee.on("b", () => {});
|
|
98
|
+
ee.removeAllListeners();
|
|
99
|
+
expect(ee.eventNames()).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("error event", () => {
|
|
104
|
+
it("throws Error when emitting 'error' with no listeners", () => {
|
|
105
|
+
const ee = new EventEmitter();
|
|
106
|
+
expect(() => ee.emit("error", new Error("boom"))).toThrow("boom");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("throws generic error when payload is not an Error", () => {
|
|
110
|
+
const ee = new EventEmitter();
|
|
111
|
+
expect(() => ee.emit("error", "string")).toThrow("Unhandled error event");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("does not throw when error listener is registered", () => {
|
|
115
|
+
const ee = new EventEmitter();
|
|
116
|
+
ee.on("error", () => {});
|
|
117
|
+
expect(() => ee.emit("error", new Error("handled"))).not.toThrow();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("Object.create pattern", () => {
|
|
122
|
+
it("works when created via Object.create(EventEmitter.prototype)", () => {
|
|
123
|
+
const obj = Object.create(EventEmitter.prototype);
|
|
124
|
+
const fn = vi.fn();
|
|
125
|
+
obj.on("test", fn);
|
|
126
|
+
obj.emit("test", 42);
|
|
127
|
+
expect(fn).toHaveBeenCalledWith(42);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("listenerCount", () => {
|
|
132
|
+
it("returns 0 for event with no listeners", () => {
|
|
133
|
+
const ee = new EventEmitter();
|
|
134
|
+
expect(ee.listenerCount("nope")).toBe(0);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("returns correct count", () => {
|
|
138
|
+
const ee = new EventEmitter();
|
|
139
|
+
ee.on("x", () => {});
|
|
140
|
+
ee.on("x", () => {});
|
|
141
|
+
expect(ee.listenerCount("x")).toBe(2);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("eventNames", () => {
|
|
146
|
+
it("returns empty array initially", () => {
|
|
147
|
+
const ee = new EventEmitter();
|
|
148
|
+
expect(ee.eventNames()).toEqual([]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("returns array of event names with listeners", () => {
|
|
152
|
+
const ee = new EventEmitter();
|
|
153
|
+
ee.on("a", () => {});
|
|
154
|
+
ee.on("b", () => {});
|
|
155
|
+
expect(ee.eventNames().sort()).toEqual(["a", "b"]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("prependListener", () => {
|
|
160
|
+
it("adds listener to beginning of list", () => {
|
|
161
|
+
const order: number[] = [];
|
|
162
|
+
const ee = new EventEmitter();
|
|
163
|
+
ee.on("x", () => order.push(1));
|
|
164
|
+
ee.prependListener("x", () => order.push(2));
|
|
165
|
+
ee.emit("x");
|
|
166
|
+
expect(order).toEqual([2, 1]);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("prependOnceListener", () => {
|
|
171
|
+
it("fires once and was prepended", () => {
|
|
172
|
+
const order: number[] = [];
|
|
173
|
+
const ee = new EventEmitter();
|
|
174
|
+
ee.on("x", () => order.push(1));
|
|
175
|
+
ee.prependOnceListener("x", () => order.push(2));
|
|
176
|
+
ee.emit("x");
|
|
177
|
+
ee.emit("x");
|
|
178
|
+
expect(order).toEqual([2, 1, 1]);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("setMaxListeners / getMaxListeners", () => {
|
|
183
|
+
it("defaults to 10", () => {
|
|
184
|
+
const ee = new EventEmitter();
|
|
185
|
+
expect(ee.getMaxListeners()).toBe(10);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("can be changed", () => {
|
|
189
|
+
const ee = new EventEmitter();
|
|
190
|
+
ee.setMaxListeners(50);
|
|
191
|
+
expect(ee.getMaxListeners()).toBe(50);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("listeners / rawListeners", () => {
|
|
196
|
+
it("returns copy of listener array", () => {
|
|
197
|
+
const ee = new EventEmitter();
|
|
198
|
+
const fn = () => {};
|
|
199
|
+
ee.on("x", fn);
|
|
200
|
+
const list = ee.listeners("x");
|
|
201
|
+
expect(list).toEqual([fn]);
|
|
202
|
+
list.pop();
|
|
203
|
+
expect(ee.listenerCount("x")).toBe(1);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("static EventEmitter.once()", () => {
|
|
208
|
+
it("returns a Promise that resolves on event", async () => {
|
|
209
|
+
const ee = new EventEmitter();
|
|
210
|
+
const p = eventModule.once(ee, "data");
|
|
211
|
+
ee.emit("data", 42, "extra");
|
|
212
|
+
const result = await p;
|
|
213
|
+
expect(result).toEqual([42, "extra"]);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("rejects on error event", async () => {
|
|
217
|
+
const ee = new EventEmitter();
|
|
218
|
+
const p = eventModule.once(ee, "data");
|
|
219
|
+
ee.emit("error", new Error("fail"));
|
|
220
|
+
await expect(p).rejects.toThrow("fail");
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("static EventEmitter.listenerCount()", () => {
|
|
225
|
+
it("returns count from target", () => {
|
|
226
|
+
const ee = new EventEmitter();
|
|
227
|
+
ee.on("x", () => {});
|
|
228
|
+
ee.on("x", () => {});
|
|
229
|
+
expect(eventModule.listenerCount(ee, "x")).toBe(2);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("module export shape", () => {
|
|
234
|
+
it("has EventEmitter as both default.EventEmitter and named export", () => {
|
|
235
|
+
expect(eventModule.EventEmitter).toBe(EventEmitter);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("has static once, on, getEventListeners, listenerCount", () => {
|
|
239
|
+
expect(typeof eventModule.once).toBe("function");
|
|
240
|
+
expect(typeof eventModule.on).toBe("function");
|
|
241
|
+
expect(typeof eventModule.getEventListeners).toBe("function");
|
|
242
|
+
expect(typeof eventModule.listenerCount).toBe("function");
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -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
|
+
});
|