vitest 0.0.7 → 0.0.11

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,11 +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.
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)
14
17
 
15
18
  ```ts
16
19
  import { it, describe, expect, assert } from 'vitest'
@@ -34,12 +37,74 @@ describe('suite name', () => {
34
37
  $ npx vitest
35
38
  ```
36
39
 
40
+ ## Filtering
41
+
42
+ ### Skipping suites and tasks
43
+
44
+ Use `.skip` to avoid running certain suites or tests
45
+
46
+ ```ts
47
+ describe.skip('skipped suite', () => {
48
+ it('task', () => {
49
+ // Suite skipped, no error
50
+ assert.equal(Math.sqrt(4), 3)
51
+ })
52
+ })
53
+
54
+ describe('suite', () => {
55
+ it.skip('skipped task', () => {
56
+ // Task skipped, no error
57
+ assert.equal(Math.sqrt(4), 3)
58
+ })
59
+ })
60
+ ```
61
+
62
+ ### Selecting suites and tests to run
63
+
64
+ Use `.only` to only run certain suites or tests
65
+
66
+ ```ts
67
+ // Only this suite (and others marked with only) are run
68
+ describe.only('suite', () => {
69
+ it('task', () => {
70
+ assert.equal(Math.sqrt(4), 3)
71
+ })
72
+ })
73
+
74
+ describe('another suite', () => {
75
+ it('skipped task', () => {
76
+ // Task skipped, as tests are running in Only mode
77
+ assert.equal(Math.sqrt(4), 3)
78
+ })
79
+
80
+ it.only('task', () => {
81
+ // Only this task (and others marked with only) are run
82
+ assert.equal(Math.sqrt(4), 2)
83
+ })
84
+ })
85
+ ```
86
+
87
+ ### Unimplemented suites and tests
88
+
89
+ Use `.todo` to stub suites and tests that should be implemented
90
+
91
+ ```ts
92
+ // An entry will be shown in the report for this suite
93
+ describe.todo('unimplemented suite')
94
+
95
+ // An entry will be shown in the report for this task
96
+ describe('suite', () => {
97
+ it.todo('unimplemented task')
98
+ })
99
+ ```
100
+
37
101
  ## TODO
38
102
 
39
- - [ ] Reporter & Better output
103
+ - [x] Reporter & Better output
104
+ - [x] Task filter
105
+ - [x] Mock
106
+ - [ ] Parallel Executing
40
107
  - [ ] CLI Help
41
- - [ ] Task filter
42
- - [ ] Mock
43
108
  - [ ] JSDom
44
109
  - [ ] Watch
45
110
  - [ ] Coverage
@@ -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;
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 } = sinon;
@@ -0,0 +1,19 @@
1
+ import { File, Reporter, RunnerContext, Suite, Task } from '../types';
2
+ export declare class DefaultReporter implements Reporter {
3
+ indent: number;
4
+ start: number;
5
+ end: number;
6
+ onStart(): void;
7
+ onCollected(): void;
8
+ onSuiteBegin(suite: Suite): void;
9
+ onSuiteEnd(suite: Suite): void;
10
+ onFileBegin(file: File): void;
11
+ onFileEnd(): 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;
18
+ onSnapshotUpdate(): void;
19
+ }
@@ -0,0 +1,114 @@
1
+ import { relative } from 'path';
2
+ import { performance } from 'perf_hooks';
3
+ import c from 'picocolors';
4
+ import ora from 'ora';
5
+ const DOT = '· ';
6
+ const CHECK = '✔ ';
7
+ const CROSS = '⤫ ';
8
+ export class DefaultReporter {
9
+ constructor() {
10
+ this.indent = 0;
11
+ this.start = 0;
12
+ this.end = 0;
13
+ }
14
+ onStart() {
15
+ this.indent = 0;
16
+ }
17
+ onCollected() {
18
+ this.start = performance.now();
19
+ }
20
+ onSuiteBegin(suite) {
21
+ if (suite.name) {
22
+ this.indent += 1;
23
+ const name = DOT + suite.name;
24
+ if (suite.mode === 'skip')
25
+ this.log(c.dim(c.yellow(`${name} (skipped)`)));
26
+ else if (suite.mode === 'todo')
27
+ this.log(c.dim(`${name} (todo)`));
28
+ else
29
+ this.log(name);
30
+ }
31
+ }
32
+ onSuiteEnd(suite) {
33
+ if (suite.name)
34
+ this.indent -= 1;
35
+ }
36
+ onFileBegin(file) {
37
+ this.log(`- ${relative(process.cwd(), file.filepath)} ${c.dim(`(${file.suites.flatMap(i => i.tasks).length} tests)`)}`);
38
+ }
39
+ onFileEnd() {
40
+ this.log();
41
+ }
42
+ onTaskBegin(task) {
43
+ this.indent += 1;
44
+ // @ts-expect-error
45
+ task.__ora = ora({ text: task.name, prefixText: this.getIndent().slice(1), spinner: 'arc' }).start();
46
+ }
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.status === 'pass') {
52
+ this.log(`${c.green(CHECK + task.name)}`);
53
+ }
54
+ else if (task.status === 'skip') {
55
+ this.log(c.dim(c.yellow(`${DOT + task.name} (skipped)`)));
56
+ }
57
+ else if (task.status === 'todo') {
58
+ this.log(c.dim(`${DOT + task.name} (todo)`));
59
+ }
60
+ else {
61
+ this.error(`${c.red(`${CROSS}${task.name}`)}`);
62
+ process.exitCode = 1;
63
+ }
64
+ this.indent -= 1;
65
+ }
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 passed = tasks.filter(i => i.status === 'pass');
71
+ const failed = tasks.filter(i => i.status === 'fail');
72
+ const skipped = tasks.filter(i => i.status === 'skip');
73
+ const todo = tasks.filter(i => i.status === 'todo');
74
+ this.indent = 0;
75
+ if (failedFiles.length) {
76
+ this.error(c.bold(`\nFailed to parse ${failedFiles.length} files:`));
77
+ failedFiles.forEach((i) => {
78
+ this.error(`\n- ${i.filepath}`);
79
+ console.error(i.error || 'Unknown error');
80
+ this.log();
81
+ });
82
+ }
83
+ if (failed.length) {
84
+ this.error(c.bold(`\nFailed Tests (${failed.length})`));
85
+ failed.forEach((task) => {
86
+ var _a;
87
+ 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}`)}`);
88
+ console.error(task.error || 'Unknown error');
89
+ this.log();
90
+ });
91
+ }
92
+ this.log(c.green(`Passed ${passed.length} / ${tasks.length}`));
93
+ if (failed.length)
94
+ this.log(c.red(`Failed ${failed.length} / ${tasks.length}`));
95
+ if (skipped.length)
96
+ this.log(c.yellow(`Skipped ${skipped.length}`));
97
+ if (todo.length)
98
+ this.log(c.dim(`Todo ${todo.length}`));
99
+ this.log(`Time ${(this.end - this.start).toFixed(2)}ms`);
100
+ }
101
+ getIndent(offest = 0) {
102
+ return ' '.repeat((this.indent + offest) * 2);
103
+ }
104
+ log(msg = '', indentOffset = 0) {
105
+ // eslint-disable-next-line no-console
106
+ console.log(`${this.getIndent(indentOffset)}${msg}`);
107
+ }
108
+ error(msg = '', indentOffset = 0) {
109
+ // eslint-disable-next-line no-console
110
+ console.error(c.red(`${this.getIndent(indentOffset)}${msg}`));
111
+ }
112
+ onSnapshotUpdate() {
113
+ }
114
+ }
package/dist/run.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { File, Options, Task, TaskResult } from './types';
2
- export declare function runTasks(tasks: Task[]): Promise<TaskResult[]>;
1
+ import { File, Options, Task, RunnerContext } from './types';
2
+ export declare function runTask(task: Task, ctx: RunnerContext): Promise<void>;
3
3
  export declare function collectFiles(files: string[]): Promise<File[]>;
4
- export declare function runFile(file: File): Promise<void>;
4
+ export declare function runFile(file: File, ctx: RunnerContext): Promise<void>;
5
5
  export declare function run(options?: Options): Promise<void>;
package/dist/run.js CHANGED
@@ -1,119 +1,115 @@
1
- import { relative } from 'path';
2
- import { performance } from 'perf_hooks';
3
- import c from 'picocolors';
4
1
  import chai from 'chai';
5
2
  import fg from 'fast-glob';
3
+ import SinonChai from 'sinon-chai';
6
4
  import { clearContext, defaultSuite } from './suite';
7
5
  import { context } from './context';
8
6
  import { afterEachHook, afterFileHook, afterAllHook, afterSuiteHook, beforeEachHook, beforeFileHook, beforeAllHook, beforeSuiteHook } from './hooks';
9
- import { SnapshotPlugin } from './snapshot/index';
10
- export async function runTasks(tasks) {
11
- const results = [];
12
- for (const task of tasks) {
13
- await beforeEachHook.fire(task);
14
- task.result = {};
7
+ import { SnapshotPlugin } from './snapshot';
8
+ import { DefaultReporter } from './reporters/default';
9
+ import { defaultIncludes, defaultExcludes } from './constants';
10
+ export async function runTask(task, ctx) {
11
+ var _a, _b;
12
+ const { reporter } = ctx;
13
+ await ((_a = reporter.onTaskBegin) === null || _a === void 0 ? void 0 : _a.call(reporter, task, ctx));
14
+ await beforeEachHook.fire(task);
15
+ if (task.suite.mode === 'skip' || task.mode === 'skip') {
16
+ task.status = 'skip';
17
+ }
18
+ else if (task.suite.mode === 'todo' || task.mode === 'todo') {
19
+ task.status = 'todo';
20
+ }
21
+ else {
15
22
  try {
16
23
  await task.fn();
24
+ task.status = 'pass';
17
25
  }
18
26
  catch (e) {
19
- task.result.error = e;
27
+ task.status = 'fail';
28
+ task.error = e;
20
29
  }
21
- await afterEachHook.fire(task);
22
30
  }
23
- return results;
31
+ await afterEachHook.fire(task);
32
+ await ((_b = reporter.onTaskEnd) === null || _b === void 0 ? void 0 : _b.call(reporter, task, ctx));
24
33
  }
25
- // TODO: REPORTER
26
- const { log } = console;
27
34
  export async function collectFiles(files) {
28
35
  const result = [];
29
36
  for (const filepath of files) {
30
- clearContext();
31
- await import(filepath);
32
- const suites = [defaultSuite, ...context.suites];
33
- const collected = [];
34
- for (const suite of suites) {
35
- context.currentSuite = suite;
36
- const tasks = await suite.collect();
37
- collected.push([suite, tasks]);
38
- }
39
37
  const file = {
40
38
  filepath,
41
- suites,
42
- collected,
39
+ suites: [],
40
+ collected: false,
43
41
  };
44
- file.collected.forEach(([, tasks]) => tasks.forEach(task => task.file = file));
42
+ clearContext();
43
+ try {
44
+ await import(filepath);
45
+ const collectors = [defaultSuite, ...context.suites];
46
+ for (const c of collectors) {
47
+ context.currentSuite = c;
48
+ file.suites.push(await c.collect(file));
49
+ }
50
+ file.collected = true;
51
+ }
52
+ catch (e) {
53
+ file.error = e;
54
+ file.collected = false;
55
+ process.exitCode = 1;
56
+ }
45
57
  result.push(file);
46
58
  }
47
59
  return result;
48
60
  }
49
- export async function runFile(file) {
61
+ export async function runFile(file, ctx) {
62
+ var _a, _b, _c, _d;
63
+ const { reporter } = ctx;
64
+ await ((_a = reporter.onFileBegin) === null || _a === void 0 ? void 0 : _a.call(reporter, file, ctx));
50
65
  await beforeFileHook.fire(file);
51
- for (const [suite, tasks] of file.collected) {
66
+ for (const suite of file.suites) {
67
+ await ((_b = reporter.onSuiteBegin) === null || _b === void 0 ? void 0 : _b.call(reporter, suite, ctx));
52
68
  await beforeSuiteHook.fire(suite);
53
- let indent = 1;
54
- if (suite.name) {
55
- log(' '.repeat(indent * 2) + suite.name);
56
- indent += 1;
57
- }
58
- if (suite.mode === 'run' || suite.mode === 'only') {
59
- // TODO: If there is a task with 'only', skip all others
60
- await runTasks(tasks);
61
- for (const t of tasks) {
62
- if (t.result && t.result.error === undefined) {
63
- log(`${' '.repeat(indent * 2)}${c.inverse(c.green(' PASS '))} ${c.green(t.name)}`);
64
- }
65
- else {
66
- console.error(`${' '.repeat(indent * 2)}${c.inverse(c.red(' FAIL '))} ${c.red(t.name)}`);
67
- console.error(' '.repeat((indent + 2) * 2) + c.red(String(t.result.error)));
68
- process.exitCode = 1;
69
- }
70
- }
71
- }
72
- else if (suite.mode === 'skip') {
73
- log(`${' '.repeat(indent * 2)}${c.inverse(c.gray(' SKIP '))}`);
74
- }
75
- else if (suite.mode === 'todo') {
76
- // TODO: In Jest, these suites are collected and printed together at the end of the report
77
- log(`${' '.repeat(indent * 2)}${c.inverse(c.gray(' TODO '))}`);
78
- }
79
- if (suite.name)
80
- indent -= 1;
69
+ for (const t of suite.tasks)
70
+ await runTask(t, ctx);
81
71
  await afterSuiteHook.fire(suite);
72
+ await ((_c = reporter.onSuiteEnd) === null || _c === void 0 ? void 0 : _c.call(reporter, suite, ctx));
82
73
  }
83
74
  await afterFileHook.fire(file);
75
+ await ((_d = reporter.onFileEnd) === null || _d === void 0 ? void 0 : _d.call(reporter, file, ctx));
84
76
  }
85
77
  export async function run(options = {}) {
78
+ var _a, _b, _c;
86
79
  const { rootDir = process.cwd() } = options;
80
+ // setup chai
87
81
  chai.use(await SnapshotPlugin({
88
82
  rootDir,
89
83
  update: options.updateSnapshot,
90
84
  }));
91
- const paths = await fg(options.includes || ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], {
85
+ chai.use(SinonChai);
86
+ // collect files
87
+ const paths = await fg(options.includes || defaultIncludes, {
92
88
  absolute: true,
93
89
  cwd: options.rootDir,
94
- ignore: options.excludes || ['**/node_modules/**', '**/dist/**'],
90
+ ignore: options.excludes || defaultExcludes,
95
91
  });
96
92
  if (!paths.length) {
97
93
  console.error('No test files found');
98
94
  process.exitCode = 1;
99
95
  return;
100
96
  }
97
+ const reporter = new DefaultReporter();
98
+ await ((_a = reporter.onStart) === null || _a === void 0 ? void 0 : _a.call(reporter, options));
101
99
  const files = await collectFiles(paths);
100
+ const ctx = {
101
+ files,
102
+ mode: isOnlyMode(files) ? 'only' : 'all',
103
+ userOptions: options,
104
+ reporter,
105
+ };
106
+ await ((_b = reporter.onCollected) === null || _b === void 0 ? void 0 : _b.call(reporter, ctx));
102
107
  await beforeAllHook.fire();
103
- const start = performance.now();
104
- for (const file of files) {
105
- log(`${relative(process.cwd(), file.filepath)}`);
106
- await runFile(file);
107
- log();
108
- }
109
- const end = performance.now();
108
+ for (const file of files)
109
+ await runFile(file, ctx);
110
110
  await afterAllHook.fire();
111
- const tasks = files.reduce((acc, file) => acc.concat(file.collected.flatMap(([, tasks]) => tasks)), []);
112
- const passed = tasks.filter(i => { var _a; return !((_a = i.result) === null || _a === void 0 ? void 0 : _a.error); });
113
- const failed = tasks.filter(i => { var _a; return (_a = i.result) === null || _a === void 0 ? void 0 : _a.error; });
114
- log(`Passed ${passed.length} / ${tasks.length}`);
115
- if (failed.length)
116
- log(`Failed ${failed.length} / ${tasks.length}`);
117
- log(`Time ${(end - start).toFixed(2)}ms`);
118
- log();
111
+ await ((_c = reporter.onFinished) === null || _c === void 0 ? void 0 : _c.call(reporter, ctx));
112
+ }
113
+ function isOnlyMode(files) {
114
+ return !!files.find(file => file.suites.find(suite => suite.mode === 'only' || suite.tasks.find(t => t.mode === 'only')));
119
115
  }
package/dist/suite.d.ts CHANGED
@@ -1,12 +1,22 @@
1
- import { Suite, TestFactory } from './types';
2
- export declare const defaultSuite: Suite;
3
- export declare const test: (name: string, fn: () => Promise<void> | void) => void;
4
- export declare function clearContext(): void;
5
- export declare function suite(suiteName: string, factory?: TestFactory): Suite;
1
+ import { SuiteCollector, TestFactory, TestFunction } from './types';
2
+ export declare const defaultSuite: SuiteCollector;
3
+ export declare const test: {
4
+ (name: string, fn: TestFunction): void;
5
+ skip(name: string, fn: TestFunction): void;
6
+ only(name: string, fn: TestFunction): void;
7
+ todo(name: string): void;
8
+ };
9
+ export declare function suite(suiteName: string, factory?: TestFactory): SuiteCollector;
6
10
  export declare namespace suite {
7
- var skip: (suiteName: string, factory?: TestFactory | undefined) => Suite;
8
- var only: (suiteName: string, factory?: TestFactory | undefined) => Suite;
9
- var todo: (suiteName: string) => Suite;
11
+ var skip: (suiteName: string, factory?: TestFactory | undefined) => SuiteCollector;
12
+ var only: (suiteName: string, factory?: TestFactory | undefined) => SuiteCollector;
13
+ var todo: (suiteName: string) => SuiteCollector;
10
14
  }
11
15
  export declare const describe: typeof suite;
12
- export declare const it: (name: string, fn: () => Promise<void> | void) => void;
16
+ export declare const it: {
17
+ (name: string, fn: TestFunction): void;
18
+ skip(name: string, fn: TestFunction): void;
19
+ only(name: string, fn: TestFunction): void;
20
+ todo(name: string): void;
21
+ };
22
+ export declare function clearContext(): void;
package/dist/suite.js CHANGED
@@ -1,55 +1,75 @@
1
1
  import { context } from './context';
2
2
  export const defaultSuite = suite('');
3
- export const test = (name, fn) => (context.currentSuite || defaultSuite).test(name, fn);
4
- export function clearContext() {
5
- context.suites.length = 0;
6
- defaultSuite.clear();
7
- context.currentSuite = defaultSuite;
3
+ function getCurrentSuite() {
4
+ return context.currentSuite || defaultSuite;
8
5
  }
9
- function processSuite(mode, suiteName, factory) {
6
+ export const test = (name, fn) => getCurrentSuite().test(name, fn);
7
+ test.skip = (name, fn) => getCurrentSuite().test.skip(name, fn);
8
+ test.only = (name, fn) => getCurrentSuite().test.only(name, fn);
9
+ test.todo = (name) => getCurrentSuite().test.todo(name);
10
+ function createSuiteCollector(mode, suiteName, factory) {
10
11
  const queue = [];
11
12
  const factoryQueue = [];
12
- const suite = {
13
+ const collector = {
13
14
  name: suiteName,
14
15
  mode,
15
16
  test,
16
17
  collect,
17
18
  clear,
18
19
  };
19
- function test(name, fn) {
20
- const task = {
21
- suite,
20
+ function collectTask(name, fn, mode) {
21
+ queue.push({
22
22
  name,
23
+ mode,
24
+ suite: {},
25
+ status: 'init',
23
26
  fn,
24
- };
25
- queue.push(task);
27
+ });
28
+ }
29
+ function test(name, fn) {
30
+ collectTask(name, fn, mode);
26
31
  }
32
+ test.skip = (name, fn) => collectTask(name, fn, 'skip');
33
+ test.only = (name, fn) => collectTask(name, fn, 'only');
34
+ test.todo = (name) => collectTask(name, () => { }, 'todo');
27
35
  function clear() {
28
36
  queue.length = 0;
29
37
  factoryQueue.length = 0;
30
38
  }
31
- async function collect() {
39
+ async function collect(file) {
32
40
  factoryQueue.length = 0;
33
41
  if (factory)
34
42
  await factory(test);
35
- return [...factoryQueue, ...queue];
43
+ const tasks = [...factoryQueue, ...queue];
44
+ const suite = {
45
+ name: collector.name,
46
+ mode: collector.mode,
47
+ tasks,
48
+ file,
49
+ };
50
+ tasks.forEach((task) => {
51
+ task.suite = suite;
52
+ if (file)
53
+ task.file = file;
54
+ });
55
+ return suite;
36
56
  }
37
- context.currentSuite = suite;
38
- context.suites.push(suite);
39
- return suite;
57
+ context.currentSuite = collector;
58
+ context.suites.push(collector);
59
+ return collector;
40
60
  }
41
61
  export function suite(suiteName, factory) {
42
- return processSuite('run', suiteName, factory);
62
+ return createSuiteCollector('run', suiteName, factory);
43
63
  }
44
- suite.skip = function skip(suiteName, factory) {
45
- return processSuite('skip', suiteName, factory);
46
- };
47
- suite.only = function skip(suiteName, factory) {
48
- return processSuite('only', suiteName, factory);
49
- };
50
- suite.todo = function skip(suiteName) {
51
- return processSuite('todo', suiteName);
52
- };
64
+ suite.skip = (suiteName, factory) => createSuiteCollector('skip', suiteName, factory);
65
+ suite.only = (suiteName, factory) => createSuiteCollector('only', suiteName, factory);
66
+ suite.todo = (suiteName) => createSuiteCollector('todo', suiteName);
53
67
  // alias
54
68
  export const describe = suite;
55
69
  export const it = test;
70
+ // utils
71
+ export function clearContext() {
72
+ context.suites.length = 0;
73
+ defaultSuite.clear();
74
+ context.currentSuite = defaultSuite;
75
+ }
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export declare type Awaitable<T> = Promise<T> | T;
1
2
  export interface UserOptions {
2
3
  includes?: string[];
3
4
  excludes?: string[];
@@ -6,31 +7,63 @@ export interface Options extends UserOptions {
6
7
  rootDir?: string;
7
8
  updateSnapshot?: boolean;
8
9
  }
9
- export interface TaskResult {
10
- error?: unknown;
11
- }
10
+ export declare type RunMode = 'run' | 'skip' | 'only' | 'todo';
11
+ export declare type TaskStatus = 'init' | 'pass' | 'fail' | 'skip' | 'todo';
12
12
  export interface Task {
13
13
  name: string;
14
+ mode: RunMode;
14
15
  suite: Suite;
15
- fn: () => Promise<void> | void;
16
+ fn: () => Awaitable<void>;
16
17
  file?: File;
17
- result?: TaskResult;
18
+ status: TaskStatus;
19
+ error?: unknown;
20
+ }
21
+ export declare type TestFunction = () => Awaitable<void>;
22
+ export interface TestCollector {
23
+ (name: string, fn: TestFunction): void;
24
+ only: (name: string, fn: TestFunction) => void;
25
+ skip: (name: string, fn: TestFunction) => void;
26
+ todo: (name: string) => void;
18
27
  }
19
- export declare type SuiteMode = 'run' | 'skip' | 'only' | 'todo';
20
28
  export interface Suite {
21
29
  name: string;
22
- mode: SuiteMode;
23
- test: (name: string, fn: () => Promise<void> | void) => void;
24
- collect: () => Promise<Task[]>;
30
+ mode: RunMode;
31
+ tasks: Task[];
32
+ file?: File;
33
+ }
34
+ export interface SuiteCollector {
35
+ name: string;
36
+ mode: RunMode;
37
+ test: TestCollector;
38
+ collect: (file?: File) => Promise<Suite>;
25
39
  clear: () => void;
26
40
  }
27
- export declare type TestFactory = (test: Suite['test']) => Promise<void> | void;
41
+ export declare type TestFactory = (test: (name: string, fn: TestFunction) => void) => Awaitable<void>;
28
42
  export interface File {
29
43
  filepath: string;
30
44
  suites: Suite[];
31
- collected: [Suite, Task[]][];
45
+ collected: boolean;
46
+ error?: unknown;
47
+ }
48
+ export interface RunnerContext {
49
+ files: File[];
50
+ mode: 'all' | 'only';
51
+ userOptions: Options;
52
+ reporter: Reporter;
32
53
  }
33
54
  export interface GlobalContext {
34
- suites: Suite[];
35
- currentSuite: Suite | null;
55
+ suites: SuiteCollector[];
56
+ currentSuite: SuiteCollector | null;
57
+ }
58
+ export interface Reporter {
59
+ onStart: (userOptions: Options) => Awaitable<void>;
60
+ onCollected: (ctx: RunnerContext) => Awaitable<void>;
61
+ onFinished: (ctx: RunnerContext) => Awaitable<void>;
62
+ onSuiteBegin: (suite: Suite, ctx: RunnerContext) => Awaitable<void>;
63
+ onSuiteEnd: (suite: Suite, ctx: RunnerContext) => Awaitable<void>;
64
+ onFileBegin: (file: File, ctx: RunnerContext) => Awaitable<void>;
65
+ onFileEnd: (file: File, ctx: RunnerContext) => Awaitable<void>;
66
+ onTaskBegin: (task: Task, ctx: RunnerContext) => Awaitable<void>;
67
+ onTaskEnd: (task: Task, ctx: RunnerContext) => Awaitable<void>;
68
+ onSnapshotUpdate: () => Awaitable<void>;
36
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest",
3
- "version": "0.0.7",
3
+ "version": "0.0.11",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "keywords": [],
@@ -32,11 +32,21 @@
32
32
  "bin": {
33
33
  "vitest": "./bin/vitest.mjs"
34
34
  },
35
+ "scripts": {
36
+ "build": "tsc",
37
+ "watch": "tsc --watch",
38
+ "lint": "eslint \"{src,test}/**/*.ts\"",
39
+ "prepublishOnly": "nr build",
40
+ "release": "bumpp --commit --push --tag && pnpm publish",
41
+ "test": "node bin/vitest.mjs --dev",
42
+ "test:update": "nr test -u"
43
+ },
35
44
  "devDependencies": {
36
45
  "@antfu/eslint-config": "^0.11.1",
37
46
  "@antfu/ni": "^0.11.0",
38
47
  "@types/minimist": "^1.2.2",
39
48
  "@types/node": "^16.11.11",
49
+ "@types/sinon": "^10.0.6",
40
50
  "bumpp": "^7.1.1",
41
51
  "eslint": "^8.3.0",
42
52
  "esno": "^0.12.1",
@@ -46,21 +56,17 @@
46
56
  "dependencies": {
47
57
  "@jest/test-result": "^27.4.2",
48
58
  "@types/chai": "^4.2.22",
59
+ "@types/sinon-chai": "^3.2.6",
49
60
  "chai": "^4.3.4",
50
61
  "fast-glob": "^3.2.7",
51
62
  "find-up": "^6.2.0",
52
63
  "jest-snapshot": "^27.4.2",
53
64
  "jest-util": "^27.4.2",
54
65
  "minimist": "^1.2.5",
66
+ "ora": "^6.0.1",
55
67
  "picocolors": "^1.0.0",
56
- "vite-node": "^0.1.9"
57
- },
58
- "scripts": {
59
- "build": "tsc",
60
- "watch": "tsc --watch",
61
- "lint": "eslint \"{src,test}/**/*.ts\"",
62
- "release": "bumpp --commit --push --tag && pnpm publish",
63
- "test": "node bin/vitest.mjs --dev",
64
- "test:update": "nr test -u"
68
+ "sinon": "^12.0.1",
69
+ "sinon-chai": "^3.7.0",
70
+ "vite-node": "^0.1.10"
65
71
  }
66
- }
72
+ }