kempo-testing-framework 1.4.14 → 1.5.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 CHANGED
@@ -195,6 +195,21 @@ export default {
195
195
  ```
196
196
 
197
197
 
198
+ ### How `pass` and `fail` Behave
199
+
200
+ - `pass(message)` marks the test as passed and `fail(message)` marks it as failed. A test that throws also fails.
201
+ - **A failure is final.** Once `fail()` has been called, a later `pass()` is ignored, and the ignored call is written to the test's logs so the contradiction is visible.
202
+ - `fail()` does not stop the test. Stop it with `return fail(...)` from the test function itself, or by throwing.
203
+
204
+ Be careful with `return fail(...)` inside a nested callback such as a helper, a promise executor or a `.then`. It only leaves that callback, so the test carries on to whatever follows. Throw an error there instead, which fails the test and stops it:
205
+
206
+ ```javascript
207
+ await withTempDir(async (dir) => {
208
+ if(!(await exists(dir))) throw new Error('directory was not created');
209
+ });
210
+ pass('directory created');
211
+ ```
212
+
198
213
  ## Running Tests
199
214
 
200
215
  ### CLI (Command-Line Interface)
package/llms.txt CHANGED
@@ -28,7 +28,8 @@ export default {
28
28
  'test name': ({ pass, fail, log }) => {
29
29
  // log(msg) — emit a log message
30
30
  // pass(msg) — mark test passed
31
- // fail(msg) — mark test failed
31
+ // fail(msg) — mark test failed. Final: a later pass() is ignored. Does not stop the test;
32
+ // return it from the test body, or throw, to stop (a throw also fails the test)
32
33
  pass('ok');
33
34
  },
34
35
  'async test': async ({ pass, fail, log }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo-testing-framework",
3
- "version": "1.4.14",
3
+ "version": "1.5.0",
4
4
  "description": "The Kempo Testing Framework is a simple testing framework built on the principle that code intended to be ran in the browser should be tested in the browser, code intended to be ran in Node should be tested in Node, and code intended to be ran in both should be tested in both. No mocking, no complexity—just testing.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/src/runTests.js CHANGED
@@ -63,6 +63,15 @@ export default async ({
63
63
  tests[name]({
64
64
  log,
65
65
  pass: message => {
66
+ /*
67
+ A failure is final. `return fail()` from inside a nested callback only leaves that callback,
68
+ so the test carries on to its own trailing pass(); letting that overwrite the failure reported
69
+ success for a test that had failed. The call is still logged so the contradiction is visible.
70
+ */
71
+ if(result.passed === false){
72
+ log(`pass() ignored because the test already failed: ${message}`, 'log', 2);
73
+ return;
74
+ }
66
75
  result.passed = true;
67
76
  log(message, 'pass', 2); // was 3; make visible at NORMAL
68
77
  },
@@ -11,6 +11,8 @@ const mkSuite = () => ({
11
11
  }
12
12
  });
13
13
 
14
+ const runOne = async (test) => (await runTests({ default: { subject: test } }, false, 0)).tests.subject;
15
+
14
16
  export default {
15
17
  'runs all tests and records logs': async ({ pass, fail, log }) => {
16
18
  try {
@@ -52,5 +54,65 @@ export default {
52
54
  } catch (e) {
53
55
  fail(e.stack || String(e));
54
56
  }
57
+ },
58
+ 'a later pass() cannot overwrite an earlier fail()': async ({ pass, fail }) => {
59
+ const result = await runOne(({ pass, fail }) => {
60
+ fail('the check broke');
61
+ pass('but then it passed');
62
+ });
63
+ if(result.passed !== false) return fail(`Expected the test to fail, got passed=${result.passed}`);
64
+ if(result.logs.some(l => l.type === 'pass')) return fail('An ignored pass() must not be logged as a pass');
65
+ if(!result.logs.some(l => l.type === 'fail' && l.message === 'the check broke')) return fail('The failure message was not recorded');
66
+ if(!result.logs.some(l => l.message.includes('pass() ignored') && l.message.includes('but then it passed'))) return fail('The ignored pass() was not logged');
67
+ pass('fail() is final');
68
+ },
69
+ 'return fail() from a nested callback is not masked by a trailing pass()': async ({ pass, fail }) => {
70
+ /*
71
+ The real-world shape: a helper takes a callback, the callback does `return fail(...)`, which only
72
+ leaves the callback, and the test then reaches its own pass().
73
+ */
74
+ const result = await runOne(async ({ pass, fail }) => {
75
+ await (async () => { return fail('inner check failed'); })();
76
+ pass('reached the end');
77
+ });
78
+ if(result.passed !== false) return fail(`A failure inside a nested callback was reported as passed=${result.passed}`);
79
+ pass('nested fail() is not masked');
80
+ },
81
+ 'pass() followed by fail() is a failure': async ({ pass, fail }) => {
82
+ const result = await runOne(({ pass, fail }) => {
83
+ pass('looked fine');
84
+ fail('then it broke');
85
+ });
86
+ if(result.passed !== false) return fail(`Expected the test to fail, got passed=${result.passed}`);
87
+ pass('fail() after pass() fails the test');
88
+ },
89
+ 'an exception after pass() is a failure': async ({ pass, fail }) => {
90
+ const result = await runOne(({ pass }) => {
91
+ pass('looked fine');
92
+ throw new Error('then it threw');
93
+ });
94
+ if(result.passed !== false) return fail(`Expected the test to fail, got passed=${result.passed}`);
95
+ pass('a throw after pass() fails the test');
96
+ },
97
+ 'a test that only passes still passes, including repeated pass() calls': async ({ pass, fail }) => {
98
+ const result = await runOne(({ pass, log }) => {
99
+ log('working');
100
+ pass('first');
101
+ pass('second');
102
+ });
103
+ if(result.passed !== true) return fail(`Expected the test to pass, got passed=${result.passed}`);
104
+ if(result.logs.filter(l => l.type === 'pass').length !== 2) return fail('Both pass() calls should be logged as passes');
105
+ pass('unaffected');
106
+ },
107
+ 'a failing test in a suite does not affect its neighbours': async ({ pass, fail }) => {
108
+ const res = await runTests({
109
+ default: {
110
+ 'bad': ({ pass, fail }) => { fail('broke'); pass('masked'); },
111
+ 'good': ({ pass }) => { pass('fine'); }
112
+ }
113
+ }, false, 0);
114
+ if(res.tests.bad.passed !== false) return fail('bad should fail');
115
+ if(res.tests.good.passed !== true) return fail('good should still pass');
116
+ pass('failures are isolated per test');
55
117
  }
56
118
  };