@gjsify/fs 0.0.4 → 0.1.1

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.
Files changed (91) hide show
  1. package/README.md +31 -2
  2. package/lib/esm/callback.js +251 -15
  3. package/lib/esm/dirent.js +47 -6
  4. package/lib/esm/encoding.js +2 -3
  5. package/lib/esm/errors.js +13 -0
  6. package/lib/esm/file-handle.js +108 -66
  7. package/lib/esm/fs-watcher.js +44 -7
  8. package/lib/esm/index.js +140 -5
  9. package/lib/esm/promises.js +290 -69
  10. package/lib/esm/read-stream.js +82 -57
  11. package/lib/esm/stats.js +138 -18
  12. package/lib/esm/sync.js +293 -44
  13. package/lib/esm/write-stream.js +4 -4
  14. package/lib/types/callback.d.ts +233 -0
  15. package/lib/types/dirent.d.ts +77 -0
  16. package/lib/types/encoding.d.ts +6 -0
  17. package/lib/types/errors.d.ts +7 -0
  18. package/lib/types/file-handle.d.ts +367 -0
  19. package/lib/types/fs-watcher.d.ts +17 -0
  20. package/lib/types/index.d.ts +149 -0
  21. package/lib/types/promises.d.ts +158 -0
  22. package/lib/types/read-stream.d.ts +21 -0
  23. package/lib/types/stats.d.ts +67 -0
  24. package/lib/types/sync.d.ts +109 -0
  25. package/lib/types/types/encoding-option.d.ts +2 -0
  26. package/lib/types/types/file-read-options.d.ts +15 -0
  27. package/lib/types/types/file-read-result.d.ts +4 -0
  28. package/lib/types/types/flag-and-open-mode.d.ts +5 -0
  29. package/lib/types/types/index.d.ts +6 -0
  30. package/lib/types/types/open-flags.d.ts +1 -0
  31. package/lib/types/types/read-options.d.ts +5 -0
  32. package/lib/types/utils.d.ts +2 -0
  33. package/lib/types/write-stream.d.ts +45 -0
  34. package/package.json +22 -35
  35. package/src/callback.spec.ts +284 -30
  36. package/src/callback.ts +352 -39
  37. package/src/dirent.ts +56 -8
  38. package/src/encoding.ts +7 -2
  39. package/src/errors.spec.ts +389 -0
  40. package/src/errors.ts +19 -0
  41. package/src/extended.spec.ts +706 -0
  42. package/src/file-handle.spec.ts +104 -23
  43. package/src/file-handle.ts +147 -79
  44. package/src/fs-watcher.ts +55 -8
  45. package/src/index.ts +146 -2
  46. package/src/new-apis.spec.ts +505 -0
  47. package/src/promises.spec.ts +651 -11
  48. package/src/promises.ts +353 -81
  49. package/src/read-stream.ts +98 -74
  50. package/src/stat.spec.ts +22 -14
  51. package/src/stats.ts +176 -75
  52. package/src/streams.spec.ts +455 -0
  53. package/src/symlink.spec.ts +176 -26
  54. package/src/sync.spec.ts +204 -32
  55. package/src/sync.ts +363 -58
  56. package/src/test.mts +7 -2
  57. package/src/types/encoding-option.ts +1 -1
  58. package/src/types/flag-and-open-mode.ts +1 -1
  59. package/src/types/read-options.ts +2 -2
  60. package/src/utils.ts +2 -0
  61. package/src/write-stream.ts +9 -7
  62. package/tsconfig.json +23 -10
  63. package/tsconfig.tsbuildinfo +1 -0
  64. package/lib/cjs/callback.js +0 -112
  65. package/lib/cjs/dirent.js +0 -98
  66. package/lib/cjs/encoding.js +0 -34
  67. package/lib/cjs/file-handle.js +0 -444
  68. package/lib/cjs/fs-watcher.js +0 -50
  69. package/lib/cjs/index.js +0 -95
  70. package/lib/cjs/promises.js +0 -160
  71. package/lib/cjs/read-stream.js +0 -78
  72. package/lib/cjs/stats.js +0 -45
  73. package/lib/cjs/sync.js +0 -126
  74. package/lib/cjs/types/encoding-option.js +0 -0
  75. package/lib/cjs/types/file-read-options.js +0 -0
  76. package/lib/cjs/types/file-read-result.js +0 -0
  77. package/lib/cjs/types/flag-and-open-mode.js +0 -0
  78. package/lib/cjs/types/index.js +0 -6
  79. package/lib/cjs/types/open-flags.js +0 -0
  80. package/lib/cjs/types/read-options.js +0 -0
  81. package/lib/cjs/utils.js +0 -18
  82. package/lib/cjs/write-stream.js +0 -116
  83. package/test/watch.js +0 -1
  84. package/test.gjs.js +0 -35359
  85. package/test.gjs.js.map +0 -7
  86. package/test.gjs.mjs +0 -40534
  87. package/test.gjs.mjs.meta.json +0 -1
  88. package/test.node.js +0 -1479
  89. package/test.node.js.map +0 -7
  90. package/test.node.mjs +0 -710
  91. package/tsconfig.types.json +0 -8
@@ -1,8 +1,12 @@
1
+ // Ported from refs/node-test/parallel/test-fs-promises.js,
2
+ // test-fs-access.js, test-fs-copyfile.js
3
+ // Original: MIT license, Node.js contributors
1
4
  import { describe, it, expect } from '@gjsify/unit';
2
- import { promises, existsSync } from 'fs';
3
- import { mkdir, readdir, mkdtemp, writeFile, rm, rmdir } from 'fs/promises';
4
- import { join } from 'path';
5
- import { Buffer } from 'buffer';
5
+ import { promises, existsSync, mkdtempSync, writeFileSync, rmdirSync, rmSync, constants as fsConstants } from 'node:fs';
6
+ import { mkdir, readdir, mkdtemp, writeFile, rm, rmdir, access, copyFile, rename, lstat, stat, unlink } from 'node:fs/promises';
7
+ import { join } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+ import { Buffer } from 'node:buffer';
6
10
 
7
11
  export default async () => {
8
12
 
@@ -91,8 +95,8 @@ export default async () => {
91
95
  expect(typeof promises.readFile).toBe("function");
92
96
  });
93
97
 
94
- await it('should be a promise', async () => {
95
- expect(promises.readFile('./test/file.txt', 'utf-8') instanceof Promise).toBeTruthy();
98
+ await it('should return a promise', async () => {
99
+ expect(promises.readFile('package.json', 'utf-8') instanceof Promise).toBeTruthy();
96
100
  });
97
101
 
98
102
  await it('should return a Buffer if no encoding was specified', async () => {
@@ -101,13 +105,23 @@ export default async () => {
101
105
  });
102
106
 
103
107
  await it('should return a string when encoding is utf-8', async () => {
104
- const utf8Data = await promises.readFile('./test/file.txt', 'utf-8');
108
+ const dir = mkdtempSync('fs-prf-');
109
+ const filePath = join(dir, 'test.txt');
110
+ writeFileSync(filePath, 'Hello World');
111
+ const utf8Data = await promises.readFile(filePath, 'utf-8');
105
112
  expect(typeof utf8Data === 'string').toBeTruthy();
113
+ rmSync(filePath);
114
+ rmdirSync(dir);
106
115
  });
107
116
 
108
- await it('should return a string with "Hello World"', async () => {
109
- const utf8Data = await promises.readFile('./test/file.txt', 'utf-8');
117
+ await it('should return the correct file content', async () => {
118
+ const dir = mkdtempSync('fs-prf-content-');
119
+ const filePath = join(dir, 'test.txt');
120
+ writeFileSync(filePath, 'Hello World');
121
+ const utf8Data = await promises.readFile(filePath, 'utf-8');
110
122
  expect(utf8Data).toBe('Hello World');
123
+ rmSync(filePath);
124
+ rmdirSync(dir);
111
125
  });
112
126
  });
113
127
 
@@ -123,7 +137,156 @@ export default async () => {
123
137
  });
124
138
  });
125
139
 
126
- await describe('fs.promises.rm', async () => {
140
+ await describe('fs.promises.access', async () => {
141
+ await it('should resolve for an existing file with F_OK', async () => {
142
+ const dir = await mkdtemp(join(tmpdir(), 'fs-acc-'));
143
+ const file = join(dir, 'exists.txt');
144
+ await writeFile(file, '');
145
+ await access(file, fsConstants.F_OK);
146
+ await rm(file);
147
+ await rmdir(dir);
148
+ });
149
+
150
+ await it('should reject with ENOENT for non-existent file', async () => {
151
+ let threw = false;
152
+ try {
153
+ await access('/nonexistent/abc123/xyz.txt', fsConstants.F_OK);
154
+ } catch (e: unknown) {
155
+ threw = true;
156
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
157
+ }
158
+ expect(threw).toBe(true);
159
+ });
160
+
161
+ await it('should resolve for readable file with R_OK', async () => {
162
+ const dir = await mkdtemp(join(tmpdir(), 'fs-acc-r-'));
163
+ const file = join(dir, 'read.txt');
164
+ await writeFile(file, 'readable');
165
+ await access(file, fsConstants.R_OK);
166
+ await rm(file);
167
+ await rmdir(dir);
168
+ });
169
+ });
170
+
171
+ await describe('fs.promises.copyFile', async () => {
172
+ await it('should copy a file', async () => {
173
+ const dir = await mkdtemp(join(tmpdir(), 'fs-cp-'));
174
+ const src = join(dir, 'src.txt');
175
+ const dst = join(dir, 'dst.txt');
176
+ await writeFile(src, 'copy content');
177
+ await copyFile(src, dst);
178
+ const content = await promises.readFile(dst, { encoding: 'utf8' });
179
+ expect(content).toBe('copy content');
180
+ await rm(src);
181
+ await rm(dst);
182
+ await rmdir(dir);
183
+ });
184
+
185
+ await it('should throw ENOENT when source does not exist', async () => {
186
+ const dir = await mkdtemp(join(tmpdir(), 'fs-cp-err-'));
187
+ const dst = join(dir, 'dst.txt');
188
+ let threw = false;
189
+ try {
190
+ await copyFile('/nonexistent/xyz123.txt', dst);
191
+ } catch (e: unknown) {
192
+ threw = true;
193
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
194
+ }
195
+ expect(threw).toBe(true);
196
+ await rmdir(dir);
197
+ });
198
+
199
+ await it('should overwrite destination by default', async () => {
200
+ const dir = await mkdtemp(join(tmpdir(), 'fs-cp-ow-'));
201
+ const src = join(dir, 'src.txt');
202
+ const dst = join(dir, 'dst.txt');
203
+ await writeFile(src, 'new content');
204
+ await writeFile(dst, 'old content');
205
+ await copyFile(src, dst);
206
+ const content = await promises.readFile(dst, { encoding: 'utf8' });
207
+ expect(content).toBe('new content');
208
+ await rm(src);
209
+ await rm(dst);
210
+ await rmdir(dir);
211
+ });
212
+ });
213
+
214
+ await describe('fs.promises.rename', async () => {
215
+ await it('should rename a file', async () => {
216
+ const dir = await mkdtemp(join(tmpdir(), 'fs-ren-'));
217
+ const src = join(dir, 'old.txt');
218
+ const dst = join(dir, 'new.txt');
219
+ await writeFile(src, 'rename content');
220
+ await rename(src, dst);
221
+ expect(existsSync(src)).toBe(false);
222
+ expect(existsSync(dst)).toBe(true);
223
+ const content = await promises.readFile(dst, { encoding: 'utf8' });
224
+ expect(content).toBe('rename content');
225
+ await rm(dst);
226
+ await rmdir(dir);
227
+ });
228
+
229
+ await it('should throw ENOENT when source does not exist', async () => {
230
+ const dir = await mkdtemp(join(tmpdir(), 'fs-ren-err-'));
231
+ let threw = false;
232
+ try {
233
+ await rename(join(dir, 'nonexistent.txt'), join(dir, 'dst.txt'));
234
+ } catch (e: unknown) {
235
+ threw = true;
236
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
237
+ }
238
+ expect(threw).toBe(true);
239
+ await rmdir(dir);
240
+ });
241
+ });
242
+
243
+ await describe('fs.promises.lstat', async () => {
244
+ await it('should return stat for a regular file', async () => {
245
+ const dir = await mkdtemp(join(tmpdir(), 'fs-lst-'));
246
+ const file = join(dir, 'file.txt');
247
+ await writeFile(file, 'lstat test');
248
+ const s = await lstat(file);
249
+ expect(s.isFile()).toBe(true);
250
+ expect(s.isDirectory()).toBe(false);
251
+ expect(s.isSymbolicLink()).toBe(false);
252
+ await rm(file);
253
+ await rmdir(dir);
254
+ });
255
+
256
+ await it('should return stat for a directory', async () => {
257
+ const dir = await mkdtemp(join(tmpdir(), 'fs-lst-d-'));
258
+ const s = await lstat(dir);
259
+ expect(s.isDirectory()).toBe(true);
260
+ expect(s.isFile()).toBe(false);
261
+ await rmdir(dir);
262
+ });
263
+
264
+ await it('should throw ENOENT for non-existent path', async () => {
265
+ let threw = false;
266
+ try {
267
+ await lstat('/nonexistent/abc/xyz123.txt');
268
+ } catch (e: unknown) {
269
+ threw = true;
270
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
271
+ }
272
+ expect(threw).toBe(true);
273
+ });
274
+ });
275
+
276
+ await describe('fs.promises.writeFile error handling', async () => {
277
+ await it('should throw ENOENT when parent directory does not exist', async () => {
278
+ let threw = false;
279
+ try {
280
+ await writeFile('/nonexistent/subdir/abc123.txt', 'data');
281
+ } catch (e: unknown) {
282
+ threw = true;
283
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
284
+ }
285
+ expect(threw).toBe(true);
286
+ });
287
+ });
288
+
289
+ await describe('fs.promises.rm', async () => {
127
290
  await it('should be a function', async () => {
128
291
  expect(typeof promises.rm).toBe("function");
129
292
  });
@@ -138,7 +301,7 @@ export default async () => {
138
301
  expect(existsSync(txt1)).toBeTruthy();
139
302
  await promises.rm(txt1);
140
303
  expect(existsSync(txt1)).toBeFalsy();
141
-
304
+
142
305
  // Clear
143
306
  await promises.rmdir(dir);
144
307
  });
@@ -169,4 +332,481 @@ export default async () => {
169
332
  expect(existsSync(dir)).toBeFalsy();
170
333
  });
171
334
  });
335
+
336
+ // --- New tests below ---
337
+
338
+ await describe('fs.promises.writeFile + readFile round-trip', async () => {
339
+ await it('should write and read back a string correctly', async () => {
340
+ const dir = await mkdtemp(join(tmpdir(), 'fs-prt-'));
341
+ const file = join(dir, 'roundtrip.txt');
342
+ const content = 'Hello, round-trip test!';
343
+ await writeFile(file, content);
344
+ const result = await promises.readFile(file, 'utf-8');
345
+ expect(result).toBe(content);
346
+ await rm(file);
347
+ await rmdir(dir);
348
+ });
349
+
350
+ await it('should write and read back a Buffer correctly', async () => {
351
+ const dir = await mkdtemp(join(tmpdir(), 'fs-prt-buf-'));
352
+ const file = join(dir, 'roundtrip-buf.bin');
353
+ const data = Buffer.from([0x00, 0x01, 0x02, 0xff]);
354
+ await writeFile(file, data);
355
+ const result = await promises.readFile(file);
356
+ expect(result instanceof Buffer).toBeTruthy();
357
+ expect(result[0]).toBe(0x00);
358
+ expect(result[1]).toBe(0x01);
359
+ expect(result[2]).toBe(0x02);
360
+ expect(result[3]).toBe(0xff);
361
+ expect(result.length).toBe(4);
362
+ await rm(file);
363
+ await rmdir(dir);
364
+ });
365
+
366
+ await it('should write an empty file and read it back', async () => {
367
+ const dir = await mkdtemp(join(tmpdir(), 'fs-prt-empty-'));
368
+ const file = join(dir, 'empty.txt');
369
+ await writeFile(file, '');
370
+ const result = await promises.readFile(file, 'utf-8');
371
+ expect(result).toBe('');
372
+ await rm(file);
373
+ await rmdir(dir);
374
+ });
375
+
376
+ await it('should write unicode content and read it back', async () => {
377
+ const dir = await mkdtemp(join(tmpdir(), 'fs-prt-uni-'));
378
+ const file = join(dir, 'unicode.txt');
379
+ const content = 'Hej! Caf\u00e9 \u2603 \ud83d\ude00';
380
+ await writeFile(file, content);
381
+ const result = await promises.readFile(file, 'utf-8');
382
+ expect(result).toBe(content);
383
+ await rm(file);
384
+ await rmdir(dir);
385
+ });
386
+ });
387
+
388
+ await describe('fs.promises.mkdir / rmdir', async () => {
389
+ await it('should create and remove a directory', async () => {
390
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pmkdir-'));
391
+ const sub = join(dir, 'subdir');
392
+ await mkdir(sub);
393
+ expect(existsSync(sub)).toBe(true);
394
+ await rmdir(sub);
395
+ expect(existsSync(sub)).toBe(false);
396
+ await rmdir(dir);
397
+ });
398
+
399
+ await it('should create nested directories with recursive option', async () => {
400
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pmkdir-rec-'));
401
+ const nested = join(dir, 'a', 'b', 'c');
402
+ await mkdir(nested, { recursive: true });
403
+ expect(existsSync(nested)).toBe(true);
404
+ await promises.rm(dir, { recursive: true });
405
+ });
406
+
407
+ await it('should not throw when recursive mkdir on existing directory', async () => {
408
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pmkdir-exist-'));
409
+ // Should not throw
410
+ await mkdir(dir, { recursive: true });
411
+ await rmdir(dir);
412
+ });
413
+ });
414
+
415
+ await describe('fs.promises.stat', async () => {
416
+ await it('should return stat for a file with correct size', async () => {
417
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pstat-'));
418
+ const file = join(dir, 'sized.txt');
419
+ const content = 'abcdef'; // 6 bytes in UTF-8
420
+ await writeFile(file, content);
421
+ const s = await stat(file);
422
+ expect(s.isFile()).toBe(true);
423
+ expect(s.isDirectory()).toBe(false);
424
+ expect(s.size).toBe(6);
425
+ await rm(file);
426
+ await rmdir(dir);
427
+ });
428
+
429
+ await it('should return stat for a directory', async () => {
430
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pstat-d-'));
431
+ const s = await stat(dir);
432
+ expect(s.isDirectory()).toBe(true);
433
+ expect(s.isFile()).toBe(false);
434
+ await rmdir(dir);
435
+ });
436
+
437
+ await it('should reject with ENOENT for non-existent file', async () => {
438
+ let threw = false;
439
+ try {
440
+ await stat('/nonexistent/xyz789/no-file.txt');
441
+ } catch (e: unknown) {
442
+ threw = true;
443
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
444
+ }
445
+ expect(threw).toBe(true);
446
+ });
447
+ });
448
+
449
+ await describe('fs.promises.access additional', async () => {
450
+ await it('should reject with EACCES or ENOENT for W_OK on read-only path', async () => {
451
+ // /etc/hosts typically is not writable by normal users
452
+ let threw = false;
453
+ try {
454
+ await access('/etc/hosts', fsConstants.W_OK);
455
+ } catch (e: unknown) {
456
+ threw = true;
457
+ const code = (e as NodeJS.ErrnoException).code;
458
+ // Either EACCES (permission denied) or ENOENT depending on system
459
+ expect(code === 'EACCES' || code === 'ENOENT').toBe(true);
460
+ }
461
+ expect(threw).toBe(true);
462
+ });
463
+ });
464
+
465
+ await describe('fs.promises.rename additional', async () => {
466
+ await it('should preserve file content after rename', async () => {
467
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pren-content-'));
468
+ const src = join(dir, 'before.txt');
469
+ const dst = join(dir, 'after.txt');
470
+ await writeFile(src, 'content to preserve');
471
+ await rename(src, dst);
472
+ const content = await promises.readFile(dst, 'utf-8');
473
+ expect(content).toBe('content to preserve');
474
+ expect(existsSync(src)).toBe(false);
475
+ await rm(dst);
476
+ await rmdir(dir);
477
+ });
478
+
479
+ await it('should overwrite destination if it already exists', async () => {
480
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pren-ow-'));
481
+ const src = join(dir, 'src.txt');
482
+ const dst = join(dir, 'dst.txt');
483
+ await writeFile(src, 'new');
484
+ await writeFile(dst, 'old');
485
+ await rename(src, dst);
486
+ const content = await promises.readFile(dst, 'utf-8');
487
+ expect(content).toBe('new');
488
+ expect(existsSync(src)).toBe(false);
489
+ await rm(dst);
490
+ await rmdir(dir);
491
+ });
492
+ });
493
+
494
+ await describe('fs.promises.copyFile additional', async () => {
495
+ await it('should not modify original file after copy', async () => {
496
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pcp-orig-'));
497
+ const src = join(dir, 'original.txt');
498
+ const dst = join(dir, 'copied.txt');
499
+ await writeFile(src, 'original');
500
+ await copyFile(src, dst);
501
+ const srcContent = await promises.readFile(src, 'utf-8');
502
+ const dstContent = await promises.readFile(dst, 'utf-8');
503
+ expect(srcContent).toBe('original');
504
+ expect(dstContent).toBe('original');
505
+ await rm(src);
506
+ await rm(dst);
507
+ await rmdir(dir);
508
+ });
509
+ });
510
+
511
+ await describe('fs.promises.unlink', async () => {
512
+ await it('should remove a file', async () => {
513
+ const dir = await mkdtemp(join(tmpdir(), 'fs-punlink-'));
514
+ const file = join(dir, 'to-delete.txt');
515
+ await writeFile(file, 'delete me');
516
+ expect(existsSync(file)).toBe(true);
517
+ await unlink(file);
518
+ expect(existsSync(file)).toBe(false);
519
+ await rmdir(dir);
520
+ });
521
+
522
+ await it('should throw ENOENT for non-existent file', async () => {
523
+ let threw = false;
524
+ try {
525
+ await unlink('/nonexistent/abc/delete-me.txt');
526
+ } catch (e: unknown) {
527
+ threw = true;
528
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
529
+ }
530
+ expect(threw).toBe(true);
531
+ });
532
+ });
533
+
534
+ await describe('fs.promises.readdir additional', async () => {
535
+ await it('should return sorted entries for multiple files', async () => {
536
+ const dir = await mkdtemp(join(tmpdir(), 'fs-preaddir-sort-'));
537
+ await writeFile(join(dir, 'c.txt'), '');
538
+ await writeFile(join(dir, 'a.txt'), '');
539
+ await writeFile(join(dir, 'b.txt'), '');
540
+ const files = await readdir(dir);
541
+ // readdir does not guarantee order, but should return all 3 entries
542
+ expect(files.length).toBe(3);
543
+ expect(files).toContain('a.txt');
544
+ expect(files).toContain('b.txt');
545
+ expect(files).toContain('c.txt');
546
+ await promises.rm(dir, { recursive: true });
547
+ });
548
+ });
549
+
550
+ await describe('fs.promises.appendFile', async () => {
551
+ await it('should append data to an existing file', async () => {
552
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pappend-'));
553
+ const file = join(dir, 'append.txt');
554
+ await writeFile(file, 'hello');
555
+ await promises.appendFile(file, ' world');
556
+ const content = await promises.readFile(file, 'utf-8');
557
+ expect(content).toBe('hello world');
558
+ await rm(file);
559
+ await rmdir(dir);
560
+ });
561
+
562
+ await it('should create a new file if it does not exist', async () => {
563
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pappend-new-'));
564
+ const file = join(dir, 'new-append.txt');
565
+ await promises.appendFile(file, 'created');
566
+ expect(existsSync(file)).toBe(true);
567
+ const content = await promises.readFile(file, 'utf-8');
568
+ expect(content).toBe('created');
569
+ await rm(file);
570
+ await rmdir(dir);
571
+ });
572
+ });
573
+
574
+ await describe('fs.promises.chmod', async () => {
575
+ await it('should change file mode', async () => {
576
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pchmod-'));
577
+ const file = join(dir, 'chmod.txt');
578
+ await writeFile(file, 'chmod test');
579
+ await promises.chmod(file, 0o444);
580
+ const s = await stat(file);
581
+ // Check that the permission bits match (mask with 0o777 for portable comparison)
582
+ expect(s.mode & 0o777).toBe(0o444);
583
+ // Restore write permission for cleanup
584
+ await promises.chmod(file, 0o644);
585
+ await rm(file);
586
+ await rmdir(dir);
587
+ });
588
+ });
589
+
590
+ await describe('fs.promises.mkdtemp additional', async () => {
591
+ await it('should create a directory with the given prefix', async () => {
592
+ const prefix = join(tmpdir(), 'fs-pmkdtemp-test-');
593
+ const dir = await promises.mkdtemp(prefix);
594
+ expect(existsSync(dir)).toBe(true);
595
+ expect(dir.startsWith(prefix)).toBe(true);
596
+ // The random suffix should add characters
597
+ expect(dir.length).toBeGreaterThan(prefix.length);
598
+ await rmdir(dir);
599
+ });
600
+ });
601
+
602
+ await describe('fs.promises.realpath', async () => {
603
+ await it('should resolve a simple path', async () => {
604
+ const dir = await mkdtemp(join(tmpdir(), 'fs-prealpath-'));
605
+ const file = join(dir, 'real.txt');
606
+ await writeFile(file, 'data');
607
+ const resolved = await promises.realpath(file);
608
+ // Should be an absolute path
609
+ expect(resolved.startsWith('/')).toBe(true);
610
+ // Should end with the file name
611
+ expect(resolved.endsWith('real.txt')).toBe(true);
612
+ await rm(file);
613
+ await rmdir(dir);
614
+ });
615
+ });
616
+
617
+ await describe('fs.promises.truncate', async () => {
618
+ await it('should truncate a file to a specified length', async () => {
619
+ const dir = await mkdtemp(join(tmpdir(), 'fs-ptrunc-'));
620
+ const file = join(dir, 'trunc.txt');
621
+ await writeFile(file, 'hello world');
622
+ await promises.truncate(file, 5);
623
+ const content = await promises.readFile(file, 'utf-8');
624
+ expect(content).toBe('hello');
625
+ await rm(file);
626
+ await rmdir(dir);
627
+ });
628
+
629
+ await it('should truncate a file to zero length when no length specified', async () => {
630
+ const dir = await mkdtemp(join(tmpdir(), 'fs-ptrunc0-'));
631
+ const file = join(dir, 'trunc0.txt');
632
+ await writeFile(file, 'data to remove');
633
+ await promises.truncate(file);
634
+ const content = await promises.readFile(file, 'utf-8');
635
+ expect(content).toBe('');
636
+ await rm(file);
637
+ await rmdir(dir);
638
+ });
639
+ });
640
+
641
+ await describe('fs.promises error cases', async () => {
642
+ await it('readFile should reject for non-existent file', async () => {
643
+ let threw = false;
644
+ try {
645
+ await promises.readFile('/nonexistent/path/xyz987.txt');
646
+ } catch (e: unknown) {
647
+ threw = true;
648
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
649
+ }
650
+ expect(threw).toBe(true);
651
+ });
652
+
653
+ await it('readdir should reject for non-existent directory', async () => {
654
+ let threw = false;
655
+ try {
656
+ await readdir('/nonexistent/xyz654/dir');
657
+ } catch (e: unknown) {
658
+ threw = true;
659
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
660
+ }
661
+ expect(threw).toBe(true);
662
+ });
663
+ });
664
+
665
+ await describe('fs.promises return types', async () => {
666
+ await it('readFile should return a Promise', async () => {
667
+ const p = promises.readFile('/etc/hosts');
668
+ expect(p instanceof Promise).toBe(true);
669
+ await p; // consume
670
+ });
671
+
672
+ await it('writeFile should return a Promise', async () => {
673
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pret-wf-'));
674
+ const file = join(dir, 'ret.txt');
675
+ const p = writeFile(file, 'test');
676
+ expect(p instanceof Promise).toBe(true);
677
+ await p;
678
+ await rm(file);
679
+ await rmdir(dir);
680
+ });
681
+
682
+ await it('stat should return a Promise', async () => {
683
+ const p = stat('/tmp');
684
+ expect(p instanceof Promise).toBe(true);
685
+ await p;
686
+ });
687
+
688
+ await it('mkdir should return a Promise', async () => {
689
+ const dir = join(tmpdir(), 'fs-pret-mkdir-' + Date.now());
690
+ const p = mkdir(dir);
691
+ expect(p instanceof Promise).toBe(true);
692
+ await p;
693
+ await rmdir(dir);
694
+ });
695
+
696
+ await it('access should return a Promise', async () => {
697
+ const p = access('/tmp', fsConstants.F_OK);
698
+ expect(p instanceof Promise).toBe(true);
699
+ await p;
700
+ });
701
+
702
+ await it('unlink should return a Promise', async () => {
703
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pret-ul-'));
704
+ const file = join(dir, 'unlinkret.txt');
705
+ await writeFile(file, '');
706
+ const p = unlink(file);
707
+ expect(p instanceof Promise).toBe(true);
708
+ await p;
709
+ await rmdir(dir);
710
+ });
711
+ });
712
+
713
+ await describe('fs.promises.mkdir recursive return value', async () => {
714
+ await it('should return the first directory created', async () => {
715
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pmkdir-retval-'));
716
+ const nested = join(dir, 'x', 'y', 'z');
717
+ const result = await mkdir(nested, { recursive: true });
718
+ // Should return the first directory created (x)
719
+ expect(typeof result).toBe('string');
720
+ expect(result).toBe(join(dir, 'x'));
721
+ expect(existsSync(nested)).toBe(true);
722
+ await promises.rm(dir, { recursive: true });
723
+ });
724
+
725
+ await it('should return undefined when directory already exists', async () => {
726
+ const dir = await mkdtemp(join(tmpdir(), 'fs-pmkdir-exist-'));
727
+ const result = await mkdir(dir, { recursive: true });
728
+ expect(result).toBeUndefined();
729
+ await rmdir(dir);
730
+ });
731
+ });
732
+
733
+ await describe('fs.promises.rmdir error handling', async () => {
734
+ await it('should throw ENOTEMPTY when directory is not empty', async () => {
735
+ const dir = await mkdtemp(join(tmpdir(), 'fs-prmdir-notempty-'));
736
+ const file = join(dir, 'file.txt');
737
+ await writeFile(file, 'data');
738
+ let threw = false;
739
+ try {
740
+ await rmdir(dir);
741
+ } catch (e: unknown) {
742
+ threw = true;
743
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOTEMPTY');
744
+ }
745
+ expect(threw).toBe(true);
746
+ await promises.rm(dir, { recursive: true });
747
+ });
748
+
749
+ await it('should throw ENOENT when directory does not exist', async () => {
750
+ let threw = false;
751
+ try {
752
+ await rmdir('/nonexistent/xyz789/no-dir');
753
+ } catch (e: unknown) {
754
+ threw = true;
755
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
756
+ }
757
+ expect(threw).toBe(true);
758
+ });
759
+ });
760
+
761
+ await describe('fs.promises.unlink error handling', async () => {
762
+ await it('should throw ENOENT for non-existent file', async () => {
763
+ let threw = false;
764
+ try {
765
+ await unlink('/nonexistent/xyz789/file.txt');
766
+ } catch (e: unknown) {
767
+ threw = true;
768
+ expect((e as NodeJS.ErrnoException).code).toBe('ENOENT');
769
+ }
770
+ expect(threw).toBe(true);
771
+ });
772
+
773
+ await it('should actually delete the file asynchronously', async () => {
774
+ const dir = await mkdtemp(join(tmpdir(), 'fs-punlink-async-'));
775
+ const file = join(dir, 'async-delete.txt');
776
+ await writeFile(file, 'data');
777
+ expect(existsSync(file)).toBe(true);
778
+ await unlink(file);
779
+ expect(existsSync(file)).toBe(false);
780
+ await rmdir(dir);
781
+ });
782
+ });
783
+
784
+ await describe('fs.promises.rm error handling', async () => {
785
+ await it('should throw when removing non-empty dir without recursive', async () => {
786
+ const dir = await mkdtemp(join(tmpdir(), 'fs-prm-notempty-'));
787
+ await writeFile(join(dir, 'file.txt'), 'data');
788
+ let threw = false;
789
+ try {
790
+ await promises.rm(dir, { recursive: false });
791
+ } catch (e: unknown) {
792
+ threw = true;
793
+ // Node.js throws ERR_FS_EISDIR, GJS throws ENOTEMPTY — both are correct
794
+ const code = (e as NodeJS.ErrnoException).code;
795
+ expect(code === 'ENOTEMPTY' || code === 'ERR_FS_EISDIR').toBe(true);
796
+ }
797
+ expect(threw).toBe(true);
798
+ await promises.rm(dir, { recursive: true });
799
+ });
800
+
801
+ await it('should not throw with force: true for non-existent path', async () => {
802
+ const path = join(tmpdir(), 'fs-prm-force-nonexistent-' + Date.now());
803
+ let threw = false;
804
+ try {
805
+ await promises.rm(path, { force: true });
806
+ } catch {
807
+ threw = true;
808
+ }
809
+ expect(threw).toBe(false);
810
+ });
811
+ });
172
812
  }