Skip to content
On this page

v0.0.1

file-img-enum

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

虽然我让大家先看 cli.ts 文件,但其实第一次运行的文件并不是它,而是 package.json 里面 bin 字段指向的 ./bin/vitest.mjs

{
  "bin": {
    "vitest": "./bin/vitest.mjs"
  },
}
1
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

我现在将 run 方法提取出来,给大家解释。

  1. 首先提取我们传递的参数。
const { rootDir = process.cwd() } = options
1
  1. 让chai库加上jest的快照功能
chai.use(SnapshotPlugin({
  rootDir,
  update: options.updateSnapshot,
}))
1
2
3
4

这个功能是调用的jest api 我们先不具体了解

  1. 扫描我们指定路径下的所有规定格式的文件
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

files的结果就是文件绝对定位的路径形成的字符串数组

  1. 如果一个文件也没扫描到我们就给用户提示没有找到文件
if (!files.length) {
  console.error('No test files found')
  process.exitCode = 1
  return
}
1
2
3
4
5
  1. 运行生命周期 beforeHook
await beforeHook.fire()
1
  1. 遍历执行所有我们找的的文件
for (const file of files) {
  log(`${relative(process.cwd(), file)}`)
  await runFile(file)
  log()
}
1
2
3
4
5
  1. 运行生命周期 afterHook
await afterHook.fire()
1

Released under the MIT License.