@dral/toml-tests 5.0.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/AGENTS.md +58 -0
- package/README.md +294 -0
- package/fixtures/basic.toml +34 -0
- package/fixtures/nameless-suite.toml +19 -0
- package/fixtures/non-toml-result.toml +11 -0
- package/fixtures/throws.toml +16 -0
- package/package.json +20 -0
- package/pnpm-workspace.yaml +3 -0
- package/src/index.d.ts +39 -0
- package/src/index.js +229 -0
- package/src/index.js.map +7 -0
- package/src/index.test.d.ts +1 -0
- package/src/index.test.js +372 -0
- package/src/index.test.js.map +7 -0
- package/src/tsconfig.json +8 -0
- package/tsconfig.json +4 -0
- package/tsconfig.test.json +11 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Project overview
|
|
4
|
+
|
|
5
|
+
This is `@dral/toml-tests`, a small framework-agnostic TypeScript library that parses TOML files describing test suites and runs them against a user-provided function. The caller provides their own test framework bindings (`describe`, `it`, `equal`) via a `TestHarness` interface.
|
|
6
|
+
|
|
7
|
+
## Repository layout
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
index.ts Main module — exports toml_test(), TomlTestFn, TomlValue, TestHarness
|
|
12
|
+
index.test.ts Tests for the utility itself (uses node:test)
|
|
13
|
+
tsconfig.json Source config — extends @dral/tsconfig, noEmit
|
|
14
|
+
fixtures/
|
|
15
|
+
basic.toml Named suites, options merging, nesting
|
|
16
|
+
nameless-suite.toml Nameless suites for option grouping
|
|
17
|
+
non-toml-result.toml Non-TOML type detection
|
|
18
|
+
package.json ESM package, "type": "module"
|
|
19
|
+
pnpm-workspace.yaml Allows esbuild build scripts
|
|
20
|
+
tsconfig.json Root — references src and test configs
|
|
21
|
+
tsconfig.test.json Test config — includes node types, noEmit
|
|
22
|
+
dist/ Build output (gitignored)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Key concepts
|
|
26
|
+
|
|
27
|
+
- **Framework-agnostic**: The library does not import `node:test` or `node:fs`. The caller passes in a `TestHarness` with `describe`, `it`, and `equal` from whatever framework they use.
|
|
28
|
+
- **TOML test document**: A recursive `TestSuite` structure with optional `name`, `options`, `tests`, and `suites` fields. Parsed by `smol-toml`.
|
|
29
|
+
- **Flexible input**: Test case `input` accepts any TOML-expressible value (`TomlValue`) — strings, numbers, booleans, dates, arrays, or objects. When `name` is omitted, `String(input)` is used as the test name.
|
|
30
|
+
- **Options merging**: Shallow merge from outer to inner: `{ ...parentOptions, ...childOptions }`.
|
|
31
|
+
- **Named vs nameless suites**: Named suites create `describe()` blocks. Nameless suites are transparent — their contents appear at the parent level.
|
|
32
|
+
- **Assertion types**: Each test must have exactly one of `output` (deep-compare return value), `throws` (`true` for any error, or a regex-pattern string to match the error message), or `doesntThrow` (`true` to assert no error is thrown; return value is ignored).
|
|
33
|
+
- **Non-TOML detection**: Before comparing results, the utility checks that the function's return value only contains TOML-expressible types. Non-TOML values cause an explicit failure with dot-path locations.
|
|
34
|
+
- **Source line numbers**: A supplementary scanner maps `[[tests]]` headers to line numbers. On failure, errors include `source:line` or `line N` in the message. The `smol-toml` parse output remains the sole source of truth for test data.
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
- `pnpm test` — Run tests with `node --test --experimental-strip-types`
|
|
39
|
+
- `pnpm run typecheck` — Type-check source and tests
|
|
40
|
+
- `pnpm run build` — Bundle with `@dral/bundle` into `dist/`
|
|
41
|
+
|
|
42
|
+
## Conventions
|
|
43
|
+
|
|
44
|
+
- **pnpm** as the package manager (not npm)
|
|
45
|
+
- ESM throughout (`"type": "module"`, `import`/`export`)
|
|
46
|
+
- TypeScript strict mode with `verbatimModuleSyntax`
|
|
47
|
+
- Use `.ts` extensions in local imports (required by `--experimental-strip-types`)
|
|
48
|
+
- Source code (`src/index.ts`) has zero Node.js dependencies — only `smol-toml` and `zod`
|
|
49
|
+
- Tests use `node:test` and `node:assert` via the harness pattern
|
|
50
|
+
- `smol-toml` for TOML parsing (lightweight, spec-compliant)
|
|
51
|
+
- `@dral/bundle` for building — compiles TypeScript, generates declarations, rewrites package.json exports
|
|
52
|
+
|
|
53
|
+
## When modifying
|
|
54
|
+
|
|
55
|
+
- Keep the public API surface small: `toml_test`, `TomlTestFn`, `TomlValue`, `TestHarness`.
|
|
56
|
+
- The TOML schema is the contract — changes to it affect all consumers.
|
|
57
|
+
- Source tsconfig extends `@dral/tsconfig` — source must not depend on `@types/node`.
|
|
58
|
+
- Run `pnpm run typecheck` and `pnpm test` to verify before committing.
|
package/README.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# toml-test
|
|
2
|
+
|
|
3
|
+
A framework-agnostic testing utility that parses TOML test suites and runs them against a function. Define test cases and expected outputs in TOML, pass in your test framework's `describe`/`it`/`equal`, and `toml_test` registers everything for you.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install toml-test
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { describe, it } from "node:test";
|
|
15
|
+
import { strict as assert } from "node:assert";
|
|
16
|
+
import { readFile } from "node:fs/promises";
|
|
17
|
+
import { toml_test } from "toml-test";
|
|
18
|
+
import type { TestHarness, TomlValue } from "toml-test";
|
|
19
|
+
|
|
20
|
+
const harness: TestHarness = {
|
|
21
|
+
describe,
|
|
22
|
+
it,
|
|
23
|
+
equal: assert.deepStrictEqual,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const toml = await readFile("./tests/my-parser.toml", "utf-8");
|
|
27
|
+
|
|
28
|
+
toml_test(
|
|
29
|
+
toml,
|
|
30
|
+
(input, options) => {
|
|
31
|
+
return myParser(input, options);
|
|
32
|
+
},
|
|
33
|
+
harness,
|
|
34
|
+
"my-parser.toml",
|
|
35
|
+
);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run with Node's test runner:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
node --test src/**/*.test.ts
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Works with any framework that provides `describe`, `it`, and a deep-equal assertion — node:test, vitest, jest, mocha, etc.
|
|
45
|
+
|
|
46
|
+
## TOML test file format
|
|
47
|
+
|
|
48
|
+
Test files use **TOML 1.1**, which allows multiline inline tables — handy for writing expected output that is a nested object. Parsed by [`smol-toml`](https://github.com/nicolo-ribaudo/smol-toml) which supports TOML 1.1.
|
|
49
|
+
|
|
50
|
+
```toml
|
|
51
|
+
[[tests]]
|
|
52
|
+
name = "string input"
|
|
53
|
+
input = "hello world"
|
|
54
|
+
output = "HELLO WORLD"
|
|
55
|
+
|
|
56
|
+
[[tests]]
|
|
57
|
+
name = "numeric input"
|
|
58
|
+
input = 42
|
|
59
|
+
output = 84
|
|
60
|
+
|
|
61
|
+
[[tests]]
|
|
62
|
+
name = "boolean input"
|
|
63
|
+
input = true
|
|
64
|
+
output = false
|
|
65
|
+
|
|
66
|
+
[[tests]]
|
|
67
|
+
name = "object input"
|
|
68
|
+
input = { x = 1, y = 2 }
|
|
69
|
+
output = 3
|
|
70
|
+
|
|
71
|
+
[[tests]]
|
|
72
|
+
name = "array input"
|
|
73
|
+
input = [10, 20, 30]
|
|
74
|
+
output = 60
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
[[suites]]
|
|
78
|
+
name = "JSON parsing"
|
|
79
|
+
|
|
80
|
+
[[suites.tests]]
|
|
81
|
+
name = "One-key object"
|
|
82
|
+
input = '{ "key": "value" }'
|
|
83
|
+
output = { key = "value" }
|
|
84
|
+
|
|
85
|
+
[[suites.tests]]
|
|
86
|
+
name = "Complex"
|
|
87
|
+
input = """
|
|
88
|
+
{
|
|
89
|
+
"number": 10,
|
|
90
|
+
"object": {
|
|
91
|
+
"nested": {
|
|
92
|
+
"key": "}"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}"""
|
|
96
|
+
output = {
|
|
97
|
+
number = 10,
|
|
98
|
+
object = {
|
|
99
|
+
nested = {
|
|
100
|
+
key = "}"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Schema
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
interface TestCase {
|
|
110
|
+
name?: string;
|
|
111
|
+
input: TomlValue;
|
|
112
|
+
options?: Record<string, TomlValue>;
|
|
113
|
+
// Exactly one of the following three:
|
|
114
|
+
output?: TomlValue;
|
|
115
|
+
throws?: true | string;
|
|
116
|
+
doesntThrow?: true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface TestSuite {
|
|
120
|
+
name?: string;
|
|
121
|
+
options?: Record<string, TomlValue>;
|
|
122
|
+
tests?: TestCase[];
|
|
123
|
+
suites?: TestSuite[];
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Assertion types
|
|
128
|
+
|
|
129
|
+
Each test case must have **exactly one** of `output`, `throws`, or `doesntThrow`.
|
|
130
|
+
|
|
131
|
+
#### `output` — compare return value
|
|
132
|
+
|
|
133
|
+
The function's return value is deep-compared against the expected value. Non-TOML-expressible values in the result cause an explicit failure (see [Non-TOML result detection](#non-toml-result-detection)).
|
|
134
|
+
|
|
135
|
+
```toml
|
|
136
|
+
[[tests]]
|
|
137
|
+
name = "uppercases input"
|
|
138
|
+
input = "hello"
|
|
139
|
+
output = "HELLO"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### `throws` — expect an error
|
|
143
|
+
|
|
144
|
+
Set `throws = true` to assert that the function throws any error. Set `throws` to a string to assert that the error message matches a regular expression pattern.
|
|
145
|
+
|
|
146
|
+
```toml
|
|
147
|
+
[[tests]]
|
|
148
|
+
name = "rejects bad input"
|
|
149
|
+
input = "bad"
|
|
150
|
+
throws = true
|
|
151
|
+
|
|
152
|
+
[[tests]]
|
|
153
|
+
name = "error message matches pattern"
|
|
154
|
+
input = "bad"
|
|
155
|
+
throws = "invalid.*format"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### `doesntThrow` — expect no error
|
|
159
|
+
|
|
160
|
+
Set `doesntThrow = true` to assert that the function completes without throwing. The return value is ignored — only the absence of an error is checked.
|
|
161
|
+
|
|
162
|
+
```toml
|
|
163
|
+
[[tests]]
|
|
164
|
+
name = "handles edge case gracefully"
|
|
165
|
+
input = ""
|
|
166
|
+
doesntThrow = true
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Options merging
|
|
170
|
+
|
|
171
|
+
Options are **shallowly merged** from outer to inner scope:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
{ ...fileSuiteOptions, ...nestedSuiteOptions, ..., ...testOptions }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
This lets you set defaults at the file level and override per-suite or per-test.
|
|
178
|
+
|
|
179
|
+
### Named vs nameless suites
|
|
180
|
+
|
|
181
|
+
- A suite **with a `name`** creates a `describe()` block in the test output.
|
|
182
|
+
- A suite **without a `name`** does not create any grouping — its tests and sub-suites appear at the parent's nesting level. Use nameless suites to combine options or split tests across files without adding noise to the output.
|
|
183
|
+
|
|
184
|
+
## Non-TOML result detection
|
|
185
|
+
|
|
186
|
+
If the function under test returns a value containing types that cannot be expressed in TOML (`undefined`, `null`, `Symbol`, `Function`, `Set`, `Map`, `RegExp`, class instances, etc.), the test fails with a message listing the offending paths:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
my-parser.toml:12: Result contains non-TOML-expressible values at: result.foo, result.items[2].bar
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This catches accidental leakage of internal types into what should be a plain-data result.
|
|
193
|
+
|
|
194
|
+
## Source line numbers
|
|
195
|
+
|
|
196
|
+
When a test fails, the error message includes the line number in the TOML file where the test case was defined. If you pass a `source` label (typically the filename), errors use `source:line` format:
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
my-parser.toml:12: Expected values to be strictly deep-equal
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Without a source label, errors use `line N` format:
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
line 12: Expected values to be strictly deep-equal
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## API
|
|
209
|
+
|
|
210
|
+
### `toml_test(toml, fn, harness, source?)`
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
function toml_test(
|
|
214
|
+
toml: string,
|
|
215
|
+
fn: TomlTestFn,
|
|
216
|
+
harness: TestHarness,
|
|
217
|
+
source?: string,
|
|
218
|
+
): void;
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Parses a TOML test document and registers test suites/cases using the provided harness.
|
|
222
|
+
|
|
223
|
+
- **toml** — TOML source string describing the test suite
|
|
224
|
+
- **fn** — the function under test, called as `fn(input, mergedOptions)`
|
|
225
|
+
- **harness** — test framework bindings (`describe`, `it`, `equal`)
|
|
226
|
+
- **source** — optional label (e.g. filename) included in error messages
|
|
227
|
+
|
|
228
|
+
### `TestHarness`
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
interface TestHarness {
|
|
232
|
+
describe: (name: string, fn: () => void) => void;
|
|
233
|
+
it: (name: string, fn: () => void) => void;
|
|
234
|
+
equal: (actual: unknown, expected: unknown) => void;
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### `TomlTestFn`
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
type TomlTestFn = (input: TomlValue, options: any) => any;
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The `input` can be any TOML-expressible value — string, number, boolean, Date, array, or object. This allows test cases to pass structured data directly:
|
|
245
|
+
|
|
246
|
+
```toml
|
|
247
|
+
[[tests]]
|
|
248
|
+
name = "string input"
|
|
249
|
+
input = "hello"
|
|
250
|
+
output = "HELLO"
|
|
251
|
+
|
|
252
|
+
[[tests]]
|
|
253
|
+
name = "numeric input"
|
|
254
|
+
input = 42
|
|
255
|
+
output = 84
|
|
256
|
+
|
|
257
|
+
[[tests]]
|
|
258
|
+
name = "object input"
|
|
259
|
+
input = { x = 1, y = 2 }
|
|
260
|
+
output = 3
|
|
261
|
+
|
|
262
|
+
[[tests]]
|
|
263
|
+
name = "array input"
|
|
264
|
+
input = [1, 2, 3]
|
|
265
|
+
output = 6
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### `TomlValue`
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
type TomlValue =
|
|
272
|
+
| string
|
|
273
|
+
| number
|
|
274
|
+
| bigint
|
|
275
|
+
| boolean
|
|
276
|
+
| Date
|
|
277
|
+
| TomlValue[]
|
|
278
|
+
| { [key: string]: TomlValue };
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Represents any value expressible in TOML. Used for both `input` and `output` fields in test cases.
|
|
282
|
+
|
|
283
|
+
## Development
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
npm install
|
|
287
|
+
npm test # run tests
|
|
288
|
+
npx tsc --noEmit -p tsconfig.src.json # type-check source (no node types)
|
|
289
|
+
npx tsc --noEmit -p tsconfig.test.json # type-check tests
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## License
|
|
293
|
+
|
|
294
|
+
ISC
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name = "String utilities"
|
|
2
|
+
|
|
3
|
+
[options]
|
|
4
|
+
trim = false
|
|
5
|
+
|
|
6
|
+
[[tests]]
|
|
7
|
+
name = "returns input as-is"
|
|
8
|
+
input = "hello"
|
|
9
|
+
output = "hello"
|
|
10
|
+
|
|
11
|
+
[[tests]]
|
|
12
|
+
name = "returns input with option override"
|
|
13
|
+
input = " hello "
|
|
14
|
+
output = "hello"
|
|
15
|
+
[tests.options]
|
|
16
|
+
trim = true
|
|
17
|
+
|
|
18
|
+
[[suites]]
|
|
19
|
+
name = "Uppercase suite"
|
|
20
|
+
|
|
21
|
+
[suites.options]
|
|
22
|
+
uppercase = true
|
|
23
|
+
|
|
24
|
+
[[suites.tests]]
|
|
25
|
+
name = "uppercases input"
|
|
26
|
+
input = "hello"
|
|
27
|
+
output = "HELLO"
|
|
28
|
+
|
|
29
|
+
[[suites.tests]]
|
|
30
|
+
name = "uppercases and trims"
|
|
31
|
+
input = " hello "
|
|
32
|
+
output = "HELLO"
|
|
33
|
+
[suites.tests.options]
|
|
34
|
+
trim = true
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Top-level suite has no name — tests should appear at root level
|
|
2
|
+
|
|
3
|
+
[options]
|
|
4
|
+
prefix = ">"
|
|
5
|
+
|
|
6
|
+
[[tests]]
|
|
7
|
+
name = "prepends prefix"
|
|
8
|
+
input = "hello"
|
|
9
|
+
output = ">hello"
|
|
10
|
+
|
|
11
|
+
[[suites]]
|
|
12
|
+
# This nested suite also has no name — just provides extra options
|
|
13
|
+
[suites.options]
|
|
14
|
+
suffix = "<"
|
|
15
|
+
|
|
16
|
+
[[suites.tests]]
|
|
17
|
+
name = "prepends prefix and appends suffix"
|
|
18
|
+
input = "hello"
|
|
19
|
+
output = ">hello<"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
name = "Non-TOML detection"
|
|
2
|
+
|
|
3
|
+
[[tests]]
|
|
4
|
+
name = "should fail when result has undefined"
|
|
5
|
+
input = "return-undefined"
|
|
6
|
+
output = "should not match"
|
|
7
|
+
|
|
8
|
+
[[tests]]
|
|
9
|
+
name = "should fail when result has a function"
|
|
10
|
+
input = "return-function"
|
|
11
|
+
output = "should not match"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name = "Throws tests"
|
|
2
|
+
|
|
3
|
+
[[tests]]
|
|
4
|
+
name = "throws on bad input"
|
|
5
|
+
input = "bad"
|
|
6
|
+
throws = true
|
|
7
|
+
|
|
8
|
+
[[tests]]
|
|
9
|
+
name = "throws matching pattern"
|
|
10
|
+
input = "bad"
|
|
11
|
+
throws = "went wrong"
|
|
12
|
+
|
|
13
|
+
[[tests]]
|
|
14
|
+
name = "doesnt throw on good input"
|
|
15
|
+
input = "good"
|
|
16
|
+
doesntThrow = true
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dral/toml-tests",
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"description": "Testing utility that loads TOML test suites and runs them against a function",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/index.d.ts",
|
|
9
|
+
"default": "./src/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"smol-toml": "^1.6.0",
|
|
17
|
+
"zod": "^4.3.6"
|
|
18
|
+
},
|
|
19
|
+
"packageManager": "pnpm@12.0.0"
|
|
20
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A value expressible in TOML: primitives, Dates, arrays, or string-keyed objects thereof.
|
|
3
|
+
*/
|
|
4
|
+
export type TomlValue = string | number | bigint | boolean | Date | TomlValue[] | {
|
|
5
|
+
[key: string]: TomlValue;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Type alias for the function under test.
|
|
9
|
+
* Generic over Input, Options, and Output so callers can narrow the types.
|
|
10
|
+
* Defaults preserve backward compatibility with untyped usage.
|
|
11
|
+
*/
|
|
12
|
+
export type TomlTestFn<Input extends TomlValue = TomlValue, Options extends Record<string, TomlValue> = Record<string, TomlValue>, Output extends TomlValue = TomlValue> = (input: Input, options: Options) => Output | Promise<Output>;
|
|
13
|
+
/**
|
|
14
|
+
* Framework-agnostic test harness interface.
|
|
15
|
+
* The caller provides these functions from whatever test framework they use
|
|
16
|
+
* (node:test, vitest, jest, mocha, etc.).
|
|
17
|
+
*/
|
|
18
|
+
export interface TestHarness {
|
|
19
|
+
/** Register a test suite / group (like `describe` in most frameworks) */
|
|
20
|
+
describe: (name: string, fn: () => void) => void;
|
|
21
|
+
/** Register a single test case (like `it` or `test`) */
|
|
22
|
+
it: (name: string, fn: () => void | Promise<void>) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Assert deep equality between actual and expected values.
|
|
25
|
+
* Should throw on mismatch.
|
|
26
|
+
*/
|
|
27
|
+
equal: (actual: unknown, expected: unknown) => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parses a TOML test document and registers test suites/cases using the
|
|
31
|
+
* provided test harness. Framework-agnostic — works with node:test, vitest,
|
|
32
|
+
* jest, mocha, or any framework that provides describe/it/equal.
|
|
33
|
+
*
|
|
34
|
+
* @param toml - TOML source string describing the test suite
|
|
35
|
+
* @param fn - The function under test
|
|
36
|
+
* @param harness - Test framework bindings (describe, it, equal)
|
|
37
|
+
* @param source - Optional source label (e.g. filename) for error messages
|
|
38
|
+
*/
|
|
39
|
+
export declare function toml_test<Input extends TomlValue = TomlValue, Options extends Record<string, TomlValue> = Record<string, TomlValue>, Output extends TomlValue = TomlValue>(toml: string, fn: TomlTestFn<Input, Options, Output>, harness: TestHarness, source?: string): void;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { parse } from "smol-toml";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var TomlValueSchema = z.lazy(
|
|
4
|
+
() => z.union([
|
|
5
|
+
z.string(),
|
|
6
|
+
z.number(),
|
|
7
|
+
z.bigint(),
|
|
8
|
+
z.boolean(),
|
|
9
|
+
z.instanceof(Date),
|
|
10
|
+
z.array(TomlValueSchema),
|
|
11
|
+
z.record(z.string(), TomlValueSchema)
|
|
12
|
+
])
|
|
13
|
+
);
|
|
14
|
+
var TestCaseSchema = z.object({
|
|
15
|
+
name: z.string().optional(),
|
|
16
|
+
input: TomlValueSchema,
|
|
17
|
+
output: TomlValueSchema.optional(),
|
|
18
|
+
throws: z.union([z.literal(true), z.string()]).optional(),
|
|
19
|
+
doesntThrow: z.literal(true).optional(),
|
|
20
|
+
options: z.record(z.string(), TomlValueSchema).optional()
|
|
21
|
+
}).refine(
|
|
22
|
+
(t) => {
|
|
23
|
+
const count = [
|
|
24
|
+
t.output !== void 0,
|
|
25
|
+
t.throws !== void 0,
|
|
26
|
+
t.doesntThrow !== void 0
|
|
27
|
+
].filter(Boolean).length;
|
|
28
|
+
return count === 1;
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
message: "Each test must have exactly one of: output, throws, or doesntThrow"
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
var TestSuiteSchema = z.lazy(
|
|
35
|
+
() => z.object({
|
|
36
|
+
name: z.string().optional(),
|
|
37
|
+
options: z.record(z.string(), TomlValueSchema).optional(),
|
|
38
|
+
tests: z.array(TestCaseSchema).optional(),
|
|
39
|
+
suites: z.array(TestSuiteSchema).optional()
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
function scanTestLines(toml) {
|
|
43
|
+
const lines = toml.split("\n");
|
|
44
|
+
const headerRegex = /^\s*\[\[(.+?)\]\]\s*$/;
|
|
45
|
+
const headers = [];
|
|
46
|
+
for (let i = 0; i < lines.length; i++) {
|
|
47
|
+
const line = lines[i];
|
|
48
|
+
if (line) {
|
|
49
|
+
const match = line.match(headerRegex);
|
|
50
|
+
if (match && match[1]) {
|
|
51
|
+
headers.push({ path: match[1].trim(), line: i + 1 });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const root = { testLines: [], suiteLines: [] };
|
|
56
|
+
for (const { path, line } of headers) {
|
|
57
|
+
const segments = path.split(".");
|
|
58
|
+
let current = root;
|
|
59
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
60
|
+
const seg = segments[i];
|
|
61
|
+
if (seg === "suites") {
|
|
62
|
+
if (current.suiteLines.length > 0) {
|
|
63
|
+
current = current.suiteLines[current.suiteLines.length - 1];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const lastSegment = segments[segments.length - 1];
|
|
68
|
+
if (lastSegment === "tests") {
|
|
69
|
+
current.testLines.push(line);
|
|
70
|
+
} else if (lastSegment === "suites") {
|
|
71
|
+
current.suiteLines.push({ testLines: [], suiteLines: [] });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return root;
|
|
75
|
+
}
|
|
76
|
+
function findNonTomlPaths(value, path = "result") {
|
|
77
|
+
const nonTomlPaths = [];
|
|
78
|
+
function isPlainObject(obj) {
|
|
79
|
+
if (obj === null || typeof obj !== "object") return false;
|
|
80
|
+
const proto = Object.getPrototypeOf(obj);
|
|
81
|
+
return proto === Object.prototype || proto === null;
|
|
82
|
+
}
|
|
83
|
+
function isTomlExpressible(val) {
|
|
84
|
+
const type = typeof val;
|
|
85
|
+
if (type === "string" || type === "number" || type === "bigint" || type === "boolean") {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if (val instanceof Date) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
if (val === null) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(val)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (isPlainObject(val)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
function walk(val, currentPath) {
|
|
103
|
+
if (!isTomlExpressible(val)) {
|
|
104
|
+
nonTomlPaths.push(currentPath);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(val)) {
|
|
108
|
+
for (let i = 0; i < val.length; i++) {
|
|
109
|
+
walk(val[i], `${currentPath}[${i}]`);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (val !== null && typeof val === "object" && isPlainObject(val)) {
|
|
114
|
+
for (const [key, value2] of Object.entries(
|
|
115
|
+
val
|
|
116
|
+
)) {
|
|
117
|
+
walk(value2, `${currentPath}.${key}`);
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
walk(value, path);
|
|
123
|
+
return nonTomlPaths;
|
|
124
|
+
}
|
|
125
|
+
function processSuite(suite, parentOptions, fn, harness, lineInfo, source) {
|
|
126
|
+
const suiteOptions = suite.options ?? {};
|
|
127
|
+
const mergedOptions = { ...parentOptions, ...suiteOptions };
|
|
128
|
+
const runContents = () => {
|
|
129
|
+
const tests = suite.tests;
|
|
130
|
+
if (Array.isArray(tests)) {
|
|
131
|
+
for (let testIndex = 0; testIndex < tests.length; testIndex++) {
|
|
132
|
+
const test = tests[testIndex];
|
|
133
|
+
const t = test;
|
|
134
|
+
const testName = t.name ?? String(t.input);
|
|
135
|
+
harness.it(testName, async () => {
|
|
136
|
+
const testOptions = t.options ?? {};
|
|
137
|
+
const finalOptions = { ...mergedOptions, ...testOptions };
|
|
138
|
+
const line = lineInfo.testLines[testIndex];
|
|
139
|
+
const location = line ? source ? `${source}:${line}` : `line ${line}` : void 0;
|
|
140
|
+
try {
|
|
141
|
+
const actual = await fn(t.input, finalOptions);
|
|
142
|
+
const hasOutput = t.output !== void 0;
|
|
143
|
+
const hasThrows = t.throws !== void 0;
|
|
144
|
+
const hasDoesntThrow = t.doesntThrow !== void 0;
|
|
145
|
+
if (hasOutput) {
|
|
146
|
+
const nonTomlPaths = findNonTomlPaths(actual);
|
|
147
|
+
if (nonTomlPaths.length > 0) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Result contains non-TOML-expressible values at: ${nonTomlPaths.join(", ")}`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
harness.equal(actual, t.output);
|
|
153
|
+
} else if (hasThrows) {
|
|
154
|
+
throw new Error("Expected function to throw");
|
|
155
|
+
} else if (hasDoesntThrow) {
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
const hasThrows = t.throws !== void 0;
|
|
159
|
+
if (hasThrows && err instanceof Error) {
|
|
160
|
+
const throwsValue = t.throws;
|
|
161
|
+
if (throwsValue === true) {
|
|
162
|
+
return;
|
|
163
|
+
} else if (typeof throwsValue === "string") {
|
|
164
|
+
const pattern = new RegExp(throwsValue);
|
|
165
|
+
if (pattern.test(err.message)) {
|
|
166
|
+
return;
|
|
167
|
+
} else {
|
|
168
|
+
const newErr = new Error(
|
|
169
|
+
`Expected error message to match pattern "${throwsValue}", but got: "${err.message}"`
|
|
170
|
+
);
|
|
171
|
+
if (location) {
|
|
172
|
+
newErr.message = `${location}: ${newErr.message}`;
|
|
173
|
+
}
|
|
174
|
+
throw newErr;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (err instanceof Error && location) {
|
|
179
|
+
err.message = `${location}: ${err.message}`;
|
|
180
|
+
}
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const suites = suite.suites;
|
|
187
|
+
if (Array.isArray(suites)) {
|
|
188
|
+
for (let suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {
|
|
189
|
+
const nestedSuite = suites[suiteIndex];
|
|
190
|
+
const nestedLineInfo = lineInfo.suiteLines[suiteIndex] ?? {
|
|
191
|
+
testLines: [],
|
|
192
|
+
suiteLines: []
|
|
193
|
+
};
|
|
194
|
+
processSuite(
|
|
195
|
+
nestedSuite,
|
|
196
|
+
mergedOptions,
|
|
197
|
+
fn,
|
|
198
|
+
harness,
|
|
199
|
+
nestedLineInfo,
|
|
200
|
+
source
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
if (suite.name && typeof suite.name === "string") {
|
|
206
|
+
harness.describe(suite.name, runContents);
|
|
207
|
+
} else {
|
|
208
|
+
runContents();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function toml_test(toml, fn, harness, source) {
|
|
212
|
+
const suite = parse(toml);
|
|
213
|
+
const lineInfo = scanTestLines(toml);
|
|
214
|
+
const validationResult = TestSuiteSchema.safeParse(suite);
|
|
215
|
+
if (!validationResult.success) {
|
|
216
|
+
for (const issue of validationResult.error.issues) {
|
|
217
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
218
|
+
const errorMessage = `Validation error at ${path}: ${issue.message}`;
|
|
219
|
+
harness.it(`Schema validation error: ${path}`, () => {
|
|
220
|
+
throw new Error(errorMessage);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
processSuite(suite, {}, fn, harness, lineInfo, source);
|
|
226
|
+
}
|
|
227
|
+
export {
|
|
228
|
+
toml_test
|
|
229
|
+
};
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["import { parse } from \"smol-toml\";\nimport { z } from \"zod\";\n\n/**\n * A value expressible in TOML: primitives, Dates, arrays, or string-keyed objects thereof.\n */\nexport type TomlValue =\n | string\n | number\n | bigint\n | boolean\n | Date\n | TomlValue[]\n | { [key: string]: TomlValue };\n\n/**\n * Type alias for the function under test.\n * Generic over Input, Options, and Output so callers can narrow the types.\n * Defaults preserve backward compatibility with untyped usage.\n */\nexport type TomlTestFn<\n Input extends TomlValue = TomlValue,\n Options extends Record<string, TomlValue> = Record<string, TomlValue>,\n Output extends TomlValue = TomlValue,\n> = (input: Input, options: Options) => Output | Promise<Output>;\n\n/**\n * Recursive structure mirroring suite nesting, containing only line numbers.\n */\ninterface SuiteLineInfo {\n testLines: number[]; // line number for each [[tests]] entry at this level\n suiteLines: SuiteLineInfo[]; // line info for each [[suites]] entry\n}\n\n/**\n * Zod schemas for validating the TOML test document structure.\n */\n\n// A TOML value: string, number, bigint, boolean, Date, or recursively arrays/objects of TOML values\nconst TomlValueSchema: z.ZodType<unknown> = z.lazy(() =>\n z.union([\n z.string(),\n z.number(),\n z.bigint(),\n z.boolean(),\n z.instanceof(Date),\n z.array(TomlValueSchema),\n z.record(z.string(), TomlValueSchema),\n ]),\n);\n\n// A single test case \u2014 name is optional (falls back to stringified input)\n// Exactly one of: output, throws, doesntThrow must be present\nconst TestCaseSchema = z\n .object({\n name: z.string().optional(),\n input: TomlValueSchema,\n output: TomlValueSchema.optional(),\n throws: z.union([z.literal(true), z.string()]).optional(),\n doesntThrow: z.literal(true).optional(),\n options: z.record(z.string(), TomlValueSchema).optional(),\n })\n .refine(\n (t) => {\n const count = [\n t.output !== undefined,\n t.throws !== undefined,\n t.doesntThrow !== undefined,\n ].filter(Boolean).length;\n return count === 1;\n },\n {\n message:\n \"Each test must have exactly one of: output, throws, or doesntThrow\",\n },\n );\n\n// Recursive suite schema\nconst TestSuiteSchema: z.ZodType<any> = z.lazy(() =>\n z.object({\n name: z.string().optional(),\n options: z.record(z.string(), TomlValueSchema).optional(),\n tests: z.array(TestCaseSchema).optional(),\n suites: z.array(TestSuiteSchema).optional(),\n }),\n);\n\n/**\n * Framework-agnostic test harness interface.\n * The caller provides these functions from whatever test framework they use\n * (node:test, vitest, jest, mocha, etc.).\n */\nexport interface TestHarness {\n /** Register a test suite / group (like `describe` in most frameworks) */\n describe: (name: string, fn: () => void) => void;\n /** Register a single test case (like `it` or `test`) */\n it: (name: string, fn: () => void | Promise<void>) => void;\n /**\n * Assert deep equality between actual and expected values.\n * Should throw on mismatch.\n */\n equal: (actual: unknown, expected: unknown) => void;\n}\n\n/**\n * Scans TOML source for array-of-tables headers and builds a tree of line numbers\n * mirroring the suite nesting structure.\n */\nfunction scanTestLines(toml: string): SuiteLineInfo {\n const lines = toml.split(\"\\n\");\n const headerRegex = /^\\s*\\[\\[(.+?)\\]\\]\\s*$/;\n\n // Collect all array-of-tables headers with their line numbers\n const headers: Array<{ path: string; line: number }> = [];\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line) {\n const match = line.match(headerRegex);\n if (match && match[1]) {\n headers.push({ path: match[1].trim(), line: i + 1 }); // 1-indexed\n }\n }\n }\n\n // Build the tree from the flat list of headers\n const root: SuiteLineInfo = { testLines: [], suiteLines: [] };\n\n for (const { path, line } of headers) {\n // Split path into segments: \"suites.suites.tests\" -> [\"suites\", \"suites\", \"tests\"]\n const segments = path.split(\".\");\n\n // Navigate to the right suite\n let current: SuiteLineInfo = root;\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i];\n if (seg === \"suites\") {\n // Navigate into the last suite at this level\n if (current.suiteLines.length > 0) {\n current = current.suiteLines[current.suiteLines.length - 1]!;\n }\n }\n // Skip other segments \u2014 they're not suite navigation\n }\n\n const lastSegment = segments[segments.length - 1];\n if (lastSegment === \"tests\") {\n current.testLines.push(line);\n } else if (lastSegment === \"suites\") {\n current.suiteLines.push({ testLines: [], suiteLines: [] });\n }\n // Ignore other paths like \"tests.options\", \"suites.options\" etc.\n }\n\n return root;\n}\n\n/**\n * Recursively finds all paths in a value that contain non-TOML-expressible types.\n * TOML-expressible types are: string, number, bigint, boolean, Date, plain objects, and arrays.\n * Returns an array of dot-path strings where non-TOML values are found.\n */\nfunction findNonTomlPaths(value: unknown, path: string = \"result\"): string[] {\n const nonTomlPaths: string[] = [];\n\n function isPlainObject(obj: unknown): boolean {\n if (obj === null || typeof obj !== \"object\") return false;\n const proto = Object.getPrototypeOf(obj) as unknown;\n return proto === Object.prototype || proto === null;\n }\n\n function isTomlExpressible(val: unknown): boolean {\n const type = typeof val;\n\n // Primitive TOML types\n if (\n type === \"string\"\n || type === \"number\"\n || type === \"bigint\"\n || type === \"boolean\"\n ) {\n return true;\n }\n\n // Date instances (including smol-toml's TomlDate)\n if (val instanceof Date) {\n return true;\n }\n\n // null is NOT TOML-expressible\n if (val === null) {\n return false;\n }\n\n // Arrays: check elements recursively\n if (Array.isArray(val)) {\n return true;\n }\n\n // Plain objects: check values recursively\n if (isPlainObject(val)) {\n return true;\n }\n\n // Everything else (undefined, Symbol, Function, Set, Map, RegExp, class instances, etc.)\n return false;\n }\n\n function walk(val: unknown, currentPath: string): void {\n if (!isTomlExpressible(val)) {\n nonTomlPaths.push(currentPath);\n return;\n }\n\n if (Array.isArray(val)) {\n for (let i = 0; i < val.length; i++) {\n walk(val[i], `${currentPath}[${i}]`);\n }\n return;\n }\n\n if (val !== null && typeof val === \"object\" && isPlainObject(val)) {\n for (const [key, value] of Object.entries(\n val as Record<string, unknown>,\n )) {\n walk(value, `${currentPath}.${key}`);\n }\n return;\n }\n }\n\n walk(value, path);\n return nonTomlPaths;\n}\n\n/**\n * Processes a test suite recursively, creating describe/it blocks as needed.\n * Handles options merging from parent to child scope.\n */\nfunction processSuite<\n Input extends TomlValue,\n Options extends Record<string, TomlValue>,\n Output extends TomlValue,\n>(\n suite: Record<string, unknown>,\n parentOptions: Record<string, unknown>,\n fn: TomlTestFn<Input, Options, Output>,\n harness: TestHarness,\n lineInfo: SuiteLineInfo,\n source?: string,\n): void {\n // Merge options: parent options + suite options\n const suiteOptions = (suite.options ?? {}) as Record<string, unknown>;\n const mergedOptions = { ...parentOptions, ...suiteOptions };\n\n const runContents = () => {\n // Process test cases\n const tests = suite.tests;\n if (Array.isArray(tests)) {\n for (let testIndex = 0; testIndex < tests.length; testIndex++) {\n const test = tests[testIndex];\n const t = test as Record<string, unknown>;\n\n // Use name if provided, otherwise fall back to stringified input\n const testName = (t.name as string | undefined) ?? String(t.input);\n\n harness.it(testName, async () => {\n // Merge test-level options on top of suite options\n const testOptions = (t.options ?? {}) as Record<string, unknown>;\n const finalOptions = { ...mergedOptions, ...testOptions };\n\n // Get line number for this test\n const line = lineInfo.testLines[testIndex];\n const location =\n line ?\n source ? `${source}:${line}`\n : `line ${line}`\n : undefined;\n\n try {\n // Call the test function (await in case it's async)\n const actual = await fn(t.input as Input, finalOptions as Options);\n\n // Determine which assertion type is being used\n const hasOutput = t.output !== undefined;\n const hasThrows = t.throws !== undefined;\n const hasDoesntThrow = t.doesntThrow !== undefined;\n\n if (hasOutput) {\n // output assertion: validate non-TOML types, then compare\n const nonTomlPaths = findNonTomlPaths(actual);\n if (nonTomlPaths.length > 0) {\n throw new Error(\n `Result contains non-TOML-expressible values at: ${nonTomlPaths.join(\", \")}`,\n );\n }\n harness.equal(actual, t.output);\n } else if (hasThrows) {\n // throws assertion: should have thrown, but didn't\n throw new Error(\"Expected function to throw\");\n } else if (hasDoesntThrow) {\n // doesntThrow assertion: just verify it didn't throw (already passed)\n // No additional checks needed\n }\n } catch (err) {\n // Check if this is a \"throws\" test that actually threw\n const hasThrows = t.throws !== undefined;\n if (hasThrows && err instanceof Error) {\n const throwsValue = t.throws;\n if (throwsValue === true) {\n // Any error is fine, test passes\n return;\n } else if (typeof throwsValue === \"string\") {\n // Check if error message matches the pattern\n const pattern = new RegExp(throwsValue);\n if (pattern.test(err.message)) {\n // Pattern matches, test passes\n return;\n } else {\n // Pattern doesn't match, fail with descriptive message\n const newErr = new Error(\n `Expected error message to match pattern \"${throwsValue}\", but got: \"${err.message}\"`,\n );\n if (location) {\n newErr.message = `${location}: ${newErr.message}`;\n }\n throw newErr;\n }\n }\n }\n\n // For non-throws tests or other errors, add location and re-throw\n if (err instanceof Error && location) {\n err.message = `${location}: ${err.message}`;\n }\n throw err;\n }\n });\n }\n }\n\n // Process nested suites\n const suites = suite.suites;\n if (Array.isArray(suites)) {\n for (let suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {\n const nestedSuite = suites[suiteIndex];\n const nestedLineInfo = lineInfo.suiteLines[suiteIndex] ?? {\n testLines: [],\n suiteLines: [],\n };\n processSuite(\n nestedSuite as Record<string, unknown>,\n mergedOptions,\n fn,\n harness,\n nestedLineInfo,\n source,\n );\n }\n }\n };\n\n // If the suite has a name, wrap in describe block\n if (suite.name && typeof suite.name === \"string\") {\n harness.describe(suite.name, runContents);\n } else {\n // Nameless suites run contents directly at the current nesting level\n runContents();\n }\n}\n\n/**\n * Parses a TOML test document and registers test suites/cases using the\n * provided test harness. Framework-agnostic \u2014 works with node:test, vitest,\n * jest, mocha, or any framework that provides describe/it/equal.\n *\n * @param toml - TOML source string describing the test suite\n * @param fn - The function under test\n * @param harness - Test framework bindings (describe, it, equal)\n * @param source - Optional source label (e.g. filename) for error messages\n */\nexport function toml_test<\n Input extends TomlValue = TomlValue,\n Options extends Record<string, TomlValue> = Record<string, TomlValue>,\n Output extends TomlValue = TomlValue,\n>(\n toml: string,\n fn: TomlTestFn<Input, Options, Output>,\n harness: TestHarness,\n source?: string,\n): void {\n const suite = parse(toml);\n const lineInfo = scanTestLines(toml);\n\n // Validate the parsed suite against the schema\n const validationResult = TestSuiteSchema.safeParse(suite);\n\n if (!validationResult.success) {\n // Register failing tests for each validation error\n for (const issue of validationResult.error.issues) {\n const path = issue.path.length > 0 ? issue.path.join(\".\") : \"root\";\n const errorMessage = `Validation error at ${path}: ${issue.message}`;\n harness.it(`Schema validation error: ${path}`, () => {\n throw new Error(errorMessage);\n });\n }\n return;\n }\n\n processSuite(suite, {}, fn, harness, lineInfo, source);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["value"]
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import { strict as assert } from "node:assert";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { toml_test } from "./index.js";
|
|
6
|
+
var harness = {
|
|
7
|
+
describe,
|
|
8
|
+
it,
|
|
9
|
+
equal: assert.deepStrictEqual
|
|
10
|
+
};
|
|
11
|
+
function fixture(name) {
|
|
12
|
+
return readFileSync(
|
|
13
|
+
path.resolve(import.meta.dirname, "../fixtures", name),
|
|
14
|
+
"utf-8"
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
describe("Test 1: Basic suite with options merging", () => {
|
|
18
|
+
function stringUtil(input, options) {
|
|
19
|
+
let result = input;
|
|
20
|
+
if (options.trim) result = result.trim();
|
|
21
|
+
if (options.uppercase) result = result.toUpperCase();
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
toml_test(fixture("basic.toml"), stringUtil, harness);
|
|
25
|
+
});
|
|
26
|
+
describe("Test 2: Nameless suites", () => {
|
|
27
|
+
function prefixSuffix(input, options) {
|
|
28
|
+
let result = input;
|
|
29
|
+
if (options.prefix) result = options.prefix + result;
|
|
30
|
+
if (options.suffix) result = result + options.suffix;
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
toml_test(fixture("nameless-suite.toml"), prefixSuffix, harness);
|
|
34
|
+
});
|
|
35
|
+
describe("Test 3: Non-TOML result detection", () => {
|
|
36
|
+
function nonTomlReturner(input, _options) {
|
|
37
|
+
if (input === "return-undefined") return { value: void 0 };
|
|
38
|
+
if (input === "return-function") return { fn: () => {
|
|
39
|
+
} };
|
|
40
|
+
return input;
|
|
41
|
+
}
|
|
42
|
+
const expectThrowsHarness = {
|
|
43
|
+
describe,
|
|
44
|
+
it: (name, fn) => it(name, async () => {
|
|
45
|
+
await assert.rejects(
|
|
46
|
+
async () => {
|
|
47
|
+
const result = fn();
|
|
48
|
+
if (result instanceof Promise) {
|
|
49
|
+
await result;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
(err) => {
|
|
53
|
+
assert.match(err.message, /non-toml-result\.toml:\d+:/);
|
|
54
|
+
assert.match(err.message, /non-TOML-expressible/);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}),
|
|
59
|
+
equal: assert.deepStrictEqual
|
|
60
|
+
};
|
|
61
|
+
toml_test(
|
|
62
|
+
fixture("non-toml-result.toml"),
|
|
63
|
+
nonTomlReturner,
|
|
64
|
+
expectThrowsHarness,
|
|
65
|
+
"non-toml-result.toml"
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
describe("Test 4: Source line numbers in error messages", () => {
|
|
69
|
+
function alwaysFails(input, _options) {
|
|
70
|
+
throw new Error("intentional failure");
|
|
71
|
+
}
|
|
72
|
+
const sourceLineHarness = {
|
|
73
|
+
describe,
|
|
74
|
+
it: (name, fn) => it(name, async () => {
|
|
75
|
+
await assert.rejects(
|
|
76
|
+
async () => {
|
|
77
|
+
const result = fn();
|
|
78
|
+
if (result instanceof Promise) {
|
|
79
|
+
await result;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
(err) => {
|
|
83
|
+
assert.match(err.message, /test\.toml:\d+:/);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}),
|
|
88
|
+
equal: assert.deepStrictEqual
|
|
89
|
+
};
|
|
90
|
+
const minimalToml = `
|
|
91
|
+
[[tests]]
|
|
92
|
+
name = "test 1"
|
|
93
|
+
input = "hello"
|
|
94
|
+
output = "hello"
|
|
95
|
+
`;
|
|
96
|
+
toml_test(minimalToml, alwaysFails, sourceLineHarness, "test.toml");
|
|
97
|
+
});
|
|
98
|
+
describe("Test: TomlDate is TOML-expressible", () => {
|
|
99
|
+
function dateReturner(_input, _options) {
|
|
100
|
+
const date = /* @__PURE__ */ new Date("2024-01-01T00:00:00Z");
|
|
101
|
+
return { timestamp: date };
|
|
102
|
+
}
|
|
103
|
+
const dateHarness = {
|
|
104
|
+
describe,
|
|
105
|
+
it,
|
|
106
|
+
equal: (actual, expected) => {
|
|
107
|
+
if (actual !== null && typeof actual === "object" && expected !== null && typeof expected === "object" && "timestamp" in actual && "timestamp" in expected) {
|
|
108
|
+
const actualDate = actual.timestamp;
|
|
109
|
+
const expectedDate = expected.timestamp;
|
|
110
|
+
if (actualDate instanceof Date && expectedDate instanceof Date) {
|
|
111
|
+
assert.strictEqual(actualDate.getTime(), expectedDate.getTime());
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
assert.deepStrictEqual(actual, expected);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const toml = `
|
|
119
|
+
[[tests]]
|
|
120
|
+
name = "date values are TOML-expressible"
|
|
121
|
+
input = "anything"
|
|
122
|
+
output = { timestamp = 2024-01-01T00:00:00Z }
|
|
123
|
+
`;
|
|
124
|
+
toml_test(toml, dateReturner, dateHarness);
|
|
125
|
+
});
|
|
126
|
+
describe("Test: Error without source shows line number only", () => {
|
|
127
|
+
function alwaysFails(_input, _options) {
|
|
128
|
+
throw new Error("intentional failure");
|
|
129
|
+
}
|
|
130
|
+
const noSourceHarness = {
|
|
131
|
+
describe,
|
|
132
|
+
it: (name, fn) => it(name, async () => {
|
|
133
|
+
await assert.rejects(
|
|
134
|
+
async () => {
|
|
135
|
+
const result = fn();
|
|
136
|
+
if (result instanceof Promise) {
|
|
137
|
+
await result;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
(err) => {
|
|
141
|
+
assert.match(err.message, /^line \d+:/);
|
|
142
|
+
assert.doesNotMatch(err.message, /\.toml/);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
}),
|
|
147
|
+
equal: assert.deepStrictEqual
|
|
148
|
+
};
|
|
149
|
+
const toml = `
|
|
150
|
+
[[tests]]
|
|
151
|
+
name = "will fail"
|
|
152
|
+
input = "hello"
|
|
153
|
+
output = "hello"
|
|
154
|
+
`;
|
|
155
|
+
toml_test(toml, alwaysFails, noSourceHarness);
|
|
156
|
+
});
|
|
157
|
+
describe("Test: Empty test array", () => {
|
|
158
|
+
const toml = `
|
|
159
|
+
name = "Empty suite"
|
|
160
|
+
tests = []
|
|
161
|
+
`;
|
|
162
|
+
let testCount = 0;
|
|
163
|
+
const countingHarness = {
|
|
164
|
+
describe: (_name, fn) => fn(),
|
|
165
|
+
it: (_name, _fn) => {
|
|
166
|
+
testCount++;
|
|
167
|
+
},
|
|
168
|
+
equal: assert.deepStrictEqual
|
|
169
|
+
};
|
|
170
|
+
toml_test(toml, () => "unused", countingHarness);
|
|
171
|
+
it("registers no tests for empty array", () => {
|
|
172
|
+
assert.strictEqual(testCount, 0);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
describe("Test: Root-level options", () => {
|
|
176
|
+
function multiply(input, options) {
|
|
177
|
+
return parseInt(input) * options.factor;
|
|
178
|
+
}
|
|
179
|
+
const toml = `
|
|
180
|
+
[options]
|
|
181
|
+
factor = 3
|
|
182
|
+
|
|
183
|
+
[[tests]]
|
|
184
|
+
name = "multiplies by factor"
|
|
185
|
+
input = "5"
|
|
186
|
+
output = 15
|
|
187
|
+
|
|
188
|
+
[[tests]]
|
|
189
|
+
name = "override factor"
|
|
190
|
+
input = "5"
|
|
191
|
+
output = 25
|
|
192
|
+
[tests.options]
|
|
193
|
+
factor = 5
|
|
194
|
+
`;
|
|
195
|
+
toml_test(toml, multiply, harness);
|
|
196
|
+
});
|
|
197
|
+
describe("Test: Object output", () => {
|
|
198
|
+
function parseKeyValue(input, _options) {
|
|
199
|
+
const parts = input.split("=");
|
|
200
|
+
const key = parts[0] ?? "";
|
|
201
|
+
const value = parts[1] ?? "";
|
|
202
|
+
return { key: key.trim(), value: value.trim() };
|
|
203
|
+
}
|
|
204
|
+
const toml = `
|
|
205
|
+
[[tests]]
|
|
206
|
+
name = "parses key-value pair"
|
|
207
|
+
input = "color = blue"
|
|
208
|
+
output = { key = "color", value = "blue" }
|
|
209
|
+
`;
|
|
210
|
+
toml_test(toml, parseKeyValue, harness);
|
|
211
|
+
});
|
|
212
|
+
describe("Test: throws assertion", () => {
|
|
213
|
+
function mayThrow(input, _options) {
|
|
214
|
+
if (input === "bad") throw new Error("something went wrong");
|
|
215
|
+
return input;
|
|
216
|
+
}
|
|
217
|
+
const toml = `
|
|
218
|
+
[[tests]]
|
|
219
|
+
name = "throws on bad input"
|
|
220
|
+
input = "bad"
|
|
221
|
+
throws = true
|
|
222
|
+
|
|
223
|
+
[[tests]]
|
|
224
|
+
name = "throws matching pattern"
|
|
225
|
+
input = "bad"
|
|
226
|
+
throws = "went wrong"
|
|
227
|
+
`;
|
|
228
|
+
toml_test(toml, mayThrow, harness);
|
|
229
|
+
});
|
|
230
|
+
describe("Test: doesntThrow assertion", () => {
|
|
231
|
+
function safeFunction(input, _options) {
|
|
232
|
+
return input.toUpperCase();
|
|
233
|
+
}
|
|
234
|
+
const toml = `
|
|
235
|
+
[[tests]]
|
|
236
|
+
name = "runs without throwing"
|
|
237
|
+
input = "hello"
|
|
238
|
+
doesntThrow = true
|
|
239
|
+
`;
|
|
240
|
+
toml_test(toml, safeFunction, harness);
|
|
241
|
+
});
|
|
242
|
+
describe("Test: Name falls back to input", () => {
|
|
243
|
+
let registeredName = "";
|
|
244
|
+
const nameCapturingHarness = {
|
|
245
|
+
describe: (_name, fn) => fn(),
|
|
246
|
+
it: (name, fn) => {
|
|
247
|
+
registeredName = name;
|
|
248
|
+
},
|
|
249
|
+
equal: assert.deepStrictEqual
|
|
250
|
+
};
|
|
251
|
+
const toml = `
|
|
252
|
+
[[tests]]
|
|
253
|
+
input = "hello world"
|
|
254
|
+
output = "hello world"
|
|
255
|
+
`;
|
|
256
|
+
toml_test(toml, (input) => input, nameCapturingHarness);
|
|
257
|
+
it("uses input as test name when name is omitted", () => {
|
|
258
|
+
assert.strictEqual(registeredName, "hello world");
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
describe("Test: Schema validation errors", () => {
|
|
262
|
+
const invalidToml = `
|
|
263
|
+
[[tests]]
|
|
264
|
+
name = "missing assertion"
|
|
265
|
+
input = "hello"
|
|
266
|
+
`;
|
|
267
|
+
let registeredTests = [];
|
|
268
|
+
const capturingHarness = {
|
|
269
|
+
describe: (_name, fn) => fn(),
|
|
270
|
+
it: (name, fn) => {
|
|
271
|
+
registeredTests.push({ name, fn });
|
|
272
|
+
},
|
|
273
|
+
equal: assert.deepStrictEqual
|
|
274
|
+
};
|
|
275
|
+
toml_test(invalidToml, (input) => input, capturingHarness);
|
|
276
|
+
it("registers a failing test for schema errors", () => {
|
|
277
|
+
assert.ok(registeredTests.length > 0);
|
|
278
|
+
const firstTest = registeredTests[0];
|
|
279
|
+
assert.ok(firstTest);
|
|
280
|
+
assert.throws(() => firstTest.fn(), /output.*throws.*doesntThrow/i);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
describe("Test: Async test function", () => {
|
|
284
|
+
async function asyncUppercase(input, _options) {
|
|
285
|
+
return input.toUpperCase();
|
|
286
|
+
}
|
|
287
|
+
const toml = `
|
|
288
|
+
[[tests]]
|
|
289
|
+
name = "async uppercase"
|
|
290
|
+
input = "hello"
|
|
291
|
+
output = "HELLO"
|
|
292
|
+
`;
|
|
293
|
+
toml_test(toml, asyncUppercase, harness);
|
|
294
|
+
});
|
|
295
|
+
describe("Test: Numeric input", () => {
|
|
296
|
+
function doubleIt(input, _options) {
|
|
297
|
+
return input * 2;
|
|
298
|
+
}
|
|
299
|
+
const toml = `
|
|
300
|
+
[[tests]]
|
|
301
|
+
name = "doubles an integer"
|
|
302
|
+
input = 21
|
|
303
|
+
output = 42
|
|
304
|
+
|
|
305
|
+
[[tests]]
|
|
306
|
+
name = "doubles a float"
|
|
307
|
+
input = 1.5
|
|
308
|
+
output = 3.0
|
|
309
|
+
`;
|
|
310
|
+
toml_test(toml, doubleIt, harness);
|
|
311
|
+
});
|
|
312
|
+
describe("Test: Boolean input", () => {
|
|
313
|
+
function negate(input, _options) {
|
|
314
|
+
return !input;
|
|
315
|
+
}
|
|
316
|
+
const toml = `
|
|
317
|
+
[[tests]]
|
|
318
|
+
name = "negates true"
|
|
319
|
+
input = true
|
|
320
|
+
output = false
|
|
321
|
+
|
|
322
|
+
[[tests]]
|
|
323
|
+
name = "negates false"
|
|
324
|
+
input = false
|
|
325
|
+
output = true
|
|
326
|
+
`;
|
|
327
|
+
toml_test(toml, negate, harness);
|
|
328
|
+
});
|
|
329
|
+
describe("Test: Object input", () => {
|
|
330
|
+
function greet(input, _options) {
|
|
331
|
+
const obj = input;
|
|
332
|
+
return `${obj.greeting}, ${obj.name}!`;
|
|
333
|
+
}
|
|
334
|
+
const toml = `
|
|
335
|
+
[[tests]]
|
|
336
|
+
name = "greets from object input"
|
|
337
|
+
input = { name = "Alice", greeting = "Hello" }
|
|
338
|
+
output = "Hello, Alice!"
|
|
339
|
+
`;
|
|
340
|
+
toml_test(toml, greet, harness);
|
|
341
|
+
});
|
|
342
|
+
describe("Test: Array input", () => {
|
|
343
|
+
function sum(input, _options) {
|
|
344
|
+
return input.reduce((a, b) => a + b, 0);
|
|
345
|
+
}
|
|
346
|
+
const toml = `
|
|
347
|
+
[[tests]]
|
|
348
|
+
name = "sums an array of numbers"
|
|
349
|
+
input = [1, 2, 3, 4]
|
|
350
|
+
output = 10
|
|
351
|
+
`;
|
|
352
|
+
toml_test(toml, sum, harness);
|
|
353
|
+
});
|
|
354
|
+
describe("Test: Name fallback with non-string input", () => {
|
|
355
|
+
let registeredName = "";
|
|
356
|
+
const nameCapturingHarness = {
|
|
357
|
+
describe: (_name, fn) => fn(),
|
|
358
|
+
it: (name, _fn) => {
|
|
359
|
+
registeredName = name;
|
|
360
|
+
},
|
|
361
|
+
equal: assert.deepStrictEqual
|
|
362
|
+
};
|
|
363
|
+
const toml = `
|
|
364
|
+
[[tests]]
|
|
365
|
+
input = 42
|
|
366
|
+
output = 42
|
|
367
|
+
`;
|
|
368
|
+
toml_test(toml, (input) => input, nameCapturingHarness);
|
|
369
|
+
it("uses String(input) as test name for non-string input", () => {
|
|
370
|
+
assert.strictEqual(registeredName, "42");
|
|
371
|
+
});
|
|
372
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/index.test.ts"],
|
|
4
|
+
"sourcesContent": ["import { describe, it } from \"node:test\";\nimport { strict as assert } from \"node:assert\";\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { toml_test } from \"./index.ts\";\nimport type { TestHarness, TomlValue } from \"./index.ts\";\n\n/** Wire up node:test as the harness */\nconst harness: TestHarness = {\n describe,\n it,\n equal: assert.deepStrictEqual,\n};\n\n/** Helper to load a fixture as a TOML string */\nfunction fixture(name: string): string {\n return readFileSync(\n path.resolve(import.meta.dirname, \"../fixtures\", name),\n \"utf-8\",\n );\n}\n\n/**\n * Test 1: Basic suite with options merging\n * Tests a string utility function that can trim and uppercase input based on options.\n */\ndescribe(\"Test 1: Basic suite with options merging\", () => {\n function stringUtil(input: TomlValue, options: any): string {\n let result = input as string;\n if (options.trim) result = result.trim();\n if (options.uppercase) result = result.toUpperCase();\n return result;\n }\n\n toml_test(fixture(\"basic.toml\"), stringUtil, harness);\n});\n\n/**\n * Test 2: Nameless suites\n * Tests a function that can prepend prefix and append suffix based on options.\n * Demonstrates that suites without names work correctly.\n */\ndescribe(\"Test 2: Nameless suites\", () => {\n function prefixSuffix(input: TomlValue, options: any): string {\n let result = input as string;\n if (options.prefix) result = options.prefix + result;\n if (options.suffix) result = result + options.suffix;\n return result;\n }\n\n toml_test(fixture(\"nameless-suite.toml\"), prefixSuffix, harness);\n});\n\n/**\n * Test 3: Non-TOML result detection\n * Wraps each registered test in an assert.throws to verify the non-TOML error.\n */\ndescribe(\"Test 3: Non-TOML result detection\", () => {\n function nonTomlReturner(input: TomlValue, _options: any): any {\n if ((input as string) === \"return-undefined\") return { value: undefined };\n if ((input as string) === \"return-function\") return { fn: () => {} };\n return input;\n }\n\n const expectThrowsHarness: TestHarness = {\n describe,\n it: (name, fn) =>\n it(name, async () => {\n await assert.rejects(\n async () => {\n const result = fn();\n if (result instanceof Promise) {\n await result;\n }\n },\n (err: Error) => {\n assert.match(err.message, /non-toml-result\\.toml:\\d+:/);\n assert.match(err.message, /non-TOML-expressible/);\n return true;\n },\n );\n }),\n equal: assert.deepStrictEqual,\n };\n\n toml_test(\n fixture(\"non-toml-result.toml\"),\n nonTomlReturner,\n expectThrowsHarness,\n \"non-toml-result.toml\",\n );\n});\n\n/**\n * Test 4: Source line numbers in error messages\n * Verifies that when a source parameter is provided, error messages include the source:line format.\n */\ndescribe(\"Test 4: Source line numbers in error messages\", () => {\n function alwaysFails(input: TomlValue, _options: any): any {\n throw new Error(\"intentional failure\");\n }\n\n const sourceLineHarness: TestHarness = {\n describe,\n it: (name, fn) =>\n it(name, async () => {\n await assert.rejects(\n async () => {\n const result = fn();\n if (result instanceof Promise) {\n await result;\n }\n },\n (err: Error) => {\n // When source is provided, error should include \"test.toml:N:\" format\n assert.match(err.message, /test\\.toml:\\d+:/);\n return true;\n },\n );\n }),\n equal: assert.deepStrictEqual,\n };\n\n // Create a minimal TOML with a test\n const minimalToml = `\n[[tests]]\nname = \"test 1\"\ninput = \"hello\"\noutput = \"hello\"\n`;\n\n toml_test(minimalToml, alwaysFails, sourceLineHarness, \"test.toml\");\n});\n\n/**\n * Test: TomlDate is TOML-expressible\n * Verifies that Date objects (including smol-toml's TomlDate) pass the non-TOML check.\n */\ndescribe(\"Test: TomlDate is TOML-expressible\", () => {\n function dateReturner(_input: TomlValue, _options: any): any {\n // Return a Date object that matches what smol-toml parses\n const date = new Date(\"2024-01-01T00:00:00Z\");\n return { timestamp: date };\n }\n\n // Use a custom harness that compares dates by value, not by type\n const dateHarness: TestHarness = {\n describe,\n it,\n equal: (actual: unknown, expected: unknown) => {\n // Custom equality that treats Date and TomlDate as equivalent if they have the same time\n if (\n actual !== null\n && typeof actual === \"object\"\n && expected !== null\n && typeof expected === \"object\"\n && \"timestamp\" in actual\n && \"timestamp\" in expected\n ) {\n const actualDate = (actual as any).timestamp;\n const expectedDate = (expected as any).timestamp;\n if (actualDate instanceof Date && expectedDate instanceof Date) {\n assert.strictEqual(actualDate.getTime(), expectedDate.getTime());\n return;\n }\n }\n assert.deepStrictEqual(actual, expected);\n },\n };\n\n const toml = `\n[[tests]]\nname = \"date values are TOML-expressible\"\ninput = \"anything\"\noutput = { timestamp = 2024-01-01T00:00:00Z }\n`;\n toml_test(toml, dateReturner, dateHarness);\n});\n\n/**\n * Test: Error without source shows line number only\n * Verifies that when no source parameter is provided, errors show \"line N:\" format.\n */\ndescribe(\"Test: Error without source shows line number only\", () => {\n function alwaysFails(_input: TomlValue, _options: any): any {\n throw new Error(\"intentional failure\");\n }\n\n const noSourceHarness: TestHarness = {\n describe,\n it: (name, fn) =>\n it(name, async () => {\n await assert.rejects(\n async () => {\n const result = fn();\n if (result instanceof Promise) {\n await result;\n }\n },\n (err: Error) => {\n assert.match(err.message, /^line \\d+:/);\n assert.doesNotMatch(err.message, /\\.toml/);\n return true;\n },\n );\n }),\n equal: assert.deepStrictEqual,\n };\n\n const toml = `\n[[tests]]\nname = \"will fail\"\ninput = \"hello\"\noutput = \"hello\"\n`;\n toml_test(toml, alwaysFails, noSourceHarness);\n});\n\n/**\n * Test: Empty test array is a no-op\n * Verifies that an empty tests array doesn't register any tests.\n */\ndescribe(\"Test: Empty test array\", () => {\n const toml = `\nname = \"Empty suite\"\ntests = []\n`;\n // Should not crash, should not register any tests\n let testCount = 0;\n const countingHarness: TestHarness = {\n describe: (_name, fn) => fn(),\n it: (_name, _fn) => {\n testCount++;\n },\n equal: assert.deepStrictEqual,\n };\n toml_test(toml, () => \"unused\", countingHarness);\n // Wrap in an it() to assert\n it(\"registers no tests for empty array\", () => {\n assert.strictEqual(testCount, 0);\n });\n});\n\n/**\n * Test: Root-level options\n * Verifies that root-level options are applied to all tests.\n */\ndescribe(\"Test: Root-level options\", () => {\n function multiply(input: TomlValue, options: any): number {\n return parseInt(input as string) * options.factor;\n }\n\n const toml = `\n[options]\nfactor = 3\n\n[[tests]]\nname = \"multiplies by factor\"\ninput = \"5\"\noutput = 15\n\n[[tests]]\nname = \"override factor\"\ninput = \"5\"\noutput = 25\n[tests.options]\nfactor = 5\n`;\n toml_test(toml, multiply, harness);\n});\n\n/**\n * Test: Object output (deep equality)\n * Verifies that object outputs are compared with deep equality.\n */\ndescribe(\"Test: Object output\", () => {\n function parseKeyValue(input: TomlValue, _options: any): any {\n const parts = (input as string).split(\"=\");\n const key = parts[0] ?? \"\";\n const value = parts[1] ?? \"\";\n return { key: key.trim(), value: value.trim() };\n }\n\n const toml = `\n[[tests]]\nname = \"parses key-value pair\"\ninput = \"color = blue\"\noutput = { key = \"color\", value = \"blue\" }\n`;\n toml_test(toml, parseKeyValue, harness);\n});\n\n/**\n * Test: throws assertion\n * Verifies that throws assertions work with both true and pattern matching.\n */\ndescribe(\"Test: throws assertion\", () => {\n function mayThrow(input: TomlValue, _options: any): any {\n if ((input as string) === \"bad\") throw new Error(\"something went wrong\");\n return input;\n }\n\n const toml = `\n[[tests]]\nname = \"throws on bad input\"\ninput = \"bad\"\nthrows = true\n\n[[tests]]\nname = \"throws matching pattern\"\ninput = \"bad\"\nthrows = \"went wrong\"\n`;\n toml_test(toml, mayThrow, harness);\n});\n\n/**\n * Test: doesntThrow assertion\n * Verifies that doesntThrow assertions work correctly.\n */\ndescribe(\"Test: doesntThrow assertion\", () => {\n function safeFunction(input: TomlValue, _options: any): any {\n return (input as string).toUpperCase();\n }\n\n const toml = `\n[[tests]]\nname = \"runs without throwing\"\ninput = \"hello\"\ndoesntThrow = true\n`;\n toml_test(toml, safeFunction, harness);\n});\n\n/**\n * Test: Name falls back to input\n * Verifies that when name is omitted, the test name is set to the input value.\n */\ndescribe(\"Test: Name falls back to input\", () => {\n let registeredName = \"\";\n const nameCapturingHarness: TestHarness = {\n describe: (_name, fn) => fn(),\n it: (name, fn) => {\n registeredName = name;\n // Don't actually run fn, just capture the name\n },\n equal: assert.deepStrictEqual,\n };\n\n const toml = `\n[[tests]]\ninput = \"hello world\"\noutput = \"hello world\"\n`;\n toml_test(toml, (input) => input, nameCapturingHarness);\n it(\"uses input as test name when name is omitted\", () => {\n assert.strictEqual(registeredName, \"hello world\");\n });\n});\n\n/**\n * Test: Schema validation errors\n * Verifies that schema validation errors are registered as failing tests.\n */\ndescribe(\"Test: Schema validation errors\", () => {\n // A test with neither output, throws, nor doesntThrow\n const invalidToml = `\n[[tests]]\nname = \"missing assertion\"\ninput = \"hello\"\n`;\n\n let registeredTests: Array<{ name: string; fn: () => void | Promise<void> }> =\n [];\n const capturingHarness: TestHarness = {\n describe: (_name, fn) => fn(),\n it: (name, fn) => {\n registeredTests.push({ name, fn });\n },\n equal: assert.deepStrictEqual,\n };\n\n toml_test(invalidToml, (input) => input, capturingHarness);\n\n it(\"registers a failing test for schema errors\", () => {\n assert.ok(registeredTests.length > 0);\n const firstTest = registeredTests[0];\n assert.ok(firstTest);\n assert.throws(() => firstTest.fn(), /output.*throws.*doesntThrow/i);\n });\n});\n\n/**\n * Test: Async test function\n * Verifies that async test functions work correctly.\n */\ndescribe(\"Test: Async test function\", () => {\n async function asyncUppercase(\n input: TomlValue,\n _options: any,\n ): Promise<string> {\n return (input as string).toUpperCase();\n }\n\n const toml = `\n[[tests]]\nname = \"async uppercase\"\ninput = \"hello\"\noutput = \"HELLO\"\n`;\n toml_test(toml, asyncUppercase, harness);\n});\n\n/**\n * Test: Numeric input\n * Verifies that numeric inputs are passed through correctly.\n */\ndescribe(\"Test: Numeric input\", () => {\n function doubleIt(input: TomlValue, _options: any): number {\n return (input as number) * 2;\n }\n\n const toml = `\n[[tests]]\nname = \"doubles an integer\"\ninput = 21\noutput = 42\n\n[[tests]]\nname = \"doubles a float\"\ninput = 1.5\noutput = 3.0\n`;\n toml_test(toml, doubleIt, harness);\n});\n\n/**\n * Test: Boolean input\n * Verifies that boolean inputs are passed through correctly.\n */\ndescribe(\"Test: Boolean input\", () => {\n function negate(input: TomlValue, _options: any): boolean {\n return !(input as boolean);\n }\n\n const toml = `\n[[tests]]\nname = \"negates true\"\ninput = true\noutput = false\n\n[[tests]]\nname = \"negates false\"\ninput = false\noutput = true\n`;\n toml_test(toml, negate, harness);\n});\n\n/**\n * Test: Object input\n * Verifies that inline table inputs are passed through correctly.\n */\ndescribe(\"Test: Object input\", () => {\n function greet(input: TomlValue, _options: any): string {\n const obj = input as { name: string; greeting: string };\n return `${obj.greeting}, ${obj.name}!`;\n }\n\n const toml = `\n[[tests]]\nname = \"greets from object input\"\ninput = { name = \"Alice\", greeting = \"Hello\" }\noutput = \"Hello, Alice!\"\n`;\n toml_test(toml, greet, harness);\n});\n\n/**\n * Test: Array input\n * Verifies that array inputs are passed through correctly.\n */\ndescribe(\"Test: Array input\", () => {\n function sum(input: TomlValue, _options: any): number {\n return (input as number[]).reduce((a, b) => a + b, 0);\n }\n\n const toml = `\n[[tests]]\nname = \"sums an array of numbers\"\ninput = [1, 2, 3, 4]\noutput = 10\n`;\n toml_test(toml, sum, harness);\n});\n\n/**\n * Test: Name fallback with non-string input\n * Verifies that when name is omitted and input is not a string, String(input) is used.\n */\ndescribe(\"Test: Name fallback with non-string input\", () => {\n let registeredName = \"\";\n const nameCapturingHarness: TestHarness = {\n describe: (_name, fn) => fn(),\n it: (name, _fn) => {\n registeredName = name;\n },\n equal: assert.deepStrictEqual,\n };\n\n const toml = `\n[[tests]]\ninput = 42\noutput = 42\n`;\n toml_test(toml, (input) => input, nameCapturingHarness);\n it(\"uses String(input) as test name for non-string input\", () => {\n assert.strictEqual(registeredName, \"42\");\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/tsconfig.json
ADDED