vitest 0.0.19 → 0.0.23

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![NPM version](https://img.shields.io/npm/v/vitest?color=a1b858&label=)](https://www.npmjs.com/package/vitest)
4
4
 
5
- A blazing fast test runner powered by Vite.
5
+ A blazing fast unit test framework powered by Vite.
6
6
 
7
7
  ## Features
8
8
 
@@ -155,10 +155,10 @@ describe('suite', () => {
155
155
  - [x] Task filter
156
156
  - [x] Mock
157
157
  - [x] Global Mode & Types
158
- - [ ] Parallel Executing
159
- - [ ] CLI Help
160
- - [ ] JSDom
161
- - [ ] Watch
158
+ - [ ] Concurrent Executing
159
+ - [x] CLI Help
160
+ - [x] JSDom
161
+ - [x] Watch
162
162
  - [ ] Source Map
163
163
  - [ ] Coverage
164
164
 
package/bin/vitest.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict'
3
3
 
4
- import '../dist/entry.js'
4
+ import '../dist/cli.js'
package/dist/cli.d.ts CHANGED
@@ -1 +1,10 @@
1
- export {};
1
+ import type { UserOptions } from './types';
2
+ declare global {
3
+ namespace NodeJS {
4
+ interface Process {
5
+ __vitest__: {
6
+ options: Required<UserOptions>;
7
+ };
8
+ }
9
+ }
10
+ }
package/dist/cli.js CHANGED
@@ -1,29 +1,56 @@
1
- var _a;
2
- import minimist from 'minimist';
3
- import c from 'picocolors';
4
- import { run } from './run';
5
- const { log } = console;
6
- const argv = minimist(process.argv.slice(2), {
7
- alias: {
8
- u: 'update',
9
- w: 'watch',
10
- },
11
- string: ['root', 'config'],
12
- boolean: ['update', 'dev', 'global', 'watch', 'jsdom'],
13
- unknown(name) {
14
- if (name[0] === '-') {
15
- console.error(c.red(`Unknown argument: ${name}`));
16
- help();
17
- process.exit(1);
18
- }
19
- return true;
20
- },
21
- });
22
- // @ts-expect-error
23
- const server = (_a = process === null || process === void 0 ? void 0 : process.__vite_node__) === null || _a === void 0 ? void 0 : _a.server;
24
- const viteConfig = (server === null || server === void 0 ? void 0 : server.config) || {};
25
- const testOptions = viteConfig.test || {};
26
- await run(Object.assign(Object.assign(Object.assign({}, argv), testOptions), { server, updateSnapshot: argv.update, rootDir: argv.root || process.cwd(), nameFilters: argv._ }));
27
- function help() {
28
- log('Help: finish help');
29
- }
1
+ import { fileURLToPath } from 'url';
2
+ import { resolve, dirname } from 'path';
3
+ import { findUp } from 'find-up';
4
+ import sade from 'sade';
5
+ import { run as startViteNode } from './node/index.js';
6
+ // TODO: use bundler
7
+ const version = '0.0.0';
8
+ sade('vitest [filter]', true)
9
+ .version(version)
10
+ .describe('A blazing fast unit test framework powered by Vite.')
11
+ .option('-r, --root', 'root path', process.cwd())
12
+ .option('-c, --config', 'path to config file')
13
+ .option('-w, --watch', 'watch mode', false)
14
+ .option('-u, --update', 'update snapshot', false)
15
+ .option('--global', 'inject apis globally', false)
16
+ .option('--dev', 'dev mode', false)
17
+ .option('--jsdom', 'mock browser api using JSDOM', false)
18
+ .action(async (filters, options) => {
19
+ process.env.VITEST = 'true';
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+ const root = resolve(options.root || process.cwd());
22
+ const configPath = options.config
23
+ ? resolve(root, options.config)
24
+ : await findUp(['vitest.config.ts', 'vitest.config.js', 'vitest.config.mjs', 'vite.config.ts', 'vite.config.js', 'vite.config.mjs'], { cwd: root });
25
+ options.config = configPath;
26
+ options.root = root;
27
+ options.filters = filters
28
+ ? Array.isArray(filters)
29
+ ? filters
30
+ : [filters]
31
+ : undefined;
32
+ process.__vitest__ = {
33
+ options,
34
+ };
35
+ await startViteNode({
36
+ root,
37
+ files: [
38
+ resolve(__dirname, options.dev ? '../src/entry.ts' : './entry.js'),
39
+ ],
40
+ config: configPath,
41
+ defaultConfig: {
42
+ optimizeDeps: {
43
+ exclude: [
44
+ 'vitest',
45
+ ],
46
+ },
47
+ },
48
+ shouldExternalize(id) {
49
+ if (id.includes('/node_modules/vitest/'))
50
+ return false;
51
+ else
52
+ return id.includes('/node_modules/');
53
+ },
54
+ });
55
+ })
56
+ .parse(process.argv);
package/dist/entry.js CHANGED
@@ -1,38 +1,6 @@
1
- import { fileURLToPath } from 'url';
2
- import { resolve, dirname } from 'path';
3
- import minimist from 'minimist';
4
- import { findUp } from 'find-up';
5
- import { run } from './node.js';
6
- process.env.VITEST = 'true';
7
- const argv = minimist(process.argv.slice(2), {
8
- alias: {
9
- c: 'config',
10
- },
11
- string: ['root', 'config'],
12
- boolean: ['dev'],
13
- });
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
- const root = resolve(argv.root || process.cwd());
16
- const configPath = argv.config
17
- ? resolve(root, argv.config)
18
- : await findUp(['vitest.config.ts', 'vitest.config.js', 'vitest.config.mjs', 'vite.config.ts', 'vite.config.js', 'vite.config.mjs'], { cwd: root });
19
- await run({
20
- root,
21
- files: [
22
- resolve(__dirname, argv.dev ? '../src/cli.ts' : './cli.js'),
23
- ],
24
- config: configPath,
25
- defaultConfig: {
26
- optimizeDeps: {
27
- exclude: [
28
- 'vitest',
29
- ],
30
- },
31
- },
32
- shouldExternalize(id) {
33
- if (id.includes('/node_modules/vitest/'))
34
- return false;
35
- else
36
- return id.includes('/node_modules/');
37
- },
38
- });
1
+ import { run } from './run';
2
+ if (!process.__vite_node__ || !process.__vitest__)
3
+ throw new Error('Vitest can only run in vite-node environment, please use the CLI to start the process');
4
+ const inlineOptions = process.__vite_node__.server.config.test || {};
5
+ const cliOptions = process.__vitest__.options || {};
6
+ await run(Object.assign(Object.assign({}, inlineOptions), cliOptions));
@@ -19,7 +19,7 @@ export function JestChaiExpect() {
19
19
  const obj = utils.flag(this, 'object');
20
20
  this.assert(Boolean(obj), 'expected #{this} to be truthy', 'expected #{this} to not be truthy', obj);
21
21
  });
22
- utils.addMethod(proto, 'toFalsy', function () {
22
+ utils.addMethod(proto, 'toBeFalsy', function () {
23
23
  const obj = utils.flag(this, 'object');
24
24
  this.assert(!obj, 'expected #{this} to be falsy', 'expected #{this} to not be falsy', obj);
25
25
  });
@@ -1,2 +1,2 @@
1
- import { Config } from 'vitest';
2
- export declare function setupChai(config: Config): Promise<void>;
1
+ import { ResolvedConfig } from 'vitest';
2
+ export declare function setupChai(config: ResolvedConfig): Promise<void>;
@@ -5,8 +5,5 @@ import { SnapshotPlugin } from './snapshot';
5
5
  export async function setupChai(config) {
6
6
  chai.use(SinonChai);
7
7
  chai.use(JestChaiExpect());
8
- chai.use(await SnapshotPlugin({
9
- rootDir: config.rootDir || process.cwd(),
10
- update: config.updateSnapshot,
11
- }));
8
+ chai.use(await SnapshotPlugin(config));
12
9
  }
@@ -1,7 +1,7 @@
1
1
  import { ChaiPlugin } from '../types';
2
2
  import { SnapshotManager } from './manager';
3
3
  export interface SnapshotOptions {
4
- rootDir: string;
4
+ root: string;
5
5
  update?: boolean;
6
6
  }
7
7
  export declare function getSnapshotManager(): SnapshotManager;
@@ -6,7 +6,7 @@ export function getSnapshotManager() {
6
6
  return _manager;
7
7
  }
8
8
  export async function SnapshotPlugin(options) {
9
- const { rootDir } = options;
9
+ const { root: rootDir } = options;
10
10
  _manager = new SnapshotManager({
11
11
  rootDir,
12
12
  update: options.update,
@@ -1,4 +1,5 @@
1
1
  import { JSDOM } from 'jsdom';
2
+ import { KEYS } from './keys';
2
3
  export function setupJSDOM(global) {
3
4
  const dom = new JSDOM('<!DOCTYPE html>', {
4
5
  pretendToBeVisual: true,
@@ -6,7 +7,7 @@ export function setupJSDOM(global) {
6
7
  // TODO: options
7
8
  url: 'http://localhost:3000',
8
9
  });
9
- const keys = Object.getOwnPropertyNames(dom.window)
10
+ const keys = KEYS.concat(Object.getOwnPropertyNames(dom.window))
10
11
  .filter(k => !k.startsWith('_'))
11
12
  .filter(k => !(k in global));
12
13
  for (const key of keys)
@@ -0,0 +1 @@
1
+ export declare const KEYS: string[];
@@ -0,0 +1,220 @@
1
+ // SEE https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/interfaces.js
2
+ const LIVING_KEYS = [
3
+ 'DOMException',
4
+ 'URL',
5
+ 'URLSearchParams',
6
+ 'EventTarget',
7
+ 'NamedNodeMap',
8
+ 'Node',
9
+ 'Attr',
10
+ 'Element',
11
+ 'DocumentFragment',
12
+ 'DOMImplementation',
13
+ 'Document',
14
+ 'XMLDocument',
15
+ 'CharacterData',
16
+ 'Text',
17
+ 'CDATASection',
18
+ 'ProcessingInstruction',
19
+ 'Comment',
20
+ 'DocumentType',
21
+ 'NodeList',
22
+ 'HTMLCollection',
23
+ 'HTMLOptionsCollection',
24
+ 'DOMStringMap',
25
+ 'DOMTokenList',
26
+ 'StyleSheetList',
27
+ 'HTMLElement',
28
+ 'HTMLHeadElement',
29
+ 'HTMLTitleElement',
30
+ 'HTMLBaseElement',
31
+ 'HTMLLinkElement',
32
+ 'HTMLMetaElement',
33
+ 'HTMLStyleElement',
34
+ 'HTMLBodyElement',
35
+ 'HTMLHeadingElement',
36
+ 'HTMLParagraphElement',
37
+ 'HTMLHRElement',
38
+ 'HTMLPreElement',
39
+ 'HTMLUListElement',
40
+ 'HTMLOListElement',
41
+ 'HTMLLIElement',
42
+ 'HTMLMenuElement',
43
+ 'HTMLDListElement',
44
+ 'HTMLDivElement',
45
+ 'HTMLAnchorElement',
46
+ 'HTMLAreaElement',
47
+ 'HTMLBRElement',
48
+ 'HTMLButtonElement',
49
+ 'HTMLCanvasElement',
50
+ 'HTMLDataElement',
51
+ 'HTMLDataListElement',
52
+ 'HTMLDetailsElement',
53
+ 'HTMLDialogElement',
54
+ 'HTMLDirectoryElement',
55
+ 'HTMLFieldSetElement',
56
+ 'HTMLFontElement',
57
+ 'HTMLFormElement',
58
+ 'HTMLHtmlElement',
59
+ 'HTMLImageElement',
60
+ 'HTMLInputElement',
61
+ 'HTMLLabelElement',
62
+ 'HTMLLegendElement',
63
+ 'HTMLMapElement',
64
+ 'HTMLMarqueeElement',
65
+ 'HTMLMediaElement',
66
+ 'HTMLMeterElement',
67
+ 'HTMLModElement',
68
+ 'HTMLOptGroupElement',
69
+ 'HTMLOptionElement',
70
+ 'HTMLOutputElement',
71
+ 'HTMLPictureElement',
72
+ 'HTMLProgressElement',
73
+ 'HTMLQuoteElement',
74
+ 'HTMLScriptElement',
75
+ 'HTMLSelectElement',
76
+ 'HTMLSlotElement',
77
+ 'HTMLSourceElement',
78
+ 'HTMLSpanElement',
79
+ 'HTMLTableCaptionElement',
80
+ 'HTMLTableCellElement',
81
+ 'HTMLTableColElement',
82
+ 'HTMLTableElement',
83
+ 'HTMLTimeElement',
84
+ 'HTMLTableRowElement',
85
+ 'HTMLTableSectionElement',
86
+ 'HTMLTemplateElement',
87
+ 'HTMLTextAreaElement',
88
+ 'HTMLUnknownElement',
89
+ 'HTMLFrameElement',
90
+ 'HTMLFrameSetElement',
91
+ 'HTMLIFrameElement',
92
+ 'HTMLEmbedElement',
93
+ 'HTMLObjectElement',
94
+ 'HTMLParamElement',
95
+ 'HTMLVideoElement',
96
+ 'HTMLAudioElement',
97
+ 'HTMLTrackElement',
98
+ 'SVGElement',
99
+ 'SVGGraphicsElement',
100
+ 'SVGSVGElement',
101
+ 'SVGTitleElement',
102
+ 'SVGAnimatedString',
103
+ 'SVGNumber',
104
+ 'SVGStringList',
105
+ 'Event',
106
+ 'CloseEvent',
107
+ 'CustomEvent',
108
+ 'MessageEvent',
109
+ 'ErrorEvent',
110
+ 'HashChangeEvent',
111
+ 'PopStateEvent',
112
+ 'StorageEvent',
113
+ 'ProgressEvent',
114
+ 'PageTransitionEvent',
115
+ 'UIEvent',
116
+ 'FocusEvent',
117
+ 'InputEvent',
118
+ 'MouseEvent',
119
+ 'KeyboardEvent',
120
+ 'TouchEvent',
121
+ 'CompositionEvent',
122
+ 'WheelEvent',
123
+ 'BarProp',
124
+ 'External',
125
+ 'Location',
126
+ 'History',
127
+ 'Screen',
128
+ 'Performance',
129
+ 'Navigator',
130
+ 'PluginArray',
131
+ 'MimeTypeArray',
132
+ 'Plugin',
133
+ 'MimeType',
134
+ 'FileReader',
135
+ 'Blob',
136
+ 'File',
137
+ 'FileList',
138
+ 'ValidityState',
139
+ 'DOMParser',
140
+ 'XMLSerializer',
141
+ 'FormData',
142
+ 'XMLHttpRequestEventTarget',
143
+ 'XMLHttpRequestUpload',
144
+ 'XMLHttpRequest',
145
+ 'WebSocket',
146
+ 'NodeFilter',
147
+ 'NodeIterator',
148
+ 'TreeWalker',
149
+ 'AbstractRange',
150
+ 'Range',
151
+ 'StaticRange',
152
+ 'Selection',
153
+ 'Storage',
154
+ 'CustomElementRegistry',
155
+ 'ShadowRoot',
156
+ 'MutationObserver',
157
+ 'MutationRecord',
158
+ 'Headers',
159
+ 'AbortController',
160
+ 'AbortSignal',
161
+ ];
162
+ const OTHER_KEYS = [
163
+ 'addEventListener',
164
+ 'alert',
165
+ 'atob',
166
+ 'blur',
167
+ 'btoa',
168
+ /* 'clearInterval', */
169
+ /* 'clearTimeout', */
170
+ 'close',
171
+ 'confirm',
172
+ /* 'console', */
173
+ 'createPopup',
174
+ 'dispatchEvent',
175
+ 'document',
176
+ 'focus',
177
+ 'frames',
178
+ 'getComputedStyle',
179
+ 'history',
180
+ 'innerHeight',
181
+ 'innerWidth',
182
+ 'length',
183
+ 'location',
184
+ 'moveBy',
185
+ 'moveTo',
186
+ 'name',
187
+ 'navigator',
188
+ 'open',
189
+ 'outerHeight',
190
+ 'outerWidth',
191
+ 'pageXOffset',
192
+ 'pageYOffset',
193
+ 'parent',
194
+ 'postMessage',
195
+ 'print',
196
+ 'prompt',
197
+ 'removeEventListener',
198
+ 'resizeBy',
199
+ 'resizeTo',
200
+ 'screen',
201
+ 'screenLeft',
202
+ 'screenTop',
203
+ 'screenX',
204
+ 'screenY',
205
+ 'scroll',
206
+ 'scrollBy',
207
+ 'scrollLeft',
208
+ 'scrollTo',
209
+ 'scrollTop',
210
+ 'scrollX',
211
+ 'scrollY',
212
+ 'self',
213
+ /* 'setInterval', */
214
+ /* 'setTimeout', */
215
+ 'stop',
216
+ /* 'toString', */
217
+ 'top',
218
+ 'window',
219
+ ];
220
+ export const KEYS = LIVING_KEYS.concat(OTHER_KEYS);
@@ -0,0 +1,22 @@
1
+ import { InlineConfig, ViteDevServer } from 'vite';
2
+ declare global {
3
+ namespace NodeJS {
4
+ interface Process {
5
+ __vite_node__: {
6
+ server: ViteDevServer;
7
+ watch?: boolean;
8
+ moduleCache: Map<string, Promise<any>>;
9
+ };
10
+ }
11
+ }
12
+ }
13
+ export interface ViteNodeOptions {
14
+ silent?: boolean;
15
+ root: string;
16
+ files: string[];
17
+ _?: string[];
18
+ shouldExternalize?: (file: string) => boolean;
19
+ config?: string;
20
+ defaultConfig?: InlineConfig;
21
+ }
22
+ export declare function run(argv: ViteNodeOptions): Promise<void>;
@@ -5,6 +5,7 @@ import vm from 'vm';
5
5
  import { createServer, mergeConfig } from 'vite';
6
6
  import c from 'picocolors';
7
7
  const { red, dim, yellow } = c;
8
+ const __pendingModules__ = new Map();
8
9
  export async function run(argv) {
9
10
  process.exitCode = 0;
10
11
  const root = argv.root || process.cwd();
@@ -19,9 +20,9 @@ export async function run(argv) {
19
20
  resolve: {},
20
21
  }));
21
22
  await server.pluginContainer.buildStart({});
22
- // @ts-expect-error
23
23
  process.__vite_node__ = {
24
24
  server,
25
+ moduleCache: __pendingModules__,
25
26
  };
26
27
  try {
27
28
  await execute(files, server, argv);
@@ -31,7 +32,8 @@ export async function run(argv) {
31
32
  throw e;
32
33
  }
33
34
  finally {
34
- await server.close();
35
+ if (!process.__vite_node__.watch)
36
+ await server.close();
35
37
  }
36
38
  }
37
39
  function normalizeId(id) {
@@ -47,7 +49,9 @@ function toFilePath(id, server) {
47
49
  ? id.slice(4)
48
50
  : id.startsWith(dirname(server.config.root))
49
51
  ? id
50
- : slash(resolve(server.config.root, id.slice(1)));
52
+ : id.startsWith('/')
53
+ ? slash(resolve(server.config.root, id.slice(1)))
54
+ : id;
51
55
  if (absolute.startsWith('//'))
52
56
  absolute = absolute.slice(1);
53
57
  if (!absolute.startsWith('/'))
@@ -55,15 +59,12 @@ function toFilePath(id, server) {
55
59
  return absolute;
56
60
  }
57
61
  async function execute(files, server, options) {
58
- const __pendingModules__ = new Map();
59
62
  const result = [];
60
63
  for (const file of files)
61
64
  result.push(await cachedRequest(`/@fs/${slash(resolve(file))}`, []));
62
65
  return result;
63
- async function directRequest(rawId, callstack) {
64
- if (builtinModules.includes(rawId))
65
- return import(rawId);
66
- callstack = [...callstack, rawId];
66
+ async function directRequest(id, fsPath, callstack) {
67
+ callstack = [...callstack, id];
67
68
  const request = async (dep) => {
68
69
  if (callstack.includes(dep)) {
69
70
  throw new Error(`${red('Circular dependency detected')}\nStack:\n${[...callstack, dep].reverse().map((i) => {
@@ -73,19 +74,15 @@ async function execute(files, server, options) {
73
74
  }
74
75
  return cachedRequest(dep, callstack);
75
76
  };
76
- const id = normalizeId(rawId);
77
- const absolute = toFilePath(id, server);
78
- if (options.shouldExternalize(absolute))
79
- return import(absolute);
80
77
  const result = await server.transformRequest(id, { ssr: true });
81
78
  if (!result)
82
79
  throw new Error(`failed to load ${id}`);
83
- const url = pathToFileURL(absolute);
80
+ const url = pathToFileURL(fsPath);
84
81
  const exports = {};
85
82
  const context = {
86
83
  require: createRequire(url),
87
- __filename: absolute,
88
- __dirname: dirname(absolute),
84
+ __filename: fsPath,
85
+ __dirname: dirname(fsPath),
89
86
  __vite_ssr_import__: request,
90
87
  __vite_ssr_dynamic_import__: request,
91
88
  __vite_ssr_exports__: exports,
@@ -93,17 +90,23 @@ async function execute(files, server, options) {
93
90
  __vite_ssr_import_meta__: { url },
94
91
  };
95
92
  const fn = vm.runInThisContext(`async (${Object.keys(context).join(',')}) => { ${result.code} }`, {
96
- filename: absolute,
93
+ filename: fsPath,
97
94
  lineOffset: 0,
98
95
  });
99
96
  await fn(...Object.values(context));
100
97
  return exports;
101
98
  }
102
- async function cachedRequest(id, callstack) {
103
- if (__pendingModules__.has(id))
104
- return __pendingModules__.get(id);
105
- __pendingModules__.set(id, directRequest(id, callstack));
106
- return await __pendingModules__.get(id);
99
+ async function cachedRequest(rawId, callstack) {
100
+ if (builtinModules.includes(rawId))
101
+ return import(rawId);
102
+ const id = normalizeId(rawId);
103
+ const fsPath = toFilePath(id, server);
104
+ if (options.shouldExternalize(fsPath))
105
+ return import(fsPath);
106
+ if (__pendingModules__.has(fsPath))
107
+ return __pendingModules__.get(fsPath);
108
+ __pendingModules__.set(fsPath, directRequest(id, fsPath, callstack));
109
+ return await __pendingModules__.get(fsPath);
107
110
  }
108
111
  function exportAll(exports, sourceModule) {
109
112
  // eslint-disable-next-line no-restricted-syntax
@@ -1,24 +1,21 @@
1
1
  import Listr from 'listr';
2
- import { Reporter, RunnerContext, Task } from '../types';
2
+ import { File, Reporter, RunnerContext, Task } from '../types';
3
3
  interface TaskPromise {
4
4
  promise: Promise<void>;
5
5
  resolve: () => void;
6
6
  reject: (e: unknown) => void;
7
7
  }
8
8
  export declare class DefaultReporter implements Reporter {
9
- indent: number;
10
9
  start: number;
11
10
  end: number;
12
11
  listr: Listr | null;
13
12
  listrPromise: Promise<void> | null;
14
13
  taskMap: Map<Task, TaskPromise>;
15
- onStart(): void;
16
- onCollected(ctx: RunnerContext): void;
14
+ onCollected(files: File[]): void;
17
15
  onTaskEnd(task: Task): void;
18
- onFinished({ files }: RunnerContext): Promise<void>;
19
- private getIndent;
20
- private log;
21
- private error;
16
+ onFinished(ctx: RunnerContext): Promise<void>;
17
+ onWatcherStart(ctx: RunnerContext): Promise<void>;
18
+ onWatcherRerun(files: string[], trigger: string): Promise<void>;
22
19
  onSnapshotUpdate(): void;
23
20
  }
24
21
  export {};
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-console */
1
2
  import { performance } from 'perf_hooks';
2
3
  import { relative } from 'path';
3
4
  import c from 'picocolors';
@@ -5,20 +6,16 @@ import Listr from 'listr';
5
6
  const CROSS = '✖ ';
6
7
  export class DefaultReporter {
7
8
  constructor() {
8
- this.indent = 0;
9
9
  this.start = 0;
10
10
  this.end = 0;
11
11
  this.listr = null;
12
12
  this.listrPromise = null;
13
13
  this.taskMap = new Map();
14
14
  }
15
- onStart() {
16
- this.indent = 0;
17
- }
18
- onCollected(ctx) {
15
+ onCollected(files) {
19
16
  this.start = performance.now();
20
17
  this.taskMap = new Map();
21
- const tasks = ctx.files.reduce((acc, file) => acc.concat(file.suites.flatMap(i => i.tasks)), []);
18
+ const tasks = files.reduce((acc, file) => acc.concat(file.suites.flatMap(i => i.tasks)), []);
22
19
  tasks.forEach((t) => {
23
20
  const obj = {};
24
21
  obj.promise = new Promise((resolve, reject) => {
@@ -42,7 +39,7 @@ export class DefaultReporter {
42
39
  const listrOptions = {
43
40
  exitOnError: false,
44
41
  };
45
- this.listr = new Listr(ctx.files.map((file) => {
42
+ this.listr = new Listr(files.map((file) => {
46
43
  return {
47
44
  title: relative(process.cwd(), file.filepath),
48
45
  task: () => {
@@ -67,12 +64,11 @@ export class DefaultReporter {
67
64
  else
68
65
  (_b = this.taskMap.get(task)) === null || _b === void 0 ? void 0 : _b.resolve();
69
66
  }
70
- async onFinished({ files }) {
67
+ async onFinished(ctx) {
71
68
  await this.listrPromise;
72
- this.log();
73
69
  this.end = performance.now();
74
- const suites = files.reduce((acc, file) => acc.concat(file.suites), []);
75
- const tasks = files.reduce((acc, file) => acc.concat(file.suites.flatMap(i => i.tasks)), []);
70
+ console.log();
71
+ const { tasks, suites, files } = ctx;
76
72
  const failedFiles = files.filter(i => i.error);
77
73
  const failedSuites = suites.filter(i => i.error);
78
74
  const runable = tasks.filter(i => i.state === 'pass' || i.state === 'fail');
@@ -80,53 +76,54 @@ export class DefaultReporter {
80
76
  const failed = tasks.filter(i => i.state === 'fail');
81
77
  const skipped = tasks.filter(i => i.state === 'skip');
82
78
  const todo = tasks.filter(i => i.state === 'todo');
83
- this.indent = 0;
84
79
  if (failedFiles.length) {
85
- this.error(c.bold(`\nFailed to parse ${failedFiles.length} files:`));
80
+ console.error(c.bold(`\nFailed to parse ${failedFiles.length} files:`));
86
81
  failedFiles.forEach((i) => {
87
- this.error(`\n- ${i.filepath}`);
82
+ console.error(c.red(`\n- ${i.filepath}`));
88
83
  console.error(i.error || 'Unknown error');
89
- this.log();
84
+ console.log();
90
85
  });
91
86
  }
92
87
  if (failedSuites.length) {
93
- this.error(c.bold(`\nFailed to run ${failedSuites.length} suites:`));
88
+ console.error(c.bold(`\nFailed to run ${failedSuites.length} suites:`));
94
89
  failedSuites.forEach((i) => {
95
90
  var _a;
96
- this.error(`\n- ${(_a = i.file) === null || _a === void 0 ? void 0 : _a.filepath} > ${i.name}`);
91
+ console.error(c.red(`\n- ${(_a = i.file) === null || _a === void 0 ? void 0 : _a.filepath} > ${i.name}`));
97
92
  console.error(i.error || 'Unknown error');
98
- this.log();
93
+ console.log();
99
94
  });
100
95
  }
101
96
  if (failed.length) {
102
- this.error(c.bold(`\nFailed Tests (${failed.length})`));
97
+ console.error(c.bold(`\nFailed Tests (${failed.length})`));
103
98
  failed.forEach((task) => {
104
99
  var _a;
105
- this.error(`\n${CROSS + c.inverse(c.red(' FAIL '))} ${[task.suite.name, task.name].filter(Boolean).join(' > ')} ${c.gray(c.dim(`${(_a = task.file) === null || _a === void 0 ? void 0 : _a.filepath}`))}`);
100
+ console.error(`\n${CROSS + c.inverse(c.red(' FAIL '))} ${[task.suite.name, task.name].filter(Boolean).join(' > ')} ${c.gray(c.dim(`${(_a = task.file) === null || _a === void 0 ? void 0 : _a.filepath}`))}`);
106
101
  console.error(task.error || 'Unknown error');
107
- this.log();
102
+ console.log();
108
103
  });
109
104
  }
110
- this.log(c.bold(c.green(`Passed ${passed.length} / ${runable.length}`)));
105
+ console.log(c.bold(c.green(`Passed ${passed.length} / ${runable.length}`)));
111
106
  if (failed.length)
112
- this.log(c.bold(c.red(`Failed ${failed.length} / ${runable.length}`)));
107
+ console.log(c.bold(c.red(`Failed ${failed.length} / ${runable.length}`)));
113
108
  if (skipped.length)
114
- this.log(c.yellow(`Skipped ${skipped.length}`));
109
+ console.log(c.yellow(`Skipped ${skipped.length}`));
115
110
  if (todo.length)
116
- this.log(c.dim(`Todo ${todo.length}`));
117
- this.log(`Time ${(this.end - this.start).toFixed(2)}ms`);
118
- }
119
- getIndent(offest = 0) {
120
- return ' '.repeat((this.indent + offest) * 2);
111
+ console.log(c.dim(`Todo ${todo.length}`));
112
+ console.log(`Time ${(this.end - this.start).toFixed(2)}ms`);
121
113
  }
122
- log(msg = '', indentOffset = 0) {
123
- // eslint-disable-next-line no-console
124
- console.log(`${this.getIndent(indentOffset)}${msg}`);
114
+ async onWatcherStart(ctx) {
115
+ await this.listrPromise;
116
+ const failed = ctx.tasks.some(i => i.state === 'fail');
117
+ if (failed)
118
+ console.log(c.red('\nTests failed. Watching for file changes...'));
119
+ else
120
+ console.log(c.green('\nWatching for file changes...'));
125
121
  }
126
- error(msg = '', indentOffset = 0) {
127
- // eslint-disable-next-line no-console
128
- console.error(c.red(`${this.getIndent(indentOffset)}${msg}`));
122
+ async onWatcherRerun(files, trigger) {
123
+ await this.listrPromise;
124
+ console.log(c.blue(`File ${relative(process.cwd(), trigger)} changed, re-running tests...`));
129
125
  }
126
+ // TODO:
130
127
  onSnapshotUpdate() {
131
128
  }
132
129
  }
@@ -0,0 +1,8 @@
1
+ import { File, ResolvedConfig, Task, RunnerContext, Suite } from '../types';
2
+ export declare function runTask(task: Task, ctx: RunnerContext): Promise<void>;
3
+ export declare function collectFiles(paths: string[]): Promise<Record<string, File>>;
4
+ export declare function runSite(suite: Suite, ctx: RunnerContext): Promise<void>;
5
+ export declare function runFile(file: File, ctx: RunnerContext): Promise<void>;
6
+ export declare function runFiles(filesMap: Record<string, File>, ctx: RunnerContext): Promise<void>;
7
+ export declare function run(config: ResolvedConfig): Promise<void>;
8
+ export declare function startWatcher(ctx: RunnerContext): Promise<void>;
@@ -1,10 +1,10 @@
1
1
  import fg from 'fast-glob';
2
- import { setupChai } from './integrations/chai/setup';
3
- import { clearContext, defaultSuite } from './suite';
4
- import { context } from './context';
5
- import { DefaultReporter } from './reporters/default';
6
- import { defaultIncludes, defaultExcludes } from './constants';
7
- import { getSnapshotManager } from './integrations/chai/snapshot';
2
+ import { setupChai } from '../integrations/chai/setup';
3
+ import { clearContext, defaultSuite } from '../suite';
4
+ import { context } from '../context';
5
+ import { DefaultReporter } from '../reporters/default';
6
+ import { defaultIncludes, defaultExcludes } from '../constants';
7
+ import { getSnapshotManager } from '../integrations/chai/snapshot';
8
8
  async function callHook(suite, name, args) {
9
9
  await Promise.all(suite.hooks[name].map(fn => fn(...args)));
10
10
  }
@@ -36,7 +36,7 @@ export async function runTask(task, ctx) {
36
36
  await ((_c = reporter.onTaskEnd) === null || _c === void 0 ? void 0 : _c.call(reporter, task, ctx));
37
37
  }
38
38
  export async function collectFiles(paths) {
39
- const files = [];
39
+ const files = {};
40
40
  for (const filepath of paths) {
41
41
  const file = {
42
42
  filepath,
@@ -58,9 +58,10 @@ export async function collectFiles(paths) {
58
58
  file.collected = false;
59
59
  process.exitCode = 1;
60
60
  }
61
- files.push(file);
61
+ files[filepath] = file;
62
62
  }
63
- const allSuites = files.reduce((suites, file) => suites.concat(file.suites), []);
63
+ const allFiles = Object.values(files);
64
+ const allSuites = allFiles.reduce((suites, file) => suites.concat(file.suites), []);
64
65
  interpretOnlyMode(allSuites);
65
66
  allSuites.forEach((i) => {
66
67
  if (i.mode === 'skip')
@@ -125,40 +126,111 @@ export async function runFile(file, ctx) {
125
126
  }
126
127
  await ((_b = reporter.onFileEnd) === null || _b === void 0 ? void 0 : _b.call(reporter, file, ctx));
127
128
  }
129
+ export async function runFiles(filesMap, ctx) {
130
+ var _a;
131
+ const { reporter } = ctx;
132
+ await ((_a = reporter.onCollected) === null || _a === void 0 ? void 0 : _a.call(reporter, Object.values(filesMap), ctx));
133
+ for (const file of Object.values(filesMap))
134
+ await runFile(file, ctx);
135
+ }
128
136
  export async function run(config) {
129
- var _a, _b, _c, _d;
137
+ var _a, _b, _c;
138
+ // if watch, tell `vite-node` not to end the process
139
+ if (config.watch)
140
+ process.__vite_node__.watch = true;
130
141
  // setup chai
131
142
  await setupChai(config);
132
143
  // collect files
133
- let paths = await fg(config.includes || defaultIncludes, {
144
+ let testFilepaths = await fg(config.includes || defaultIncludes, {
134
145
  absolute: true,
135
- cwd: config.rootDir,
146
+ cwd: config.root,
136
147
  ignore: config.excludes || defaultExcludes,
137
148
  });
138
- if ((_a = config.nameFilters) === null || _a === void 0 ? void 0 : _a.length)
139
- paths = paths.filter(i => config.nameFilters.some(f => i.includes(f)));
140
- if (!paths.length) {
149
+ // if name filters are provided by the CLI
150
+ if ((_a = config.filters) === null || _a === void 0 ? void 0 : _a.length)
151
+ testFilepaths = testFilepaths.filter(i => config.filters.some(f => i.includes(f)));
152
+ if (!testFilepaths.length) {
141
153
  console.error('No test files found');
142
154
  process.exitCode = 1;
143
155
  return;
144
156
  }
145
- const reporter = new DefaultReporter();
146
- await ((_b = reporter.onStart) === null || _b === void 0 ? void 0 : _b.call(reporter, config));
157
+ // setup envs
147
158
  if (config.global)
148
- (await import('./global')).registerApiGlobally();
159
+ (await import('../global')).registerApiGlobally();
149
160
  if (config.jsdom)
150
- (await import('./integrations/jsdom')).setupJSDOM(globalThis);
151
- const files = await collectFiles(paths);
161
+ (await import('../integrations/jsdom')).setupJSDOM(globalThis);
162
+ const reporter = new DefaultReporter();
163
+ await ((_b = reporter.onStart) === null || _b === void 0 ? void 0 : _b.call(reporter, config));
164
+ const filesMap = await collectFiles(testFilepaths);
152
165
  const ctx = {
153
- files,
166
+ filesMap,
167
+ get files() {
168
+ return Object.values(this.filesMap);
169
+ },
170
+ get suites() {
171
+ return Object.values(this.filesMap)
172
+ .reduce((suites, file) => suites.concat(file.suites), []);
173
+ },
174
+ get tasks() {
175
+ return this.suites
176
+ .reduce((tasks, suite) => tasks.concat(suite.tasks), []);
177
+ },
154
178
  config,
155
179
  reporter,
156
180
  };
157
- await ((_c = reporter.onCollected) === null || _c === void 0 ? void 0 : _c.call(reporter, ctx));
158
- for (const file of files)
159
- await runFile(file, ctx);
181
+ await runFiles(filesMap, ctx);
160
182
  const snapshot = getSnapshotManager();
161
183
  snapshot === null || snapshot === void 0 ? void 0 : snapshot.saveSnap();
162
184
  snapshot === null || snapshot === void 0 ? void 0 : snapshot.report();
163
- await ((_d = reporter.onFinished) === null || _d === void 0 ? void 0 : _d.call(reporter, ctx));
185
+ await ((_c = reporter.onFinished) === null || _c === void 0 ? void 0 : _c.call(reporter, ctx));
186
+ if (config.watch)
187
+ startWatcher(ctx);
188
+ }
189
+ export async function startWatcher(ctx) {
190
+ var _a, _b;
191
+ await ((_b = (_a = ctx.reporter).onWatcherStart) === null || _b === void 0 ? void 0 : _b.call(_a, ctx));
192
+ let timer;
193
+ const changedTests = new Set();
194
+ const seen = new Set();
195
+ const { server, moduleCache } = process.__vite_node__;
196
+ server.watcher.on('change', async (id) => {
197
+ getDependencyTests(id, ctx, changedTests, seen);
198
+ seen.forEach(i => moduleCache.delete(i));
199
+ seen.clear();
200
+ if (changedTests.size === 0)
201
+ return;
202
+ clearTimeout(timer);
203
+ timer = setTimeout(async () => {
204
+ var _a, _b, _c, _d;
205
+ const snapshot = getSnapshotManager();
206
+ const pathes = Array.from(changedTests);
207
+ changedTests.clear();
208
+ await ((_b = (_a = ctx.reporter).onWatcherRerun) === null || _b === void 0 ? void 0 : _b.call(_a, pathes, id, ctx));
209
+ pathes.forEach(i => moduleCache.delete(i));
210
+ const files = await collectFiles(pathes);
211
+ Object.assign(ctx.filesMap, files);
212
+ await runFiles(files, ctx);
213
+ // TODO: clear snapshot state
214
+ snapshot === null || snapshot === void 0 ? void 0 : snapshot.saveSnap();
215
+ snapshot === null || snapshot === void 0 ? void 0 : snapshot.report();
216
+ await ((_d = (_c = ctx.reporter).onWatcherStart) === null || _d === void 0 ? void 0 : _d.call(_c, ctx));
217
+ }, 100);
218
+ });
219
+ }
220
+ function getDependencyTests(id, ctx, set = new Set(), seen = new Set()) {
221
+ if (seen.has(id) || set.has(id))
222
+ return set;
223
+ seen.add(id);
224
+ if (id in ctx.filesMap) {
225
+ set.add(id);
226
+ return set;
227
+ }
228
+ const mod = process.__vite_node__.server.moduleGraph.getModuleById(id);
229
+ if (mod) {
230
+ mod.importers.forEach((i) => {
231
+ if (i.id)
232
+ getDependencyTests(i.id, ctx, set, seen);
233
+ });
234
+ }
235
+ return set;
164
236
  }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { ViteDevServer } from 'vite';
2
1
  export declare type Awaitable<T> = Promise<T> | T;
3
2
  export interface UserOptions {
4
3
  includes?: string[];
@@ -21,13 +20,22 @@ export interface UserOptions {
21
20
  * @default false
22
21
  */
23
22
  parallel?: boolean;
24
- }
25
- export interface Config extends UserOptions {
26
- rootDir?: string;
27
- updateSnapshot?: boolean;
28
- nameFilters?: string[];
29
- server: ViteDevServer;
23
+ /**
24
+ * Update snapshot files
25
+ *
26
+ * @default false
27
+ */
28
+ update?: boolean;
29
+ /**
30
+ * Watch mode
31
+ *
32
+ * @default false
33
+ */
30
34
  watch?: boolean;
35
+ root?: string;
36
+ }
37
+ export interface ResolvedConfig extends Required<UserOptions> {
38
+ filters?: string[];
31
39
  }
32
40
  export declare type RunMode = 'run' | 'skip' | 'only' | 'todo';
33
41
  export declare type TaskState = RunMode | 'pass' | 'fail';
@@ -78,8 +86,11 @@ export interface File {
78
86
  error?: unknown;
79
87
  }
80
88
  export interface RunnerContext {
89
+ filesMap: Record<string, File>;
81
90
  files: File[];
82
- config: Config;
91
+ suites: Suite[];
92
+ tasks: Task[];
93
+ config: ResolvedConfig;
83
94
  reporter: Reporter;
84
95
  }
85
96
  export interface GlobalContext {
@@ -87,8 +98,8 @@ export interface GlobalContext {
87
98
  currentSuite: SuiteCollector | null;
88
99
  }
89
100
  export interface Reporter {
90
- onStart?: (userOptions: Config) => Awaitable<void>;
91
- onCollected?: (ctx: RunnerContext) => Awaitable<void>;
101
+ onStart?: (userOptions: ResolvedConfig) => Awaitable<void>;
102
+ onCollected?: (files: File[], ctx: RunnerContext) => Awaitable<void>;
92
103
  onFinished?: (ctx: RunnerContext) => Awaitable<void>;
93
104
  onSuiteBegin?: (suite: Suite, ctx: RunnerContext) => Awaitable<void>;
94
105
  onSuiteEnd?: (suite: Suite, ctx: RunnerContext) => Awaitable<void>;
@@ -96,5 +107,7 @@ export interface Reporter {
96
107
  onFileEnd?: (file: File, ctx: RunnerContext) => Awaitable<void>;
97
108
  onTaskBegin?: (task: Task, ctx: RunnerContext) => Awaitable<void>;
98
109
  onTaskEnd?: (task: Task, ctx: RunnerContext) => Awaitable<void>;
110
+ onWatcherStart?: (ctx: RunnerContext) => Awaitable<void>;
111
+ onWatcherRerun?: (files: string[], trigger: string, ctx: RunnerContext) => Awaitable<void>;
99
112
  onSnapshotUpdate?: () => Awaitable<void>;
100
113
  }
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "vitest",
3
- "version": "0.0.19",
4
- "description": "",
5
- "keywords": [],
3
+ "version": "0.0.23",
4
+ "description": "A blazing fast unit test framework powered by Vite",
5
+ "keywords": [
6
+ "vite",
7
+ "vite-node",
8
+ "test",
9
+ "jest"
10
+ ],
6
11
  "homepage": "https://github.com/antfu/vitest#readme",
7
12
  "bugs": {
8
13
  "url": "https://github.com/antfu/vitest/issues"
@@ -44,9 +49,8 @@
44
49
  "jest-util": "^27.4.2",
45
50
  "jsdom": "^19.0.0",
46
51
  "listr": "^0.14.3",
47
- "minimist": "^1.2.5",
48
- "ora": "^6.0.1",
49
52
  "picocolors": "^1.0.0",
53
+ "sade": "^1.7.4",
50
54
  "sinon": "^12.0.1",
51
55
  "sinon-chai": "^3.7.0"
52
56
  },
@@ -55,8 +59,8 @@
55
59
  "@antfu/ni": "^0.11.0",
56
60
  "@types/jsdom": "^16.2.13",
57
61
  "@types/listr": "^0.14.4",
58
- "@types/minimist": "^1.2.2",
59
62
  "@types/node": "^16.11.11",
63
+ "@types/sade": "^1.7.3",
60
64
  "@types/sinon": "^10.0.6",
61
65
  "bumpp": "^7.1.1",
62
66
  "eslint": "^8.3.0",
package/dist/node.d.ts DELETED
@@ -1,11 +0,0 @@
1
- import { InlineConfig } from 'vite';
2
- export interface ViteNodeOptions {
3
- silent?: boolean;
4
- root: string;
5
- files: string[];
6
- _?: string[];
7
- shouldExternalize?: (file: string) => boolean;
8
- config?: string;
9
- defaultConfig?: InlineConfig;
10
- }
11
- export declare function run(argv: ViteNodeOptions): Promise<void>;
package/dist/run.d.ts DELETED
@@ -1,6 +0,0 @@
1
- import { File, Config, Task, RunnerContext, Suite } from './types';
2
- export declare function runTask(task: Task, ctx: RunnerContext): Promise<void>;
3
- export declare function collectFiles(paths: string[]): Promise<File[]>;
4
- export declare function runSite(suite: Suite, ctx: RunnerContext): Promise<void>;
5
- export declare function runFile(file: File, ctx: RunnerContext): Promise<void>;
6
- export declare function run(config: Config): Promise<void>;