vitest 0.0.8 → 0.0.12

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
@@ -6,12 +6,14 @@ A blazing fast test runner powered by Vite.
6
6
 
7
7
  ## Features
8
8
 
9
- - Vite's transformer, resolver, and plugin system. Powered by [vite-node](https://github.com/antfu/vite-node).
10
- - Jest Snapshot.
11
- - Chai for assertions.
12
- - Async suite / test.
13
- - ESM friendly, top level await.
14
- - Suite and Test filtering (skip, only, todo).
9
+ - [Vite](https://vitejs.dev/)'s config, transformers, resolvers, and plugins. Powered by [vite-node](https://github.com/antfu/vite-node)
10
+ - [Jest Snapshot](https://jestjs.io/docs/snapshot-testing)
11
+ - [Chai](https://www.chaijs.com/) for assertions
12
+ - [Sinon](https://sinonjs.org/) for mocking
13
+ - Async suite / test, top level await
14
+ - ESM friendly
15
+ - Out-of-box TypeScript support
16
+ - Suite and Test filtering (skip, only, todo)
15
17
 
16
18
  ```ts
17
19
  import { it, describe, expect, assert } from 'vitest'
@@ -35,6 +37,27 @@ describe('suite name', () => {
35
37
  $ npx vitest
36
38
  ```
37
39
 
40
+ ## Configuration
41
+
42
+ `vitest` will read your root `vite.config.ts` when it present to match with the plugins and setup as your Vite app. If you want to it to have a different configuration for testing, you could either:
43
+
44
+ - Create `vitest.config.ts`, which will have the higher priority
45
+ - Pass `--config` option to CLI, e.g. `vitest --config ./path/to/vitest.config.ts`
46
+ - Use `process.env.VITEST` to conditionally apply differnet configuration in `vite.config.ts`
47
+
48
+ To configure `vitest` itself, add `test` property in your Vite config
49
+
50
+ ```ts
51
+ // vite.config.ts
52
+ import { defineConfig } from 'vite'
53
+
54
+ export default defineConfig({
55
+ test: {
56
+ // ...
57
+ }
58
+ })
59
+ ```
60
+
38
61
  ## Filtering
39
62
 
40
63
  ### Skipping suites and tasks
@@ -91,19 +114,22 @@ Use `.todo` to stub suites and tests that should be implemented
91
114
  describe.todo('unimplemented suite')
92
115
 
93
116
  // An entry will be shown in the report for this task
94
- describe.suite('suite', () => {
117
+ describe('suite', () => {
95
118
  it.todo('unimplemented task')
96
119
  })
97
120
  ```
98
121
 
99
122
  ## TODO
100
123
 
101
- - [ ] Reporter & Better output
124
+ - [x] Reporter & Better output
125
+ - [x] Task filter
126
+ - [x] Mock
127
+ - [ ] Global Mode & Types
128
+ - [ ] Parallel Executing
102
129
  - [ ] CLI Help
103
- - [ ] Task filter
104
- - [ ] Mock
105
130
  - [ ] JSDom
106
131
  - [ ] Watch
132
+ - [ ] Source Map
107
133
  - [ ] Coverage
108
134
 
109
135
  ## Sponsors
package/bin/vitest.mjs CHANGED
@@ -7,6 +7,8 @@ import { run } from 'vite-node'
7
7
  import minimist from 'minimist'
8
8
  import { findUp } from 'find-up'
9
9
 
10
+ process.env.VITEST = 'true'
11
+
10
12
  const argv = minimist(process.argv.slice(2), {
11
13
  alias: {
12
14
  c: 'config',
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ const argv = minimist(process.argv.slice(2), {
22
22
  const server = (_a = process === null || process === void 0 ? void 0 : process.__vite_node__) === null || _a === void 0 ? void 0 : _a.server;
23
23
  const viteConfig = (server === null || server === void 0 ? void 0 : server.config) || {};
24
24
  const testOptions = viteConfig.test || {};
25
- await run(Object.assign(Object.assign({}, testOptions), { updateSnapshot: argv.update, rootDir: argv.root || process.cwd() }));
25
+ await run(Object.assign(Object.assign({}, testOptions), { server, updateSnapshot: argv.update, rootDir: argv.root || process.cwd(), nameFilters: argv._ }));
26
26
  function help() {
27
27
  log('Help: finish help');
28
28
  }
@@ -0,0 +1,2 @@
1
+ export declare const defaultIncludes: string[];
2
+ export declare const defaultExcludes: string[];
@@ -0,0 +1,2 @@
1
+ export const defaultIncludes = ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'];
2
+ export const defaultExcludes = ['**/node_modules/**', '**/dist/**'];
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
+ import sinon from 'sinon';
1
2
  export * from './types';
2
3
  export * from './suite';
3
4
  export * from './config';
4
5
  export * from './chai';
5
6
  export { beforeAll, afterAll, beforeEach, afterEach, beforeFile, afterFile, beforeSuite, afterSuite } from './hooks';
7
+ export { sinon };
8
+ export declare const mock: sinon.SinonMockStatic, spy: sinon.SinonSpyStatic, stub: sinon.SinonStubStatic;
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
+ import sinon from 'sinon';
1
2
  export * from './types';
2
3
  export * from './suite';
3
4
  export * from './config';
4
5
  export * from './chai';
5
6
  export { beforeAll, afterAll, beforeEach, afterEach, beforeFile, afterFile, beforeSuite, afterSuite } from './hooks';
7
+ export { sinon };
8
+ export const { mock, spy, stub } = sinon;
@@ -5,14 +5,15 @@ export declare class DefaultReporter implements Reporter {
5
5
  end: number;
6
6
  onStart(): void;
7
7
  onCollected(): void;
8
- onFinished({ files }: RunnerContext): void;
9
8
  onSuiteBegin(suite: Suite): void;
10
9
  onSuiteEnd(suite: Suite): void;
11
10
  onFileBegin(file: File): void;
12
11
  onFileEnd(): void;
13
- onTaskBegin(): void;
14
- onTaskEnd(t: Task): void;
15
- log(msg?: string, indentDelta?: number): void;
16
- error(msg?: string, indentDelta?: number): void;
12
+ onTaskBegin(task: Task): void;
13
+ onTaskEnd(task: Task): void;
14
+ onFinished({ files }: RunnerContext): void;
15
+ private getIndent;
16
+ private log;
17
+ private error;
17
18
  onSnapshotUpdate(): void;
18
19
  }
@@ -1,7 +1,10 @@
1
1
  import { relative } from 'path';
2
2
  import { performance } from 'perf_hooks';
3
3
  import c from 'picocolors';
4
+ import ora from 'ora';
4
5
  const DOT = '· ';
6
+ const CHECK = '✔ ';
7
+ const CROSS = '⤫ ';
5
8
  export class DefaultReporter {
6
9
  constructor() {
7
10
  this.indent = 0;
@@ -14,23 +17,6 @@ export class DefaultReporter {
14
17
  onCollected() {
15
18
  this.start = performance.now();
16
19
  }
17
- onFinished({ files }) {
18
- this.end = performance.now();
19
- const tasks = files.reduce((acc, file) => acc.concat(file.suites.flatMap(i => i.tasks)), []);
20
- const passed = tasks.filter(i => i.status === 'pass');
21
- const failed = tasks.filter(i => i.status === 'fail');
22
- const skipped = tasks.filter(i => i.status === 'skip');
23
- const todo = tasks.filter(i => i.status === 'todo');
24
- this.indent = 0;
25
- this.log(c.green(`Passed ${passed.length} / ${tasks.length}`));
26
- if (skipped.length)
27
- this.log(c.yellow(`Skipped ${skipped.length}`));
28
- if (todo.length)
29
- this.log(c.dim(`Todo ${todo.length}`));
30
- if (failed.length)
31
- this.log(c.red(`Failed ${failed.length} / ${tasks.length}`));
32
- this.log(`Time ${(this.end - this.start).toFixed(2)}ms`);
33
- }
34
20
  onSuiteBegin(suite) {
35
21
  if (suite.name) {
36
22
  this.indent += 1;
@@ -53,33 +39,76 @@ export class DefaultReporter {
53
39
  onFileEnd() {
54
40
  this.log();
55
41
  }
56
- onTaskBegin() {
42
+ onTaskBegin(task) {
57
43
  this.indent += 1;
44
+ // @ts-expect-error
45
+ task.__ora = ora({ text: task.name, prefixText: this.getIndent().slice(1), spinner: 'arc' }).start();
58
46
  }
59
- onTaskEnd(t) {
60
- if (t.status === 'pass') {
61
- this.log(`${c.green(`✔ ${t.name}`)}`);
47
+ onTaskEnd(task) {
48
+ var _a;
49
+ // @ts-expect-error
50
+ (_a = task.__ora) === null || _a === void 0 ? void 0 : _a.stop();
51
+ if (task.state === 'pass') {
52
+ this.log(`${c.green(CHECK + task.name)}`);
62
53
  }
63
- else if (t.status === 'skip') {
64
- this.log(c.dim(c.yellow(`${DOT + t.name} (skipped)`)));
54
+ else if (task.state === 'skip') {
55
+ this.log(c.dim(c.yellow(`${DOT + task.name} (skipped)`)));
65
56
  }
66
- else if (t.status === 'todo') {
67
- this.log(c.dim(`${DOT + t.name} (todo)`));
57
+ else if (task.state === 'todo') {
58
+ this.log(c.dim(`${DOT + task.name} (todo)`));
68
59
  }
69
60
  else {
70
- this.error(`${c.red(`⤫ ${c.inverse(c.red(' FAIL '))} ${t.name}`)}`);
71
- this.error(String(t.error), 1);
61
+ this.error(`${c.red(`${CROSS}${task.name}`)}`);
72
62
  process.exitCode = 1;
73
63
  }
74
64
  this.indent -= 1;
75
65
  }
76
- log(msg = '', indentDelta = 0) {
66
+ onFinished({ files }) {
67
+ this.end = performance.now();
68
+ const failedFiles = files.filter(i => i.error);
69
+ const tasks = files.reduce((acc, file) => acc.concat(file.suites.flatMap(i => i.tasks)), []);
70
+ const runable = tasks.filter(i => i.state === 'pass' || i.state === 'fail');
71
+ const passed = tasks.filter(i => i.state === 'pass');
72
+ const failed = tasks.filter(i => i.state === 'fail');
73
+ const skipped = tasks.filter(i => i.state === 'skip');
74
+ const todo = tasks.filter(i => i.state === 'todo');
75
+ this.indent = 0;
76
+ if (failedFiles.length) {
77
+ this.error(c.bold(`\nFailed to parse ${failedFiles.length} files:`));
78
+ failedFiles.forEach((i) => {
79
+ this.error(`\n- ${i.filepath}`);
80
+ console.error(i.error || 'Unknown error');
81
+ this.log();
82
+ });
83
+ }
84
+ if (failed.length) {
85
+ this.error(c.bold(`\nFailed Tests (${failed.length})`));
86
+ failed.forEach((task) => {
87
+ var _a;
88
+ this.error(`\n${CROSS + c.inverse(c.red(' FAIL '))} ${[task.suite.name, task.name].filter(Boolean).join(' > ')} ${c.gray(`${(_a = task.file) === null || _a === void 0 ? void 0 : _a.filepath}`)}`);
89
+ console.error(task.error || 'Unknown error');
90
+ this.log();
91
+ });
92
+ }
93
+ this.log(c.green(`Passed ${passed.length} / ${runable.length}`));
94
+ if (failed.length)
95
+ this.log(c.red(`Failed ${failed.length} / ${runable.length}`));
96
+ if (skipped.length)
97
+ this.log(c.yellow(`Skipped ${skipped.length}`));
98
+ if (todo.length)
99
+ this.log(c.dim(`Todo ${todo.length}`));
100
+ this.log(`Time ${(this.end - this.start).toFixed(2)}ms`);
101
+ }
102
+ getIndent(offest = 0) {
103
+ return ' '.repeat((this.indent + offest) * 2);
104
+ }
105
+ log(msg = '', indentOffset = 0) {
77
106
  // eslint-disable-next-line no-console
78
- console.log(`${' '.repeat((this.indent + indentDelta) * 2)}${msg}`);
107
+ console.log(`${this.getIndent(indentOffset)}${msg}`);
79
108
  }
80
- error(msg = '', indentDelta = 0) {
109
+ error(msg = '', indentOffset = 0) {
81
110
  // eslint-disable-next-line no-console
82
- console.error(c.red(`${' '.repeat((this.indent + indentDelta) * 2)}${msg}`));
111
+ console.error(c.red(`${this.getIndent(indentOffset)}${msg}`));
83
112
  }
84
113
  onSnapshotUpdate() {
85
114
  }
package/dist/run.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { File, Options, Task, RunnerContext } from './types';
1
+ import { File, Config, Task, RunnerContext } from './types';
2
2
  export declare function runTask(task: Task, ctx: RunnerContext): Promise<void>;
3
- export declare function collectFiles(files: string[]): Promise<File[]>;
3
+ export declare function collectFiles(paths: string[]): Promise<File[]>;
4
4
  export declare function runFile(file: File, ctx: RunnerContext): Promise<void>;
5
- export declare function run(options?: Options): Promise<void>;
5
+ export declare function run(config: Config): Promise<void>;
package/dist/run.js CHANGED
@@ -1,57 +1,84 @@
1
1
  import chai from 'chai';
2
2
  import fg from 'fast-glob';
3
+ import SinonChai from 'sinon-chai';
3
4
  import { clearContext, defaultSuite } from './suite';
4
5
  import { context } from './context';
5
6
  import { afterEachHook, afterFileHook, afterAllHook, afterSuiteHook, beforeEachHook, beforeFileHook, beforeAllHook, beforeSuiteHook } from './hooks';
6
7
  import { SnapshotPlugin } from './snapshot';
7
8
  import { DefaultReporter } from './reporters/default';
9
+ import { defaultIncludes, defaultExcludes } from './constants';
8
10
  export async function runTask(task, ctx) {
9
11
  var _a, _b;
10
12
  const { reporter } = ctx;
11
13
  await ((_a = reporter.onTaskBegin) === null || _a === void 0 ? void 0 : _a.call(reporter, task, ctx));
12
- await beforeEachHook.fire(task);
13
- if (task.suite.mode === 'skip' || task.mode === 'skip') {
14
- task.status = 'skip';
15
- }
16
- else if (task.suite.mode === 'todo' || task.mode === 'todo') {
17
- task.status = 'todo';
18
- }
19
- else {
14
+ if (task.mode === 'run') {
15
+ await beforeEachHook.fire(task);
20
16
  try {
21
17
  await task.fn();
22
- task.status = 'pass';
18
+ task.state = 'pass';
23
19
  }
24
20
  catch (e) {
25
- task.status = 'fail';
21
+ task.state = 'fail';
26
22
  task.error = e;
27
23
  }
24
+ await afterEachHook.fire(task);
28
25
  }
29
- await afterEachHook.fire(task);
30
26
  await ((_b = reporter.onTaskEnd) === null || _b === void 0 ? void 0 : _b.call(reporter, task, ctx));
31
27
  }
32
- export async function collectFiles(files) {
33
- const result = [];
34
- for (const filepath of files) {
35
- clearContext();
36
- await import(filepath);
37
- const collectors = [defaultSuite, ...context.suites];
38
- const suites = [];
28
+ export async function collectFiles(paths) {
29
+ const files = [];
30
+ for (const filepath of paths) {
39
31
  const file = {
40
32
  filepath,
41
33
  suites: [],
34
+ collected: false,
42
35
  };
43
- for (const c of collectors) {
44
- context.currentSuite = c;
45
- suites.push(await c.collect(file));
36
+ clearContext();
37
+ try {
38
+ await import(filepath);
39
+ const collectors = [defaultSuite, ...context.suites];
40
+ for (const c of collectors) {
41
+ context.currentSuite = c;
42
+ file.suites.push(await c.collect(file));
43
+ }
44
+ file.collected = true;
45
+ }
46
+ catch (e) {
47
+ file.error = e;
48
+ file.collected = false;
49
+ process.exitCode = 1;
46
50
  }
47
- file.suites = suites;
48
- result.push(file);
51
+ files.push(file);
52
+ }
53
+ const allSuites = files.reduce((suites, file) => suites.concat(file.suites), []);
54
+ interpretOnlyMode(allSuites);
55
+ allSuites.forEach((i) => {
56
+ if (i.mode === 'skip')
57
+ i.tasks.forEach(t => t.mode === 'run' && (t.state = 'skip'));
58
+ else
59
+ interpretOnlyMode(i.tasks);
60
+ });
61
+ return files;
62
+ }
63
+ /**
64
+ * If any items been marked as `only`, mark all other items as `skip`.
65
+ */
66
+ function interpretOnlyMode(items) {
67
+ if (items.some(i => i.mode === 'only')) {
68
+ items.forEach((i) => {
69
+ if (i.mode === 'run')
70
+ i.mode = 'skip';
71
+ else if (i.mode === 'only')
72
+ i.mode = 'run';
73
+ });
49
74
  }
50
- return result;
51
75
  }
52
76
  export async function runFile(file, ctx) {
53
77
  var _a, _b, _c, _d;
54
78
  const { reporter } = ctx;
79
+ const runableSuites = file.suites.filter(i => i.mode === 'run');
80
+ if (runableSuites.length === 0)
81
+ return;
55
82
  await ((_a = reporter.onFileBegin) === null || _a === void 0 ? void 0 : _a.call(reporter, file, ctx));
56
83
  await beforeFileHook.fire(file);
57
84
  for (const suite of file.suites) {
@@ -65,39 +92,40 @@ export async function runFile(file, ctx) {
65
92
  await afterFileHook.fire(file);
66
93
  await ((_d = reporter.onFileEnd) === null || _d === void 0 ? void 0 : _d.call(reporter, file, ctx));
67
94
  }
68
- export async function run(options = {}) {
69
- var _a, _b, _c;
70
- const { rootDir = process.cwd() } = options;
95
+ export async function run(config) {
96
+ var _a, _b, _c, _d;
97
+ const { rootDir = process.cwd() } = config;
98
+ // setup chai
71
99
  chai.use(await SnapshotPlugin({
72
100
  rootDir,
73
- update: options.updateSnapshot,
101
+ update: config.updateSnapshot,
74
102
  }));
75
- const paths = await fg(options.includes || ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], {
103
+ chai.use(SinonChai);
104
+ // collect files
105
+ let paths = await fg(config.includes || defaultIncludes, {
76
106
  absolute: true,
77
- cwd: options.rootDir,
78
- ignore: options.excludes || ['**/node_modules/**', '**/dist/**'],
107
+ cwd: config.rootDir,
108
+ ignore: config.excludes || defaultExcludes,
79
109
  });
110
+ if ((_a = config.nameFilters) === null || _a === void 0 ? void 0 : _a.length)
111
+ paths = paths.filter(i => config.nameFilters.some(f => i.includes(f)));
80
112
  if (!paths.length) {
81
113
  console.error('No test files found');
82
114
  process.exitCode = 1;
83
115
  return;
84
116
  }
85
117
  const reporter = new DefaultReporter();
86
- await ((_a = reporter.onStart) === null || _a === void 0 ? void 0 : _a.call(reporter, options));
118
+ await ((_b = reporter.onStart) === null || _b === void 0 ? void 0 : _b.call(reporter, config));
87
119
  const files = await collectFiles(paths);
88
120
  const ctx = {
89
121
  files,
90
- mode: isOnlyMode(files) ? 'only' : 'all',
91
- userOptions: options,
122
+ config,
92
123
  reporter,
93
124
  };
94
- await ((_b = reporter.onCollected) === null || _b === void 0 ? void 0 : _b.call(reporter, ctx));
125
+ await ((_c = reporter.onCollected) === null || _c === void 0 ? void 0 : _c.call(reporter, ctx));
95
126
  await beforeAllHook.fire();
96
127
  for (const file of files)
97
128
  await runFile(file, ctx);
98
129
  await afterAllHook.fire();
99
- await ((_c = reporter.onFinished) === null || _c === void 0 ? void 0 : _c.call(reporter, ctx));
100
- }
101
- function isOnlyMode(files) {
102
- return !!files.find(file => file.suites.find(suite => suite.mode === 'only' || suite.tasks.find(t => t.mode === 'only')));
130
+ await ((_d = reporter.onFinished) === null || _d === void 0 ? void 0 : _d.call(reporter, ctx));
103
131
  }
package/dist/suite.js CHANGED
@@ -22,7 +22,7 @@ function createSuiteCollector(mode, suiteName, factory) {
22
22
  name,
23
23
  mode,
24
24
  suite: {},
25
- status: 'init',
25
+ state: mode !== 'run' ? mode : undefined,
26
26
  fn,
27
27
  });
28
28
  }
package/dist/types.d.ts CHANGED
@@ -1,21 +1,25 @@
1
+ import { ViteDevServer } from 'vite';
1
2
  export declare type Awaitable<T> = Promise<T> | T;
2
3
  export interface UserOptions {
3
4
  includes?: string[];
4
5
  excludes?: string[];
5
6
  }
6
- export interface Options extends UserOptions {
7
+ export interface Config extends UserOptions {
7
8
  rootDir?: string;
8
9
  updateSnapshot?: boolean;
10
+ nameFilters?: string[];
11
+ server: ViteDevServer;
12
+ watch?: boolean;
9
13
  }
10
14
  export declare type RunMode = 'run' | 'skip' | 'only' | 'todo';
11
- export declare type TaskStatus = 'init' | 'pass' | 'fail' | 'skip' | 'todo';
15
+ export declare type TaskState = RunMode | 'pass' | 'fail';
12
16
  export interface Task {
13
17
  name: string;
14
18
  mode: RunMode;
15
19
  suite: Suite;
16
20
  fn: () => Awaitable<void>;
17
21
  file?: File;
18
- status: TaskStatus;
22
+ state?: TaskState;
19
23
  error?: unknown;
20
24
  }
21
25
  export declare type TestFunction = () => Awaitable<void>;
@@ -42,11 +46,12 @@ export declare type TestFactory = (test: (name: string, fn: TestFunction) => voi
42
46
  export interface File {
43
47
  filepath: string;
44
48
  suites: Suite[];
49
+ collected: boolean;
50
+ error?: unknown;
45
51
  }
46
52
  export interface RunnerContext {
47
53
  files: File[];
48
- mode: 'all' | 'only';
49
- userOptions: Options;
54
+ config: Config;
50
55
  reporter: Reporter;
51
56
  }
52
57
  export interface GlobalContext {
@@ -54,7 +59,7 @@ export interface GlobalContext {
54
59
  currentSuite: SuiteCollector | null;
55
60
  }
56
61
  export interface Reporter {
57
- onStart: (userOptions: Options) => Awaitable<void>;
62
+ onStart: (userOptions: Config) => Awaitable<void>;
58
63
  onCollected: (ctx: RunnerContext) => Awaitable<void>;
59
64
  onFinished: (ctx: RunnerContext) => Awaitable<void>;
60
65
  onSuiteBegin: (suite: Suite, ctx: RunnerContext) => Awaitable<void>;
package/dist/types.js CHANGED
@@ -1,2 +1 @@
1
- /* eslint-disable no-use-before-define */
2
1
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest",
3
- "version": "0.0.8",
3
+ "version": "0.0.12",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "keywords": [],
@@ -37,6 +37,7 @@
37
37
  "@antfu/ni": "^0.11.0",
38
38
  "@types/minimist": "^1.2.2",
39
39
  "@types/node": "^16.11.11",
40
+ "@types/sinon": "^10.0.6",
40
41
  "bumpp": "^7.1.1",
41
42
  "eslint": "^8.3.0",
42
43
  "esno": "^0.12.1",
@@ -46,13 +47,17 @@
46
47
  "dependencies": {
47
48
  "@jest/test-result": "^27.4.2",
48
49
  "@types/chai": "^4.2.22",
50
+ "@types/sinon-chai": "^3.2.6",
49
51
  "chai": "^4.3.4",
50
52
  "fast-glob": "^3.2.7",
51
53
  "find-up": "^6.2.0",
52
54
  "jest-snapshot": "^27.4.2",
53
55
  "jest-util": "^27.4.2",
54
56
  "minimist": "^1.2.5",
57
+ "ora": "^6.0.1",
55
58
  "picocolors": "^1.0.0",
59
+ "sinon": "^12.0.1",
60
+ "sinon-chai": "^3.7.0",
56
61
  "vite-node": "^0.1.10"
57
62
  },
58
63
  "scripts": {