borp 0.2.0 → 0.3.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/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +35 -0
- package/README.md +85 -0
- package/borp.js +59 -64
- package/fixtures/fails/test/wrong.test.js +6 -0
- package/fixtures/ts-cjs/package.json +3 -0
- package/fixtures/ts-cjs/test/add2.test.ts +7 -0
- package/{fixture → fixtures/ts-cjs}/tsconfig.json +2 -1
- package/fixtures/ts-esm/src/add.ts +4 -0
- package/fixtures/ts-esm/test/add2.test.ts +7 -0
- package/fixtures/ts-esm/tsconfig.json +24 -0
- package/lib/run.js +144 -0
- package/package.json +8 -2
- package/test/basic.test.js +47 -0
- package/test/cli.test.js +25 -0
- package/test/coverage.test.js +36 -0
- package/test/watch.test.js +107 -0
- /package/{fixture → fixtures/ts-cjs}/src/add.ts +0 -0
- /package/{fixture → fixtures/ts-cjs}/test/add.test.ts +0 -0
- /package/{fixture/test/add2.test.ts → fixtures/ts-esm/test/add.test.ts} +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
paths-ignore:
|
|
6
|
+
- 'docs/**'
|
|
7
|
+
- '*.md'
|
|
8
|
+
pull_request:
|
|
9
|
+
paths-ignore:
|
|
10
|
+
- 'docs/**'
|
|
11
|
+
- '*.md'
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
runs-on: ${{matrix.os}}
|
|
16
|
+
|
|
17
|
+
strategy:
|
|
18
|
+
matrix:
|
|
19
|
+
node-version: [18.x, 20.x, 21.x]
|
|
20
|
+
os: [ubuntu-latest, windows-latest]
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v3
|
|
23
|
+
|
|
24
|
+
- name: Use Node.js
|
|
25
|
+
uses: actions/setup-node@v2
|
|
26
|
+
with:
|
|
27
|
+
node-version: ${{ matrix.node-version }}
|
|
28
|
+
|
|
29
|
+
- name: Install
|
|
30
|
+
run: |
|
|
31
|
+
npm install
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: |
|
|
35
|
+
npm run test
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# borp
|
|
2
|
+
|
|
3
|
+
Borp is a typescript-aware runner for tests written using `node:test`.
|
|
4
|
+
It also support code coverage via [c8](http://npm.im/c8).
|
|
5
|
+
|
|
6
|
+
Borp is self-hosted, i.e. Borp runs its own tests.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm i borp --save-dev
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
borp --coverage
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Borp will autumatically run all tests files matching `*.test.{js|ts}`.
|
|
21
|
+
|
|
22
|
+
### Example project setup
|
|
23
|
+
|
|
24
|
+
As an example, consider having a `src/add.ts` file
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
export function add (x: number, y: number): number {
|
|
28
|
+
return x + y
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
and a `test/add.test.ts` file:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { test } from 'node:test'
|
|
36
|
+
import { add } from '../src/add.js'
|
|
37
|
+
import { strictEqual } from 'node:assert'
|
|
38
|
+
|
|
39
|
+
test('add', () => {
|
|
40
|
+
strictEqual(add(1, 2), 3)
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
and the following `tsconfig`:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
49
|
+
"compilerOptions": {
|
|
50
|
+
"outDir": "dist",
|
|
51
|
+
"sourceMap": true,
|
|
52
|
+
"target": "ES2022",
|
|
53
|
+
"module": "NodeNext",
|
|
54
|
+
"moduleResolution": "NodeNext",
|
|
55
|
+
"esModuleInterop": true,
|
|
56
|
+
"strict": true,
|
|
57
|
+
"resolveJsonModule": true,
|
|
58
|
+
"removeComments": true,
|
|
59
|
+
"newLine": "lf",
|
|
60
|
+
"noUnusedLocals": true,
|
|
61
|
+
"noFallthroughCasesInSwitch": true,
|
|
62
|
+
"isolatedModules": true,
|
|
63
|
+
"forceConsistentCasingInFileNames": true,
|
|
64
|
+
"skipLibCheck": true,
|
|
65
|
+
"lib": [
|
|
66
|
+
"ESNext"
|
|
67
|
+
],
|
|
68
|
+
"incremental": true
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Note the use of `incremental: true`, which speed up compilation massively.
|
|
74
|
+
|
|
75
|
+
## Options
|
|
76
|
+
|
|
77
|
+
* `--coverage` or `-C`, enables code coverage
|
|
78
|
+
* `--only` or `-o`, only run `node:test` with the `only` option set
|
|
79
|
+
* `--watch` or `-w`, re-run tests on changes
|
|
80
|
+
* `--timeout` or `-t`, timeouts the tests after a given time; default is 30000 ms
|
|
81
|
+
* `--coverage-exclude` or `-X`, a list of comma-separated patterns to exclude from the coverage report. All tests files are ignored by default.
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT
|
package/borp.js
CHANGED
|
@@ -2,27 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import { parseArgs } from 'node:util'
|
|
4
4
|
import { tap, spec } from 'node:test/reporters'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import { execa } from 'execa'
|
|
12
|
-
|
|
13
|
-
async function isFileAccessible (filename, directory) {
|
|
14
|
-
try {
|
|
15
|
-
const filePath = directory ? resolve(directory, filename) : filename
|
|
16
|
-
await access(filePath)
|
|
17
|
-
return true
|
|
18
|
-
} catch (err) {
|
|
19
|
-
return false
|
|
20
|
-
}
|
|
21
|
-
}
|
|
5
|
+
import { mkdtemp, rm } from 'node:fs/promises'
|
|
6
|
+
import { finished } from 'node:stream/promises'
|
|
7
|
+
import { join, relative } from 'node:path'
|
|
8
|
+
import posix from 'node:path/posix'
|
|
9
|
+
import runWithTypeScript from './lib/run.js'
|
|
10
|
+
import { Report } from 'c8'
|
|
22
11
|
|
|
23
12
|
let reporter
|
|
13
|
+
/* c8 ignore next 4 */
|
|
24
14
|
if (process.stdout.isTTY) {
|
|
25
|
-
|
|
15
|
+
/* eslint new-cap: "off" */
|
|
16
|
+
reporter = new spec()
|
|
26
17
|
} else {
|
|
27
18
|
reporter = tap
|
|
28
19
|
}
|
|
@@ -33,7 +24,10 @@ const args = parseArgs({
|
|
|
33
24
|
only: { type: 'boolean', short: 'o' },
|
|
34
25
|
watch: { type: 'boolean', short: 'w' },
|
|
35
26
|
pattern: { type: 'string', short: 'p' },
|
|
36
|
-
concurrency: { type: 'string', short: 'c' }
|
|
27
|
+
concurrency: { type: 'string', short: 'c' },
|
|
28
|
+
coverage: { type: 'boolean', short: 'C' },
|
|
29
|
+
timeout: { type: 'string', short: 't', default: '30000' },
|
|
30
|
+
'coverage-exclude': { type: 'string', short: 'X' }
|
|
37
31
|
},
|
|
38
32
|
allowPositionals: true
|
|
39
33
|
})
|
|
@@ -42,56 +36,57 @@ if (args.values.concurrency) {
|
|
|
42
36
|
args.values.concurrency = parseInt(args.values.concurrency)
|
|
43
37
|
}
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let prefix = ''
|
|
48
|
-
|
|
49
|
-
if (tsconfigPath) {
|
|
50
|
-
try {
|
|
51
|
-
const _require = createRequire(process.cwd())
|
|
52
|
-
const typescriptPathCWD = _require.resolve('typescript')
|
|
53
|
-
const tscPath = join(typescriptPathCWD, '..', '..', 'bin', 'tsc')
|
|
54
|
-
if (tscPath) {
|
|
55
|
-
const isAccessible = await isFileAccessible(tscPath)
|
|
56
|
-
if (isAccessible) {
|
|
57
|
-
await execa(tscPath, { cwd: dirname(tsconfigPath), stdio: 'inherit' })
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
const tsconfig = JSON.parse(await readFile(tsconfigPath))
|
|
61
|
-
const outDir = tsconfig.compilerOptions.outDir
|
|
62
|
-
if (outDir) {
|
|
63
|
-
prefix = join(dirname(tsconfigPath), outDir)
|
|
64
|
-
}
|
|
65
|
-
} catch (err) {
|
|
66
|
-
console.log(err)
|
|
67
|
-
}
|
|
39
|
+
if (args.values.timeout) {
|
|
40
|
+
args.values.timeout = parseInt(args.values.timeout)
|
|
68
41
|
}
|
|
69
42
|
|
|
70
|
-
let
|
|
71
|
-
if (args.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
} else {
|
|
75
|
-
files = args.positionals
|
|
76
|
-
}
|
|
77
|
-
} else if (args.values.pattern) {
|
|
78
|
-
if (prefix) {
|
|
79
|
-
args.values.pattern = join(prefix, args.values.pattern)
|
|
80
|
-
}
|
|
81
|
-
files = await glob(args.values.pattern, { ignore: 'node_modules/**' })
|
|
82
|
-
} else {
|
|
83
|
-
if (prefix) {
|
|
84
|
-
files = await glob(join(prefix, 'test/**/*.test.{cjs,mjs,js}'), { ignore: 'node_modules/**' })
|
|
85
|
-
} else {
|
|
86
|
-
files = await glob('test/**/*.test.{cjs,mjs,js}', { ignore: 'node_modules/**' })
|
|
87
|
-
}
|
|
43
|
+
let covDir
|
|
44
|
+
if (args.values.coverage) {
|
|
45
|
+
covDir = await mkdtemp(join(process.cwd(), 'coverage-'))
|
|
46
|
+
process.env.NODE_V8_COVERAGE = covDir
|
|
88
47
|
}
|
|
89
48
|
|
|
90
49
|
const config = {
|
|
91
50
|
...args.values,
|
|
92
|
-
files
|
|
51
|
+
files: args.positionals,
|
|
52
|
+
pattern: args.values.pattern,
|
|
53
|
+
cwd: process.cwd()
|
|
93
54
|
}
|
|
94
55
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
56
|
+
try {
|
|
57
|
+
const stream = await runWithTypeScript(config)
|
|
58
|
+
|
|
59
|
+
stream.on('test:fail', () => {
|
|
60
|
+
process.exitCode = 1
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
stream.compose(reporter).pipe(process.stdout)
|
|
64
|
+
|
|
65
|
+
await finished(stream)
|
|
66
|
+
|
|
67
|
+
if (covDir) {
|
|
68
|
+
let exclude = (args.values['coverage-exclude'] || '').split(',').filter(Boolean)
|
|
69
|
+
|
|
70
|
+
if (exclude.length === 0) {
|
|
71
|
+
exclude = undefined
|
|
72
|
+
} else if (config.prefix) {
|
|
73
|
+
const localPrefix = relative(process.cwd(), config.prefix)
|
|
74
|
+
exclude = exclude.map((file) => posix.join(localPrefix, file))
|
|
75
|
+
}
|
|
76
|
+
console.log('>> Excluding from coverage:', exclude)
|
|
77
|
+
const report = Report({
|
|
78
|
+
reporter: ['text'],
|
|
79
|
+
tempDirectory: covDir,
|
|
80
|
+
exclude
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
await report.run()
|
|
84
|
+
}
|
|
85
|
+
/* c8 ignore next 3 */
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error(err)
|
|
88
|
+
} finally {
|
|
89
|
+
if (covDir) {
|
|
90
|
+
await rm(covDir, { recursive: true })
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "dist",
|
|
5
|
+
"sourceMap": true,
|
|
6
|
+
"target": "ES2022",
|
|
7
|
+
"module": "NodeNext",
|
|
8
|
+
"moduleResolution": "NodeNext",
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"removeComments": true,
|
|
13
|
+
"newLine": "lf",
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noFallthroughCasesInSwitch": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"forceConsistentCasingInFileNames": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"lib": [
|
|
20
|
+
"ESNext"
|
|
21
|
+
],
|
|
22
|
+
"incremental": true
|
|
23
|
+
}
|
|
24
|
+
}
|
package/lib/run.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { run } from 'node:test'
|
|
2
|
+
import { glob } from 'glob'
|
|
3
|
+
import { findUp } from 'find-up'
|
|
4
|
+
import { createRequire } from 'node:module'
|
|
5
|
+
import { resolve, join, dirname } from 'node:path'
|
|
6
|
+
import { access, readFile } from 'node:fs/promises'
|
|
7
|
+
import { execa } from 'execa'
|
|
8
|
+
|
|
9
|
+
async function isFileAccessible (filename, directory) {
|
|
10
|
+
try {
|
|
11
|
+
const filePath = directory ? resolve(directory, filename) : filename
|
|
12
|
+
await access(filePath)
|
|
13
|
+
return true
|
|
14
|
+
} catch (err) {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function deferred () {
|
|
20
|
+
let resolve
|
|
21
|
+
let reject
|
|
22
|
+
const promise = new Promise((_resolve, _reject) => {
|
|
23
|
+
resolve = _resolve
|
|
24
|
+
reject = _reject
|
|
25
|
+
})
|
|
26
|
+
return { resolve, reject, promise }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default async function runWithTypeScript (config) {
|
|
30
|
+
const { cwd } = config
|
|
31
|
+
const chunks = []
|
|
32
|
+
const tsconfigPath = await findUp('tsconfig.json', { cwd })
|
|
33
|
+
|
|
34
|
+
let prefix = ''
|
|
35
|
+
let tscPath
|
|
36
|
+
|
|
37
|
+
if (tsconfigPath) {
|
|
38
|
+
const _require = createRequire(cwd)
|
|
39
|
+
const typescriptPathCWD = _require.resolve('typescript')
|
|
40
|
+
tscPath = join(typescriptPathCWD, '..', '..', 'bin', 'tsc')
|
|
41
|
+
if (tscPath) {
|
|
42
|
+
const isAccessible = await isFileAccessible(tscPath)
|
|
43
|
+
if (isAccessible) {
|
|
44
|
+
// Watch is handled aftterwards
|
|
45
|
+
if (!config.watch) {
|
|
46
|
+
const start = Date.now()
|
|
47
|
+
await execa('node', [tscPath], { cwd: dirname(tsconfigPath) })
|
|
48
|
+
chunks.push({
|
|
49
|
+
type: 'test:diagnostic',
|
|
50
|
+
data: {
|
|
51
|
+
nesting: 0,
|
|
52
|
+
message: `TypeScript compilation complete (${Date.now() - start}ms)`
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
throw new Error('Could not find tsc')
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const tsconfig = JSON.parse(await readFile(tsconfigPath))
|
|
61
|
+
const outDir = tsconfig.compilerOptions.outDir
|
|
62
|
+
if (outDir) {
|
|
63
|
+
prefix = join(dirname(tsconfigPath), outDir)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
config.prefix = prefix
|
|
67
|
+
config.setup = (test) => {
|
|
68
|
+
for (const chunk of chunks) {
|
|
69
|
+
test.reporter.push(chunk)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let tscChild
|
|
74
|
+
/* eslint prefer-const: "off" */
|
|
75
|
+
let stream
|
|
76
|
+
let p
|
|
77
|
+
|
|
78
|
+
if (config.watch) {
|
|
79
|
+
p = deferred()
|
|
80
|
+
const start = Date.now()
|
|
81
|
+
tscChild = execa('node', [tscPath, '--watch'], { cwd })
|
|
82
|
+
tscChild.stdout.setEncoding('utf8')
|
|
83
|
+
tscChild.stdout.on('data', (data) => {
|
|
84
|
+
if (data.includes('Watching for file changes')) {
|
|
85
|
+
chunks.push({
|
|
86
|
+
type: 'test:diagnostic',
|
|
87
|
+
data: {
|
|
88
|
+
nesting: 0,
|
|
89
|
+
message: `TypeScript compilation complete (${Date.now() - start}ms)`
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
p.resolve()
|
|
94
|
+
}
|
|
95
|
+
if (data.includes('error TS')) {
|
|
96
|
+
const toPush = stream || chunks
|
|
97
|
+
toPush.push({
|
|
98
|
+
type: 'test:fail',
|
|
99
|
+
data: {
|
|
100
|
+
nesting: 0,
|
|
101
|
+
name: data.trim()
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
if (config.signal) {
|
|
107
|
+
config.signal.addEventListener('abort', () => {
|
|
108
|
+
tscChild.kill()
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (p) {
|
|
114
|
+
await p.promise
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let files = config.files || []
|
|
118
|
+
const ignore = join('node_modules', '**')
|
|
119
|
+
if (files.length > 0) {
|
|
120
|
+
if (prefix) {
|
|
121
|
+
files = files.map((file) => join(prefix, file.replace(/ts$/, 'js')))
|
|
122
|
+
}
|
|
123
|
+
} else if (config.pattern) {
|
|
124
|
+
if (prefix) {
|
|
125
|
+
config.pattern = join(prefix, config.pattern)
|
|
126
|
+
}
|
|
127
|
+
files = await glob(config.pattern, { ignore, cwd, windowsPathsNoEscape: true })
|
|
128
|
+
} else if (prefix) {
|
|
129
|
+
files = await glob(join(prefix, join('test', '**', '*.test.{cjs,mjs,js}')), { ignore, cwd, windowsPathsNoEscape: true })
|
|
130
|
+
} else {
|
|
131
|
+
files = await glob(join('test', '**', '*.test.{cjs,mjs,js}'), { ignore, cwd, windowsPathsNoEscape: true })
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
config.files = files
|
|
135
|
+
|
|
136
|
+
stream = run(config)
|
|
137
|
+
|
|
138
|
+
stream.on('close', () => {
|
|
139
|
+
if (tscChild) {
|
|
140
|
+
tscChild.kill()
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
return stream
|
|
144
|
+
}
|
package/package.json
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "node:test wrapper with TypeScript support",
|
|
6
6
|
"main": "borp.js",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"
|
|
8
|
+
"clean": "rm -rf fixtures/*/dist .test-* coverage-*",
|
|
9
|
+
"lint": "standard | snazzy",
|
|
10
|
+
"unit": "node borp.js --concurrency=1 --coverage --coverage-exclude \"fixtures/**/*,test/**/*\"",
|
|
11
|
+
"test": "npm run clean ; npm run lint && npm run unit"
|
|
9
12
|
},
|
|
10
13
|
"keywords": [],
|
|
11
14
|
"author": "Matteo Collina <hello@matteocollina.com>",
|
|
12
15
|
"license": "MIT",
|
|
13
16
|
"devDependencies": {
|
|
17
|
+
"@matteo.collina/tspl": "^0.1.0",
|
|
14
18
|
"@types/node": "^20.10.0",
|
|
19
|
+
"desm": "^1.3.0",
|
|
15
20
|
"snazzy": "^9.0.0",
|
|
16
21
|
"standard": "^17.1.0",
|
|
17
22
|
"typescript": "^5.3.2"
|
|
18
23
|
},
|
|
19
24
|
"dependencies": {
|
|
25
|
+
"c8": "^8.0.1",
|
|
20
26
|
"execa": "^8.0.1",
|
|
21
27
|
"find-up": "^7.0.0",
|
|
22
28
|
"glob": "^10.3.10"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import { tspl } from '@matteo.collina/tspl'
|
|
3
|
+
import runWithTypeScript from '../lib/run.js'
|
|
4
|
+
import { join } from 'desm'
|
|
5
|
+
|
|
6
|
+
test('ts-esm', async (t) => {
|
|
7
|
+
const { strictEqual, completed, match } = tspl(t, { plan: 4 })
|
|
8
|
+
const config = {
|
|
9
|
+
files: [],
|
|
10
|
+
cwd: join(import.meta.url, '..', 'fixtures', 'ts-esm')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const stream = await runWithTypeScript(config)
|
|
14
|
+
|
|
15
|
+
const names = new Set(['add', 'add2'])
|
|
16
|
+
|
|
17
|
+
stream.once('data', (test) => {
|
|
18
|
+
strictEqual(test.type, 'test:diagnostic')
|
|
19
|
+
match(test.data.message, /TypeScript compilation complete \(\d+ms\)/)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
stream.on('test:pass', (test) => {
|
|
23
|
+
strictEqual(names.has(test.name), true)
|
|
24
|
+
names.delete(test.name)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
await completed
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('ts-cjs', async (t) => {
|
|
31
|
+
const { strictEqual, completed } = tspl(t, { plan: 2 })
|
|
32
|
+
const config = {
|
|
33
|
+
files: [],
|
|
34
|
+
cwd: join(import.meta.url, '..', 'fixtures', 'ts-cjs')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const stream = await runWithTypeScript(config)
|
|
38
|
+
|
|
39
|
+
const names = new Set(['add', 'add2'])
|
|
40
|
+
|
|
41
|
+
stream.on('test:pass', (test) => {
|
|
42
|
+
strictEqual(names.has(test.name), true)
|
|
43
|
+
names.delete(test.name)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
await completed
|
|
47
|
+
})
|
package/test/cli.test.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import { execa } from 'execa'
|
|
3
|
+
import { join } from 'desm'
|
|
4
|
+
import { rejects } from 'node:assert'
|
|
5
|
+
|
|
6
|
+
const borp = join(import.meta.url, '..', 'borp.js')
|
|
7
|
+
|
|
8
|
+
test('limit concurrency', async () => {
|
|
9
|
+
await execa('node', [
|
|
10
|
+
borp,
|
|
11
|
+
'--concurrency',
|
|
12
|
+
'1'
|
|
13
|
+
], {
|
|
14
|
+
cwd: join(import.meta.url, '..', 'fixtures', 'ts-esm')
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('failing test set correct status code', async () => {
|
|
19
|
+
// execa rejects if status code is not 0
|
|
20
|
+
await rejects(execa('node', [
|
|
21
|
+
borp
|
|
22
|
+
], {
|
|
23
|
+
cwd: join(import.meta.url, '..', 'fixtures', 'fails')
|
|
24
|
+
}))
|
|
25
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import { match, doesNotMatch } from 'node:assert'
|
|
3
|
+
import { execa } from 'execa'
|
|
4
|
+
import { join } from 'desm'
|
|
5
|
+
|
|
6
|
+
const borp = join(import.meta.url, '..', 'borp.js')
|
|
7
|
+
|
|
8
|
+
test('coverage', async () => {
|
|
9
|
+
const res = await execa('node', [
|
|
10
|
+
borp,
|
|
11
|
+
'--coverage'
|
|
12
|
+
], {
|
|
13
|
+
cwd: join(import.meta.url, '..', 'fixtures', 'ts-esm')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
match(res.stdout, /% Stmts/)
|
|
17
|
+
match(res.stdout, /All files/)
|
|
18
|
+
match(res.stdout, /add\.ts/)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('coverage excludes', async () => {
|
|
22
|
+
const res = await execa('node', [
|
|
23
|
+
borp,
|
|
24
|
+
'--coverage',
|
|
25
|
+
'--coverage-exclude=src'
|
|
26
|
+
], {
|
|
27
|
+
cwd: join(import.meta.url, '..', 'fixtures', 'ts-esm')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
match(res.stdout, /% Stmts/)
|
|
31
|
+
match(res.stdout, /All files/)
|
|
32
|
+
doesNotMatch(res.stdout, /add\.ts/)
|
|
33
|
+
// The test files are shown
|
|
34
|
+
match(res.stdout, /add\.test\.ts/)
|
|
35
|
+
match(res.stdout, /add2\.test\.ts/)
|
|
36
|
+
})
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import { tspl } from '@matteo.collina/tspl'
|
|
3
|
+
import runWithTypeScript from '../lib/run.js'
|
|
4
|
+
import { join } from 'desm'
|
|
5
|
+
import { mkdtemp, cp, writeFile, rm } from 'node:fs/promises'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import { once } from 'node:events'
|
|
8
|
+
|
|
9
|
+
test('watch', async (t) => {
|
|
10
|
+
const { strictEqual, completed, match } = tspl(t, { plan: 3 })
|
|
11
|
+
|
|
12
|
+
const dir = path.resolve(await mkdtemp('.test-watch'))
|
|
13
|
+
await cp(join(import.meta.url, '..', 'fixtures', 'ts-esm'), dir, {
|
|
14
|
+
recursive: true
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const controller = new AbortController()
|
|
18
|
+
t.after(async () => {
|
|
19
|
+
controller.abort()
|
|
20
|
+
await rm(dir, { recursive: true })
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const config = {
|
|
24
|
+
files: [],
|
|
25
|
+
cwd: dir,
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
watch: true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const stream = await runWithTypeScript(config)
|
|
31
|
+
|
|
32
|
+
const fn = (test) => {
|
|
33
|
+
if (test.type === 'test:fail') {
|
|
34
|
+
strictEqual(test.data.name, 'add')
|
|
35
|
+
stream.removeListener('data', fn)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
stream.on('data', fn)
|
|
39
|
+
|
|
40
|
+
const [test] = await once(stream, 'data')
|
|
41
|
+
strictEqual(test.type, 'test:diagnostic')
|
|
42
|
+
match(test.data.message, /TypeScript compilation complete \(\d+ms\)/)
|
|
43
|
+
|
|
44
|
+
const toWrite = `
|
|
45
|
+
import { test } from 'node:test'
|
|
46
|
+
import { add } from '../src/add.js'
|
|
47
|
+
import { strictEqual } from 'node:assert'
|
|
48
|
+
|
|
49
|
+
test('add', () => {
|
|
50
|
+
strictEqual(add(1, 2), 4)
|
|
51
|
+
})
|
|
52
|
+
`
|
|
53
|
+
const file = path.join(dir, 'test', 'add.test.ts')
|
|
54
|
+
await writeFile(file, toWrite)
|
|
55
|
+
|
|
56
|
+
await completed
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('watch file syntax error', async (t) => {
|
|
60
|
+
const { strictEqual, completed, match } = tspl(t, { plan: 3 })
|
|
61
|
+
|
|
62
|
+
const dir = path.resolve(await mkdtemp('.test-watch'))
|
|
63
|
+
await cp(join(import.meta.url, '..', 'fixtures', 'ts-esm'), dir, {
|
|
64
|
+
recursive: true
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const controller = new AbortController()
|
|
68
|
+
t.after(async () => {
|
|
69
|
+
controller.abort()
|
|
70
|
+
await rm(dir, { recursive: true })
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const config = {
|
|
74
|
+
files: [],
|
|
75
|
+
cwd: dir,
|
|
76
|
+
signal: controller.signal,
|
|
77
|
+
watch: true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const stream = await runWithTypeScript(config)
|
|
81
|
+
|
|
82
|
+
const fn = (test) => {
|
|
83
|
+
if (test.type === 'test:fail') {
|
|
84
|
+
match(test.data.name, /add\.test\.ts/)
|
|
85
|
+
stream.removeListener('data', fn)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
stream.on('data', fn)
|
|
89
|
+
|
|
90
|
+
const [test] = await once(stream, 'data')
|
|
91
|
+
strictEqual(test.type, 'test:diagnostic')
|
|
92
|
+
match(test.data.message, /TypeScript compilation complete \(\d+ms\)/)
|
|
93
|
+
|
|
94
|
+
const toWrite = `
|
|
95
|
+
import { test } from 'node:test'
|
|
96
|
+
import { add } from '../src/add.js'
|
|
97
|
+
import { strictEqual } from 'node:assert'
|
|
98
|
+
|
|
99
|
+
test('add', () => {
|
|
100
|
+
strictEqual(add(1, 2), 3
|
|
101
|
+
})
|
|
102
|
+
`
|
|
103
|
+
const file = path.join(dir, 'test', 'add.test.ts')
|
|
104
|
+
await writeFile(file, toWrite)
|
|
105
|
+
|
|
106
|
+
await completed
|
|
107
|
+
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|