beartest-js 6.0.4 → 7.0.1
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/.prettierrc +6 -2
- package/README.md +14 -2
- package/beartest.js +130 -86
- package/cli.js +22 -0
- package/index.d.ts +92 -16
- package/package.json +9 -7
- package/testrunner.js +0 -23
package/.prettierrc
CHANGED
package/README.md
CHANGED
|
@@ -22,7 +22,19 @@ The Beartest test runner uses common js to load files.
|
|
|
22
22
|
|
|
23
23
|
### Usage
|
|
24
24
|
|
|
25
|
-
_Beartest_
|
|
25
|
+
_Beartest_ exports a `test` function that provides the following API:
|
|
26
|
+
- `test(name, fn)` - Define a test
|
|
27
|
+
- `test.describe(name, fn)` - Group tests into a suite
|
|
28
|
+
- `test.before(fn)` - Run before all tests in a suite
|
|
29
|
+
- `test.beforeEach(fn)` - Run before each test in a suite
|
|
30
|
+
- `test.after(fn)` - Run after all tests in a suite
|
|
31
|
+
- `test.afterEach(fn)` - Run after each test in a suite
|
|
32
|
+
- `test.skip(name, fn)` - Skip a test
|
|
33
|
+
- `test.only(name, fn)` - Run only this test
|
|
34
|
+
- `test.describe.skip(name, fn)` - Skip a suite
|
|
35
|
+
- `test.describe.only(name, fn)` - Run only this suite
|
|
36
|
+
|
|
37
|
+
All provided functions work in a similar way as the corresponding functions in Jest.
|
|
26
38
|
|
|
27
39
|
### Example
|
|
28
40
|
|
|
@@ -43,7 +55,7 @@ test.describe("Math Testing", () => {
|
|
|
43
55
|
|
|
44
56
|
### Running Tests
|
|
45
57
|
|
|
46
|
-
Additionally, a very basic test runner is included. This test runner accepts a glob pattern as a command line argument. The test runner can be invoked with `yarn beartest "glob-pattern"`. By default, it will look for `**/*.test
|
|
58
|
+
Additionally, a very basic test runner is included. This test runner accepts a glob pattern as a command line argument. The test runner can be invoked with `yarn beartest "glob-pattern"`. By default, it will look for `**/*.test.*`.
|
|
47
59
|
|
|
48
60
|
Suggested package script:
|
|
49
61
|
|
package/beartest.js
CHANGED
|
@@ -1,100 +1,144 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
1
|
+
const path = require('node:path')
|
|
3
2
|
async function RunSerially(fnArray) {
|
|
4
|
-
for (const fn of fnArray)
|
|
5
|
-
await fn();
|
|
6
|
-
}
|
|
3
|
+
for (const fn of fnArray) await fn()
|
|
7
4
|
}
|
|
8
|
-
const suiteStack = [];
|
|
9
|
-
let testRunPromise = Promise.resolve();
|
|
10
|
-
const top = () => (suiteStack.length ? suiteStack[suiteStack.length - 1] : null);
|
|
11
|
-
const topSafe = () => (suiteStack.length ? suiteStack[suiteStack.length - 1] : makeSuite(""));
|
|
12
5
|
|
|
13
|
-
|
|
14
|
-
const
|
|
6
|
+
async function* runTestSuite(options = {}) {
|
|
7
|
+
const suite = top()
|
|
8
|
+
const tests = (suite.tests.some((t) => t.only) ? suite.tests.filter((t) => t.only) : suite.tests).filter(
|
|
9
|
+
(t) => !options.only?.length || options.only[0] === t.name
|
|
10
|
+
)
|
|
15
11
|
try {
|
|
16
|
-
await suite.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
await RunSerially(suite.before)
|
|
13
|
+
for (let index = 0; index < tests.length; index++) {
|
|
14
|
+
const test = tests[index]
|
|
15
|
+
let startTime
|
|
16
|
+
|
|
17
|
+
const testDetails = { name: test.name, nesting: suiteStack.length, testNumber: index, skip: test.skip }
|
|
18
|
+
const startEvent = { type: 'test:start', data: { name: test.name, nesting: suiteStack.length, type: test.type } }
|
|
19
|
+
const pass = (duration) => ({
|
|
20
|
+
type: 'test:pass',
|
|
21
|
+
data: { details: { duration_ms: duration, type: test.type }, ...testDetails }
|
|
22
|
+
})
|
|
23
|
+
if (test.skip) {
|
|
24
|
+
yield startEvent
|
|
25
|
+
yield pass(0)
|
|
26
|
+
} else {
|
|
27
|
+
try {
|
|
28
|
+
await RunSerially(suite.beforeEach)
|
|
29
|
+
yield startEvent
|
|
30
|
+
startTime = Date.now()
|
|
31
|
+
|
|
32
|
+
if (test.type === 'suite') {
|
|
33
|
+
suiteStack.push({ before: [], beforeEach: [], after: [], afterEach: [], tests: [], name: test.name })
|
|
34
|
+
try {
|
|
35
|
+
await test.fn()
|
|
36
|
+
for await (const event of runTestSuite({ only: options.only ? options.only.slice(1) : undefined })) {
|
|
37
|
+
yield event
|
|
38
|
+
}
|
|
39
|
+
} finally {
|
|
40
|
+
suiteStack.pop()
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
await test.fn()
|
|
44
|
+
}
|
|
45
|
+
yield pass(Date.now() - startTime)
|
|
46
|
+
} catch (e) {
|
|
47
|
+
const error = new Error('[TEST FAILURE]', { cause: e })
|
|
48
|
+
yield {
|
|
49
|
+
type: 'test:fail',
|
|
50
|
+
data: {
|
|
51
|
+
details: { duration_ms: startTime ? Date.now() - startTime : NaN, type: test.type, error },
|
|
52
|
+
...testDetails
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw e
|
|
56
|
+
} finally {
|
|
57
|
+
await RunSerially(suite.afterEach)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
23
61
|
} finally {
|
|
24
|
-
await suite.
|
|
62
|
+
await RunSerially(suite.after)
|
|
25
63
|
}
|
|
26
|
-
}
|
|
64
|
+
}
|
|
27
65
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
await suite.beforeAll();
|
|
34
|
-
await RunSerially(tests);
|
|
35
|
-
} finally {
|
|
36
|
-
await suite.afterAll();
|
|
66
|
+
const suiteStack = []
|
|
67
|
+
|
|
68
|
+
function top() {
|
|
69
|
+
if (!suiteStack.length) {
|
|
70
|
+
suiteStack.push({ before: [], beforeEach: [], after: [], afterEach: [], tests: [], name: '' })
|
|
37
71
|
}
|
|
72
|
+
return suiteStack[suiteStack.length - 1]
|
|
38
73
|
}
|
|
39
74
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
const describe = (name, fn) => top().tests.push({ name: name, type: 'suite', fn, skip: false, only: false })
|
|
76
|
+
describe.skip = (name, fn) => top().tests.push({ name: name, type: 'suite', fn, skip: true, only: false })
|
|
77
|
+
describe.only = (name, fn) => top().tests.push({ name: name, type: 'suite', fn, skip: false, only: true })
|
|
78
|
+
const it = (name, fn) => top().tests.push({ name: name, type: 'test', fn, skip: false, only: false })
|
|
79
|
+
it.only = (name, fn) => top().tests.push({ name: name, type: 'test', fn, skip: false, only: true })
|
|
80
|
+
it.skip = (name, fn) => top().tests.push({ name: name, type: 'test', fn, skip: true, only: false })
|
|
81
|
+
const before = (fn) => top().before.push(fn)
|
|
82
|
+
const beforeEach = (fn) => top().beforeEach.push(fn)
|
|
83
|
+
const after = (fn) => top().after.push(fn)
|
|
84
|
+
const afterEach = (fn) => top().afterEach.push(fn)
|
|
85
|
+
|
|
86
|
+
async function* runTests(options = {}) {
|
|
87
|
+
let index = 0
|
|
88
|
+
for await (const file of options.files) {
|
|
89
|
+
const name = file
|
|
90
|
+
const suiteDetails = { name: name, nesting: 0, testNumber: index, skip: false }
|
|
91
|
+
suiteStack.push({ before: [], beforeEach: [], after: [], afterEach: [], tests: [], name })
|
|
92
|
+
let suiteStart
|
|
93
|
+
try {
|
|
94
|
+
require(file)
|
|
95
|
+
yield { type: 'test:start', data: { name: name, nesting: 0, type: 'suite' } }
|
|
96
|
+
suiteStart = Date.now()
|
|
97
|
+
for await (let event of runTestSuite({ only: options.only })) {
|
|
98
|
+
yield event
|
|
99
|
+
}
|
|
100
|
+
yield {
|
|
101
|
+
type: 'test:pass',
|
|
102
|
+
data: { details: { duration_ms: Date.now() - suiteStart, type: 'suite' }, ...suiteDetails }
|
|
103
|
+
}
|
|
104
|
+
} catch (e) {
|
|
105
|
+
yield {
|
|
106
|
+
type: 'test:fail',
|
|
107
|
+
data: {
|
|
108
|
+
details: { duration_ms: suiteStart ? Date.now() - suiteStart : NaN, type: 'suite', error: e },
|
|
109
|
+
...suiteDetails
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
throw e
|
|
113
|
+
} finally {
|
|
114
|
+
suiteStack.pop()
|
|
115
|
+
index++
|
|
116
|
+
}
|
|
76
117
|
}
|
|
77
|
-
return self;
|
|
78
118
|
}
|
|
79
119
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
120
|
+
if (!suiteStack.length) {
|
|
121
|
+
new Promise((resolve) => setTimeout(resolve, 0)).then(async () => {
|
|
122
|
+
if (suiteStack.length) {
|
|
123
|
+
for await (let event of runTestSuite()) {
|
|
124
|
+
const prefix = ' '.repeat(event.data.nesting)
|
|
125
|
+
if (event.type === 'test:start' && event.data.type === 'suite') {
|
|
126
|
+
if (path.isAbsolute(event.data.name)) {
|
|
127
|
+
console.log(
|
|
128
|
+
`\x1b[36m${prefix}${path.parse(event.data.name).name} (${path.relative('./', event.data.name)})\x1b[0m`
|
|
129
|
+
)
|
|
130
|
+
} else {
|
|
131
|
+
console.log(`\x1b[36m${prefix}${event.data.name}\x1b[0m`)
|
|
132
|
+
}
|
|
133
|
+
} else if (event.type === 'test:pass' && event.data.details.type === 'test' && !event.data.skip) {
|
|
134
|
+
console.log(`\x1b[32m\n${prefix}✓\x1b[0m\x1b[90m ${event.data.name}\n\x1b[0m`)
|
|
135
|
+
} else if (event.type === 'test:fail' && event.data.details.type === 'test') {
|
|
136
|
+
console.log(`\x1b[31m\n${prefix}✗ ${event.data.name}\n\x1b[0m`)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
process.exit(0)
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
}
|
|
90
143
|
|
|
91
|
-
module.exports = {
|
|
92
|
-
test: Object.assign(it, {
|
|
93
|
-
describe,
|
|
94
|
-
before,
|
|
95
|
-
after,
|
|
96
|
-
beforeEach,
|
|
97
|
-
afterEach,
|
|
98
|
-
}),
|
|
99
|
-
runner: { waitForTests: () => testRunPromise },
|
|
100
|
-
};
|
|
144
|
+
module.exports = { test: Object.assign(it, { describe, before, beforeEach, after, afterEach }), run: runTests }
|
package/cli.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { run } = require('./beartest')
|
|
3
|
+
const fs = require('node:fs')
|
|
4
|
+
|
|
5
|
+
async function cli() {
|
|
6
|
+
const globStr = process.argv[2] || '**/*.test.*'
|
|
7
|
+
|
|
8
|
+
const files = await Array.fromAsync(fs.promises.glob(globStr))
|
|
9
|
+
|
|
10
|
+
for await (let event of run({ files: files })) {
|
|
11
|
+
const prefix = ' '.repeat(event.data.nesting)
|
|
12
|
+
if (event.type === 'test:start' && event.data.type === 'suite') {
|
|
13
|
+
process.stdout.write(`\x1b[36m${prefix}${event.data.name} \n\x1b[0m`)
|
|
14
|
+
} else if (event.type === 'test:pass' && event.data.details.type === 'test' && !event.data.skip) {
|
|
15
|
+
process.stdout.write(`\x1b[32m${prefix}✓\x1b[0m\x1b[90m ${event.data.name}\n\x1b[0m`)
|
|
16
|
+
} else if (event.type === 'test:fail' && event.data.details.type === 'test') {
|
|
17
|
+
process.stdout.write(`\x1b[31m\n${prefix}✗ ${event.data.name} \n\n\x1b[0m`)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
cli()
|
package/index.d.ts
CHANGED
|
@@ -1,16 +1,92 @@
|
|
|
1
|
-
type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
1
|
+
export type TestConfig = {
|
|
2
|
+
name: string
|
|
3
|
+
skip: boolean
|
|
4
|
+
only: boolean
|
|
5
|
+
type: 'suite' | 'test'
|
|
6
|
+
fn: () => unknown
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface TestSuite {
|
|
10
|
+
before: (() => unknown)[]
|
|
11
|
+
beforeEach: (() => unknown)[]
|
|
12
|
+
after: (() => unknown)[]
|
|
13
|
+
afterEach: (() => unknown)[]
|
|
14
|
+
tests: TestConfig[]
|
|
15
|
+
name: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type TestEvent = TestStart | TestPass | TestFail
|
|
19
|
+
|
|
20
|
+
export interface TestStart {
|
|
21
|
+
type: 'test:start'
|
|
22
|
+
data: {
|
|
23
|
+
/** The test name. */
|
|
24
|
+
name: string
|
|
25
|
+
/** The nesting level of the test. */
|
|
26
|
+
nesting: number
|
|
27
|
+
type: 'suite' | 'test' | undefined
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TestPass {
|
|
32
|
+
type: 'test:pass'
|
|
33
|
+
data: {
|
|
34
|
+
details: {
|
|
35
|
+
/** The duration of the test in milliseconds. */
|
|
36
|
+
duration_ms: number
|
|
37
|
+
/** The type of the test, used to denote whether this is a suite. */
|
|
38
|
+
type: 'suite' | 'test' | undefined
|
|
39
|
+
}
|
|
40
|
+
/** The test name. */
|
|
41
|
+
name: string
|
|
42
|
+
/** The nesting level of the test. */
|
|
43
|
+
nesting: number
|
|
44
|
+
/** The ordinal number of the test. */
|
|
45
|
+
testNumber: number
|
|
46
|
+
/** Present if context.skip is called. */
|
|
47
|
+
skip: boolean
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TestFail {
|
|
52
|
+
type: 'test:fail'
|
|
53
|
+
data: {
|
|
54
|
+
/** Additional execution metadata. */
|
|
55
|
+
details: {
|
|
56
|
+
/** The duration of the test in milliseconds. */
|
|
57
|
+
duration_ms: number
|
|
58
|
+
/** An error wrapping the error thrown by the test. */
|
|
59
|
+
error: Error
|
|
60
|
+
/** The type of the test, used to denote whether this is a suite. */
|
|
61
|
+
type: 'suite' | 'test' | undefined
|
|
62
|
+
}
|
|
63
|
+
/** The test name. */
|
|
64
|
+
name: string
|
|
65
|
+
/** The nesting level of the test. */
|
|
66
|
+
nesting: number
|
|
67
|
+
/** The ordinal number of the test. */
|
|
68
|
+
testNumber: number
|
|
69
|
+
/** Present if context.skip is called. */
|
|
70
|
+
skip: boolean
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface TestFunction {
|
|
75
|
+
(name: string, fn: () => unknown): void
|
|
76
|
+
only(name: string, fn: () => unknown): void
|
|
77
|
+
skip(name: string, fn: () => unknown): void
|
|
78
|
+
describe: DescribeFunction
|
|
79
|
+
before(callback: () => unknown): void
|
|
80
|
+
beforeEach(callback: () => unknown): void
|
|
81
|
+
after(callback: () => unknown): void
|
|
82
|
+
afterEach(callback: () => unknown): void
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DescribeFunction {
|
|
86
|
+
(name: string, fn: () => unknown): void
|
|
87
|
+
only: (name: string, fn: () => unknown) => void
|
|
88
|
+
skip: (name: string, fn: () => unknown) => void
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const test: TestFunction
|
|
92
|
+
export const run: (options: { files: AsyncIterable<string> | Iterable<string> }) => AsyncGenerator<TestEvent>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beartest-js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.1",
|
|
4
4
|
"description": "Bear Bones Testing",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,21 +8,23 @@
|
|
|
8
8
|
},
|
|
9
9
|
"types": "./index.d.ts",
|
|
10
10
|
"main": "./index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"require": "./index.js",
|
|
14
|
+
"import": "./index.js"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
11
18
|
"bin": {
|
|
12
|
-
"beartest": "
|
|
19
|
+
"beartest": "cli.js"
|
|
13
20
|
},
|
|
14
21
|
"author": "Grant Colestock",
|
|
15
22
|
"license": "MIT",
|
|
16
23
|
"type": "commonjs",
|
|
17
|
-
"dependencies": {
|
|
18
|
-
"barecolor": "^1.0.1",
|
|
19
|
-
"tiny-glob": "^0.2.8"
|
|
20
|
-
},
|
|
21
24
|
"bugs": {
|
|
22
25
|
"url": "https://github.com/rubber-duck-software/beartest/issues"
|
|
23
26
|
},
|
|
24
27
|
"homepage": "https://github.com/rubber-duck-software/beartest#readme",
|
|
25
|
-
"devDependencies": {},
|
|
26
28
|
"scripts": {
|
|
27
29
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
28
30
|
}
|
package/testrunner.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const beartest = require("./beartest");
|
|
3
|
-
const glob = require("tiny-glob");
|
|
4
|
-
const rgb = require("barecolor");
|
|
5
|
-
const path = require("path");
|
|
6
|
-
|
|
7
|
-
async function runTests() {
|
|
8
|
-
try {
|
|
9
|
-
const globStr = process.argv[2] || "**/*.test.*";
|
|
10
|
-
const files = await glob(globStr, { absolute: true });
|
|
11
|
-
for (const file of files) {
|
|
12
|
-
rgb.blue(`${path.parse(file).name} (${path.relative("./", file)})\n`);
|
|
13
|
-
require(file);
|
|
14
|
-
await beartest.runner.waitForTests();
|
|
15
|
-
}
|
|
16
|
-
process.exit(0);
|
|
17
|
-
} catch (e) {
|
|
18
|
-
console.error(e);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
runTests();
|