functionalscript 0.18.0 → 0.20.0

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 (242) hide show
  1. package/fs/asn.1/module.f.js +7 -8
  2. package/fs/asn.1/{test.f.d.ts → proof.f.d.ts} +1 -2
  3. package/fs/asn.1/{test.f.js → proof.f.js} +12 -13
  4. package/fs/base128/proof.f.d.ts +1 -0
  5. package/fs/base128/{test.f.js → proof.f.js} +1 -1
  6. package/fs/bnf/data/{test.f.d.ts → proof.f.d.ts} +1 -2
  7. package/fs/bnf/data/{test.f.js → proof.f.js} +1 -1
  8. package/fs/bnf/proof.f.d.ts +3 -0
  9. package/fs/bnf/{test.f.js → proof.f.js} +1 -1
  10. package/fs/cas/module.f.js +2 -12
  11. package/fs/cas/proof.f.d.ts +1 -0
  12. package/fs/cas/proof.f.js +1 -0
  13. package/fs/cbase32/{test.f.d.ts → proof.f.d.ts} +1 -2
  14. package/fs/cbase32/{test.f.js → proof.f.js} +1 -1
  15. package/fs/ci/config/module.f.d.ts +4 -4
  16. package/fs/ci/config/module.f.js +4 -4
  17. package/fs/ci/node/module.f.js +12 -7
  18. package/fs/ci/{test.f.d.ts → proof.f.d.ts} +1 -2
  19. package/fs/ci/{test.f.js → proof.f.js} +1 -1
  20. package/fs/crypto/hmac/{test.f.d.ts → proof.f.d.ts} +1 -2
  21. package/fs/crypto/hmac/{test.f.js → proof.f.js} +1 -1
  22. package/fs/crypto/secp/{test.f.d.ts → proof.f.d.ts} +1 -2
  23. package/fs/crypto/secp/{test.f.js → proof.f.js} +1 -1
  24. package/fs/crypto/sha2/{test.f.d.ts → proof.f.d.ts} +1 -2
  25. package/fs/crypto/sha2/{test.f.js → proof.f.js} +1 -1
  26. package/fs/crypto/sign/module.f.js +3 -3
  27. package/fs/crypto/sign/{test.f.d.ts → proof.f.d.ts} +1 -2
  28. package/fs/crypto/sign/{test.f.js → proof.f.js} +1 -1
  29. package/fs/dev/module.f.d.ts +28 -2
  30. package/fs/dev/module.f.js +38 -22
  31. package/fs/dev/{test.f.d.ts → proof.f.d.ts} +5 -2
  32. package/fs/dev/{test.f.js → proof.f.js} +25 -2
  33. package/fs/dev/tf/module.d.ts +1 -2
  34. package/fs/dev/tf/module.f.d.ts +70 -7
  35. package/fs/dev/tf/module.f.js +115 -27
  36. package/fs/dev/tf/module.js +4 -103
  37. package/fs/dev/tf/{test.f.d.ts → proof.f.d.ts} +26 -0
  38. package/fs/dev/tf/{test.f.js → proof.f.js} +85 -38
  39. package/fs/dev/tf/scenarios/all.d.ts +1 -0
  40. package/fs/dev/tf/scenarios/all.js +3 -0
  41. package/fs/dev/tf/scenarios/async-subtests.fail.d.ts +4 -0
  42. package/fs/dev/tf/scenarios/async-subtests.fail.js +7 -0
  43. package/fs/dev/tf/scenarios/async-subtests.pass.d.ts +4 -0
  44. package/fs/dev/tf/scenarios/async-subtests.pass.js +7 -0
  45. package/fs/dev/tf/scenarios/async.fail.d.ts +1 -0
  46. package/fs/dev/tf/scenarios/async.fail.js +4 -0
  47. package/fs/dev/tf/scenarios/async.pass.d.ts +1 -0
  48. package/fs/dev/tf/scenarios/async.pass.js +3 -0
  49. package/fs/dev/tf/scenarios/fail.fail.f.d.ts +1 -0
  50. package/fs/dev/tf/scenarios/fail.fail.f.js +1 -0
  51. package/fs/dev/tf/scenarios/return-value.pass.f.d.ts +1 -0
  52. package/fs/dev/tf/scenarios/return-value.pass.f.js +2 -0
  53. package/fs/dev/tf/scenarios/thenable.pass.d.ts +3 -0
  54. package/fs/dev/tf/scenarios/thenable.pass.js +9 -0
  55. package/fs/dev/tf/scenarios/thenable2.pass.f.d.ts +3 -0
  56. package/fs/dev/tf/scenarios/thenable2.pass.f.js +3 -0
  57. package/fs/{bnf/test.f.d.ts → dev/tf/scenarios/throw.pass.f.d.ts} +3 -1
  58. package/fs/dev/tf/scenarios/throw.pass.f.js +3 -0
  59. package/fs/dev/version/proof.f.d.ts +3 -0
  60. package/fs/dev/version/{test.f.js → proof.f.js} +1 -1
  61. package/fs/djs/ast/{test.f.d.ts → proof.f.d.ts} +1 -2
  62. package/fs/djs/ast/{test.f.js → proof.f.js} +1 -1
  63. package/fs/djs/parser/{test.f.d.ts → proof.f.d.ts} +1 -2
  64. package/fs/djs/parser/{test.f.js → proof.f.js} +1 -1
  65. package/fs/djs/{test.f.d.ts → proof.f.d.ts} +1 -2
  66. package/fs/djs/{test.f.js → proof.f.js} +1 -1
  67. package/fs/djs/serializer/module.f.d.ts +2 -2
  68. package/fs/djs/serializer/module.f.js +47 -79
  69. package/fs/djs/serializer/{test.f.d.ts → proof.f.d.ts} +1 -2
  70. package/fs/djs/serializer/{test.f.js → proof.f.js} +8 -8
  71. package/fs/djs/tokenizer/{test.f.d.ts → proof.f.d.ts} +1 -2
  72. package/fs/djs/tokenizer/{test.f.js → proof.f.js} +1 -1
  73. package/fs/djs/tokenizer-new/{test.f.d.ts → proof.f.d.ts} +1 -2
  74. package/fs/djs/tokenizer-new/{test.f.js → proof.f.js} +1 -1
  75. package/fs/djs/transpiler/module.f.d.ts +15 -0
  76. package/fs/djs/transpiler/module.f.js +10 -2
  77. package/fs/djs/transpiler/{test.f.d.ts → proof.f.d.ts} +1 -2
  78. package/fs/djs/transpiler/{test.f.js → proof.f.js} +1 -1
  79. package/fs/fsc/{test.f.d.ts → proof.f.d.ts} +1 -2
  80. package/fs/fsc/{test.f.js → proof.f.js} +1 -1
  81. package/fs/fsm/proof.f.d.ts +4 -0
  82. package/fs/fsm/{test.f.js → proof.f.js} +1 -1
  83. package/fs/html/{test.f.d.ts → proof.f.d.ts} +1 -2
  84. package/fs/html/{test.f.js → proof.f.js} +1 -1
  85. package/fs/io/module.d.ts +1 -1
  86. package/fs/io/module.f.d.ts +8 -3
  87. package/fs/io/module.f.js +12 -12
  88. package/fs/io/module.js +43 -5
  89. package/fs/js/tokenizer/{test.f.d.ts → proof.f.d.ts} +1 -2
  90. package/fs/js/tokenizer/{test.f.js → proof.f.js} +1 -1
  91. package/fs/json/parser/{test.f.d.ts → proof.f.d.ts} +1 -2
  92. package/fs/json/parser/{test.f.js → proof.f.js} +1 -1
  93. package/fs/json/{test.f.d.ts → proof.f.d.ts} +1 -2
  94. package/fs/json/{test.f.js → proof.f.js} +1 -1
  95. package/fs/json/serializer/{test.f.d.ts → proof.f.d.ts} +1 -2
  96. package/fs/json/serializer/{test.f.js → proof.f.js} +1 -1
  97. package/fs/json/tokenizer/{test.f.d.ts → proof.f.d.ts} +1 -2
  98. package/fs/json/tokenizer/{test.f.js → proof.f.js} +1 -1
  99. package/fs/path/proof.f.d.ts +5 -0
  100. package/fs/path/{test.f.js → proof.f.js} +4 -3
  101. package/fs/sul/id/module.f.js +3 -4
  102. package/fs/sul/id/{test.f.d.ts → proof.f.d.ts} +1 -2
  103. package/fs/sul/id/{test.f.js → proof.f.js} +1 -1
  104. package/fs/sul/level/hash/{test.f.d.ts → proof.f.d.ts} +1 -2
  105. package/fs/sul/level/hash/{test.f.js → proof.f.js} +1 -1
  106. package/fs/sul/level/literal/module.f.js +3 -3
  107. package/fs/sul/level/literal/{test.f.d.ts → proof.f.d.ts} +1 -2
  108. package/fs/sul/level/literal/{test.f.js → proof.f.js} +1 -1
  109. package/fs/sul/{test.f.d.ts → proof.f.d.ts} +1 -2
  110. package/fs/sul/{test.f.js → proof.f.js} +1 -1
  111. package/fs/text/ascii/proof.f.d.ts +3 -0
  112. package/fs/text/ascii/{test.f.js → proof.f.js} +1 -1
  113. package/fs/text/code_point/module.f.d.ts +28 -0
  114. package/fs/text/code_point/module.f.js +31 -0
  115. package/fs/text/{test.f.d.ts → proof.f.d.ts} +1 -2
  116. package/fs/text/{test.f.js → proof.f.js} +1 -1
  117. package/fs/text/sgr/proof.f.d.ts +1 -0
  118. package/fs/text/sgr/proof.f.js +23 -0
  119. package/fs/text/utf16/module.f.js +3 -53
  120. package/fs/text/utf16/{test.f.d.ts → proof.f.d.ts} +1 -2
  121. package/fs/text/utf16/{test.f.js → proof.f.js} +1 -1
  122. package/fs/text/utf8/module.f.js +3 -25
  123. package/fs/text/utf8/{test.f.d.ts → proof.f.d.ts} +1 -2
  124. package/fs/text/utf8/{test.f.js → proof.f.js} +1 -1
  125. package/fs/types/array/{test.f.d.ts → proof.f.d.ts} +1 -2
  126. package/fs/types/array/{test.f.js → proof.f.js} +1 -1
  127. package/fs/types/bigfloat/{test.f.d.ts → proof.f.d.ts} +1 -2
  128. package/fs/types/bigfloat/{test.f.js → proof.f.js} +1 -1
  129. package/fs/types/bigint/{test.f.d.ts → proof.f.d.ts} +1 -2
  130. package/fs/types/bigint/{test.f.js → proof.f.js} +1 -1
  131. package/fs/types/bit_vec/module.f.d.ts +9 -4
  132. package/fs/types/bit_vec/module.f.js +7 -9
  133. package/fs/types/bit_vec/{test.f.d.ts → proof.f.d.ts} +1 -2
  134. package/fs/types/bit_vec/{test.f.js → proof.f.js} +1 -1
  135. package/fs/types/btree/find/proof.f.d.ts +1 -0
  136. package/fs/types/btree/find/{test.f.js → proof.f.js} +1 -1
  137. package/fs/types/btree/{test.f.d.ts → proof.f.d.ts} +1 -2
  138. package/fs/types/btree/{test.f.js → proof.f.js} +1 -1
  139. package/fs/types/btree/remove/module.f.d.ts +1 -1
  140. package/fs/types/btree/remove/module.f.js +7 -2
  141. package/fs/types/btree/remove/proof.f.d.ts +4 -0
  142. package/fs/types/btree/remove/{test.f.js → proof.f.js} +1 -1
  143. package/fs/types/btree/set/module.f.d.ts +1 -1
  144. package/fs/types/btree/set/module.f.js +7 -2
  145. package/fs/types/btree/set/proof.f.d.ts +1 -0
  146. package/fs/types/btree/set/{test.f.js → proof.f.js} +1 -1
  147. package/fs/types/btree/types/module.f.d.ts +9 -0
  148. package/fs/types/btree/types/module.f.js +9 -1
  149. package/fs/types/byte_set/{test.f.d.ts → proof.f.d.ts} +1 -2
  150. package/fs/types/byte_set/{test.f.js → proof.f.js} +1 -1
  151. package/fs/types/effects/module.f.d.ts +17 -0
  152. package/fs/types/effects/module.f.js +17 -0
  153. package/fs/types/effects/node/module.f.d.ts +66 -4
  154. package/fs/types/effects/node/module.f.js +5 -1
  155. package/fs/types/effects/node/{test.f.d.ts → proof.f.d.ts} +1 -2
  156. package/fs/types/effects/node/{test.f.js → proof.f.js} +1 -1
  157. package/fs/types/effects/node/virtual/module.f.d.ts +2 -1
  158. package/fs/types/effects/node/virtual/module.f.js +2 -0
  159. package/fs/types/effects/proof.f.d.ts +11 -0
  160. package/fs/types/effects/proof.f.js +57 -0
  161. package/fs/types/function/compare/proof.f.d.ts +1 -0
  162. package/fs/types/function/compare/{test.f.js → proof.f.js} +1 -1
  163. package/fs/types/function/operator/proof.f.d.ts +12 -0
  164. package/fs/types/function/operator/{test.f.js → proof.f.js} +11 -10
  165. package/fs/types/function/proof.f.d.ts +1 -0
  166. package/fs/types/function/{test.f.js → proof.f.js} +1 -1
  167. package/fs/types/list/{test.f.d.ts → proof.f.d.ts} +1 -2
  168. package/fs/types/list/{test.f.js → proof.f.js} +1 -1
  169. package/fs/types/map/proof.f.d.ts +4 -0
  170. package/fs/types/map/{test.f.js → proof.f.js} +1 -1
  171. package/fs/types/monoid/{test.f.d.ts → proof.f.d.ts} +1 -2
  172. package/fs/types/monoid/{test.f.js → proof.f.js} +1 -1
  173. package/fs/types/nibble_set/{test.f.d.ts → proof.f.d.ts} +1 -2
  174. package/fs/types/nibble_set/{test.f.js → proof.f.js} +1 -1
  175. package/fs/types/nominal/proof.f.d.ts +4 -0
  176. package/fs/types/nominal/{test.f.js → proof.f.js} +1 -1
  177. package/fs/types/nullable/proof.f.d.ts +1 -0
  178. package/fs/types/nullable/{test.f.js → proof.f.js} +11 -2
  179. package/fs/types/number/{test.f.d.ts → proof.f.d.ts} +1 -2
  180. package/fs/types/number/{test.f.js → proof.f.js} +1 -1
  181. package/fs/types/object/{test.f.d.ts → proof.f.d.ts} +1 -2
  182. package/fs/types/object/{test.f.js → proof.f.js} +1 -1
  183. package/fs/types/ordered_map/{test.f.d.ts → proof.f.d.ts} +1 -2
  184. package/fs/types/ordered_map/{test.f.js → proof.f.js} +1 -1
  185. package/fs/types/patricia_trie/{test.f.d.ts → proof.f.d.ts} +1 -2
  186. package/fs/types/patricia_trie/{test.f.js → proof.f.js} +1 -1
  187. package/fs/types/prime_field/{test.f.d.ts → proof.f.d.ts} +1 -2
  188. package/fs/types/prime_field/{test.f.js → proof.f.js} +1 -1
  189. package/fs/types/range/proof.f.d.ts +1 -0
  190. package/fs/types/range/{test.f.js → proof.f.js} +1 -1
  191. package/fs/types/range_map/{test.f.d.ts → proof.f.d.ts} +1 -2
  192. package/fs/types/range_map/{test.f.js → proof.f.js} +1 -1
  193. package/fs/types/result/proof.f.d.ts +5 -0
  194. package/fs/types/result/{test.f.js → proof.f.js} +18 -2
  195. package/fs/types/rtti/parse/{test.f.d.ts → proof.f.d.ts} +1 -2
  196. package/fs/types/rtti/parse/{test.f.js → proof.f.js} +1 -1
  197. package/fs/types/rtti/{test.f.d.ts → proof.f.d.ts} +1 -2
  198. package/fs/types/rtti/{test.f.js → proof.f.js} +1 -1
  199. package/fs/types/rtti/ts/{test.f.d.ts → proof.f.d.ts} +1 -2
  200. package/fs/types/rtti/ts/{test.f.js → proof.f.js} +1 -1
  201. package/fs/types/rtti/validate/{test.f.d.ts → proof.f.d.ts} +1 -2
  202. package/fs/types/rtti/validate/{test.f.js → proof.f.js} +1 -1
  203. package/fs/types/sorted_list/{test.f.d.ts → proof.f.d.ts} +1 -2
  204. package/fs/types/sorted_list/{test.f.js → proof.f.js} +1 -1
  205. package/fs/types/sorted_set/{test.f.d.ts → proof.f.d.ts} +1 -2
  206. package/fs/types/sorted_set/{test.f.js → proof.f.js} +1 -1
  207. package/fs/types/string/{test.f.d.ts → proof.f.d.ts} +1 -2
  208. package/fs/types/string/{test.f.js → proof.f.js} +1 -1
  209. package/fs/types/string_set/{test.f.d.ts → proof.f.d.ts} +1 -2
  210. package/fs/types/string_set/{test.f.js → proof.f.js} +1 -1
  211. package/fs/types/ts/{test.f.d.ts → proof.f.d.ts} +20 -0
  212. package/fs/types/ts/{test.f.js → proof.f.js} +1 -0
  213. package/fs/types/uint8array/{test.f.d.ts → proof.f.d.ts} +1 -2
  214. package/fs/types/uint8array/{test.f.js → proof.f.js} +1 -1
  215. package/issues/demo/sample/{test.f.js → proof.f.js} +1 -1
  216. package/issues/{test.f.d.ts → proof.f.d.ts} +1 -2
  217. package/issues/{test.f.js → proof.f.js} +1 -1
  218. package/nanvm-lib/tests/{test.f.d.ts → proof.f.d.ts} +1 -2
  219. package/nanvm-lib/tests/{test.f.js → proof.f.js} +1 -1
  220. package/nanvm-lib/tests/vm/{test.f.d.ts → proof.f.d.ts} +1 -2
  221. package/nanvm-lib/tests/vm/{test.f.js → proof.f.js} +1 -1
  222. package/package.json +5 -5
  223. package/fs/base128/test.f.d.ts +0 -2
  224. package/fs/cas/test.f.d.ts +0 -2
  225. package/fs/cas/test.f.js +0 -1
  226. package/fs/dev/version/test.f.d.ts +0 -4
  227. package/fs/fsm/test.f.d.ts +0 -5
  228. package/fs/path/test.f.d.ts +0 -3
  229. package/fs/text/ascii/test.f.d.ts +0 -4
  230. package/fs/text/sgr/test.f.d.ts +0 -2
  231. package/fs/text/sgr/test.f.js +0 -8
  232. package/fs/types/btree/find/test.f.d.ts +0 -2
  233. package/fs/types/btree/remove/test.f.d.ts +0 -5
  234. package/fs/types/btree/set/test.f.d.ts +0 -2
  235. package/fs/types/function/compare/test.f.d.ts +0 -2
  236. package/fs/types/function/operator/test.f.d.ts +0 -10
  237. package/fs/types/function/test.f.d.ts +0 -2
  238. package/fs/types/map/test.f.d.ts +0 -5
  239. package/fs/types/nominal/test.f.d.ts +0 -5
  240. package/fs/types/nullable/test.f.d.ts +0 -2
  241. package/fs/types/range/test.f.d.ts +0 -2
  242. package/fs/types/result/test.f.d.ts +0 -2
@@ -1,5 +1,8 @@
1
- import { todo } from "./module.f.js";
2
- export default {
1
+ import { todo, env } from "./module.f.js";
2
+ export const proof = {
3
+ shouldPass: () => ({
4
+ then: () => undefined
5
+ }),
3
6
  ctor: () => {
4
7
  const c = (() => { })['constructor'];
5
8
  const f = c('return 5');
@@ -52,5 +55,25 @@ export default {
52
55
  },
53
56
  throw: () => {
54
57
  todo();
58
+ },
59
+ env: () => {
60
+ const mockIo = { process: { env: { MY_VAR: 'hello' } } };
61
+ // missing key → undefined
62
+ const r1 = env(mockIo)('MISSING');
63
+ if (r1 !== undefined) {
64
+ throw r1;
65
+ }
66
+ // regular value property → returns value
67
+ const r2 = env(mockIo)('MY_VAR');
68
+ if (r2 !== 'hello') {
69
+ throw r2;
70
+ }
71
+ // getter property → calls get()
72
+ const envWithGetter = Object.defineProperty({}, 'GETTER_VAR', { get: () => 'from-getter', enumerable: true, configurable: true });
73
+ const mockIo2 = { process: { env: envWithGetter } };
74
+ const r3 = env(mockIo2)('GETTER_VAR');
75
+ if (r3 !== 'from-getter') {
76
+ throw r3;
77
+ }
55
78
  }
56
79
  };
@@ -1,2 +1 @@
1
- export declare const run3: () => Promise<void>;
2
- export declare const run: () => Promise<void>;
1
+ export declare const run: () => Promise<number>;
@@ -1,21 +1,42 @@
1
- import { type All, type NodeProgram, type NodeProgramOptions, type Program, type Sandbox, type SandboxResult, type Write } from '../../types/effects/node/module.f.ts';
1
+ import { type All, type Await, type NodeProgram, type NodeProgramOptions, type Program, type Sandbox, type SandboxResult, type Test, type TestContext, type Write } from '../../types/effects/node/module.f.ts';
2
2
  import { type Effect, type Operation } from '../../types/effects/module.f.ts';
3
3
  import { type LoadModuleOperations, type ModuleMap } from '../module.f.ts';
4
- export declare const isTest: (s: string) => boolean;
5
- export type Test = () => unknown;
4
+ /** A zero-argument test function whose return value may contain sub-tests. */
5
+ export type TestFn = () => unknown;
6
+ /**
7
+ * A leaf test bundled with its throw expectation.
8
+ *
9
+ * `throws: true` means the test is expected to throw; the runner inverts the
10
+ * `sandbox` result so a caught error becomes a pass and a clean return becomes
11
+ * a failure. Using a record instead of a wrapper function avoids a double
12
+ * `sandbox` call and gives accurate per-test timing.
13
+ */
6
14
  export type TestEntry = {
7
- readonly fn: Test;
15
+ readonly fn: TestFn;
8
16
  readonly throws: boolean;
9
17
  };
18
+ /**
19
+ * Either a leaf `TestEntry` (function + throw flag) or a named sub-tree of
20
+ * `[key, value]` pairs to recurse into. Discriminate with `Array.isArray`.
21
+ */
10
22
  export type TestSet = TestEntry | readonly (readonly [string, unknown])[];
23
+ /**
24
+ * Converts an arbitrary JS value into a `TestSet`.
25
+ *
26
+ * - Zero-argument functions become a `TestEntry`; the `throws` flag is set if
27
+ * `throws` is already `true` or the function's `.name === 'throw'`.
28
+ * - Non-null objects become an array of `[key, value]` pairs to recurse into.
29
+ * - All other values (including functions with parameters) produce an empty array.
30
+ */
11
31
  export declare const parseTestSet: (throws: boolean, x: unknown) => TestSet;
32
+ type TestAndPath = readonly [Path, TestEntry];
12
33
  /**
13
34
  * Recursively collects all leaf tests reachable from `v` as `[path, entry]`
14
35
  * pairs, without running anything. Return-value sub-trees are not walked
15
36
  * (that requires execution); only the static object/array/function structure
16
37
  * is traversed.
17
38
  */
18
- export declare const collectTests: (path: Path, throws: boolean, v: unknown) => readonly (readonly [Path, TestEntry])[];
39
+ export declare const collectTests: (path: Path, throws: boolean, v: unknown) => readonly TestAndPath[];
19
40
  /**
20
41
  * Receives semantic test-run events. Each method is the runner's notification
21
42
  * of an event; the reporter decides how to render it (terminal, GitHub
@@ -29,10 +50,38 @@ export type Reporter<O extends Operation> = {
29
50
  readonly summary: (pass: number, fail: number, time: number) => Effect<O, void>;
30
51
  readonly test: (file: string, path: Path, set: TestEntry) => Effect<O, SandboxResult<unknown>>;
31
52
  };
53
+ /**
54
+ * Registers all tests reachable from module export `v` (keyed by `k`) with
55
+ * the given `TestContext`.
56
+ *
57
+ * Unlike `runModule`, which sandboxes only the leaf function, `registerModule`
58
+ * lets the external framework own scheduling: each registered test callback
59
+ * calls `fn`, then recursively registers any sub-trees returned by the function.
60
+ * This is the correct model for Node `--test`, Bun, and Playwright, where tests
61
+ * must be declared upfront and the framework drives execution.
62
+ */
63
+ export declare const registerModule: (ctx: TestContext, k: string, v: unknown) => Effect<Test | All | Await, void>;
64
+ /**
65
+ * Runs all test modules in `moduleMap` whose names pass `isTest`, accumulates
66
+ * pass/fail/time via `reporter`, and returns an exit code (0 = all passed,
67
+ * 1 = at least one failure).
68
+ */
32
69
  export declare const runModuleMap: <O extends Operation>(reporter: Reporter<O>) => (moduleMap: ModuleMap) => Effect<O | All, number>;
33
- export declare const test: <O extends Operation>(reporter: Reporter<O>) => Program<O | All | LoadModuleOperations>;
70
+ /**
71
+ * Discovers all test modules via `loadModuleMap`, then runs them through
72
+ * `runModuleMap`. The composed effect is a `NodeProgram` entry point for the
73
+ * `fjs t` test runner.
74
+ */
75
+ export declare const testAll: <O extends Operation>(reporter: Reporter<O>) => Program<O | All | LoadModuleOperations>;
76
+ /**
77
+ * A chain of property-access keys leading to a test location. String entries
78
+ * are object/array keys; `null` marks a function-call boundary (the return
79
+ * value was walked as a sub-tree).
80
+ */
34
81
  export type Path = readonly (string | null)[];
82
+ /** Returns `true` if `s` is a non-negative decimal integer without a leading zero. */
35
83
  export declare const isInteger: (s: string) => boolean;
84
+ /** Returns `true` if `s` is a valid JS identifier (ASCII subset: `[A-Za-z_$][A-Za-z0-9_$]*`). */
36
85
  export declare const isIdentifier: (s: string) => boolean;
37
86
  /**
38
87
  * Renders a key chain as a JS property-access expression: identifier keys use
@@ -43,7 +92,7 @@ export declare const isIdentifier: (s: string) => boolean;
43
92
  export declare const fmtPath: (path: Path) => string;
44
93
  /**
45
94
  * Formats a fully-qualified test identifier as a JS-like expression, e.g.
46
- * `import("./math.test.f.ts").add()` or `import("./a.test.f.ts").users[3].name()`.
95
+ * `import("./math.proof.f.ts").add()` or `import("./a.proof.f.ts").users[3].name()`.
47
96
  * Self-contained per line — suitable for parallel output and as a CLI filter argument.
48
97
  */
49
98
  export declare const fmtImport: (file: string, path: Path) => string;
@@ -60,6 +109,10 @@ export declare const fmtTerm: (path: Path) => string;
60
109
  * https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions
61
110
  */
62
111
  export declare const ghEscape: (s: string) => string;
112
+ /**
113
+ * Default `Reporter.test` implementation: sandboxes `fn` once and inverts the
114
+ * result when `throws` is `true` (caught error → pass, clean return → fail).
115
+ */
63
116
  export declare const defaultTest: (file: string, path: Path, { fn, throws }: TestEntry) => Effect<Sandbox, SandboxResult<unknown>>;
64
117
  /**
65
118
  * The terminal/GitHub reporter used by `fjs t`. Output goes through
@@ -69,4 +122,14 @@ export declare const defaultTest: (file: string, path: Path, { fn, throws }: Tes
69
122
  * GitHub format path can be exercised directly from tests.
70
123
  */
71
124
  export declare const defaultReporter: (options: NodeProgramOptions) => Reporter<Write | Sandbox>;
125
+ /** The `fjs t` entry point: runs all tests using `defaultReporter`. */
72
126
  export declare const main: NodeProgram;
127
+ /**
128
+ * Entry point for external test frameworks (Node `--test`, Bun, Playwright).
129
+ *
130
+ * Discovers test modules via `loadModuleMap`, then registers each with the
131
+ * framework-appropriate `TestContext` selected from `NodeProgramOptions`
132
+ * based on the detected `engine`.
133
+ */
134
+ export declare const register: NodeProgram;
135
+ export {};
@@ -1,14 +1,20 @@
1
1
  /**
2
2
  * Test-framework helpers for running and reporting FunctionalScript tests.
3
3
  *
4
+ * Two parallel execution paths:
5
+ * - `runModule` / `Reporter<O>` — self-hosted Effects runner used by `fjs t`;
6
+ * sandboxes each leaf call individually and accumulates `TestState`.
7
+ * - `registerModule` / `TestContext` — registers tests with an external
8
+ * framework (Node `--test`, Bun, Playwright) at import time; the framework
9
+ * owns scheduling and pass/fail counting.
10
+ *
4
11
  * @module
5
12
  */
6
13
  import { reset, fgGreen, fgRed, bold, csiWrite } from "../../text/sgr/module.f.js";
7
- import { all, sandbox } from "../../types/effects/node/module.f.js";
14
+ import { all, awaitIfPromise, sandbox, test } from "../../types/effects/node/module.f.js";
8
15
  import { pure } from "../../types/effects/module.f.js";
9
- import { loadModuleMap } from "../module.f.js";
16
+ import { loadModuleMap, shouldLoad } from "../module.f.js";
10
17
  import { invert } from "../../types/result/module.f.js";
11
- export const isTest = (s) => s.endsWith('test.f.js') || s.endsWith('test.f.ts');
12
18
  const addPass = (delta) => (ts) => ({ ...ts, time: ts.time + delta, pass: ts.pass + 1 });
13
19
  const addFail = (delta) => (ts) => ({ ...ts, time: ts.time + delta, fail: ts.fail + 1 });
14
20
  const timeFormat = (a) => {
@@ -20,6 +26,14 @@ const timeFormat = (a) => {
20
26
  const e = x.substring(s);
21
27
  return `${b}.${e} ms`;
22
28
  };
29
+ /**
30
+ * Converts an arbitrary JS value into a `TestSet`.
31
+ *
32
+ * - Zero-argument functions become a `TestEntry`; the `throws` flag is set if
33
+ * `throws` is already `true` or the function's `.name === 'throw'`.
34
+ * - Non-null objects become an array of `[key, value]` pairs to recurse into.
35
+ * - All other values (including functions with parameters) produce an empty array.
36
+ */
23
37
  export const parseTestSet = (throws, x) => {
24
38
  switch (typeof x) {
25
39
  case 'function': {
@@ -51,28 +65,56 @@ export const collectTests = (path, throws, v) => {
51
65
  }
52
66
  return [[path, set]];
53
67
  };
68
+ /**
69
+ * Registers all tests reachable from module export `v` (keyed by `k`) with
70
+ * the given `TestContext`.
71
+ *
72
+ * Unlike `runModule`, which sandboxes only the leaf function, `registerModule`
73
+ * lets the external framework own scheduling: each registered test callback
74
+ * calls `fn`, then recursively registers any sub-trees returned by the function.
75
+ * This is the correct model for Node `--test`, Bun, and Playwright, where tests
76
+ * must be declared upfront and the framework drives execution.
77
+ */
78
+ export const registerModule = (ctx, k, v) => {
79
+ const registerOne = (ctx, [path, { fn, throws }]) => test(ctx, fmtImport(k, path), throws, (t) => awaitIfPromise(fn())
80
+ .step(resolved => {
81
+ if (throws) {
82
+ return pure(undefined);
83
+ }
84
+ const sub = collectTests([...path, null], false, resolved);
85
+ if (sub.length === 0) {
86
+ return pure(undefined);
87
+ }
88
+ return all(...sub.map(e => registerOne(t, e))).step(() => pure(undefined));
89
+ }));
90
+ const tests = collectTests([], false, v);
91
+ if (tests.length === 0) {
92
+ return pure(undefined);
93
+ }
94
+ return all(...tests.map(e => registerOne(ctx, e))).step(() => pure(undefined));
95
+ };
54
96
  const mergeState = (a, b) => ({ time: a.time + b.time, pass: a.pass + b.pass, fail: a.fail + b.fail });
55
97
  const zero = { time: 0, pass: 0, fail: 0 };
56
98
  const runModule = ({ result, test }) => (k, v) => (ts) => {
57
- const walk = (path, throws, v) => {
58
- const effects = collectTests(path, throws, v)
59
- .map(([testPath, set]) => test(k, testPath, set)
60
- .step(sr => {
61
- const { result: [s, r], duration } = sr;
62
- return result(k, testPath, sr)
63
- .step(() => {
64
- if (s === 'ok') {
65
- if (set.throws) {
66
- return pure(addPass(duration)(zero));
67
- }
68
- // Walk return-value sub-tree; null marks the call boundary so
69
- // paths render as e.g. `outer().inner`. throws resets to false.
70
- return walk([...testPath, null], false, r)
71
- .step(sub => pure(mergeState(addPass(duration)(zero), sub)));
99
+ const one = ([testPath, set]) => test(k, testPath, set)
100
+ .step(sr => {
101
+ const { result: [s, r], duration } = sr;
102
+ return result(k, testPath, sr)
103
+ .step(() => {
104
+ if (s === 'ok') {
105
+ if (set.throws) {
106
+ return pure(addPass(duration)(zero));
72
107
  }
73
- return pure(addFail(duration)(zero));
74
- });
75
- }));
108
+ // Walk return-value sub-tree; null marks the call boundary so
109
+ // paths render as e.g. `outer().inner`. throws resets to false.
110
+ return walk([...testPath, null], false, r)
111
+ .step(sub => pure(mergeState(addPass(duration)(zero), sub)));
112
+ }
113
+ return pure(addFail(duration)(zero));
114
+ });
115
+ });
116
+ const walk = (path, throws, v) => {
117
+ const effects = collectTests(path, throws, v).map(one);
76
118
  return all(...effects)
77
119
  .step(states => pure(states.reduce(mergeState, zero)));
78
120
  };
@@ -80,17 +122,43 @@ const runModule = ({ result, test }) => (k, v) => (ts) => {
80
122
  .step(delta => pure(mergeState(ts, delta)));
81
123
  };
82
124
  const { entries } = Object;
125
+ /**
126
+ * Runs all test modules in `moduleMap` whose names pass `isTest`, accumulates
127
+ * pass/fail/time via `reporter`, and returns an exit code (0 = all passed,
128
+ * 1 = at least one failure).
129
+ */
83
130
  export const runModuleMap = (reporter) => (moduleMap) => {
84
131
  const { summary } = reporter;
85
- const modules = entries(moduleMap).filter(([k]) => isTest(k));
86
- return modules.reduce((acc, [k, v]) => acc.step(runModule(reporter)(k, v)), pure({ time: 0, pass: 0, fail: 0 }))
132
+ const modules = entries(moduleMap)
133
+ .flatMap(([k, v]) => v.proof !== undefined ? [[k, v.proof]] : []);
134
+ return all(...modules.map(([k, v]) => runModule(reporter)(k, v)(zero)))
135
+ .step(m => pure(m.reduce(mergeState, zero)))
87
136
  .step(ts => summary(ts.pass, ts.fail, ts.time)
88
137
  .step(() => pure(ts.fail !== 0 ? 1 : 0)));
89
138
  };
90
- export const test = (reporter) => options => loadModuleMap(options.env).step(runModuleMap(reporter));
139
+ /**
140
+ * Discovers all test modules via `loadModuleMap`, then runs them through
141
+ * `runModuleMap`. The composed effect is a `NodeProgram` entry point for the
142
+ * `fjs t` test runner.
143
+ */
144
+ export const testAll = (reporter) => options => loadModuleMap(options.env).step(runModuleMap(reporter));
145
+ /**
146
+ * Registers all modules in `moduleMap` that export a `proof` property with
147
+ * `ctx`. Delegates to `registerModule` for each matching entry.
148
+ */
149
+ const registerModuleMap = (ctx) => (moduleMap) => {
150
+ const modules = entries(moduleMap)
151
+ .flatMap(([k, v]) => v.proof !== undefined ? [[k, v.proof]] : []);
152
+ if (modules.length === 0) {
153
+ return pure(undefined);
154
+ }
155
+ return all(...modules.map(([k, v]) => registerModule(ctx, k, v))).step(() => pure(undefined));
156
+ };
91
157
  const isAlpha = (c) => (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c === '_' || c === '$';
92
158
  const isDigit = (c) => c >= '0' && c <= '9';
159
+ /** Returns `true` if `s` is a non-negative decimal integer without a leading zero. */
93
160
  export const isInteger = (s) => s.length > 0 && [...s].every(isDigit) && (s === '0' || s[0] !== '0');
161
+ /** Returns `true` if `s` is a valid JS identifier (ASCII subset: `[A-Za-z_$][A-Za-z0-9_$]*`). */
94
162
  export const isIdentifier = (s) => s.length > 0 && isAlpha(s[0]) && [...s.slice(1)].every(c => isAlpha(c) || isDigit(c));
95
163
  const fmtKey = (k) => k === null ? '()'
96
164
  : isInteger(k) ? `[${k}]`
@@ -105,10 +173,10 @@ const fmtKey = (k) => k === null ? '()'
105
173
  export const fmtPath = (path) => path.reduce((acc, k) => acc + fmtKey(k), '');
106
174
  /**
107
175
  * Formats a fully-qualified test identifier as a JS-like expression, e.g.
108
- * `import("./math.test.f.ts").add()` or `import("./a.test.f.ts").users[3].name()`.
176
+ * `import("./math.proof.f.ts").add()` or `import("./a.proof.f.ts").users[3].name()`.
109
177
  * Self-contained per line — suitable for parallel output and as a CLI filter argument.
110
178
  */
111
- export const fmtImport = (file, path) => `import(${JSON.stringify(file)})${fmtPath(path)}()`;
179
+ export const fmtImport = (file, path) => `import(${JSON.stringify(file)}).proof${fmtPath(path)}()`;
112
180
  /**
113
181
  * Renders a key chain for terminal output: `| ` per level of depth, followed
114
182
  * by the last segment formatted as a bare integer, a bare identifier, or a
@@ -134,6 +202,10 @@ export const ghEscape = (s) => s.replaceAll('%', '%25')
134
202
  .replaceAll(',', '%2C')
135
203
  .replaceAll('\r', '%0D')
136
204
  .replaceAll('\n', '%0A');
205
+ /**
206
+ * Default `Reporter.test` implementation: sandboxes `fn` once and inverts the
207
+ * result when `throws` is `true` (caught error → pass, clean return → fail).
208
+ */
137
209
  export const defaultTest = (file, path, { fn, throws }) => sandbox(fn)
138
210
  .step(r => pure(throws ? { ...r, result: invert(r.result) } : r));
139
211
  const fmtResultLine = (file, path, color, label, duration) => `${fmtImport(file, path)}: ${color}${label}${reset}, ${timeFormat(duration)}`;
@@ -169,4 +241,20 @@ export const defaultReporter = (options) => {
169
241
  test: defaultTest,
170
242
  };
171
243
  };
172
- export const main = options => test(defaultReporter(options))(options);
244
+ /** The `fjs t` entry point: runs all tests using `defaultReporter`. */
245
+ export const main = options => testAll(defaultReporter(options))(options);
246
+ /**
247
+ * Entry point for external test frameworks (Node `--test`, Bun, Playwright).
248
+ *
249
+ * Discovers test modules via `loadModuleMap`, then registers each with the
250
+ * framework-appropriate `TestContext` selected from `NodeProgramOptions`
251
+ * based on the detected `engine`.
252
+ */
253
+ export const register = o => {
254
+ const r = registerModuleMap(o.engine === 'bun' ? o.bunTestContext :
255
+ o.engine === 'playwright' ? o.playwrightTestContext :
256
+ o.testContext);
257
+ return loadModuleMap(o.env)
258
+ .step(r)
259
+ .step(() => pure(0));
260
+ };
@@ -1,103 +1,4 @@
1
- import { io, tryCatch } from "../../io/module.js";
2
- import { loadModuleMap } from "../module.f.js";
3
- import { fmtImport, isTest, parseTestSet, runModuleMap } from "./module.f.js";
4
- import * as nodeTest from 'node:test';
5
- import { asyncImport } from "../../io/module.js";
6
- import { fromIo } from "../../io/module.f.js";
7
- import { pure } from "../../types/effects/module.f.js";
8
- import { asyncRun } from "../../types/effects/module.js";
9
- import {} from "../../types/effects/node/module.f.js";
10
- const isBun = typeof Bun !== 'undefined';
11
- const isPlaywright = typeof process !== 'undefined' && process?.env?.PLAYWRIGHT_TEST !== undefined;
12
- const createFramework = (fw) => (prefix, f) => fw.test(prefix, t => f((name, v) => t.test(name, v)));
13
- // Bun doesn't support nested tests yet.
14
- const createBunFramework = (fw) => (prefix, f) => f((name, v) => fw.test(`${prefix}: ${name}`, v));
15
- const createPlaywrightFramework = async () => {
16
- const pwTest = (await asyncImport('@playwright/test')).test;
17
- return (prefix, f) => f((name, v) => pwTest(`${prefix}: ${name}`, v));
18
- };
19
- const framework = isPlaywright ? await createPlaywrightFramework() :
20
- isBun ? createBunFramework(nodeTest) :
21
- createFramework(nodeTest);
22
- const scanModule = (x) => async (subTestRunner) => {
23
- let subTests = [x];
24
- while (true) {
25
- const [first, ...rest] = subTests;
26
- if (first === undefined) {
27
- break;
28
- }
29
- subTests = rest;
30
- //
31
- const [name, value, throws] = first;
32
- const set = parseTestSet(throws, value);
33
- if (set instanceof Array) {
34
- for (const [j, y] of set) {
35
- const pr = `${name}/${j}`;
36
- subTests = [...subTests, [pr, y, throws || j === 'throw']];
37
- }
38
- }
39
- else {
40
- await subTestRunner(name, () => {
41
- if (set.throws) {
42
- let threw = false;
43
- try {
44
- set.fn();
45
- }
46
- catch (_) {
47
- threw = true;
48
- }
49
- if (!threw) {
50
- throw new Error(`${name}() expected to throw`);
51
- }
52
- }
53
- else {
54
- const r = set.fn();
55
- // The result of a function is walked as a fresh sub-tree;
56
- // the parent's `throws` flag does not propagate into it.
57
- subTests = [...subTests, [`${name}()`, r, false]];
58
- }
59
- });
60
- }
61
- }
62
- };
63
- const noOp = () => pure(undefined);
64
- const reporter = {
65
- result: noOp,
66
- summary: noOp,
67
- test: (file, path, { throws, fn }) => {
68
- nodeTest.test(fmtImport(file, path), async (_t) => {
69
- const [s, r] = tryCatch(fn);
70
- if (throws === (s === 'ok')) {
71
- throw r;
72
- }
73
- if (!throws) {
74
- // TODO: add subtests
75
- }
76
- });
77
- return pure({
78
- result: ['ok', undefined],
79
- duration: 0,
80
- });
81
- }
82
- };
83
- const map = {
84
- // TODO: we use the same algorithm twice. Refactor by creating a `createAll(map)`
85
- // helper that takes a `map` and returns an `all` function that runs effects
86
- // according to the map. There could be a problem with circular dependencies,
87
- // but we can use a lazy function `() => ToAsyncOperationMap<All>` instead od `map`.
88
- all: async (...effects) => Promise.all(effects.map(asyncRun(map))),
89
- };
90
- export const run3 = async () => {
91
- const fio = fromIo(io);
92
- const moduleMap = await fio(loadModuleMap(io.process.env));
93
- const runner = runModuleMap(reporter)(moduleMap);
94
- await asyncRun(map)(runner);
95
- };
96
- export const run = async () => {
97
- const moduleMap = await fromIo(io)(loadModuleMap(io.process.env));
98
- for (const [i, v] of Object.entries(moduleMap)) {
99
- if (isTest(i)) {
100
- framework(i, scanModule(['', v, false]));
101
- }
102
- }
103
- };
1
+ import { io } from "../../io/module.js";
2
+ import { register } from "./module.f.js";
3
+ import { runProgram } from "../../io/module.f.js";
4
+ export const run = () => runProgram(io)([])(register);
@@ -15,8 +15,34 @@ export declare const githubReporterOutput: () => void;
15
15
  export declare const helpers: {
16
16
  isInteger: () => void;
17
17
  isIdentifier: () => void;
18
+ shouldLoad: () => void;
18
19
  fmtImport: () => void;
19
20
  fmtPath: () => void;
20
21
  fmtTerm: () => void;
21
22
  ghEscape: () => void;
22
23
  };
24
+ export declare const proof: {
25
+ flat: () => void;
26
+ nested: () => void;
27
+ throwKey: () => void;
28
+ throwKeyFail: () => void;
29
+ mixedPassFail: () => void;
30
+ returnValueSubTree: () => void;
31
+ arrayKeys: () => void;
32
+ nonTestFilesSkipped: () => void;
33
+ multipleFiles: () => void;
34
+ throwByFunctionName: () => void;
35
+ namedExports: () => void;
36
+ defaultReporterOutput: () => void;
37
+ defaultReporterFailOutput: () => void;
38
+ githubReporterOutput: () => void;
39
+ helpers: {
40
+ isInteger: () => void;
41
+ isIdentifier: () => void;
42
+ shouldLoad: () => void;
43
+ fmtImport: () => void;
44
+ fmtPath: () => void;
45
+ fmtTerm: () => void;
46
+ ghEscape: () => void;
47
+ };
48
+ };