Appearance
v0.0.1
vitest v0.0.1 仓库地址:https://github.com/vitest-dev/vitest/tree/v0.0.1
zt-vitest v0.0.1 仓库地址:https://github.com/zxTick/zt-vitest/tree/0.0.1
这个是 vitest 的起航版本,我将我的理解给大家分享出来,一定有些地方是写的非常烂的,希望大家能够见谅,并且提pr帮助我改进。
大家可以吧代码下载下来,跟着我写的文章一起看,这样我觉的可以事半功倍。
文件目录
目录结构如下:
开始
请点开 cli.ts
文件
如果不方便下载代码,下面我将 cli.ts
文件内容展示到下面。
import { join } from 'path'
import minimist from 'minimist'
import c from 'picocolors'
import { run } from './run'
const { log } = console
const argv = minimist(process.argv.slice(2), {
alias: {
u: 'update',
},
string: ['root'],
boolean: ['update'],
unknown(name) {
if (name[0] === '-') {
console.error(c.red(`Unknown argument: ${name}`))
help()
process.exit(1)
}
return true
},
})
// TODO: load config, CLI
run({
rootDir: argv.root || join(process.cwd(), 'test'),
updateSnapshot: argv.update,
})
function help() {
log('Help: finish help')
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
虽然我让大家先看 cli.ts
文件,但其实第一次运行的文件并不是它,而是 package.json
里面 bin
字段指向的 ./bin/vitest.mjs
{
"bin": {
"vitest": "./bin/vitest.mjs"
},
}
1
2
3
4
5
2
3
4
5
它注册了一个终端命令 vitest
,而运行后其实执行的就是 cli.ts
文件,所以我就直接说一下 cli.ts
文件的内容。
cli.ts
文件执行的内容非常简单,那就是识别我们输入的指令检测出字符串类型的参数 root
的值,还有 布尔类型的参数 updateSnapshot
。
root
: 根目录,默认是终端路径。updateSnapshot
: 是否更新快照。
收集了这些信息之后,就会传递到我们的 run
函数里面,这个函数就是我们的主函数。现在我们就已经可以将视线移动到 run.ts
文件中了。
一下我为大家展示run.ts
文件中的内容。
import { relative } from 'path'
import c from 'picocolors'
import chai from 'chai'
import fg from 'fast-glob'
import { clearContext, defaultSuite } from './suite'
import { context } from './context'
import { File, Options, Suite, Task, TaskResult } from './types'
import { afterEachHook, afterFileHook, afterHook, afterSuiteHook, beforeEachHook, beforeFileHook, beforeHook, beforeSuiteHook } from './hooks'
import { SnapshotPlugin } from './snapshot/index'
export async function runTasks(tasks: Task[]) {
const results: TaskResult[] = []
for (const task of tasks) {
const result: TaskResult = { task }
await beforeEachHook.fire(task)
try {
await task.fn()
}
catch (e) {
result.error = e
}
results.push(result)
await afterEachHook.fire(task, result)
}
return results
}
// TODO: REPORTER
const { log } = console
export async function parseFile(filepath: string) {
clearContext()
await import(filepath)
const suites = [defaultSuite, ...context.suites]
const tasks = await Promise.all(suites.map(async(suite) => {
await beforeSuiteHook.fire(suite)
context.currentSuite = suite
return [suite, await suite.collect()] as [Suite, Task[]]
}))
const file: File = {
filepath,
suites,
tasks,
}
file.tasks.forEach(([, tasks]) =>
tasks.forEach(task => task.file = file),
)
return file
}
export async function runFile(filepath: string) {
await beforeFileHook.fire(filepath)
const file = await parseFile(filepath)
for (const [suite, tasks] of file.tasks) {
let indent = 1
if (suite.name) {
log(' '.repeat(indent * 2) + suite.name)
indent += 1
}
const result = await runTasks(tasks)
for (const r of result) {
if (r.error === undefined) {
log(`${' '.repeat(indent * 2)}${c.inverse(c.green(' PASS '))} ${c.green(r.task.name)}`)
}
else {
console.error(`${' '.repeat(indent * 2)}${c.inverse(c.red(' FAIL '))} ${c.red(r.task.name)}`)
console.error(' '.repeat((indent + 2) * 2) + c.red(String(r.error)))
process.exitCode = 1
}
}
if (suite.name)
indent -= 1
await afterSuiteHook.fire(suite)
}
await afterFileHook.fire(filepath)
}
export async function run(options: Options = {}) {
const { rootDir = process.cwd() } = options
chai.use(SnapshotPlugin({
rootDir,
update: options.updateSnapshot,
}))
const files = await fg(options.includes || ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], {
absolute: true,
cwd: options.rootDir,
})
if (!files.length) {
console.error('No test files found')
process.exitCode = 1
return
}
await beforeHook.fire()
for (const file of files) {
log(`${relative(process.cwd(), file)}`)
await runFile(file)
log()
}
await afterHook.fire()
log()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
我现在将 run
方法提取出来,给大家解释。
- 首先提取我们传递的参数。
const { rootDir = process.cwd() } = options
1
- 让chai库加上jest的快照功能
chai.use(SnapshotPlugin({
rootDir,
update: options.updateSnapshot,
}))
1
2
3
4
2
3
4
这个功能是调用的jest api 我们先不具体了解
- 扫描我们指定路径下的所有规定格式的文件
const files = await fg(options.includes || ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], {
absolute: true,
cwd: options.rootDir,
})
1
2
3
4
2
3
4
files的结果就是文件绝对定位的路径形成的字符串数组
- 如果一个文件也没扫描到我们就给用户提示没有找到文件
if (!files.length) {
console.error('No test files found')
process.exitCode = 1
return
}
1
2
3
4
5
2
3
4
5
- 运行生命周期 beforeHook
await beforeHook.fire()
1
- 遍历执行所有我们找的的文件
for (const file of files) {
log(`${relative(process.cwd(), file)}`)
await runFile(file)
log()
}
1
2
3
4
5
2
3
4
5
- 运行生命周期 afterHook
await afterHook.fire()
1