vitest 0.0.18 → 0.0.22

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
@@ -156,9 +156,9 @@ describe('suite', () => {
156
156
  - [x] Mock
157
157
  - [x] Global Mode & Types
158
158
  - [ ] Parallel Executing
159
- - [ ] CLI Help
160
- - [ ] JSDom
161
- - [ ] Watch
159
+ - [ ] CLI Help (Use yargs)
160
+ - [x] JSDom
161
+ - [x] Watch
162
162
  - [ ] Source Map
163
163
  - [ ] Coverage
164
164
 
package/dist/cli.js CHANGED
@@ -1,4 +1,3 @@
1
- var _a;
2
1
  import minimist from 'minimist';
3
2
  import c from 'picocolors';
4
3
  import { run } from './run';
@@ -19,8 +18,9 @@ const argv = minimist(process.argv.slice(2), {
19
18
  return true;
20
19
  },
21
20
  });
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;
21
+ if (!process.__vite_node__)
22
+ throw new Error('Vite can only run in Vite environment, please use the CLI to start the process');
23
+ const server = process.__vite_node__.server;
24
24
  const viteConfig = (server === null || server === void 0 ? void 0 : server.config) || {};
25
25
  const testOptions = viteConfig.test || {};
26
26
  await run(Object.assign(Object.assign(Object.assign({}, argv), testOptions), { server, updateSnapshot: argv.update, rootDir: argv.root || process.cwd(), nameFilters: argv._ }));
package/dist/entry.js CHANGED
@@ -2,7 +2,7 @@ import { fileURLToPath } from 'url';
2
2
  import { resolve, dirname } from 'path';
3
3
  import minimist from 'minimist';
4
4
  import { findUp } from 'find-up';
5
- import { run } from './node.js';
5
+ import { run } from './node/index.js';
6
6
  process.env.VITEST = 'true';
7
7
  const argv = minimist(process.argv.slice(2), {
8
8
  alias: {
package/dist/index.d.ts CHANGED
@@ -22,6 +22,7 @@ declare global {
22
22
  toBeCalledTimes(n: number): void;
23
23
  toBeCalledOnce(): void;
24
24
  toBeCalled(): void;
25
+ toHaveBeenCalled(): void;
25
26
  }
26
27
  interface ExpectStatic {
27
28
  addSnapshotSerializer: import('pretty-format').Plugin;
@@ -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
  });
@@ -48,5 +48,8 @@ export function JestChaiExpect() {
48
48
  utils.addMethod(proto, 'toBeCalled', function () {
49
49
  return this.called;
50
50
  });
51
+ utils.addMethod(proto, 'toHaveBeenCalled', function () {
52
+ return this.called;
53
+ });
51
54
  };
52
55
  }
@@ -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,55 +64,66 @@ 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();
70
+ console.log();
71
+ const { tasks, suites, files } = ctx;
74
72
  const failedFiles = files.filter(i => i.error);
75
- const tasks = files.reduce((acc, file) => acc.concat(file.suites.flatMap(i => i.tasks)), []);
73
+ const failedSuites = suites.filter(i => i.error);
76
74
  const runable = tasks.filter(i => i.state === 'pass' || i.state === 'fail');
77
75
  const passed = tasks.filter(i => i.state === 'pass');
78
76
  const failed = tasks.filter(i => i.state === 'fail');
79
77
  const skipped = tasks.filter(i => i.state === 'skip');
80
78
  const todo = tasks.filter(i => i.state === 'todo');
81
- this.indent = 0;
82
79
  if (failedFiles.length) {
83
- this.error(c.bold(`\nFailed to parse ${failedFiles.length} files:`));
80
+ console.error(c.bold(`\nFailed to parse ${failedFiles.length} files:`));
84
81
  failedFiles.forEach((i) => {
85
- this.error(`\n- ${i.filepath}`);
82
+ console.error(c.red(`\n- ${i.filepath}`));
86
83
  console.error(i.error || 'Unknown error');
87
- this.log();
84
+ console.log();
85
+ });
86
+ }
87
+ if (failedSuites.length) {
88
+ console.error(c.bold(`\nFailed to run ${failedSuites.length} suites:`));
89
+ failedSuites.forEach((i) => {
90
+ var _a;
91
+ console.error(c.red(`\n- ${(_a = i.file) === null || _a === void 0 ? void 0 : _a.filepath} > ${i.name}`));
92
+ console.error(i.error || 'Unknown error');
93
+ console.log();
88
94
  });
89
95
  }
90
96
  if (failed.length) {
91
- this.error(c.bold(`\nFailed Tests (${failed.length})`));
97
+ console.error(c.bold(`\nFailed Tests (${failed.length})`));
92
98
  failed.forEach((task) => {
93
99
  var _a;
94
- 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}`))}`);
95
101
  console.error(task.error || 'Unknown error');
96
- this.log();
102
+ console.log();
97
103
  });
98
104
  }
99
- this.log(c.bold(c.green(`Passed ${passed.length} / ${runable.length}`)));
105
+ console.log(c.bold(c.green(`Passed ${passed.length} / ${runable.length}`)));
100
106
  if (failed.length)
101
- this.log(c.bold(c.red(`Failed ${failed.length} / ${runable.length}`)));
107
+ console.log(c.bold(c.red(`Failed ${failed.length} / ${runable.length}`)));
102
108
  if (skipped.length)
103
- this.log(c.yellow(`Skipped ${skipped.length}`));
109
+ console.log(c.yellow(`Skipped ${skipped.length}`));
104
110
  if (todo.length)
105
- this.log(c.dim(`Todo ${todo.length}`));
106
- this.log(`Time ${(this.end - this.start).toFixed(2)}ms`);
107
- }
108
- getIndent(offest = 0) {
109
- 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`);
110
113
  }
111
- log(msg = '', indentOffset = 0) {
112
- // eslint-disable-next-line no-console
113
- 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...'));
114
121
  }
115
- error(msg = '', indentOffset = 0) {
116
- // eslint-disable-next-line no-console
117
- 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...`));
118
125
  }
126
+ // TODO:
119
127
  onSnapshotUpdate() {
120
128
  }
121
129
  }
@@ -0,0 +1,8 @@
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<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: Config): Promise<void>;
8
+ export declare function startWatcher(ctx: RunnerContext): Promise<void>;
@@ -0,0 +1,236 @@
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';
8
+ async function callHook(suite, name, args) {
9
+ await Promise.all(suite.hooks[name].map(fn => fn(...args)));
10
+ }
11
+ export async function runTask(task, ctx) {
12
+ var _a, _b, _c;
13
+ const { reporter } = ctx;
14
+ (_a = getSnapshotManager()) === null || _a === void 0 ? void 0 : _a.setTask(task);
15
+ await ((_b = reporter.onTaskBegin) === null || _b === void 0 ? void 0 : _b.call(reporter, task, ctx));
16
+ if (task.mode === 'run') {
17
+ try {
18
+ await callHook(task.suite, 'beforeEach', [task, task.suite]);
19
+ await task.fn();
20
+ task.state = 'pass';
21
+ }
22
+ catch (e) {
23
+ task.state = 'fail';
24
+ task.error = e;
25
+ process.exitCode = 1;
26
+ }
27
+ try {
28
+ await callHook(task.suite, 'afterEach', [task, task.suite]);
29
+ }
30
+ catch (e) {
31
+ task.state = 'fail';
32
+ task.error = e;
33
+ process.exitCode = 1;
34
+ }
35
+ }
36
+ await ((_c = reporter.onTaskEnd) === null || _c === void 0 ? void 0 : _c.call(reporter, task, ctx));
37
+ }
38
+ export async function collectFiles(paths) {
39
+ const files = {};
40
+ for (const filepath of paths) {
41
+ const file = {
42
+ filepath,
43
+ suites: [],
44
+ collected: false,
45
+ };
46
+ clearContext();
47
+ try {
48
+ await import(filepath);
49
+ const collectors = [defaultSuite, ...context.suites];
50
+ for (const c of collectors) {
51
+ context.currentSuite = c;
52
+ file.suites.push(await c.collect(file));
53
+ }
54
+ file.collected = true;
55
+ }
56
+ catch (e) {
57
+ file.error = e;
58
+ file.collected = false;
59
+ process.exitCode = 1;
60
+ }
61
+ files[filepath] = file;
62
+ }
63
+ const allFiles = Object.values(files);
64
+ const allSuites = allFiles.reduce((suites, file) => suites.concat(file.suites), []);
65
+ interpretOnlyMode(allSuites);
66
+ allSuites.forEach((i) => {
67
+ if (i.mode === 'skip')
68
+ i.tasks.forEach(t => t.mode === 'run' && (t.state = 'skip'));
69
+ else
70
+ interpretOnlyMode(i.tasks);
71
+ });
72
+ return files;
73
+ }
74
+ /**
75
+ * If any items been marked as `only`, mark all other items as `skip`.
76
+ */
77
+ function interpretOnlyMode(items) {
78
+ if (items.some(i => i.mode === 'only')) {
79
+ items.forEach((i) => {
80
+ if (i.mode === 'run')
81
+ i.mode = 'skip';
82
+ else if (i.mode === 'only')
83
+ i.mode = 'run';
84
+ });
85
+ }
86
+ }
87
+ export async function runSite(suite, ctx) {
88
+ var _a, _b;
89
+ const { reporter } = ctx;
90
+ await ((_a = reporter.onSuiteBegin) === null || _a === void 0 ? void 0 : _a.call(reporter, suite, ctx));
91
+ if (suite.mode === 'skip') {
92
+ suite.status = 'skip';
93
+ }
94
+ else if (suite.mode === 'todo') {
95
+ suite.status = 'todo';
96
+ }
97
+ else {
98
+ try {
99
+ await callHook(suite, 'beforeAll', [suite]);
100
+ await Promise.all(suite.tasks.map(i => runTask(i, ctx)));
101
+ // for (const t of suite.tasks)
102
+ // await runTask(t, ctx)
103
+ await callHook(suite, 'afterAll', [suite]);
104
+ }
105
+ catch (e) {
106
+ suite.error = e;
107
+ suite.status = 'fail';
108
+ process.exitCode = 1;
109
+ }
110
+ }
111
+ await ((_b = reporter.onSuiteEnd) === null || _b === void 0 ? void 0 : _b.call(reporter, suite, ctx));
112
+ }
113
+ export async function runFile(file, ctx) {
114
+ var _a, _b;
115
+ const { reporter } = ctx;
116
+ const runableSuites = file.suites.filter(i => i.mode === 'run');
117
+ if (runableSuites.length === 0)
118
+ return;
119
+ await ((_a = reporter.onFileBegin) === null || _a === void 0 ? void 0 : _a.call(reporter, file, ctx));
120
+ if (ctx.config.parallel) {
121
+ await Promise.all(file.suites.map(suite => runSite(suite, ctx)));
122
+ }
123
+ else {
124
+ for (const suite of file.suites)
125
+ await runSite(suite, ctx);
126
+ }
127
+ await ((_b = reporter.onFileEnd) === null || _b === void 0 ? void 0 : _b.call(reporter, file, ctx));
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
+ }
136
+ export async function run(config) {
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;
141
+ // setup chai
142
+ await setupChai(config);
143
+ // collect files
144
+ let testFilepaths = await fg(config.includes || defaultIncludes, {
145
+ absolute: true,
146
+ cwd: config.rootDir,
147
+ ignore: config.excludes || defaultExcludes,
148
+ });
149
+ // if name filters are provided by the CLI
150
+ if ((_a = config.nameFilters) === null || _a === void 0 ? void 0 : _a.length)
151
+ testFilepaths = testFilepaths.filter(i => config.nameFilters.some(f => i.includes(f)));
152
+ if (!testFilepaths.length) {
153
+ console.error('No test files found');
154
+ process.exitCode = 1;
155
+ return;
156
+ }
157
+ // setup envs
158
+ if (config.global)
159
+ (await import('../global')).registerApiGlobally();
160
+ if (config.jsdom)
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);
165
+ const ctx = {
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
+ },
178
+ config,
179
+ reporter,
180
+ };
181
+ await runFiles(filesMap, ctx);
182
+ const snapshot = getSnapshotManager();
183
+ snapshot === null || snapshot === void 0 ? void 0 : snapshot.saveSnap();
184
+ snapshot === null || snapshot === void 0 ? void 0 : snapshot.report();
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;
236
+ }
package/dist/types.d.ts CHANGED
@@ -15,6 +15,12 @@ export interface UserOptions {
15
15
  * @default false
16
16
  */
17
17
  jsdom?: boolean;
18
+ /**
19
+ * Run tests files in parallel
20
+ *
21
+ * @default false
22
+ */
23
+ parallel?: boolean;
18
24
  }
19
25
  export interface Config extends UserOptions {
20
26
  rootDir?: string;
@@ -47,6 +53,8 @@ export interface Suite {
47
53
  mode: RunMode;
48
54
  tasks: Task[];
49
55
  file?: File;
56
+ error?: unknown;
57
+ status?: TaskState;
50
58
  hooks: {
51
59
  beforeAll: HookListener<[Suite]>[];
52
60
  afterAll: HookListener<[Suite]>[];
@@ -70,7 +78,10 @@ export interface File {
70
78
  error?: unknown;
71
79
  }
72
80
  export interface RunnerContext {
81
+ filesMap: Record<string, File>;
73
82
  files: File[];
83
+ suites: Suite[];
84
+ tasks: Task[];
74
85
  config: Config;
75
86
  reporter: Reporter;
76
87
  }
@@ -80,7 +91,7 @@ export interface GlobalContext {
80
91
  }
81
92
  export interface Reporter {
82
93
  onStart?: (userOptions: Config) => Awaitable<void>;
83
- onCollected?: (ctx: RunnerContext) => Awaitable<void>;
94
+ onCollected?: (files: File[], ctx: RunnerContext) => Awaitable<void>;
84
95
  onFinished?: (ctx: RunnerContext) => Awaitable<void>;
85
96
  onSuiteBegin?: (suite: Suite, ctx: RunnerContext) => Awaitable<void>;
86
97
  onSuiteEnd?: (suite: Suite, ctx: RunnerContext) => Awaitable<void>;
@@ -88,5 +99,7 @@ export interface Reporter {
88
99
  onFileEnd?: (file: File, ctx: RunnerContext) => Awaitable<void>;
89
100
  onTaskBegin?: (task: Task, ctx: RunnerContext) => Awaitable<void>;
90
101
  onTaskEnd?: (task: Task, ctx: RunnerContext) => Awaitable<void>;
102
+ onWatcherStart?: (ctx: RunnerContext) => Awaitable<void>;
103
+ onWatcherRerun?: (files: string[], trigger: string, ctx: RunnerContext) => Awaitable<void>;
91
104
  onSnapshotUpdate?: () => Awaitable<void>;
92
105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest",
3
- "version": "0.0.18",
3
+ "version": "0.0.22",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/antfu/vitest#readme",
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,5 +0,0 @@
1
- import { File, Config, Task, RunnerContext } 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 runFile(file: File, ctx: RunnerContext): Promise<void>;
5
- export declare function run(config: Config): Promise<void>;
package/dist/run.js DELETED
@@ -1,134 +0,0 @@
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';
8
- async function callHook(suite, name, args) {
9
- await Promise.all(suite.hooks[name].map(fn => fn(...args)));
10
- }
11
- export async function runTask(task, ctx) {
12
- var _a, _b, _c;
13
- const { reporter } = ctx;
14
- (_a = getSnapshotManager()) === null || _a === void 0 ? void 0 : _a.setTask(task);
15
- await ((_b = reporter.onTaskBegin) === null || _b === void 0 ? void 0 : _b.call(reporter, task, ctx));
16
- if (task.mode === 'run') {
17
- await callHook(task.suite, 'afterEach', [task, task.suite]);
18
- try {
19
- await task.fn();
20
- task.state = 'pass';
21
- }
22
- catch (e) {
23
- task.state = 'fail';
24
- task.error = e;
25
- }
26
- await callHook(task.suite, 'afterEach', [task, task.suite]);
27
- }
28
- await ((_c = reporter.onTaskEnd) === null || _c === void 0 ? void 0 : _c.call(reporter, task, ctx));
29
- }
30
- export async function collectFiles(paths) {
31
- const files = [];
32
- for (const filepath of paths) {
33
- const file = {
34
- filepath,
35
- suites: [],
36
- collected: false,
37
- };
38
- clearContext();
39
- try {
40
- await import(filepath);
41
- const collectors = [defaultSuite, ...context.suites];
42
- for (const c of collectors) {
43
- context.currentSuite = c;
44
- file.suites.push(await c.collect(file));
45
- }
46
- file.collected = true;
47
- }
48
- catch (e) {
49
- file.error = e;
50
- file.collected = false;
51
- process.exitCode = 1;
52
- }
53
- files.push(file);
54
- }
55
- const allSuites = files.reduce((suites, file) => suites.concat(file.suites), []);
56
- interpretOnlyMode(allSuites);
57
- allSuites.forEach((i) => {
58
- if (i.mode === 'skip')
59
- i.tasks.forEach(t => t.mode === 'run' && (t.state = 'skip'));
60
- else
61
- interpretOnlyMode(i.tasks);
62
- });
63
- return files;
64
- }
65
- /**
66
- * If any items been marked as `only`, mark all other items as `skip`.
67
- */
68
- function interpretOnlyMode(items) {
69
- if (items.some(i => i.mode === 'only')) {
70
- items.forEach((i) => {
71
- if (i.mode === 'run')
72
- i.mode = 'skip';
73
- else if (i.mode === 'only')
74
- i.mode = 'run';
75
- });
76
- }
77
- }
78
- export async function runFile(file, ctx) {
79
- var _a, _b;
80
- const { reporter } = ctx;
81
- const runableSuites = file.suites.filter(i => i.mode === 'run');
82
- if (runableSuites.length === 0)
83
- return;
84
- await ((_a = reporter.onFileBegin) === null || _a === void 0 ? void 0 : _a.call(reporter, file, ctx));
85
- // TODO: support toggling parallel or serial
86
- await Promise.all(file.suites.map(async (suite) => {
87
- var _a, _b;
88
- await ((_a = reporter.onSuiteBegin) === null || _a === void 0 ? void 0 : _a.call(reporter, suite, ctx));
89
- await callHook(suite, 'beforeAll', [suite]);
90
- await Promise.all(suite.tasks.map(i => runTask(i, ctx)));
91
- // for (const t of suite.tasks)
92
- // await runTask(t, ctx)
93
- await callHook(suite, 'afterAll', [suite]);
94
- await ((_b = reporter.onSuiteEnd) === null || _b === void 0 ? void 0 : _b.call(reporter, suite, ctx));
95
- }));
96
- await ((_b = reporter.onFileEnd) === null || _b === void 0 ? void 0 : _b.call(reporter, file, ctx));
97
- }
98
- export async function run(config) {
99
- var _a, _b, _c, _d;
100
- // setup chai
101
- await setupChai(config);
102
- // collect files
103
- let paths = await fg(config.includes || defaultIncludes, {
104
- absolute: true,
105
- cwd: config.rootDir,
106
- ignore: config.excludes || defaultExcludes,
107
- });
108
- if ((_a = config.nameFilters) === null || _a === void 0 ? void 0 : _a.length)
109
- paths = paths.filter(i => config.nameFilters.some(f => i.includes(f)));
110
- if (!paths.length) {
111
- console.error('No test files found');
112
- process.exitCode = 1;
113
- return;
114
- }
115
- const reporter = new DefaultReporter();
116
- await ((_b = reporter.onStart) === null || _b === void 0 ? void 0 : _b.call(reporter, config));
117
- if (config.global)
118
- (await import('./global')).registerApiGlobally();
119
- if (config.jsdom)
120
- (await import('./integrations/jsdom')).setupJSDOM(globalThis);
121
- const files = await collectFiles(paths);
122
- const ctx = {
123
- files,
124
- config,
125
- reporter,
126
- };
127
- await ((_c = reporter.onCollected) === null || _c === void 0 ? void 0 : _c.call(reporter, ctx));
128
- for (const file of files)
129
- await runFile(file, ctx);
130
- const snapshot = getSnapshotManager();
131
- snapshot === null || snapshot === void 0 ? void 0 : snapshot.saveSnap();
132
- snapshot === null || snapshot === void 0 ? void 0 : snapshot.report();
133
- await ((_d = reporter.onFinished) === null || _d === void 0 ? void 0 : _d.call(reporter, ctx));
134
- }