functionalscript 0.19.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.
- package/fs/asn.1/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/asn.1/{test.f.js → proof.f.js} +1 -1
- package/fs/base128/proof.f.d.ts +1 -0
- package/fs/base128/{test.f.js → proof.f.js} +1 -1
- package/fs/bnf/data/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/bnf/data/{test.f.js → proof.f.js} +1 -1
- package/fs/bnf/proof.f.d.ts +3 -0
- package/fs/bnf/{test.f.js → proof.f.js} +1 -1
- package/fs/cas/module.f.js +2 -12
- package/fs/cas/proof.f.d.ts +1 -0
- package/fs/cas/proof.f.js +1 -0
- package/fs/cbase32/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/cbase32/{test.f.js → proof.f.js} +1 -1
- package/fs/ci/node/module.f.js +12 -7
- package/fs/ci/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/ci/{test.f.js → proof.f.js} +1 -1
- package/fs/crypto/hmac/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/crypto/hmac/{test.f.js → proof.f.js} +1 -1
- package/fs/crypto/secp/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/crypto/secp/{test.f.js → proof.f.js} +1 -1
- package/fs/crypto/sha2/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/crypto/sha2/{test.f.js → proof.f.js} +1 -1
- package/fs/crypto/sign/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/crypto/sign/{test.f.js → proof.f.js} +1 -1
- package/fs/dev/module.f.d.ts +28 -2
- package/fs/dev/module.f.js +38 -22
- package/fs/dev/{test.f.d.ts → proof.f.d.ts} +5 -2
- package/fs/dev/{test.f.js → proof.f.js} +25 -2
- package/fs/dev/tf/module.f.d.ts +63 -5
- package/fs/dev/tf/module.f.js +77 -20
- package/fs/dev/tf/{test.f.d.ts → proof.f.d.ts} +26 -0
- package/fs/dev/tf/{test.f.js → proof.f.js} +76 -34
- package/fs/dev/tf/scenarios/async-subtests.fail.d.ts +4 -0
- package/fs/dev/tf/scenarios/async-subtests.fail.js +7 -0
- package/fs/dev/tf/scenarios/async-subtests.pass.d.ts +4 -0
- package/fs/dev/tf/scenarios/async-subtests.pass.js +7 -0
- package/fs/dev/tf/scenarios/async.fail.d.ts +1 -0
- package/fs/dev/tf/scenarios/async.fail.js +4 -0
- package/fs/dev/tf/scenarios/async.pass.d.ts +1 -0
- package/fs/dev/tf/scenarios/async.pass.js +3 -0
- package/fs/dev/tf/scenarios/thenable.pass.d.ts +3 -0
- package/fs/dev/tf/scenarios/thenable.pass.js +9 -0
- package/fs/dev/tf/scenarios/thenable2.pass.f.d.ts +3 -0
- package/fs/dev/tf/scenarios/thenable2.pass.f.js +3 -0
- package/fs/dev/version/proof.f.d.ts +3 -0
- package/fs/dev/version/{test.f.js → proof.f.js} +1 -1
- package/fs/djs/ast/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/djs/ast/{test.f.js → proof.f.js} +1 -1
- package/fs/djs/parser/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/djs/parser/{test.f.js → proof.f.js} +1 -1
- package/fs/djs/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/djs/{test.f.js → proof.f.js} +1 -1
- package/fs/djs/serializer/module.f.d.ts +2 -2
- package/fs/djs/serializer/module.f.js +47 -79
- package/fs/djs/serializer/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/djs/serializer/{test.f.js → proof.f.js} +8 -8
- package/fs/djs/tokenizer/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/djs/tokenizer/{test.f.js → proof.f.js} +1 -1
- package/fs/djs/tokenizer-new/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/djs/tokenizer-new/{test.f.js → proof.f.js} +1 -1
- package/fs/djs/transpiler/module.f.d.ts +15 -0
- package/fs/djs/transpiler/module.f.js +10 -2
- package/fs/djs/transpiler/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/djs/transpiler/{test.f.js → proof.f.js} +1 -1
- package/fs/fsc/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/fsc/{test.f.js → proof.f.js} +1 -1
- package/fs/fsm/proof.f.d.ts +4 -0
- package/fs/fsm/{test.f.js → proof.f.js} +1 -1
- package/fs/html/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/html/{test.f.js → proof.f.js} +1 -1
- package/fs/io/module.d.ts +1 -1
- package/fs/io/module.f.d.ts +3 -2
- package/fs/io/module.f.js +4 -3
- package/fs/io/module.js +19 -11
- package/fs/js/tokenizer/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/js/tokenizer/{test.f.js → proof.f.js} +1 -1
- package/fs/json/parser/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/json/parser/{test.f.js → proof.f.js} +1 -1
- package/fs/json/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/json/{test.f.js → proof.f.js} +1 -1
- package/fs/json/serializer/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/json/serializer/{test.f.js → proof.f.js} +1 -1
- package/fs/json/tokenizer/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/json/tokenizer/{test.f.js → proof.f.js} +1 -1
- package/fs/path/proof.f.d.ts +5 -0
- package/fs/path/{test.f.js → proof.f.js} +4 -3
- package/fs/sul/id/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/sul/id/{test.f.js → proof.f.js} +1 -1
- package/fs/sul/level/hash/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/sul/level/hash/{test.f.js → proof.f.js} +1 -1
- package/fs/sul/level/literal/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/sul/level/literal/{test.f.js → proof.f.js} +1 -1
- package/fs/sul/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/sul/{test.f.js → proof.f.js} +1 -1
- package/fs/text/ascii/proof.f.d.ts +3 -0
- package/fs/text/ascii/{test.f.js → proof.f.js} +1 -1
- package/fs/text/code_point/module.f.d.ts +28 -0
- package/fs/text/code_point/module.f.js +31 -0
- package/fs/text/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/text/{test.f.js → proof.f.js} +1 -1
- package/fs/text/sgr/proof.f.d.ts +1 -0
- package/fs/text/sgr/{test.f.js → proof.f.js} +1 -1
- package/fs/text/utf16/module.f.js +3 -53
- package/fs/text/utf16/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/text/utf16/{test.f.js → proof.f.js} +1 -1
- package/fs/text/utf8/module.f.js +3 -25
- package/fs/text/utf8/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/text/utf8/{test.f.js → proof.f.js} +1 -1
- package/fs/types/array/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/array/{test.f.js → proof.f.js} +1 -1
- package/fs/types/bigfloat/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/bigfloat/{test.f.js → proof.f.js} +1 -1
- package/fs/types/bigint/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/bigint/{test.f.js → proof.f.js} +1 -1
- package/fs/types/bit_vec/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/bit_vec/{test.f.js → proof.f.js} +1 -1
- package/fs/types/btree/find/proof.f.d.ts +1 -0
- package/fs/types/btree/find/{test.f.js → proof.f.js} +1 -1
- package/fs/types/btree/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/btree/{test.f.js → proof.f.js} +1 -1
- package/fs/types/btree/remove/proof.f.d.ts +4 -0
- package/fs/types/btree/remove/{test.f.js → proof.f.js} +1 -1
- package/fs/types/btree/set/proof.f.d.ts +1 -0
- package/fs/types/btree/set/{test.f.js → proof.f.js} +1 -1
- package/fs/types/btree/types/module.f.d.ts +8 -0
- package/fs/types/btree/types/module.f.js +8 -0
- package/fs/types/byte_set/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/byte_set/{test.f.js → proof.f.js} +1 -1
- package/fs/types/effects/module.f.d.ts +17 -0
- package/fs/types/effects/module.f.js +17 -0
- package/fs/types/effects/node/module.f.d.ts +54 -5
- package/fs/types/effects/node/module.f.js +4 -1
- package/fs/types/effects/node/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/effects/node/{test.f.js → proof.f.js} +1 -1
- package/fs/types/effects/node/virtual/module.f.js +1 -0
- package/fs/types/effects/proof.f.d.ts +11 -0
- package/fs/types/effects/proof.f.js +57 -0
- package/fs/types/function/compare/proof.f.d.ts +1 -0
- package/fs/types/function/compare/{test.f.js → proof.f.js} +1 -1
- package/fs/types/function/operator/proof.f.d.ts +12 -0
- package/fs/types/function/operator/{test.f.js → proof.f.js} +11 -10
- package/fs/types/function/proof.f.d.ts +1 -0
- package/fs/types/function/{test.f.js → proof.f.js} +1 -1
- package/fs/types/list/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/list/{test.f.js → proof.f.js} +1 -1
- package/fs/types/map/proof.f.d.ts +4 -0
- package/fs/types/map/{test.f.js → proof.f.js} +1 -1
- package/fs/types/monoid/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/monoid/{test.f.js → proof.f.js} +1 -1
- package/fs/types/nibble_set/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/nibble_set/{test.f.js → proof.f.js} +1 -1
- package/fs/types/nominal/proof.f.d.ts +4 -0
- package/fs/types/nominal/{test.f.js → proof.f.js} +1 -1
- package/fs/types/nullable/proof.f.d.ts +1 -0
- package/fs/types/nullable/{test.f.js → proof.f.js} +1 -1
- package/fs/types/number/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/number/{test.f.js → proof.f.js} +1 -1
- package/fs/types/object/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/object/{test.f.js → proof.f.js} +1 -1
- package/fs/types/ordered_map/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/ordered_map/{test.f.js → proof.f.js} +1 -1
- package/fs/types/patricia_trie/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/patricia_trie/{test.f.js → proof.f.js} +1 -1
- package/fs/types/prime_field/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/prime_field/{test.f.js → proof.f.js} +1 -1
- package/fs/types/range/proof.f.d.ts +1 -0
- package/fs/types/range/{test.f.js → proof.f.js} +1 -1
- package/fs/types/range_map/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/range_map/{test.f.js → proof.f.js} +1 -1
- package/fs/types/result/proof.f.d.ts +5 -0
- package/fs/types/result/{test.f.js → proof.f.js} +18 -2
- package/fs/types/rtti/parse/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/rtti/parse/{test.f.js → proof.f.js} +1 -1
- package/fs/types/rtti/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/rtti/{test.f.js → proof.f.js} +1 -1
- package/fs/types/rtti/ts/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/rtti/ts/{test.f.js → proof.f.js} +1 -1
- package/fs/types/rtti/validate/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/rtti/validate/{test.f.js → proof.f.js} +1 -1
- package/fs/types/sorted_list/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/sorted_list/{test.f.js → proof.f.js} +1 -1
- package/fs/types/sorted_set/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/sorted_set/{test.f.js → proof.f.js} +1 -1
- package/fs/types/string/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/string/{test.f.js → proof.f.js} +1 -1
- package/fs/types/string_set/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/string_set/{test.f.js → proof.f.js} +1 -1
- package/fs/types/ts/{test.f.d.ts → proof.f.d.ts} +20 -0
- package/fs/types/ts/{test.f.js → proof.f.js} +1 -0
- package/fs/types/uint8array/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/fs/types/uint8array/{test.f.js → proof.f.js} +1 -1
- package/issues/demo/sample/{test.f.js → proof.f.js} +1 -1
- package/issues/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/issues/{test.f.js → proof.f.js} +1 -1
- package/nanvm-lib/tests/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/nanvm-lib/tests/{test.f.js → proof.f.js} +1 -1
- package/nanvm-lib/tests/vm/{test.f.d.ts → proof.f.d.ts} +1 -2
- package/nanvm-lib/tests/vm/{test.f.js → proof.f.js} +1 -1
- package/package.json +2 -2
- package/fs/base128/test.f.d.ts +0 -2
- package/fs/bnf/test.f.d.ts +0 -4
- package/fs/cas/test.f.d.ts +0 -2
- package/fs/cas/test.f.js +0 -1
- package/fs/dev/version/test.f.d.ts +0 -4
- package/fs/fsm/test.f.d.ts +0 -5
- package/fs/path/test.f.d.ts +0 -3
- package/fs/text/ascii/test.f.d.ts +0 -4
- package/fs/text/sgr/test.f.d.ts +0 -2
- package/fs/types/btree/find/test.f.d.ts +0 -2
- package/fs/types/btree/remove/test.f.d.ts +0 -5
- package/fs/types/btree/set/test.f.d.ts +0 -2
- package/fs/types/function/compare/test.f.d.ts +0 -2
- package/fs/types/function/operator/test.f.d.ts +0 -10
- package/fs/types/function/test.f.d.ts +0 -2
- package/fs/types/map/test.f.d.ts +0 -5
- package/fs/types/nominal/test.f.d.ts +0 -5
- package/fs/types/nullable/test.f.d.ts +0 -2
- package/fs/types/range/test.f.d.ts +0 -2
- package/fs/types/result/test.f.d.ts +0 -2
package/fs/dev/tf/module.f.js
CHANGED
|
@@ -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, test } from "../../types/effects/node/module.f.js";
|
|
8
|
-
import { pure
|
|
9
|
-
import { loadModuleMap } from "../module.f.js";
|
|
14
|
+
import { all, awaitIfPromise, sandbox, test } from "../../types/effects/node/module.f.js";
|
|
15
|
+
import { pure } from "../../types/effects/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,19 +65,28 @@ 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
|
+
*/
|
|
54
78
|
export const registerModule = (ctx, k, v) => {
|
|
55
|
-
const registerOne = (ctx, [path, { fn, throws }]) => test(ctx, fmtImport(k, path), throws, (t) =>
|
|
79
|
+
const registerOne = (ctx, [path, { fn, throws }]) => test(ctx, fmtImport(k, path), throws, (t) => awaitIfPromise(fn())
|
|
80
|
+
.step(resolved => {
|
|
56
81
|
if (throws) {
|
|
57
|
-
fn();
|
|
58
82
|
return pure(undefined);
|
|
59
83
|
}
|
|
60
|
-
const
|
|
61
|
-
const sub = collectTests([...path, null], false, r);
|
|
84
|
+
const sub = collectTests([...path, null], false, resolved);
|
|
62
85
|
if (sub.length === 0) {
|
|
63
86
|
return pure(undefined);
|
|
64
87
|
}
|
|
65
88
|
return all(...sub.map(e => registerOne(t, e))).step(() => pure(undefined));
|
|
66
|
-
});
|
|
89
|
+
}));
|
|
67
90
|
const tests = collectTests([], false, v);
|
|
68
91
|
if (tests.length === 0) {
|
|
69
92
|
return pure(undefined);
|
|
@@ -99,16 +122,33 @@ const runModule = ({ result, test }) => (k, v) => (ts) => {
|
|
|
99
122
|
.step(delta => pure(mergeState(ts, delta)));
|
|
100
123
|
};
|
|
101
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
|
+
*/
|
|
102
130
|
export const runModuleMap = (reporter) => (moduleMap) => {
|
|
103
131
|
const { summary } = reporter;
|
|
104
|
-
const modules = entries(moduleMap)
|
|
105
|
-
|
|
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)))
|
|
106
136
|
.step(ts => summary(ts.pass, ts.fail, ts.time)
|
|
107
137
|
.step(() => pure(ts.fail !== 0 ? 1 : 0)));
|
|
108
138
|
};
|
|
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
|
+
*/
|
|
109
144
|
export const testAll = (reporter) => options => loadModuleMap(options.env).step(runModuleMap(reporter));
|
|
110
|
-
|
|
111
|
-
|
|
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]] : []);
|
|
112
152
|
if (modules.length === 0) {
|
|
113
153
|
return pure(undefined);
|
|
114
154
|
}
|
|
@@ -116,7 +156,9 @@ export const registerModuleMap = (ctx, moduleMap) => {
|
|
|
116
156
|
};
|
|
117
157
|
const isAlpha = (c) => (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c === '_' || c === '$';
|
|
118
158
|
const isDigit = (c) => c >= '0' && c <= '9';
|
|
159
|
+
/** Returns `true` if `s` is a non-negative decimal integer without a leading zero. */
|
|
119
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_$]*`). */
|
|
120
162
|
export const isIdentifier = (s) => s.length > 0 && isAlpha(s[0]) && [...s.slice(1)].every(c => isAlpha(c) || isDigit(c));
|
|
121
163
|
const fmtKey = (k) => k === null ? '()'
|
|
122
164
|
: isInteger(k) ? `[${k}]`
|
|
@@ -131,10 +173,10 @@ const fmtKey = (k) => k === null ? '()'
|
|
|
131
173
|
export const fmtPath = (path) => path.reduce((acc, k) => acc + fmtKey(k), '');
|
|
132
174
|
/**
|
|
133
175
|
* Formats a fully-qualified test identifier as a JS-like expression, e.g.
|
|
134
|
-
* `import("./math.
|
|
176
|
+
* `import("./math.proof.f.ts").add()` or `import("./a.proof.f.ts").users[3].name()`.
|
|
135
177
|
* Self-contained per line — suitable for parallel output and as a CLI filter argument.
|
|
136
178
|
*/
|
|
137
|
-
export const fmtImport = (file, path) => `import(${JSON.stringify(file)})${fmtPath(path)}()`;
|
|
179
|
+
export const fmtImport = (file, path) => `import(${JSON.stringify(file)}).proof${fmtPath(path)}()`;
|
|
138
180
|
/**
|
|
139
181
|
* Renders a key chain for terminal output: `| ` per level of depth, followed
|
|
140
182
|
* by the last segment formatted as a bare integer, a bare identifier, or a
|
|
@@ -160,6 +202,10 @@ export const ghEscape = (s) => s.replaceAll('%', '%25')
|
|
|
160
202
|
.replaceAll(',', '%2C')
|
|
161
203
|
.replaceAll('\r', '%0D')
|
|
162
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
|
+
*/
|
|
163
209
|
export const defaultTest = (file, path, { fn, throws }) => sandbox(fn)
|
|
164
210
|
.step(r => pure(throws ? { ...r, result: invert(r.result) } : r));
|
|
165
211
|
const fmtResultLine = (file, path, color, label, duration) => `${fmtImport(file, path)}: ${color}${label}${reset}, ${timeFormat(duration)}`;
|
|
@@ -195,9 +241,20 @@ export const defaultReporter = (options) => {
|
|
|
195
241
|
test: defaultTest,
|
|
196
242
|
};
|
|
197
243
|
};
|
|
244
|
+
/** The `fjs t` entry point: runs all tests using `defaultReporter`. */
|
|
198
245
|
export const main = options => testAll(defaultReporter(options))(options);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -3,6 +3,7 @@ import { emptyState } from "../../types/effects/node/virtual/module.f.js";
|
|
|
3
3
|
import { virtual } from "../../types/effects/node/virtual/module.f.js";
|
|
4
4
|
import { assert, assertEq, todo } from "../module.f.js";
|
|
5
5
|
import { testAll, defaultReporter, fmtPath, fmtTerm, fmtImport, ghEscape, isInteger, isIdentifier, defaultTest, } from "./module.f.js";
|
|
6
|
+
import { shouldLoad } from "../module.f.js";
|
|
6
7
|
const makeReporter = () => {
|
|
7
8
|
const events = [];
|
|
8
9
|
const reporter = {
|
|
@@ -42,7 +43,7 @@ const runMain = (dir, github = false) => {
|
|
|
42
43
|
// flat object: two passing tests
|
|
43
44
|
export const flat = () => {
|
|
44
45
|
const [events, exit] = run({
|
|
45
|
-
'a.
|
|
46
|
+
'a.proof.f.ts': () => ({ proof: { a: ok0, b: ok1 } }),
|
|
46
47
|
});
|
|
47
48
|
assertEq(exit, 0);
|
|
48
49
|
const [e0, e1, e2] = events;
|
|
@@ -56,7 +57,7 @@ export const flat = () => {
|
|
|
56
57
|
// nested object: leaf tests carry the full path including the sub-tree key
|
|
57
58
|
export const nested = () => {
|
|
58
59
|
const [events, exit] = run({
|
|
59
|
-
'n.
|
|
60
|
+
'n.proof.f.ts': () => ({ proof: { math: { add: ok0, sub: ok0 } } }),
|
|
60
61
|
});
|
|
61
62
|
assertEq(exit, 0);
|
|
62
63
|
const [e0, e1, e2] = events;
|
|
@@ -70,7 +71,7 @@ export const nested = () => {
|
|
|
70
71
|
// throw key: tests inside 'throw' pass on error result
|
|
71
72
|
export const throwKey = () => {
|
|
72
73
|
const [events, exit] = run({
|
|
73
|
-
't.
|
|
74
|
+
't.proof.f.ts': () => ({ proof: { throw: { a: fail0 } } }),
|
|
74
75
|
});
|
|
75
76
|
assertEq(exit, 0);
|
|
76
77
|
const [e0, e1] = events;
|
|
@@ -83,7 +84,7 @@ export const throwKey = () => {
|
|
|
83
84
|
// throw key fails when test does not throw (returns ok in throw context)
|
|
84
85
|
export const throwKeyFail = () => {
|
|
85
86
|
const [events, exit] = run({
|
|
86
|
-
't.
|
|
87
|
+
't.proof.f.ts': () => ({ proof: { throw: { a: ok0 } } }),
|
|
87
88
|
});
|
|
88
89
|
assertEq(exit, 1);
|
|
89
90
|
const [e0, e1] = events;
|
|
@@ -95,7 +96,7 @@ export const throwKeyFail = () => {
|
|
|
95
96
|
// mixed pass/fail updates summary counts
|
|
96
97
|
export const mixedPassFail = () => {
|
|
97
98
|
const [events, exit] = run({
|
|
98
|
-
'm.
|
|
99
|
+
'm.proof.f.ts': () => ({ proof: { good: ok0, bad: fail0 } }),
|
|
99
100
|
});
|
|
100
101
|
assertEq(exit, 1);
|
|
101
102
|
const summary = events[events.length - 1];
|
|
@@ -108,11 +109,13 @@ export const mixedPassFail = () => {
|
|
|
108
109
|
export const returnValueSubTree = () => {
|
|
109
110
|
const inner = () => ({ result: ['ok', undefined], duration: 0 });
|
|
110
111
|
const [events, exit] = run({
|
|
111
|
-
'r.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
'r.proof.f.ts': () => ({
|
|
113
|
+
proof: {
|
|
114
|
+
outer: () => ({
|
|
115
|
+
result: ['ok', { inner }],
|
|
116
|
+
duration: 0,
|
|
117
|
+
}),
|
|
118
|
+
}
|
|
116
119
|
}),
|
|
117
120
|
});
|
|
118
121
|
// outer passes, then inner (from return value) also passes
|
|
@@ -126,7 +129,7 @@ export const returnValueSubTree = () => {
|
|
|
126
129
|
// integer-indexed array keys appear as numeric path segments
|
|
127
130
|
export const arrayKeys = () => {
|
|
128
131
|
const [events, exit] = run({
|
|
129
|
-
'a.
|
|
132
|
+
'a.proof.f.ts': () => ({ proof: { arr: [ok0, ok0] } }),
|
|
130
133
|
});
|
|
131
134
|
assertEq(exit, 0);
|
|
132
135
|
const passEvents = events.filter(e => e[0] === 'result');
|
|
@@ -134,22 +137,24 @@ export const arrayKeys = () => {
|
|
|
134
137
|
assertEq(passEvents[0][2][1], '0');
|
|
135
138
|
assertEq(passEvents[1][2][1], '1');
|
|
136
139
|
};
|
|
137
|
-
// non-
|
|
140
|
+
// non-proof files are skipped: plain `.ts` is not loaded; `.f.ts` without
|
|
141
|
+
// a `proof` export is loaded but produces no events
|
|
138
142
|
export const nonTestFilesSkipped = () => {
|
|
139
143
|
const [events, exit] = run({
|
|
140
|
-
'helper.ts': () => ({ a: ok0 }),
|
|
141
|
-
'
|
|
144
|
+
'helper.ts': () => ({ a: ok0 }), // not loaded (plain .ts)
|
|
145
|
+
'module.f.ts': () => ({ someExport: ok0 }), // loaded, no proof → skipped
|
|
146
|
+
'b.proof.f.ts': () => ({ proof: { x: ok0 } }), // loaded, has proof → runs
|
|
142
147
|
});
|
|
143
148
|
assertEq(exit, 0);
|
|
144
149
|
const results = events.filter(e => e[0] === 'result');
|
|
145
150
|
assertEq(results.length, 1);
|
|
146
|
-
assertEq(results[0][1], './b.
|
|
151
|
+
assertEq(results[0][1], './b.proof.f.ts');
|
|
147
152
|
};
|
|
148
153
|
// multiple test files each produce result events
|
|
149
154
|
export const multipleFiles = () => {
|
|
150
155
|
const [events, exit] = run({
|
|
151
|
-
'a.
|
|
152
|
-
'b.
|
|
156
|
+
'a.proof.f.ts': () => ({ proof: { x: ok0 } }),
|
|
157
|
+
'b.proof.f.ts': () => ({ proof: { y: ok0 } }),
|
|
153
158
|
});
|
|
154
159
|
assertEq(exit, 0);
|
|
155
160
|
const results = events.filter(e => e[0] === 'result');
|
|
@@ -164,51 +169,51 @@ export const throwByFunctionName = () => {
|
|
|
164
169
|
// const named = ({ throw: () => fail0() }).throw
|
|
165
170
|
const x = { throw: () => fail0() };
|
|
166
171
|
const [events, exit] = run({
|
|
167
|
-
't.
|
|
172
|
+
't.proof.f.ts': () => ({ proof: { here: x.throw } }),
|
|
168
173
|
});
|
|
169
174
|
assertEq(exit, 0);
|
|
170
175
|
const passEvents = events.filter(e => e[0] === 'result');
|
|
171
176
|
assertEq(passEvents.length, 1);
|
|
172
177
|
assertEq(passEvents[0][2][0], 'here');
|
|
173
178
|
};
|
|
174
|
-
//
|
|
179
|
+
// only the `proof` export is used; other module properties are ignored
|
|
175
180
|
export const namedExports = () => {
|
|
176
181
|
const [events, exit] = run({
|
|
177
|
-
'e.
|
|
182
|
+
'e.proof.f.ts': () => ({ proof: { a: ok0, b: ok0 }, other: ok0 }),
|
|
178
183
|
});
|
|
179
184
|
assertEq(exit, 0);
|
|
180
185
|
const passEvents = events.filter(e => e[0] === 'result');
|
|
181
|
-
assertEq(passEvents.length, 2);
|
|
182
|
-
assertEq(passEvents[0][2][0], '
|
|
183
|
-
assertEq(passEvents[1][2][0], '
|
|
186
|
+
assertEq(passEvents.length, 2); // `other` is ignored
|
|
187
|
+
assertEq(passEvents[0][2][0], 'a');
|
|
188
|
+
assertEq(passEvents[1][2][0], 'b');
|
|
184
189
|
};
|
|
185
190
|
// the default (non-GitHub) reporter formats module/pass/summary lines on stdout
|
|
186
191
|
export const defaultReporterOutput = () => {
|
|
187
192
|
const [stdout, stderr, exit] = runMain({
|
|
188
|
-
'a.
|
|
193
|
+
'a.proof.f.ts': () => ({ proof: { x: ok0 } }),
|
|
189
194
|
});
|
|
190
195
|
assertEq(exit, 0);
|
|
191
196
|
assertEq(stderr, '');
|
|
192
|
-
assertEq(stdout, 'import("./a.
|
|
197
|
+
assertEq(stdout, 'import("./a.proof.f.ts").proof.x(): ok, 0.0000 ms\n'
|
|
193
198
|
+ 'Number of tests: pass: 1, fail: 0, total: 1\n'
|
|
194
199
|
+ 'Time: 0.0000 ms\n');
|
|
195
200
|
};
|
|
196
201
|
// a failure on the non-GitHub reporter writes the error to stderr, not stdout
|
|
197
202
|
export const defaultReporterFailOutput = () => {
|
|
198
203
|
const [, stderr, exit] = runMain({
|
|
199
|
-
'a.
|
|
204
|
+
'a.proof.f.ts': () => ({ proof: { bad: fail0 } }),
|
|
200
205
|
});
|
|
201
206
|
assertEq(exit, 1);
|
|
202
|
-
assertEq(stderr, 'import("./a.
|
|
207
|
+
assertEq(stderr, 'import("./a.proof.f.ts").proof.bad(): error, 0.0000 ms\noops\n');
|
|
203
208
|
};
|
|
204
209
|
// the GitHub reporter emits an `::error` annotation with a percent-encoded
|
|
205
210
|
// title (the JSON path) and message
|
|
206
211
|
export const githubReporterOutput = () => {
|
|
207
212
|
const [, stderr, exit] = runMain({
|
|
208
|
-
's.
|
|
213
|
+
's.proof.f.ts': () => ({ proof: { 'a:b,c%d': fail0 } }),
|
|
209
214
|
}, true);
|
|
210
215
|
assertEq(exit, 1);
|
|
211
|
-
assertEq(stderr, '::error file=./s.
|
|
216
|
+
assertEq(stderr, '::error file=./s.proof.f.ts,line=1,title=import("./s.proof.f.ts").proof["a%3Ab%2Cc%25d"]()::oops\n');
|
|
212
217
|
};
|
|
213
218
|
// direct unit tests for the pure path-format helpers
|
|
214
219
|
export const helpers = {
|
|
@@ -229,12 +234,32 @@ export const helpers = {
|
|
|
229
234
|
assert(!isIdentifier('1a'));
|
|
230
235
|
assert(!isIdentifier('a-b'));
|
|
231
236
|
},
|
|
237
|
+
shouldLoad: () => {
|
|
238
|
+
// all .f.ts / .f.js — FS modules are safe to bulk-load
|
|
239
|
+
assert(shouldLoad('module.f.ts'));
|
|
240
|
+
assert(shouldLoad('module.f.js'));
|
|
241
|
+
assert(shouldLoad('a.proof.f.ts'));
|
|
242
|
+
assert(shouldLoad('dir/module.f.ts'));
|
|
243
|
+
// vanilla opt-in by filename
|
|
244
|
+
assert(shouldLoad('proof.ts'));
|
|
245
|
+
assert(shouldLoad('proof.js'));
|
|
246
|
+
assert(shouldLoad('proof.mts'));
|
|
247
|
+
assert(shouldLoad('proof.mjs'));
|
|
248
|
+
assert(shouldLoad('math.proof.ts'));
|
|
249
|
+
assert(shouldLoad('math.proof.js'));
|
|
250
|
+
assert(shouldLoad('math.proof.mts'));
|
|
251
|
+
assert(shouldLoad('dir/math.proof.ts'));
|
|
252
|
+
// non-FS, non-proof vanilla files are not loaded
|
|
253
|
+
assert(!shouldLoad('helper.ts'));
|
|
254
|
+
assert(!shouldLoad('module.ts'));
|
|
255
|
+
assert(!shouldLoad('proof.tsx'));
|
|
256
|
+
},
|
|
232
257
|
fmtImport: () => {
|
|
233
|
-
assertEq(fmtImport('./a.
|
|
234
|
-
assertEq(fmtImport('./a.
|
|
235
|
-
assertEq(fmtImport('./a.
|
|
236
|
-
assertEq(fmtImport('./a.
|
|
237
|
-
assertEq(fmtImport('./a.
|
|
258
|
+
assertEq(fmtImport('./a.proof.f.ts', []), 'import("./a.proof.f.ts").proof()');
|
|
259
|
+
assertEq(fmtImport('./a.proof.f.ts', ['math', 'add']), 'import("./a.proof.f.ts").proof.math.add()');
|
|
260
|
+
assertEq(fmtImport('./a.proof.f.ts', ['users', '3']), 'import("./a.proof.f.ts").proof.users[3]()');
|
|
261
|
+
assertEq(fmtImport('./a.proof.f.ts', ['x', 'hello world']), 'import("./a.proof.f.ts").proof.x["hello world"]()');
|
|
262
|
+
assertEq(fmtImport('./a.proof.f.ts', ['outer', null, 'inner']), 'import("./a.proof.f.ts").proof.outer().inner()');
|
|
238
263
|
},
|
|
239
264
|
fmtPath: () => {
|
|
240
265
|
assertEq(fmtPath([]), '');
|
|
@@ -257,3 +282,20 @@ export const helpers = {
|
|
|
257
282
|
assertEq(ghEscape('a%b:c,d'), 'a%25b%3Ac%2Cd');
|
|
258
283
|
},
|
|
259
284
|
};
|
|
285
|
+
export const proof = {
|
|
286
|
+
flat,
|
|
287
|
+
nested,
|
|
288
|
+
throwKey,
|
|
289
|
+
throwKeyFail,
|
|
290
|
+
mixedPassFail,
|
|
291
|
+
returnValueSubTree,
|
|
292
|
+
arrayKeys,
|
|
293
|
+
nonTestFilesSkipped,
|
|
294
|
+
multipleFiles,
|
|
295
|
+
throwByFunctionName,
|
|
296
|
+
namedExports,
|
|
297
|
+
defaultReporterOutput,
|
|
298
|
+
defaultReporterFailOutput,
|
|
299
|
+
githubReporterOutput,
|
|
300
|
+
helpers
|
|
301
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sleep_fail: () => Promise<never>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sleep: () => Promise<void>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// A test that returns a thenable (Promise-like object, not a real Promise).
|
|
2
|
+
// Per FunctionalScript convention, thenables are treated as plain values —
|
|
3
|
+
// not awaited. Both sandbox (fjs) and registerModule (node/bun/deno/playwright)
|
|
4
|
+
// must exit 0: the thenable object is walked as a sub-tree whose only key
|
|
5
|
+
// `then` is a function with parameters, so no leaf tests are found and the
|
|
6
|
+
// test trivially passes.
|
|
7
|
+
export const thenableResolves = () => ({
|
|
8
|
+
then(resolve) { resolve(undefined); }
|
|
9
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sort } from "../../types/object/module.f.js";
|
|
2
2
|
import { run } from "./module.f.js";
|
|
3
3
|
import { stringifyAsTree } from "../serializer/module.f.js";
|
|
4
|
-
export
|
|
4
|
+
export const proof = {
|
|
5
5
|
test: () => {
|
|
6
6
|
const djs = run([1])([]);
|
|
7
7
|
const result = stringifyAsTree(sort)(djs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const
|
|
1
|
+
export declare const proof: {
|
|
2
2
|
valid: (() => void)[];
|
|
3
3
|
invalid: (() => void)[];
|
|
4
4
|
errorMetadata: (() => void)[];
|
|
@@ -11,4 +11,3 @@ declare const _default: {
|
|
|
11
11
|
invalidWithArgs: (() => void)[];
|
|
12
12
|
comments: (() => void)[];
|
|
13
13
|
};
|
|
14
|
-
export default _default;
|
|
@@ -7,7 +7,7 @@ import { stringifyAsTree } from "../serializer/module.f.js";
|
|
|
7
7
|
import { stringify } from "../../json/module.f.js";
|
|
8
8
|
const tokenizeString = s => toArray(tokenize(stringToList(s))(''));
|
|
9
9
|
const stringifyDjsModule = stringifyAsTree(sort);
|
|
10
|
-
export
|
|
10
|
+
export const proof = {
|
|
11
11
|
valid: [
|
|
12
12
|
() => {
|
|
13
13
|
const tokenList = tokenizeString('export default null');
|
|
@@ -7,11 +7,11 @@ import type { Unknown } from '../module.f.ts';
|
|
|
7
7
|
import type { Entry as ObjectEntry } from '../../types/object/module.f.ts';
|
|
8
8
|
import { type List } from '../../types/list/module.f.ts';
|
|
9
9
|
export declare const undefinedSerialize: string[];
|
|
10
|
-
type RefCounter = [number, number
|
|
10
|
+
type RefCounter = readonly [number, number];
|
|
11
11
|
type Entry = ObjectEntry<Unknown>;
|
|
12
12
|
type Entries = List<Entry>;
|
|
13
13
|
type MapEntries = (entries: Entries) => Entries;
|
|
14
|
-
type Refs =
|
|
14
|
+
type Refs = ReadonlyMap<Unknown, RefCounter>;
|
|
15
15
|
export declare const serializeWithoutConst: (mapEntries: MapEntries) => (value: Unknown) => List<string>;
|
|
16
16
|
export declare const stringify: (sort: MapEntries) => (djs: Unknown) => string;
|
|
17
17
|
export declare const stringifyAsTree: (mapEntries: MapEntries) => (value: Unknown) => string;
|