vitest 0.0.26 → 0.0.30

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.gh.md CHANGED
@@ -9,6 +9,8 @@ A blazing fast unit test framework powered by Vite.
9
9
 
10
10
  > ⚠️ **DISCLAIMER**: Vitest is still in development and not stable yet. It's not recommended to use it in production.
11
11
 
12
+ > Vitest requires Vite v2.7.0 or above
13
+
12
14
  ## Features
13
15
 
14
16
  - [Vite](https://vitejs.dev/)'s config, transformers, resolvers, and plugins.
@@ -114,10 +116,25 @@ export default defineConfig({
114
116
  $ vitest -w
115
117
  ```
116
118
 
117
- Vitest will smartly search for the module graph to only rerun the related tests.
119
+ Vitest smartly searches the module graph and only rerun the related tests (just like how HMR works in Vite!).
118
120
 
119
121
  ## Filtering
120
122
 
123
+ ### CLI
124
+
125
+ You can use CLI to filter test files my name:
126
+
127
+ ```bash
128
+ $ vitest basic
129
+ ```
130
+
131
+ Will only execute test files that contain `basic`, e.g.
132
+
133
+ ```
134
+ basic.test.ts
135
+ basic-foo.test.ts
136
+ ```
137
+
121
138
  ### Skipping suites and tasks
122
139
 
123
140
  Use `.skip` to avoid running certain suites or tests
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ declare global {
19
19
  toEqual(expected: any): void;
20
20
  toStrictEqual(expected: any): void;
21
21
  toBe(expected: any): void;
22
+ toMatch(expected: string | RegExp): void;
23
+ toMatchObject(expected: any): void;
22
24
  toContain(item: any): void;
23
25
  toBeTruthy(): void;
24
26
  toBeFalsy(): void;
@@ -27,10 +29,10 @@ declare global {
27
29
  toBeNull(): void;
28
30
  toBeDefined(): void;
29
31
  toBeInstanceOf(c: any): void;
30
- toBeCalledTimes(n: number): void;
31
- toBeCalledOnce(): void;
32
- toBeCalled(): void;
32
+ toHaveBeenCalledTimes(n: number): void;
33
+ toHaveBeenCalledOnce(): void;
33
34
  toHaveBeenCalled(): void;
35
+ toHaveBeenCalledWith(...args: any[]): void;
34
36
  }
35
37
  interface ExpectStatic {
36
38
  addSnapshotSerializer: import('pretty-format').Plugin;
@@ -12,6 +12,15 @@ export function JestChaiExpect() {
12
12
  utils.addMethod(proto, 'toBe', function (expected) {
13
13
  return this.equal(expected);
14
14
  });
15
+ utils.addMethod(proto, 'toMatchObject', function (expected) {
16
+ return this.containSubset(expected);
17
+ });
18
+ utils.addMethod(proto, 'toMatch', function (expected) {
19
+ if (typeof expected === 'string')
20
+ return this.include(expected);
21
+ else
22
+ return this.match(expected);
23
+ });
15
24
  utils.addMethod(proto, 'toContain', function (item) {
16
25
  return this.contain(item);
17
26
  });
@@ -39,17 +48,20 @@ export function JestChaiExpect() {
39
48
  return this.instanceOf(obj);
40
49
  });
41
50
  // mock
42
- utils.addMethod(proto, 'toBeCalledTimes', function (number) {
51
+ utils.addMethod(proto, 'toHaveBeenCalledTimes', function (number) {
43
52
  return this.callCount(number);
44
53
  });
45
- utils.addMethod(proto, 'toBeCalledOnce', function () {
54
+ utils.addMethod(proto, 'toHaveBeenCalledOnce', function () {
46
55
  return this.callCount(1);
47
56
  });
48
- utils.addMethod(proto, 'toBeCalled', function () {
57
+ utils.addMethod(proto, 'toHaveBeenCalled', function () {
49
58
  return this.called;
50
59
  });
51
60
  utils.addMethod(proto, 'toHaveBeenCalled', function () {
52
61
  return this.called;
53
62
  });
63
+ utils.addMethod(proto, 'toHaveBeenCalledWith', function (...args) {
64
+ return this.calledWith(...args);
65
+ });
54
66
  };
55
67
  }
@@ -1,9 +1,11 @@
1
1
  import chai from 'chai';
2
2
  import SinonChai from 'sinon-chai';
3
+ import Subset from 'chai-subset';
3
4
  import { JestChaiExpect } from './jest-expect';
4
5
  import { SnapshotPlugin } from './snapshot';
5
6
  export async function setupChai(config) {
6
7
  chai.use(SinonChai);
7
8
  chai.use(JestChaiExpect());
9
+ chai.use(Subset);
8
10
  chai.use(await SnapshotPlugin(config));
9
11
  }
@@ -24,6 +24,7 @@ export declare class SnapshotManager {
24
24
  setTask(task: Task): void;
25
25
  setContext(context: Context): void;
26
26
  assert(received: unknown, message: string): void;
27
+ clear(): void;
27
28
  saveSnap(): void;
28
- report(): void;
29
+ report(): string[] | undefined;
29
30
  }
@@ -58,6 +58,9 @@ export class SnapshotManager {
58
58
  expect(actual.trim()).equals(expected ? expected.trim() : '', message || `Snapshot name: \`${key}\``);
59
59
  }
60
60
  }
61
+ clear() {
62
+ this.snapshotSummary = makeEmptySnapshotSummary(this.snapshotOptions);
63
+ }
61
64
  saveSnap() {
62
65
  if (!this.testFile || !this.snapshotState)
63
66
  return;
@@ -69,7 +72,6 @@ export class SnapshotManager {
69
72
  report() {
70
73
  const outputs = getSnapshotSummaryOutput(this.rootDir, this.snapshotSummary);
71
74
  if (outputs.length > 1)
72
- // eslint-disable-next-line no-console
73
- console.log(`\n${outputs.join('\n')}`);
75
+ return outputs;
74
76
  }
75
77
  }
package/dist/node/cli.js CHANGED
@@ -19,13 +19,19 @@ sade('vitest [filter]', true)
19
19
  .option('--global', 'inject apis globally', false)
20
20
  .option('--dev', 'dev mode', false)
21
21
  .option('--jsdom', 'mock browser api using JSDOM', false)
22
- .action(async (filters, options) => {
22
+ .action(async (filters, argv) => {
23
23
  process.env.VITEST = 'true';
24
+ const defaultInline = [
25
+ 'vue',
26
+ '@vue',
27
+ 'diff',
28
+ ];
24
29
  const __dirname = dirname(fileURLToPath(import.meta.url));
25
- const root = resolve(options.root || process.cwd());
26
- const configPath = options.config
27
- ? resolve(root, options.config)
30
+ const root = resolve(argv.root || process.cwd());
31
+ const configPath = argv.config
32
+ ? resolve(root, argv.config)
28
33
  : await findUp(['vitest.config.ts', 'vitest.config.js', 'vitest.config.mjs', 'vite.config.ts', 'vite.config.js', 'vite.config.mjs'], { cwd: root });
34
+ const options = argv;
29
35
  options.config = configPath;
30
36
  options.root = root;
31
37
  options.filters = filters
@@ -39,7 +45,7 @@ sade('vitest [filter]', true)
39
45
  await startViteNode({
40
46
  root,
41
47
  files: [
42
- resolve(__dirname, options.dev ? '../../src/node/entry.ts' : './entry.js'),
48
+ resolve(__dirname, argv.dev ? '../../src/node/entry.ts' : './entry.js'),
43
49
  ],
44
50
  config: configPath,
45
51
  defaultConfig: {
@@ -49,11 +55,31 @@ sade('vitest [filter]', true)
49
55
  ],
50
56
  },
51
57
  },
52
- shouldExternalize(id) {
53
- if (id.includes('/node_modules/vitest/'))
54
- return false;
55
- else
56
- return id.includes('/node_modules/');
58
+ shouldExternalize(id, server) {
59
+ var _a, _b, _c, _d;
60
+ const inline = ['vitest', ...defaultInline, ...((_b = (_a = server.config.test) === null || _a === void 0 ? void 0 : _a.deps) === null || _b === void 0 ? void 0 : _b.inline) || []];
61
+ const external = ((_d = (_c = server.config.test) === null || _c === void 0 ? void 0 : _c.deps) === null || _d === void 0 ? void 0 : _d.external) || [];
62
+ for (const ex of inline) {
63
+ if (typeof ex === 'string') {
64
+ if (id.includes(`/node_modules/${ex}/`))
65
+ return false;
66
+ }
67
+ else {
68
+ if (ex.test(id))
69
+ return false;
70
+ }
71
+ }
72
+ for (const ex of external) {
73
+ if (typeof ex === 'string') {
74
+ if (id.includes(`/node_modules/${ex}/`))
75
+ return true;
76
+ }
77
+ else {
78
+ if (ex.test(id))
79
+ return true;
80
+ }
81
+ }
82
+ return id.includes('/node_modules/');
57
83
  },
58
84
  });
59
85
  })
@@ -3,4 +3,5 @@ if (!process.__vite_node__ || !process.__vitest__)
3
3
  throw new Error('Vitest can only run in vite-node environment, please use the CLI to start the process');
4
4
  const inlineOptions = process.__vite_node__.server.config.test || {};
5
5
  const cliOptions = process.__vitest__.options || {};
6
- await run(Object.assign(Object.assign({}, inlineOptions), cliOptions));
6
+ const options = Object.assign(Object.assign({}, cliOptions), inlineOptions);
7
+ await run(options);
@@ -1,4 +1,4 @@
1
- import { InlineConfig, ViteDevServer } from 'vite';
1
+ import { InlineConfig, ViteDevServer, TransformResult } from 'vite';
2
2
  declare global {
3
3
  namespace NodeJS {
4
4
  interface Process {
@@ -6,6 +6,7 @@ declare global {
6
6
  server: ViteDevServer;
7
7
  watch?: boolean;
8
8
  moduleCache: Map<string, Promise<any>>;
9
+ modulesTransformResult: Map<string, TransformResult>;
9
10
  };
10
11
  }
11
12
  }
@@ -15,7 +16,7 @@ export interface ViteNodeOptions {
15
16
  root: string;
16
17
  files: string[];
17
18
  _?: string[];
18
- shouldExternalize?: (file: string) => boolean;
19
+ shouldExternalize?: (file: string, server: ViteDevServer) => boolean;
19
20
  config?: string;
20
21
  defaultConfig?: InlineConfig;
21
22
  }
package/dist/node/node.js CHANGED
@@ -5,7 +5,8 @@ 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
+ const moduleCache = new Map();
9
+ const modulesTransformResult = new Map();
9
10
  export async function run(argv) {
10
11
  process.exitCode = 0;
11
12
  const root = argv.root || process.cwd();
@@ -22,7 +23,8 @@ export async function run(argv) {
22
23
  await server.pluginContainer.buildStart({});
23
24
  process.__vite_node__ = {
24
25
  server,
25
- moduleCache: __pendingModules__,
26
+ moduleCache,
27
+ modulesTransformResult,
26
28
  };
27
29
  try {
28
30
  await execute(files, server, argv);
@@ -42,6 +44,8 @@ function normalizeId(id) {
42
44
  id = `\0${id.slice('/@id/__x00__'.length)}`;
43
45
  if (id && id.startsWith('/@id/'))
44
46
  id = id.slice('/@id/'.length);
47
+ if (id.startsWith('__vite-browser-external:'))
48
+ id = id.slice('__vite-browser-external:'.length);
45
49
  return id;
46
50
  }
47
51
  function toFilePath(id, server) {
@@ -58,6 +62,29 @@ function toFilePath(id, server) {
58
62
  absolute = `/${absolute}`;
59
63
  return absolute;
60
64
  }
65
+ const stubRequests = {
66
+ '/@vite/client': {
67
+ injectQuery: (id) => id,
68
+ createHotContext() {
69
+ return {
70
+ accept: () => { },
71
+ };
72
+ },
73
+ },
74
+ };
75
+ async function transform(server, id) {
76
+ if (id.match(/\.(?:[cm]?[jt]sx?|json)$/)) {
77
+ return await server.transformRequest(id, { ssr: true });
78
+ }
79
+ else {
80
+ // for components like Vue, we want to use the client side
81
+ // plugins but then covert the code to be consumed by the server
82
+ const result = await server.transformRequest(id);
83
+ if (!result)
84
+ return undefined;
85
+ return await server.ssrTransform(result.code, result.map, id);
86
+ }
87
+ }
61
88
  async function execute(files, server, options) {
62
89
  const result = [];
63
90
  for (const file of files)
@@ -74,9 +101,12 @@ async function execute(files, server, options) {
74
101
  }
75
102
  return cachedRequest(dep, callstack);
76
103
  };
77
- const result = await server.transformRequest(id, { ssr: true });
104
+ if (id in stubRequests)
105
+ return stubRequests[id];
106
+ const result = await transform(server, id);
78
107
  if (!result)
79
108
  throw new Error(`failed to load ${id}`);
109
+ modulesTransformResult.set(id, result);
80
110
  const url = pathToFileURL(fsPath);
81
111
  const exports = {};
82
112
  const context = {
@@ -97,16 +127,16 @@ async function execute(files, server, options) {
97
127
  return exports;
98
128
  }
99
129
  async function cachedRequest(rawId, callstack) {
100
- if (builtinModules.includes(rawId))
101
- return import(rawId);
102
130
  const id = normalizeId(rawId);
131
+ if (builtinModules.includes(id))
132
+ return import(id);
103
133
  const fsPath = toFilePath(id, server);
104
- if (options.shouldExternalize(fsPath))
134
+ if (options.shouldExternalize(fsPath, server))
105
135
  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);
136
+ if (moduleCache.has(fsPath))
137
+ return moduleCache.get(fsPath);
138
+ moduleCache.set(fsPath, directRequest(id, fsPath, callstack));
139
+ return await moduleCache.get(fsPath);
110
140
  }
111
141
  function exportAll(exports, sourceModule) {
112
142
  // eslint-disable-next-line no-restricted-syntax
@@ -1,5 +1,5 @@
1
1
  import Listr from 'listr';
2
- import { File, Reporter, RunnerContext, Task } from '../types';
2
+ import { File, Reporter, RunnerContext, Task, ResolvedConfig } from '../types';
3
3
  interface TaskPromise {
4
4
  promise: Promise<void>;
5
5
  resolve: () => void;
@@ -11,9 +11,12 @@ export declare class DefaultReporter implements Reporter {
11
11
  listr: Listr | null;
12
12
  listrPromise: Promise<void> | null;
13
13
  taskMap: Map<Task, TaskPromise>;
14
+ cwd: string;
15
+ relative(path: string): string;
16
+ onStart(config: ResolvedConfig): void;
14
17
  onCollected(files: File[]): void;
15
18
  onTaskEnd(task: Task): void;
16
- onFinished(ctx: RunnerContext): Promise<void>;
19
+ onFinished(ctx: RunnerContext, files?: File[]): Promise<void>;
17
20
  onWatcherStart(ctx: RunnerContext): Promise<void>;
18
21
  onWatcherRerun(files: string[], trigger: string): Promise<void>;
19
22
  onSnapshotUpdate(): void;
@@ -3,6 +3,7 @@ import { performance } from 'perf_hooks';
3
3
  import { relative } from 'path';
4
4
  import c from 'picocolors';
5
5
  import Listr from 'listr';
6
+ import { printError } from './error';
6
7
  const CROSS = '✖ ';
7
8
  export class DefaultReporter {
8
9
  constructor() {
@@ -11,6 +12,14 @@ export class DefaultReporter {
11
12
  this.listr = null;
12
13
  this.listrPromise = null;
13
14
  this.taskMap = new Map();
15
+ this.cwd = process.cwd();
16
+ }
17
+ relative(path) {
18
+ return relative(this.cwd, path);
19
+ }
20
+ onStart(config) {
21
+ this.cwd = config.root;
22
+ console.log(c.green(`Running tests under ${c.gray(this.cwd)}\n`));
14
23
  }
15
24
  onCollected(files) {
16
25
  this.start = performance.now();
@@ -41,9 +50,14 @@ export class DefaultReporter {
41
50
  };
42
51
  this.listr = new Listr(files.map((file) => {
43
52
  return {
44
- title: relative(process.cwd(), file.filepath),
53
+ title: this.relative(file.filepath),
45
54
  task: () => {
46
- return new Listr(file.suites.flatMap((suite) => {
55
+ if (file.error)
56
+ throw file.error;
57
+ const suites = file.suites.filter(i => i.tasks.length);
58
+ if (!suites.length)
59
+ throw new Error('No tasks found');
60
+ return new Listr(suites.flatMap((suite) => {
47
61
  if (!suite.name)
48
62
  return createTasksListr(suite.tasks);
49
63
  return [{
@@ -64,11 +78,16 @@ export class DefaultReporter {
64
78
  else
65
79
  (_b = this.taskMap.get(task)) === null || _b === void 0 ? void 0 : _b.resolve();
66
80
  }
67
- async onFinished(ctx) {
81
+ async onFinished(ctx, files = ctx.files) {
82
+ var _a;
68
83
  await this.listrPromise;
69
84
  this.end = performance.now();
70
85
  console.log();
71
- const { tasks, suites, files } = ctx;
86
+ const snapshot = ctx.snapshotManager.report();
87
+ if (snapshot)
88
+ console.log(snapshot.join('\n'));
89
+ const suites = files.flatMap(i => i.suites);
90
+ const tasks = suites.flatMap(i => i.tasks);
72
91
  const failedFiles = files.filter(i => i.error);
73
92
  const failedSuites = suites.filter(i => i.error);
74
93
  const runnable = tasks.filter(i => i.state === 'pass' || i.state === 'fail');
@@ -77,30 +96,30 @@ export class DefaultReporter {
77
96
  const skipped = tasks.filter(i => i.state === 'skip');
78
97
  const todo = tasks.filter(i => i.state === 'todo');
79
98
  if (failedFiles.length) {
80
- console.error(c.bold(`\nFailed to parse ${failedFiles.length} files:`));
81
- failedFiles.forEach((i) => {
82
- console.error(c.red(`\n- ${i.filepath}`));
83
- console.error(i.error || 'Unknown error');
99
+ console.error(c.red(c.bold(`\nFailed to parse ${failedFiles.length} files:`)));
100
+ for (const file of failedFiles)
101
+ console.error(c.red(`- ${file.filepath}`));
102
+ console.log();
103
+ for (const file of failedFiles) {
104
+ await printError(file.error);
84
105
  console.log();
85
- });
106
+ }
86
107
  }
87
108
  if (failedSuites.length) {
88
109
  console.error(c.bold(c.red(`\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');
110
+ for (const suite of failedSuites) {
111
+ console.error(c.red(`\n- ${(_a = suite.file) === null || _a === void 0 ? void 0 : _a.filepath} > ${suite.name}`));
112
+ await printError(suite.error);
93
113
  console.log();
94
- });
114
+ }
95
115
  }
96
116
  if (failed.length) {
97
117
  console.error(c.bold(c.red(`\nFailed Tests (${failed.length})`)));
98
- failed.forEach((task) => {
99
- var _a;
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}`))}`);
101
- console.error(task.error || 'Unknown error');
118
+ for (const task of failed) {
119
+ console.error(`${c.red(`\n${CROSS + c.inverse(' FAIL ')}`)} ${[task.suite.name, task.name].filter(Boolean).join(' > ')}`);
120
+ await printError(task.error);
102
121
  console.log();
103
- });
122
+ }
104
123
  }
105
124
  console.log(c.bold(c.green(`Passed ${passed.length} / ${runnable.length}`)));
106
125
  if (failed.length)
@@ -122,7 +141,7 @@ export class DefaultReporter {
122
141
  async onWatcherRerun(files, trigger) {
123
142
  await this.listrPromise;
124
143
  console.clear();
125
- console.log(c.blue('Re-running tests...') + c.dim(` [ ${relative(process.cwd(), trigger)} ]\n`));
144
+ console.log(c.blue('Re-running tests...') + c.dim(` [ ${this.relative(trigger)} ]\n`));
126
145
  }
127
146
  // TODO:
128
147
  onSnapshotUpdate() {
@@ -0,0 +1,9 @@
1
+ export declare function printError(error: unknown): Promise<void>;
2
+ interface Poisition {
3
+ line: number;
4
+ column: number;
5
+ }
6
+ export declare function posToNumber(source: string, pos: number | Poisition): number;
7
+ export declare function numberToPos(source: string, offset: number | Poisition): Poisition;
8
+ export declare function generateCodeFrame(source: string, start?: number | Poisition, end?: number, range?: number): string;
9
+ export {};
@@ -0,0 +1,182 @@
1
+ /* eslint-disable no-console */
2
+ import { promises as fs, existsSync } from 'fs';
3
+ import c from 'picocolors';
4
+ import * as diff from 'diff';
5
+ import { notNullish } from '@antfu/utils';
6
+ import { SourceMapConsumer } from 'source-map';
7
+ export async function printError(error) {
8
+ if (!(error instanceof Error)) {
9
+ console.error(error);
10
+ return;
11
+ }
12
+ const { modulesTransformResult } = process.__vite_node__;
13
+ const e = error;
14
+ let codeFramePrinted = false;
15
+ const stacks = parseStack(e.stack || '');
16
+ const nearest = stacks.find(stack => modulesTransformResult.has(stack.file));
17
+ if (nearest) {
18
+ const transformResult = modulesTransformResult.get(nearest.file);
19
+ const pos = await getOriginalPos(transformResult === null || transformResult === void 0 ? void 0 : transformResult.map, nearest);
20
+ if (pos && existsSync(nearest.file)) {
21
+ const sourceCode = await fs.readFile(nearest.file, 'utf-8');
22
+ console.error(`${c.red(`${c.bold(e.name)}: ${e.message}`)}`);
23
+ console.log(c.gray(`${nearest.file}:${pos.line}:${pos.column}`));
24
+ console.log(c.yellow(generateCodeFrame(sourceCode, pos)));
25
+ codeFramePrinted = true;
26
+ }
27
+ }
28
+ if (!codeFramePrinted)
29
+ console.error(e);
30
+ if (e.showDiff)
31
+ console.error(c.gray(generateDiff(stringify(e.actual), stringify(e.expected))));
32
+ }
33
+ function getOriginalPos(map, { line, column }) {
34
+ return new Promise((resolve) => {
35
+ if (!map)
36
+ return resolve(null);
37
+ SourceMapConsumer.with(map, null, (consumer) => {
38
+ const pos = consumer.originalPositionFor({ line, column });
39
+ if (pos.line != null && pos.column != null)
40
+ resolve(pos);
41
+ else
42
+ resolve(null);
43
+ });
44
+ });
45
+ }
46
+ const splitRE = /\r?\n/;
47
+ export function posToNumber(source, pos) {
48
+ if (typeof pos === 'number')
49
+ return pos;
50
+ const lines = source.split(splitRE);
51
+ const { line, column } = pos;
52
+ let start = 0;
53
+ for (let i = 0; i < line - 1; i++)
54
+ start += lines[i].length + 1;
55
+ return start + column;
56
+ }
57
+ export function numberToPos(source, offset) {
58
+ if (typeof offset !== 'number')
59
+ return offset;
60
+ if (offset > source.length) {
61
+ throw new Error(`offset is longer than source length! offset ${offset} > length ${source.length}`);
62
+ }
63
+ const lines = source.split(splitRE);
64
+ let counted = 0;
65
+ let line = 0;
66
+ let column = 0;
67
+ for (; line < lines.length; line++) {
68
+ const lineLength = lines[line].length + 1;
69
+ if (counted + lineLength >= offset) {
70
+ column = offset - counted + 1;
71
+ break;
72
+ }
73
+ counted += lineLength;
74
+ }
75
+ return { line: line + 1, column };
76
+ }
77
+ export function generateCodeFrame(source, start = 0, end, range = 2) {
78
+ start = posToNumber(source, start);
79
+ end = end || start;
80
+ const lines = source.split(splitRE);
81
+ let count = 0;
82
+ const res = [];
83
+ for (let i = 0; i < lines.length; i++) {
84
+ count += lines[i].length + 1;
85
+ if (count >= start) {
86
+ for (let j = i - range; j <= i + range || end > count; j++) {
87
+ if (j < 0 || j >= lines.length)
88
+ continue;
89
+ const line = j + 1;
90
+ res.push(`${c.gray(`${line}${' '.repeat(Math.max(3 - String(line).length, 0))}|`)} ${lines[j]}`);
91
+ const lineLength = lines[j].length;
92
+ if (j === i) {
93
+ // push underline
94
+ const pad = start - (count - lineLength) + 1;
95
+ const length = Math.max(1, end > count ? lineLength - pad : end - start);
96
+ res.push(`${c.gray(' |')} ${' '.repeat(pad)}${'^'.repeat(length)}`);
97
+ }
98
+ else if (j > i) {
99
+ if (end > count) {
100
+ const length = Math.max(Math.min(end - count, lineLength), 1);
101
+ res.push(`${c.gray(' |')} ${'^'.repeat(length)}`);
102
+ }
103
+ count += lineLength + 1;
104
+ }
105
+ }
106
+ break;
107
+ }
108
+ }
109
+ return res.join('\n');
110
+ }
111
+ function stringify(obj) {
112
+ // TODO: handle more types
113
+ return String(obj);
114
+ }
115
+ const stackFnCallRE = /at (.*) \((.+):(\d+):(\d+)\)$/;
116
+ const stackBarePathRE = /at ()(.+):(\d+):(\d+)$/;
117
+ function parseStack(stack) {
118
+ const lines = stack.split('\n');
119
+ const stackFrames = lines.map((raw) => {
120
+ const line = raw.trim();
121
+ const match = line.match(stackFnCallRE) || line.match(stackBarePathRE);
122
+ if (!match)
123
+ return null;
124
+ let file = match[2];
125
+ if (file.startsWith('file://'))
126
+ file = file.slice(7);
127
+ return {
128
+ method: match[1],
129
+ file: match[2],
130
+ line: parseInt(match[3]),
131
+ column: parseInt(match[4]),
132
+ };
133
+ });
134
+ return stackFrames.filter(notNullish);
135
+ }
136
+ /**
137
+ * Returns a diff between 2 strings with coloured ANSI output.
138
+ *
139
+ * @description
140
+ * The diff will be either inline or unified dependent on the value
141
+ * of `Base.inlineDiff`.
142
+ *
143
+ * @param {string} actual
144
+ * @param {string} expected
145
+ * @return {string} Diff
146
+ */
147
+ function generateDiff(actual, expected) {
148
+ const diffSize = 2048;
149
+ if (actual.length > diffSize)
150
+ actual = `${actual.substring(0, diffSize)} ... Lines skipped`;
151
+ if (expected.length > diffSize)
152
+ expected = `${expected.substring(0, diffSize)} ... Lines skipped`;
153
+ return unifiedDiff(actual, expected);
154
+ }
155
+ /**
156
+ * Returns unified diff between two strings with coloured ANSI output.
157
+ *
158
+ * @private
159
+ * @param {String} actual
160
+ * @param {String} expected
161
+ * @return {string} The diff.
162
+ */
163
+ function unifiedDiff(actual, expected) {
164
+ const indent = ' ';
165
+ function cleanUp(line) {
166
+ if (line[0] === '+')
167
+ return indent + c.green(`${line[0]} ${line.slice(1)}`);
168
+ if (line[0] === '-')
169
+ return indent + c.red(`${line[0]} ${line.slice(1)}`);
170
+ if (line.match(/@@/))
171
+ return '--';
172
+ if (line.match(/\\ No newline/))
173
+ return null;
174
+ return indent + line;
175
+ }
176
+ const msg = diff.createPatch('string', actual, expected);
177
+ const lines = msg.split('\n').splice(5);
178
+ return (`\n${indent}${c.red('- actual')}\n${indent}${c.green('+ expected')}\n\n${lines.map(cleanUp).filter(notBlank).join('\n')}`);
179
+ }
180
+ function notBlank(line) {
181
+ return typeof line !== 'undefined' && line !== null;
182
+ }
package/dist/run/index.js CHANGED
@@ -163,6 +163,7 @@ export async function run(config) {
163
163
  (await import('../integrations/jsdom')).setupJSDOM(globalThis);
164
164
  await ((_b = reporter.onStart) === null || _b === void 0 ? void 0 : _b.call(reporter, config));
165
165
  const filesMap = await collectFiles(testFilepaths);
166
+ const snapshotManager = getSnapshotManager();
166
167
  const ctx = {
167
168
  filesMap,
168
169
  get files() {
@@ -177,19 +178,19 @@ export async function run(config) {
177
178
  .reduce((tasks, suite) => tasks.concat(suite.tasks), []);
178
179
  },
179
180
  config,
180
- reporter: config.reporter,
181
+ reporter,
182
+ snapshotManager,
181
183
  };
182
184
  await runFiles(filesMap, ctx);
183
- const snapshot = getSnapshotManager();
184
- snapshot === null || snapshot === void 0 ? void 0 : snapshot.saveSnap();
185
- snapshot === null || snapshot === void 0 ? void 0 : snapshot.report();
185
+ snapshotManager.saveSnap();
186
186
  await ((_c = reporter.onFinished) === null || _c === void 0 ? void 0 : _c.call(reporter, ctx));
187
187
  if (config.watch)
188
188
  startWatcher(ctx);
189
189
  }
190
190
  export async function startWatcher(ctx) {
191
- var _a, _b;
192
- await ((_b = (_a = ctx.reporter).onWatcherStart) === null || _b === void 0 ? void 0 : _b.call(_a, ctx));
191
+ var _a;
192
+ const { reporter, snapshotManager, filesMap } = ctx;
193
+ await ((_a = reporter.onWatcherStart) === null || _a === void 0 ? void 0 : _a.call(reporter, ctx));
193
194
  let timer;
194
195
  const changedTests = new Set();
195
196
  const seen = new Set();
@@ -202,21 +203,20 @@ export async function startWatcher(ctx) {
202
203
  return;
203
204
  clearTimeout(timer);
204
205
  timer = setTimeout(async () => {
205
- var _a, _b, _c, _d;
206
+ var _a, _b, _c;
206
207
  if (changedTests.size === 0)
207
208
  return;
208
- const snapshot = getSnapshotManager();
209
+ snapshotManager.clear();
209
210
  const paths = Array.from(changedTests);
210
211
  changedTests.clear();
211
- await ((_b = (_a = ctx.reporter).onWatcherRerun) === null || _b === void 0 ? void 0 : _b.call(_a, paths, id, ctx));
212
+ await ((_a = reporter.onWatcherRerun) === null || _a === void 0 ? void 0 : _a.call(reporter, paths, id, ctx));
212
213
  paths.forEach(i => moduleCache.delete(i));
213
- const files = await collectFiles(paths);
214
- Object.assign(ctx.filesMap, files);
215
- await runFiles(files, ctx);
216
- // TODO: clear snapshot state
217
- snapshot === null || snapshot === void 0 ? void 0 : snapshot.saveSnap();
218
- snapshot === null || snapshot === void 0 ? void 0 : snapshot.report();
219
- await ((_d = (_c = ctx.reporter).onWatcherStart) === null || _d === void 0 ? void 0 : _d.call(_c, ctx));
214
+ const newFilesMap = await collectFiles(paths);
215
+ Object.assign(filesMap, newFilesMap);
216
+ await runFiles(newFilesMap, ctx);
217
+ snapshotManager.saveSnap();
218
+ await ((_b = reporter.onFinished) === null || _b === void 0 ? void 0 : _b.call(reporter, ctx, Object.values(newFilesMap)));
219
+ await ((_c = reporter.onWatcherStart) === null || _c === void 0 ? void 0 : _c.call(reporter, ctx));
220
220
  }, 100);
221
221
  });
222
222
  }
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { SnapshotManager } from './integrations/chai/snapshot/manager';
1
2
  export declare type Awaitable<T> = Promise<T> | T;
2
3
  export interface UserOptions {
3
4
  /**
@@ -11,6 +12,13 @@ export interface UserOptions {
11
12
  * @default ['**\/node_modules\/**']
12
13
  */
13
14
  excludes?: string[];
15
+ /**
16
+ * Handling for dependencies inlining or externalizing
17
+ */
18
+ deps?: {
19
+ external?: (string | RegExp)[];
20
+ inline?: (string | RegExp)[];
21
+ };
14
22
  /**
15
23
  * Register apis globally
16
24
  *
@@ -52,6 +60,7 @@ export interface UserOptions {
52
60
  }
53
61
  export interface ResolvedConfig extends Required<UserOptions> {
54
62
  filters?: string[];
63
+ config?: string;
55
64
  }
56
65
  export declare type RunMode = 'run' | 'skip' | 'only' | 'todo';
57
66
  export declare type TaskState = RunMode | 'pass' | 'fail';
@@ -108,15 +117,16 @@ export interface RunnerContext {
108
117
  tasks: Task[];
109
118
  config: ResolvedConfig;
110
119
  reporter: Reporter;
120
+ snapshotManager: SnapshotManager;
111
121
  }
112
122
  export interface GlobalContext {
113
123
  suites: SuiteCollector[];
114
124
  currentSuite: SuiteCollector | null;
115
125
  }
116
126
  export interface Reporter {
117
- onStart?: (userOptions: ResolvedConfig) => Awaitable<void>;
127
+ onStart?: (config: ResolvedConfig) => Awaitable<void>;
118
128
  onCollected?: (files: File[], ctx: RunnerContext) => Awaitable<void>;
119
- onFinished?: (ctx: RunnerContext) => Awaitable<void>;
129
+ onFinished?: (ctx: RunnerContext, files?: File[]) => Awaitable<void>;
120
130
  onSuiteBegin?: (suite: Suite, ctx: RunnerContext) => Awaitable<void>;
121
131
  onSuiteEnd?: (suite: Suite, ctx: RunnerContext) => Awaitable<void>;
122
132
  onFileBegin?: (file: File, ctx: RunnerContext) => Awaitable<void>;
@@ -125,5 +135,4 @@ export interface Reporter {
125
135
  onTaskEnd?: (task: Task, ctx: RunnerContext) => Awaitable<void>;
126
136
  onWatcherStart?: (ctx: RunnerContext) => Awaitable<void>;
127
137
  onWatcherRerun?: (files: string[], trigger: string, ctx: RunnerContext) => Awaitable<void>;
128
- onSnapshotUpdate?: () => Awaitable<void>;
129
138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest",
3
- "version": "0.0.26",
3
+ "version": "0.0.30",
4
4
  "description": "A blazing fast unit test framework powered by Vite",
5
5
  "keywords": [
6
6
  "vite",
@@ -44,15 +44,19 @@
44
44
  "lint": "eslint \"{src,test}/**/*.ts\"",
45
45
  "prepublishOnly": "nr build",
46
46
  "release": "bumpp --commit --push --tag && esmo scripts/publish.ts",
47
- "test": "node bin/vitest.mjs --dev",
48
- "test:update": "nr test -u",
47
+ "test": "run-s test:*",
48
+ "test:core": "node bin/vitest.mjs --dev -r test/core",
49
+ "test:vue": "node bin/vitest.mjs --dev -r test/vue",
49
50
  "watch": "tsc -p src/tsconfig.json --watch"
50
51
  },
51
52
  "dependencies": {
53
+ "@antfu/utils": "^0.3.0",
52
54
  "@jest/test-result": "^27.4.2",
53
55
  "@types/chai": "^4.2.22",
54
56
  "@types/sinon-chai": "^3.2.6",
55
57
  "chai": "^4.3.4",
58
+ "chai-subset": "^1.6.0",
59
+ "diff": "^5.0.0",
56
60
  "fast-glob": "^3.2.7",
57
61
  "find-up": "^6.2.0",
58
62
  "jest-snapshot": "^27.4.2",
@@ -62,21 +66,30 @@
62
66
  "picocolors": "^1.0.0",
63
67
  "sade": "^1.7.4",
64
68
  "sinon": "^12.0.1",
65
- "sinon-chai": "^3.7.0"
69
+ "sinon-chai": "^3.7.0",
70
+ "source-map": "^0.7.3"
71
+ },
72
+ "peerDependencies": {
73
+ "vite": "^2.7.0"
66
74
  },
67
75
  "devDependencies": {
68
76
  "@antfu/eslint-config": "^0.12.1",
69
77
  "@antfu/ni": "^0.11.0",
78
+ "@types/chai-subset": "^1.3.3",
79
+ "@types/diff": "^5.0.1",
70
80
  "@types/jsdom": "^16.2.13",
71
81
  "@types/listr": "^0.14.4",
72
82
  "@types/node": "^16.11.11",
73
83
  "@types/sade": "^1.7.3",
74
84
  "@types/sinon": "^10.0.6",
85
+ "@vitejs/plugin-vue": "^1.10.1",
75
86
  "bumpp": "^7.1.1",
76
87
  "eslint": "^8.4.0",
77
88
  "esno": "^0.12.1",
89
+ "npm-run-all": "^4.1.5",
78
90
  "rimraf": "^3.0.2",
79
91
  "typescript": "^4.5.2",
80
- "vite": "^2.6.14"
92
+ "vite": "^2.7.0",
93
+ "vue": "^3.2.24"
81
94
  }
82
95
  }