@mikrojs/firmware 0.0.7

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/bin/idf.py +7 -0
  4. package/chips.json +3 -0
  5. package/cmake.js +9 -0
  6. package/components/mikrojs/CMakeLists.txt +187 -0
  7. package/components/mikrojs/Kconfig +55 -0
  8. package/components/mikrojs/idf_component.yml +6 -0
  9. package/components/mikrojs/include/mem.h +3 -0
  10. package/components/mikrojs/include/mik_color.h +3 -0
  11. package/components/mikrojs/include/mik_http_internal.h +77 -0
  12. package/components/mikrojs/include/mikrojs.h +5 -0
  13. package/components/mikrojs/include/mikrojs_esp32.h +65 -0
  14. package/components/mikrojs/include/private.h +10 -0
  15. package/components/mikrojs/include/utils.h +3 -0
  16. package/components/mikrojs/mik_ble.cpp +1588 -0
  17. package/components/mikrojs/mik_ble_c_shim.c +61 -0
  18. package/components/mikrojs/mik_ble_c_shim.h +37 -0
  19. package/components/mikrojs/mik_config.cpp +167 -0
  20. package/components/mikrojs/mik_deploy.cpp +584 -0
  21. package/components/mikrojs/mik_http.cpp +916 -0
  22. package/components/mikrojs/mik_i2c.cpp +364 -0
  23. package/components/mikrojs/mik_main.cpp +542 -0
  24. package/components/mikrojs/mik_neopixel.cpp +437 -0
  25. package/components/mikrojs/mik_nvs_kv.cpp +219 -0
  26. package/components/mikrojs/mik_pin.cpp +195 -0
  27. package/components/mikrojs/mik_pwm.cpp +525 -0
  28. package/components/mikrojs/mik_recovery.cpp +86 -0
  29. package/components/mikrojs/mik_rtc.cpp +305 -0
  30. package/components/mikrojs/mik_serial_io.cpp +362 -0
  31. package/components/mikrojs/mik_sleep.cpp +226 -0
  32. package/components/mikrojs/mik_sntp.cpp +275 -0
  33. package/components/mikrojs/mik_spi.cpp +330 -0
  34. package/components/mikrojs/mik_uart.cpp +497 -0
  35. package/components/mikrojs/mik_wifi.cpp +1434 -0
  36. package/components/mikrojs/platform_esp32.cpp +192 -0
  37. package/components/mikrojs/test/CMakeLists.txt +32 -0
  38. package/components/mikrojs/test/abort_test.cpp +254 -0
  39. package/components/mikrojs/test/ble_test.cpp +714 -0
  40. package/components/mikrojs/test/fs_js_test.cpp +458 -0
  41. package/components/mikrojs/test/fs_pub_test.cpp +312 -0
  42. package/components/mikrojs/test/http_test.cpp +475 -0
  43. package/components/mikrojs/test/i2c_test.cpp +138 -0
  44. package/components/mikrojs/test/modules_extended_test.cpp +137 -0
  45. package/components/mikrojs/test/modules_test.cpp +131 -0
  46. package/components/mikrojs/test/pins_test.cpp +47 -0
  47. package/components/mikrojs/test/pwm_test.cpp +166 -0
  48. package/components/mikrojs/test/repl_protocol_test.cpp +405 -0
  49. package/components/mikrojs/test/rtc_test.cpp +331 -0
  50. package/components/mikrojs/test/runtime_test.cpp +89 -0
  51. package/components/mikrojs/test/sleep_test.cpp +222 -0
  52. package/components/mikrojs/test/sntp_test.cpp +249 -0
  53. package/components/mikrojs/test/stdio_test.cpp +449 -0
  54. package/components/mikrojs/test/sys_test.cpp +165 -0
  55. package/components/mikrojs/test/text_encoding_test.cpp +224 -0
  56. package/components/mikrojs/test/timers_js_test.cpp +244 -0
  57. package/components/mikrojs/test/timers_test.cpp +79 -0
  58. package/components/mikrojs/test/wifi_test.cpp +599 -0
  59. package/default-app/main/CMakeLists.txt +3 -0
  60. package/default-app/main/main.cpp +5 -0
  61. package/discover.js +77 -0
  62. package/index.d.ts +7 -0
  63. package/index.js +20 -0
  64. package/package.json +61 -0
  65. package/partitions.csv +5 -0
  66. package/prebuilds/esp32/bootloader/bootloader.bin +0 -0
  67. package/prebuilds/esp32/flasher_args.json +24 -0
  68. package/prebuilds/esp32/mikrojs.bin +0 -0
  69. package/prebuilds/esp32/partition_table/partition-table.bin +0 -0
  70. package/prebuilds/esp32c3/bootloader/bootloader.bin +0 -0
  71. package/prebuilds/esp32c3/flasher_args.json +24 -0
  72. package/prebuilds/esp32c3/mikrojs.bin +0 -0
  73. package/prebuilds/esp32c3/partition_table/partition-table.bin +0 -0
  74. package/prebuilds/esp32c6/bootloader/bootloader.bin +0 -0
  75. package/prebuilds/esp32c6/flasher_args.json +24 -0
  76. package/prebuilds/esp32c6/mikrojs.bin +0 -0
  77. package/prebuilds/esp32c6/partition_table/partition-table.bin +0 -0
  78. package/prebuilds/esp32s3/bootloader/bootloader.bin +0 -0
  79. package/prebuilds/esp32s3/flasher_args.json +24 -0
  80. package/prebuilds/esp32s3/mikrojs.bin +0 -0
  81. package/prebuilds/esp32s3/partition_table/partition-table.bin +0 -0
  82. package/project.cmake +101 -0
  83. package/resolve.js +54 -0
  84. package/sdkconfig.defaults +127 -0
  85. package/sdkconfig.defaults.esp32 +8 -0
  86. package/sdkconfig.defaults.esp32c3 +15 -0
  87. package/sdkconfig.defaults.esp32c6 +26 -0
  88. package/sdkconfig.defaults.esp32s3 +22 -0
@@ -0,0 +1,458 @@
1
+ #include "esp_littlefs.h"
2
+ #include "mikrojs.h"
3
+ #include "private.h"
4
+ #include "quickjs.h"
5
+ #include "unity.h"
6
+
7
+ /*
8
+ * These tests exercise the native:fs JS module (FileHandle, readFile, stat, etc.)
9
+ * through the mikrojs runtime. They require LittleFS to be mounted at /littlefs
10
+ * with a dummy.txt file containing "Hello World".
11
+ *
12
+ * native:fs returns Result values; tests unwrap via `.value` / check `.ok`
13
+ * explicitly rather than wrapping in try/catch.
14
+ *
15
+ * IMPORTANT: teardown must be called BEFORE any TEST_ASSERT macros, because
16
+ * Unity's assertions use longjmp which would skip teardown and leak the runtime.
17
+ */
18
+
19
+ /* Unity calls setUp/tearDown automatically before/after each TEST_CASE tagged [fs]. */
20
+ void setUp() {
21
+ esp_vfs_littlefs_conf_t conf = {
22
+ .base_path = "/littlefs",
23
+ .partition_label = "littlefs",
24
+ .partition = NULL,
25
+ .format_if_mount_failed = true,
26
+ .read_only = false,
27
+ .dont_mount = false,
28
+ .grow_on_mount = false,
29
+ };
30
+ esp_vfs_littlefs_register(&conf);
31
+ }
32
+
33
+ void tearDown() { esp_vfs_littlefs_unregister("littlefs"); }
34
+
35
+ static MIKRuntime* mik_rt;
36
+ static JSContext* mik_ctx;
37
+
38
+ static void fs_js_setup() {
39
+ mik_rt = MIK_NewRuntime();
40
+ MIK_SetFSBasePath(mik_rt, "/littlefs");
41
+ mik_ctx = MIK_GetJSContext(mik_rt);
42
+ }
43
+
44
+ static void fs_js_teardown() { MIK_FreeRuntime(mik_rt); }
45
+
46
+ static JSValue fs_eval_module(const char* code) {
47
+ JSValue ret = MIK_EvalModuleContent(mik_ctx, "mikrojs/test", code, strlen(code));
48
+ if (!JS_IsException(ret)) {
49
+ JS_FreeValue(mik_ctx, ret);
50
+ mik__execute_jobs(mik_ctx);
51
+ }
52
+ return ret;
53
+ }
54
+
55
+ static char* fs_get_global_string(const char* name) {
56
+ JSValue global = JS_GetGlobalObject(mik_ctx);
57
+ JSValue val = JS_GetPropertyStr(mik_ctx, global, name);
58
+ const char* str = JS_ToCString(mik_ctx, val);
59
+ char* result = str ? strdup(str) : nullptr;
60
+ JS_FreeCString(mik_ctx, str);
61
+ JS_FreeValue(mik_ctx, val);
62
+ JS_FreeValue(mik_ctx, global);
63
+ return result;
64
+ }
65
+
66
+ static bool fs_get_global_bool(const char* name) {
67
+ JSValue global = JS_GetGlobalObject(mik_ctx);
68
+ JSValue val = JS_GetPropertyStr(mik_ctx, global, name);
69
+ bool result = JS_ToBool(mik_ctx, val);
70
+ JS_FreeValue(mik_ctx, val);
71
+ JS_FreeValue(mik_ctx, global);
72
+ return result;
73
+ }
74
+
75
+ static int32_t fs_get_global_int32(const char* name) {
76
+ JSValue global = JS_GetGlobalObject(mik_ctx);
77
+ JSValue val = JS_GetPropertyStr(mik_ctx, global, name);
78
+ int32_t result;
79
+ JS_ToInt32(mik_ctx, &result, val);
80
+ JS_FreeValue(mik_ctx, val);
81
+ JS_FreeValue(mik_ctx, global);
82
+ return result;
83
+ }
84
+
85
+ TEST_CASE("native:fs open + FileHandle.read returns file contents", "[fs]") {
86
+ fs_js_setup();
87
+
88
+ JSValue ret = fs_eval_module(R"(
89
+ import { open } from "native:fs";
90
+ const r = open("/dummy.txt");
91
+ globalThis.__ok = r.ok;
92
+ if (r.ok) {
93
+ const fh = r.value;
94
+ const data = fh.read(11);
95
+ if (data.ok) globalThis.__content = new TextDecoder().decode(data.value);
96
+ fh.close();
97
+ }
98
+ )");
99
+ bool was_exception = JS_IsException(ret);
100
+ bool ok = fs_get_global_bool("__ok");
101
+ char* content = fs_get_global_string("__content");
102
+ fs_js_teardown();
103
+
104
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
105
+ TEST_ASSERT_TRUE(ok);
106
+ TEST_ASSERT_EQUAL_STRING("Hello World", content);
107
+ free(content);
108
+ }
109
+
110
+ TEST_CASE("native:fs open returns NotFound for non-existent file", "[fs]") {
111
+ fs_js_setup();
112
+
113
+ JSValue ret = fs_eval_module(R"(
114
+ import { open } from "native:fs";
115
+ const r = open("/nonexistent.txt");
116
+ globalThis.__ok = r.ok;
117
+ if (!r.ok) globalThis.__name = r.error.name;
118
+ )");
119
+ bool was_exception = JS_IsException(ret);
120
+ bool ok = fs_get_global_bool("__ok");
121
+ char* name = fs_get_global_string("__name");
122
+ fs_js_teardown();
123
+
124
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
125
+ TEST_ASSERT_FALSE(ok);
126
+ TEST_ASSERT_EQUAL_STRING("NotFound", name);
127
+ free(name);
128
+ }
129
+
130
+ TEST_CASE("native:fs FileHandle.write and readFile roundtrip", "[fs]") {
131
+ fs_js_setup();
132
+
133
+ JSValue ret = fs_eval_module(R"(
134
+ import { open, readFile } from "native:fs";
135
+ const fh = open("/test_write.txt", "w").value;
136
+ fh.write("test content");
137
+ fh.close();
138
+ const r = readFile("/test_write.txt");
139
+ if (r.ok) globalThis.__readback = new TextDecoder().decode(r.value);
140
+ )");
141
+ bool was_exception = JS_IsException(ret);
142
+ char* readback = fs_get_global_string("__readback");
143
+ fs_js_teardown();
144
+
145
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
146
+ TEST_ASSERT_EQUAL_STRING("test content", readback);
147
+ free(readback);
148
+ }
149
+
150
+ TEST_CASE("native:fs FileHandle.stat returns size and type flags", "[fs]") {
151
+ fs_js_setup();
152
+
153
+ JSValue ret = fs_eval_module(R"(
154
+ import { open } from "native:fs";
155
+ const fh = open("/dummy.txt").value;
156
+ const st = fh.stat().value;
157
+ globalThis.__size = st.size;
158
+ globalThis.__isFile = st.isFile;
159
+ globalThis.__isDir = st.isDirectory;
160
+ fh.close();
161
+ )");
162
+ bool was_exception = JS_IsException(ret);
163
+ int32_t size = fs_get_global_int32("__size");
164
+ bool is_file = fs_get_global_bool("__isFile");
165
+ bool is_dir = fs_get_global_bool("__isDir");
166
+ fs_js_teardown();
167
+
168
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
169
+ TEST_ASSERT_EQUAL_INT32(11, size);
170
+ TEST_ASSERT_TRUE(is_file);
171
+ TEST_ASSERT_FALSE(is_dir);
172
+ }
173
+
174
+ TEST_CASE("native:fs FileHandle.path returns the virtual path", "[fs]") {
175
+ fs_js_setup();
176
+
177
+ JSValue ret = fs_eval_module(R"(
178
+ import { open } from "native:fs";
179
+ const fh = open("/dummy.txt").value;
180
+ globalThis.__path = fh.path;
181
+ fh.close();
182
+ )");
183
+ bool was_exception = JS_IsException(ret);
184
+ char* path = fs_get_global_string("__path");
185
+ fs_js_teardown();
186
+
187
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
188
+ TEST_ASSERT_EQUAL_STRING("/dummy.txt", path);
189
+ free(path);
190
+ }
191
+
192
+ TEST_CASE("native:fs FileHandle.seek absolute and relative", "[fs]") {
193
+ fs_js_setup();
194
+
195
+ JSValue ret = fs_eval_module(R"(
196
+ import { open, writeFile, unlink } from "native:fs";
197
+ writeFile("/seek.txt", "0123456789");
198
+ const fh = open("/seek.txt").value;
199
+ const dec = new TextDecoder();
200
+ const p1 = fh.seek(4).value;
201
+ const d1 = dec.decode(fh.read(2).value);
202
+ const p2 = fh.seek(-1, "current").value;
203
+ const d2 = dec.decode(fh.read(2).value);
204
+ const p3 = fh.seek(-3, "end").value;
205
+ const d3 = dec.decode(fh.read(3).value);
206
+ fh.close();
207
+ unlink("/seek.txt");
208
+ globalThis.__p1 = p1; globalThis.__p2 = p2; globalThis.__p3 = p3;
209
+ globalThis.__d = d1 + "|" + d2 + "|" + d3;
210
+ )");
211
+ bool was_exception = JS_IsException(ret);
212
+ int32_t p1 = fs_get_global_int32("__p1");
213
+ int32_t p2 = fs_get_global_int32("__p2");
214
+ int32_t p3 = fs_get_global_int32("__p3");
215
+ char* d = fs_get_global_string("__d");
216
+ fs_js_teardown();
217
+
218
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
219
+ TEST_ASSERT_EQUAL_INT32(4, p1);
220
+ TEST_ASSERT_EQUAL_INT32(5, p2);
221
+ TEST_ASSERT_EQUAL_INT32(7, p3);
222
+ TEST_ASSERT_EQUAL_STRING("45|56|789", d);
223
+ free(d);
224
+ }
225
+
226
+ TEST_CASE("native:fs FileHandle after close returns BadFileDescriptor", "[fs]") {
227
+ fs_js_setup();
228
+
229
+ JSValue ret = fs_eval_module(R"(
230
+ import { open, writeFile, unlink } from "native:fs";
231
+ writeFile("/bad.txt", "x");
232
+ const fh = open("/bad.txt").value;
233
+ fh.close();
234
+ const r = fh.read(1);
235
+ unlink("/bad.txt");
236
+ globalThis.__ok = r.ok;
237
+ if (!r.ok) globalThis.__name = r.error.name;
238
+ )");
239
+ bool was_exception = JS_IsException(ret);
240
+ bool ok = fs_get_global_bool("__ok");
241
+ char* name = fs_get_global_string("__name");
242
+ fs_js_teardown();
243
+
244
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
245
+ TEST_ASSERT_FALSE(ok);
246
+ TEST_ASSERT_EQUAL_STRING("BadFileDescriptor", name);
247
+ free(name);
248
+ }
249
+
250
+ TEST_CASE("native:fs FileHandle.read returns undefined at EOF", "[fs]") {
251
+ fs_js_setup();
252
+
253
+ JSValue ret = fs_eval_module(R"(
254
+ import { open } from "native:fs";
255
+ const fh = open("/dummy.txt").value;
256
+ fh.read(11);
257
+ const after = fh.read(1);
258
+ globalThis.__atEof = after.ok && after.value === undefined;
259
+ fh.close();
260
+ )");
261
+ bool was_exception = JS_IsException(ret);
262
+ bool at_eof = fs_get_global_bool("__atEof");
263
+ fs_js_teardown();
264
+
265
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
266
+ TEST_ASSERT_TRUE_MESSAGE(at_eof, "read past EOF should return ok(undefined)");
267
+ }
268
+
269
+ TEST_CASE("native:fs stat on path returns correct info", "[fs]") {
270
+ fs_js_setup();
271
+
272
+ JSValue ret = fs_eval_module(R"(
273
+ import { stat } from "native:fs";
274
+ const st = stat("/dummy.txt").value;
275
+ globalThis.__size = st.size;
276
+ globalThis.__isFile = st.isFile;
277
+ )");
278
+ bool was_exception = JS_IsException(ret);
279
+ int32_t size = fs_get_global_int32("__size");
280
+ bool is_file = fs_get_global_bool("__isFile");
281
+ fs_js_teardown();
282
+
283
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
284
+ TEST_ASSERT_EQUAL_INT32(11, size);
285
+ TEST_ASSERT_TRUE(is_file);
286
+ }
287
+
288
+ TEST_CASE("native:fs unlink removes a file", "[fs]") {
289
+ fs_js_setup();
290
+
291
+ JSValue ret = fs_eval_module(R"(
292
+ import { open, unlink, stat } from "native:fs";
293
+ const fh = open("/to_delete.txt", "w").value;
294
+ fh.write("delete me");
295
+ fh.close();
296
+ unlink("/to_delete.txt");
297
+ const r = stat("/to_delete.txt");
298
+ globalThis.__deleted = !r.ok;
299
+ )");
300
+ bool was_exception = JS_IsException(ret);
301
+ bool deleted = fs_get_global_bool("__deleted");
302
+ fs_js_teardown();
303
+
304
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
305
+ TEST_ASSERT_TRUE(deleted);
306
+ }
307
+
308
+ TEST_CASE("native:fs rename moves a file", "[fs]") {
309
+ fs_js_setup();
310
+
311
+ JSValue ret = fs_eval_module(R"(
312
+ import { open, rename, readFile } from "native:fs";
313
+ const fh = open("/rename_src.txt", "w").value;
314
+ fh.write("renamed content");
315
+ fh.close();
316
+ rename("/rename_src.txt", "/rename_dst.txt");
317
+ const r = readFile("/rename_dst.txt");
318
+ if (r.ok) globalThis.__content = new TextDecoder().decode(r.value);
319
+ )");
320
+ bool was_exception = JS_IsException(ret);
321
+ char* content = fs_get_global_string("__content");
322
+ fs_js_teardown();
323
+
324
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
325
+ TEST_ASSERT_EQUAL_STRING("renamed content", content);
326
+ free(content);
327
+ }
328
+
329
+ TEST_CASE("native:fs mkdir and readDir", "[fs]") {
330
+ fs_js_setup();
331
+
332
+ JSValue ret = fs_eval_module(R"(
333
+ import { mkdir, readDir, open, rmdir, unlink } from "native:fs";
334
+ mkdir("/testdir");
335
+ const fh = open("/testdir/file.txt", "w").value;
336
+ fh.write("hi");
337
+ fh.close();
338
+ const entries = readDir("/testdir").value;
339
+ globalThis.__hasFile = entries.some(e => e.name === "file.txt" && e.isFile);
340
+ globalThis.__entryCount = entries.length;
341
+ unlink("/testdir/file.txt");
342
+ rmdir("/testdir");
343
+ )");
344
+ bool was_exception = JS_IsException(ret);
345
+ bool has_file = fs_get_global_bool("__hasFile");
346
+ int32_t count = fs_get_global_int32("__entryCount");
347
+ fs_js_teardown();
348
+
349
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
350
+ TEST_ASSERT_TRUE_MESSAGE(has_file, "readDir should list the file");
351
+ TEST_ASSERT_GREATER_OR_EQUAL_INT32_MESSAGE(1, count,
352
+ "Directory should have at least 1 entry");
353
+ }
354
+
355
+ TEST_CASE("native:fs open with mode=w creates a new file", "[fs]") {
356
+ fs_js_setup();
357
+
358
+ JSValue ret = fs_eval_module(R"(
359
+ import { open, unlink } from "native:fs";
360
+ const fh = open("/create_true.txt", "w").value;
361
+ fh.write("created");
362
+ fh.close();
363
+ const fh2 = open("/create_true.txt").value;
364
+ const data = fh2.read(7).value;
365
+ globalThis.__content = new TextDecoder().decode(data);
366
+ fh2.close();
367
+ unlink("/create_true.txt");
368
+ )");
369
+ bool was_exception = JS_IsException(ret);
370
+ char* content = fs_get_global_string("__content");
371
+ fs_js_teardown();
372
+
373
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
374
+ TEST_ASSERT_EQUAL_STRING("created", content);
375
+ free(content);
376
+ }
377
+
378
+ TEST_CASE("native:fs exists returns true for existing file", "[fs]") {
379
+ fs_js_setup();
380
+
381
+ JSValue ret = fs_eval_module(R"(
382
+ import { exists } from "native:fs";
383
+ globalThis.__exists = exists("/dummy.txt");
384
+ )");
385
+ bool was_exception = JS_IsException(ret);
386
+ bool exists = fs_get_global_bool("__exists");
387
+ fs_js_teardown();
388
+
389
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
390
+ TEST_ASSERT_TRUE(exists);
391
+ }
392
+
393
+ TEST_CASE("native:fs exists returns false for non-existent file", "[fs]") {
394
+ fs_js_setup();
395
+
396
+ JSValue ret = fs_eval_module(R"(
397
+ import { exists } from "native:fs";
398
+ globalThis.__exists = exists("/no_such_file.txt");
399
+ )");
400
+ bool was_exception = JS_IsException(ret);
401
+ bool exists = fs_get_global_bool("__exists");
402
+ fs_js_teardown();
403
+
404
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
405
+ TEST_ASSERT_FALSE(exists);
406
+ }
407
+
408
+ TEST_CASE("native:fs unlink returns NotFound for missing file", "[fs]") {
409
+ fs_js_setup();
410
+
411
+ JSValue ret = fs_eval_module(R"(
412
+ import { unlink } from "native:fs";
413
+ const r = unlink("/no_such_file.txt");
414
+ globalThis.__ok = r.ok;
415
+ if (!r.ok) globalThis.__name = r.error.name;
416
+ )");
417
+ bool was_exception = JS_IsException(ret);
418
+ bool ok = fs_get_global_bool("__ok");
419
+ char* name = fs_get_global_string("__name");
420
+ fs_js_teardown();
421
+
422
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
423
+ TEST_ASSERT_FALSE(ok);
424
+ TEST_ASSERT_EQUAL_STRING("NotFound", name);
425
+ free(name);
426
+ }
427
+
428
+ TEST_CASE("native:fs readDir returns error for missing directory", "[fs]") {
429
+ fs_js_setup();
430
+
431
+ JSValue ret = fs_eval_module(R"(
432
+ import { readDir } from "native:fs";
433
+ const r = readDir("/no_such_dir");
434
+ globalThis.__ok = r.ok;
435
+ )");
436
+ bool was_exception = JS_IsException(ret);
437
+ bool ok = fs_get_global_bool("__ok");
438
+ fs_js_teardown();
439
+
440
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
441
+ TEST_ASSERT_FALSE(ok);
442
+ }
443
+
444
+ TEST_CASE("native:fs rmdir returns error for missing directory", "[fs]") {
445
+ fs_js_setup();
446
+
447
+ JSValue ret = fs_eval_module(R"(
448
+ import { rmdir } from "native:fs";
449
+ const r = rmdir("/no_such_dir");
450
+ globalThis.__ok = r.ok;
451
+ )");
452
+ bool was_exception = JS_IsException(ret);
453
+ bool ok = fs_get_global_bool("__ok");
454
+ fs_js_teardown();
455
+
456
+ TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
457
+ TEST_ASSERT_FALSE(ok);
458
+ }