@yamf/test 0.1.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/README.md ADDED
@@ -0,0 +1,428 @@
1
+ # @yamf/test
2
+
3
+ A minimal, zero-dependency testing library with polymorphic async/await and simultaneous assertion reports.
4
+
5
+ [![Node](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen)]()
6
+ [![License](https://img.shields.io/badge/license-MIT-blue)]()
7
+
8
+ ## Features
9
+
10
+ ✨ **Zero Dependencies** - Pure Node.js implementation
11
+ 🔄 **Automatic Async Detection** - Just add `await` when needed
12
+ 📊 **Multi-Assertion Reports** - All failures reported simultaneously
13
+ 🏃 **Sequential Test Execution** - Predictable, ordered test runs
14
+ 🎯 **Solo/Mute Flags** - Focus on specific tests or skip others
15
+ 📦 **Suite Support** - Organize tests into logical groups
16
+ 🎨 **Colorized Output** - Clear pass/fail visualization
17
+
18
+ ## Installation
19
+
20
+ ```sh
21
+ npm install @yamf/test
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```javascript
27
+ import { assert, runTests } from '@yamf/test'
28
+
29
+ function testAddition() {
30
+ assert(2 + 2, n => n === 4)
31
+ }
32
+
33
+ async function testAsyncFetch() {
34
+ await assert(
35
+ async () => await fetchData(),
36
+ data => data.status === 'ok'
37
+ )
38
+ }
39
+
40
+ runTests({ testAddition, testAsyncFetch })
41
+ ```
42
+
43
+ ## Core Assertions
44
+
45
+ ### `assert(value, ...assertFns)`
46
+
47
+ Assert that a value or function result passes all assertion functions.
48
+
49
+ ```javascript
50
+ // Direct value
51
+ assert(5, n => n > 0, n => n < 10)
52
+
53
+ // Function result
54
+ assert(() => getValue(), n => n === 42)
55
+
56
+ // Multiple conditions
57
+ const user = { name: 'Alice', age: 30 }
58
+ assert(user,
59
+ u => u.name === 'Alice',
60
+ u => u.age > 18
61
+ )
62
+
63
+ // Async (auto-detected - just add await)
64
+ await assert(Promise.resolve(10), n => n === 10)
65
+ await assert(async () => await fetchData(), d => d.status === 'ok')
66
+ await assert(() => fetchData(), d => d.status === 'ok') // Promise-returning
67
+ ```
68
+
69
+ ### `assertErr(error, ...assertFns)`
70
+
71
+ Assert that a function throws an error matching all assertion functions.
72
+
73
+ ```javascript
74
+ // Direct error
75
+ assertErr(new Error('test'), err => err.message === 'test')
76
+
77
+ // Throwing function
78
+ assertErr(
79
+ () => { throw new Error('expected') },
80
+ err => err.message === 'expected',
81
+ err => err instanceof Error
82
+ )
83
+
84
+ // Async throwing (auto-detected)
85
+ await assertErr(
86
+ async () => { throw new Error('async failure') },
87
+ err => err.message === 'async failure'
88
+ )
89
+
90
+ // Promise rejection
91
+ await assertErr(
92
+ () => Promise.reject(new Error('rejected')),
93
+ err => err.message === 'rejected'
94
+ )
95
+ ```
96
+
97
+ ## Array Assertions
98
+
99
+ ### `assertEach(values, ...assertFns)`
100
+
101
+ Assert that **each value** in an array passes **all** assertion functions.
102
+
103
+ ```javascript
104
+ // All values must pass all assertions
105
+ assertEach([1, 2, 3, 4, 5],
106
+ n => n > 0,
107
+ n => n < 10
108
+ )
109
+
110
+ // Async values
111
+ await assertEach([promise1, promise2, promise3],
112
+ r => r.status === 'ok',
113
+ r => r.code === 200
114
+ )
115
+ ```
116
+
117
+ ### `assertSequence(values, ...assertFns)`
118
+
119
+ Assert that **each value** passes its **corresponding** assertion function.
120
+
121
+ ```javascript
122
+ // 1:1 mapping of values to assertions
123
+ assertSequence(['first', 'second', 'third'],
124
+ v => v === 'first',
125
+ v => v === 'second',
126
+ v => v === 'third'
127
+ )
128
+
129
+ // Async sequence
130
+ await assertSequence([promise1, promise2, promise3],
131
+ n => n === 10,
132
+ n => n === 20,
133
+ n => n === 30
134
+ )
135
+ ```
136
+
137
+ ### `assertErrEach(errors, ...assertFns)`
138
+
139
+ Assert that **each error** in an array passes **all** assertion functions.
140
+
141
+ ```javascript
142
+ // Error objects
143
+ assertErrEach([
144
+ new Error('Connection failed'),
145
+ new Error('Timeout occurred')
146
+ ],
147
+ err => err instanceof Error,
148
+ err => err.message.length > 0
149
+ )
150
+
151
+ // Throwing functions
152
+ assertErrEach([
153
+ () => { throw new Error('error1') },
154
+ () => { throw new Error('error2') }
155
+ ],
156
+ err => err instanceof Error,
157
+ err => err.message.includes('error')
158
+ )
159
+ ```
160
+
161
+ ### `assertErrSequence(errors, ...assertFns)`
162
+
163
+ Assert that **each error** passes its **corresponding** assertion function.
164
+
165
+ ```javascript
166
+ assertErrSequence([
167
+ () => { throw new Error('Step 1 failed') },
168
+ () => { throw new Error('Step 2 failed') }
169
+ ],
170
+ err => err.message.includes('Step 1'),
171
+ err => err.message.includes('Step 2')
172
+ )
173
+ ```
174
+
175
+ ## Test Runners
176
+
177
+ ### `runTests(tests)`
178
+
179
+ Run tests directly without organizing into suites.
180
+
181
+ ```javascript
182
+ import { runTests } from '@yamf/test'
183
+
184
+ const tests = {
185
+ testOne() { assert(1, n => n === 1) },
186
+ testTwo() { assert(2, n => n === 2) }
187
+ }
188
+
189
+ runTests(tests)
190
+ .then(() => process.exit(0))
191
+ .catch(err => process.exit(err.code || 1))
192
+ ```
193
+
194
+ ### `TestRunner` Class
195
+
196
+ Organize tests into suites for better organization.
197
+
198
+ ```javascript
199
+ import { TestRunner } from '@yamf/test'
200
+
201
+ const runner = new TestRunner()
202
+
203
+ // Add individual suite
204
+ runner.addSuite('math', {
205
+ testAddition() { assert(2 + 2, n => n === 4) },
206
+ testSubtraction() { assert(5 - 3, n => n === 2) }
207
+ })
208
+
209
+ // Add multiple suites
210
+ runner.addSuites({
211
+ strings: {
212
+ testConcat() { assert('a' + 'b', s => s === 'ab') }
213
+ },
214
+ arrays: {
215
+ testPush() {
216
+ const arr = [1, 2]
217
+ arr.push(3)
218
+ assert(arr.length, n => n === 3)
219
+ }
220
+ }
221
+ })
222
+
223
+ runner.run()
224
+ ```
225
+
226
+ ## Test Helpers
227
+
228
+ ### `sleep(ms)`
229
+
230
+ Simple promise-based delay.
231
+
232
+ ```javascript
233
+ import { sleep } from '@yamf/test'
234
+
235
+ async function testWithDelay() {
236
+ await sleep(100) // Wait 100ms
237
+ assert(true, v => v === true)
238
+ }
239
+ ```
240
+
241
+ ### `terminateAfter(...servers, testFn)`
242
+
243
+ Run tests with automatic server cleanup. Terminates all servers after the test completes (success or failure).
244
+
245
+ ```javascript
246
+ import { terminateAfter } from '@yamf/test'
247
+ import { registryServer, createService } from '@yamf/core'
248
+
249
+ async function testServices() {
250
+ await terminateAfter(
251
+ registryServer(),
252
+ createService(function myService(p) { return { ok: true } }),
253
+ async (registry, service) => {
254
+ const result = await callService('myService', {})
255
+ assert(result.ok, v => v === true)
256
+ }
257
+ )
258
+ }
259
+ ```
260
+
261
+ ### `withEnv(envVars, testFn)`
262
+
263
+ Run a test with temporary environment variables that are restored after.
264
+
265
+ ```javascript
266
+ import { withEnv } from '@yamf/test'
267
+
268
+ async function testWithEnv() {
269
+ await withEnv(
270
+ { NODE_ENV: 'test', API_KEY: 'secret' },
271
+ async () => {
272
+ assert(process.env.NODE_ENV, v => v === 'test')
273
+ assert(process.env.API_KEY, v => v === 'secret')
274
+ }
275
+ )
276
+ // Original env vars restored here
277
+ }
278
+ ```
279
+
280
+ ## Solo and Mute Flags
281
+
282
+ Focus on specific tests or skip others during development.
283
+
284
+ ```javascript
285
+ function testNormal() {
286
+ assert(1, n => n === 1)
287
+ }
288
+
289
+ // Only run this test (and others marked solo)
290
+ function testFocused() {
291
+ assert(2, n => n === 2)
292
+ }
293
+ testFocused.solo = true
294
+
295
+ // Skip this test
296
+ function testSkipped() {
297
+ assert(3, n => n === 3)
298
+ }
299
+ testSkipped.mute = true
300
+
301
+ runTests({ testNormal, testFocused, testSkipped })
302
+ // Only testFocused will run
303
+ ```
304
+
305
+ ## Error Types
306
+
307
+ ### `AssertionFailure`
308
+
309
+ Thrown when an assertion fails with details about the failure.
310
+
311
+ ### `MultiAssertionFailure`
312
+
313
+ Thrown when multiple assertions fail in a single test, containing all failure details.
314
+
315
+ ## Best Practices
316
+
317
+ ### Name Test Functions
318
+
319
+ Named functions provide clear test output:
320
+
321
+ ```javascript
322
+ // Good - clear output: "✔ testUserValidation"
323
+ function testUserValidation() {
324
+ assert(user.isValid, v => v === true)
325
+ }
326
+
327
+ // Avoid - unclear output
328
+ const test1 = () => assert(user.isValid, v => v === true)
329
+ ```
330
+
331
+ ### Export Test Functions
332
+
333
+ Export tests for CLI usage and composability:
334
+
335
+ ```javascript
336
+ // user-tests.js
337
+ export function testUserCreation() { /* ... */ }
338
+ export function testUserDeletion() { /* ... */ }
339
+
340
+ // run-all.js
341
+ import * as userTests from './user-tests.js'
342
+ import * as authTests from './auth-tests.js'
343
+
344
+ runTests({ ...userTests, ...authTests })
345
+ ```
346
+
347
+ ### Use TODO for Pending Tests
348
+
349
+ Mark incomplete tests with TODO in the name:
350
+
351
+ ```javascript
352
+ function TODOtestNewFeature() {
353
+ throw new Error('TODO: implement this test')
354
+ }
355
+ // Test will be counted but not run
356
+ ```
357
+
358
+ ## Example Output
359
+
360
+ ```
361
+ ✔ testDirectValue (1ms)
362
+ ✔ testFunctionResult (0ms)
363
+ ✔ testAsyncFunction (15ms)
364
+ ✘ testFailingAssertion
365
+
366
+ ----- Testing Complete -----
367
+ ✔ ✔ ✔ Success Report ✔ ✔ ✔
368
+
369
+ testDirectValue
370
+ testFunctionResult
371
+ testAsyncFunction
372
+
373
+ ✘ ✘ ✘ Failure Report ✘ ✘ ✘
374
+
375
+ testFailingAssertion
376
+
377
+ testFailingAssertion failed with error: AssertionFailure: ...
378
+
379
+ ----- Test Overview -----
380
+ ℹ tests 4
381
+ ℹ pass 3
382
+ ℹ fail 1
383
+ ℹ skipped 0
384
+ ℹ todo 0
385
+ ℹ duration_ms 42
386
+ ```
387
+
388
+ ## API Reference
389
+
390
+ ### Assertions
391
+
392
+ | Function | Description |
393
+ |----------|-------------|
394
+ | `assert(value, ...fns)` | Assert value passes all assertion functions |
395
+ | `assertErr(error, ...fns)` | Assert error matches all assertion functions |
396
+ | `assertEach(values, ...fns)` | Assert each value passes all assertions |
397
+ | `assertSequence(values, ...fns)` | Assert each value passes corresponding assertion |
398
+ | `assertErrEach(errors, ...fns)` | Assert each error passes all assertions |
399
+ | `assertErrSequence(errors, ...fns)` | Assert each error passes corresponding assertion |
400
+
401
+ ### Runners
402
+
403
+ | Function | Description |
404
+ |----------|-------------|
405
+ | `runTests(tests)` | Run tests directly |
406
+ | `TestRunner` | Class for organizing tests into suites |
407
+ | `runTestFnsSequentially(fns)` | Low-level sequential test execution |
408
+ | `mergeAllTestsSafely(...tests)` | Merge test objects with duplicate detection |
409
+
410
+ ### Helpers
411
+
412
+ | Function | Description |
413
+ |----------|-------------|
414
+ | `sleep(ms)` | Promise-based delay |
415
+ | `terminateAfter(...servers, fn)` | Run test with server cleanup |
416
+ | `withEnv(vars, fn)` | Run test with temporary env vars |
417
+
418
+ ### Error Types
419
+
420
+ | Type | Description |
421
+ |------|-------------|
422
+ | `AssertionFailure` | Single assertion failure |
423
+ | `AssertionFailureDetail` | Detailed failure information |
424
+ | `MultiAssertionFailure` | Multiple assertion failures |
425
+
426
+ ## License
427
+
428
+ MIT
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@yamf/test",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Functional testing module - with polymorphic async/await and simultaneous assertion reports",
6
+ "main": "src/index.js",
7
+ "homepage": "https://github.com/mcbrumagin/yamf",
8
+ "exports": {
9
+ ".": "./src/index.js",
10
+ "./validators": "./src/validators.js",
11
+ "./utils": "./src/utils.js"
12
+ },
13
+ "engines": {
14
+ "node": ">=20.0.0"
15
+ },
16
+ "keywords": [
17
+ "testing",
18
+ "async/await",
19
+ "assert",
20
+ "zero-dependency"
21
+ ],
22
+ "files": [
23
+ "src",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "license": "MIT",
28
+ "author": "Matthew C Brumagin",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/mcbrumagin/yamf"
32
+ },
33
+ "devDependencies": {
34
+ "@yamf/core": "link:../core"
35
+ },
36
+ "scripts": {
37
+ "test": "./tests/test.sh"
38
+ }
39
+ }