@mpcore/vitest 0.2.7 → 1.1.0

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
@@ -1,16 +1,44 @@
1
1
  # @mpcore/vitest
2
2
 
3
- Vitest 5 integration for `@mpcore/test`. Add `mpcoreTest()` to the Vitest plugin list, then use `createMpcoreTest()` for an isolated mini-program project fixture per test.
3
+ Vitest 5 integration for `@mpcore/test`. Import the configuration plugin from `@mpcore/vitest/config`; this entry does not load the test worker's `expect` or `test`. Test files import fixtures from `@mpcore/vitest`, using the same Vitest peer instance as setup and matchers.
4
+
5
+ Configure an explicit artifact factory. The integration does not depend on a particular compiler; for weapp-vite:
4
6
 
5
7
  ```ts
6
- import { createMpcoreTest } from '@mpcore/vitest'
8
+ // vitest.config.ts
9
+ import { mpcoreTest } from '@mpcore/vitest/config'
10
+ import { buildWeappViteTestArtifact, watchWeappViteTestArtifact } from '@mpcore/weapp-vite'
11
+ import { defineConfig } from 'vitest/config'
7
12
 
8
- const test = createMpcoreTest({
9
- artifact: { projectPath: process.cwd() },
13
+ const options = { cwd: import.meta.dirname, platform: 'weapp' as const }
14
+
15
+ export default defineConfig({
16
+ plugins: [mpcoreTest({
17
+ artifact: {
18
+ build: () => buildWeappViteTestArtifact(options),
19
+ watch: callbacks => watchWeappViteTestArtifact({ ...options, ...callbacks }),
20
+ },
21
+ })],
10
22
  })
23
+ ```
24
+
25
+ Run mode calls `build` once per project. Watch mode calls `watch` instead; the returned watcher owns its initial build and exposes that artifact. After a successful update, the plugin provides the new artifact and asks the current runner to rerun only that project's specifications. Build and scheduling errors are recorded and printed by the runner. Closing waits for startup, pending updates and the owned watcher; it does not start or stop another test process.
26
+
27
+ Vite+ uses the same plugin and `vp test`; import `defineConfig` from `vite-plus` in that host's configuration and keep its Vitest peer aligned with the runner.
28
+
29
+ Each test receives an isolated mini-program runtime from the injected artifact:
30
+
31
+ ```ts
32
+ import { createMpcoreTest } from '@mpcore/vitest'
33
+
34
+ const test = createMpcoreTest()
11
35
 
12
36
  test('renders a page', async ({ mpcore, expect }) => {
13
37
  const result = await mpcore.renderPage('/pages/index/index')
14
38
  expect(result.screen.getByText('ready')).toBeInTheMiniProgram()
15
39
  })
16
40
  ```
41
+
42
+ `createMpcoreTest({ artifact: { projectPath: process.cwd() } })` still accepts an existing artifact directly. `createVitestProject()` also uses the configured artifact when omitted, and closes its runtime with the current test. Optional host mocks and `failOnConsoleError` can be passed without replacing the injected artifact. Calling either helper without a configured or explicit artifact reports an actionable error.
43
+
44
+ Setup also registers the semantic tree snapshot serializer from `@mpcore/test`. Use `expect(result.screen).toMatchSnapshot()` or snapshot an individual node. Snapshots preserve public tree and event-binding semantics while removing runtime IDs, style scope markers and generated handler names. Full diagnostic snapshots returned by `page.snapshot()` remain unchanged; use `toMiniProgramSnapshot()` explicitly to serialize one.
@@ -0,0 +1,77 @@
1
+ import { fileURLToPath } from "node:url";
2
+ //#region src/artifact.ts
3
+ const MPCORE_ARTIFACT_KEY = "mpcoreArtifact";
4
+ /** 每次 configureVitest 调用只持有当前 project 的 watcher 和重跑队列。 */
5
+ async function configureArtifact(factory, { project, vitest }) {
6
+ let closed = false;
7
+ let ready = false;
8
+ let earlyArtifact;
9
+ let watcher;
10
+ let pending = Promise.resolve();
11
+ let starting = Promise.resolve();
12
+ let closing;
13
+ const onError = (error) => {
14
+ if (!closed) {
15
+ vitest.state.catchError(error, "Mpcore Artifact Error");
16
+ vitest.logger.printError(error);
17
+ }
18
+ };
19
+ const onRebuilt = (artifact) => {
20
+ if (closed) return Promise.resolve();
21
+ if (!ready) {
22
+ earlyArtifact = artifact;
23
+ return Promise.resolve();
24
+ }
25
+ pending = pending.then(async () => {
26
+ if (closed) return;
27
+ project.provide(MPCORE_ARTIFACT_KEY, artifact);
28
+ const specifications = await vitest.globTestSpecifications();
29
+ if (!closed) await vitest.rerunTestSpecifications(specifications.filter((specification) => specification.project === project));
30
+ }).catch(onError);
31
+ return pending;
32
+ };
33
+ vitest.onClose(() => {
34
+ return closing ??= (async () => {
35
+ closed = true;
36
+ await starting.catch(() => {});
37
+ const ownedWatcher = watcher;
38
+ watcher = void 0;
39
+ const errors = (await Promise.allSettled([Promise.resolve().then(() => ownedWatcher?.close()), pending])).filter((result) => result.status === "rejected").map((result) => result.reason);
40
+ if (errors.length) throw new AggregateError(errors, "Mpcore artifact cleanup failed");
41
+ })();
42
+ });
43
+ starting = (async () => {
44
+ if (closed) return;
45
+ let artifact;
46
+ if (vitest.config.watch && factory.watch) {
47
+ watcher = await factory.watch({
48
+ onRebuilt,
49
+ onError
50
+ });
51
+ artifact = watcher.artifact;
52
+ } else artifact = await factory.build();
53
+ if (!closed) {
54
+ project.provide(MPCORE_ARTIFACT_KEY, earlyArtifact ?? artifact);
55
+ ready = true;
56
+ }
57
+ })();
58
+ await starting;
59
+ }
60
+ //#endregion
61
+ //#region src/config.ts
62
+ /** 配置入口仅依赖 runner 的公开类型,不提前加载测试线程的 expect/test。 */
63
+ function mpcoreTest(options = {}) {
64
+ const setupFile = fileURLToPath(new URL("./setup.mjs", import.meta.url));
65
+ return {
66
+ name: "mpcore:vitest",
67
+ config(config) {
68
+ const existing = config.test?.setupFiles;
69
+ return { test: { setupFiles: (typeof existing === "string" ? [existing] : existing ?? []).includes(setupFile) ? [] : [setupFile] } };
70
+ },
71
+ async configureVitest(context) {
72
+ if (options.artifact) await configureArtifact(options.artifact, context);
73
+ }
74
+ };
75
+ }
76
+ //#endregion
77
+ export { MPCORE_ARTIFACT_KEY as n, mpcoreTest as t };
@@ -0,0 +1,43 @@
1
+ import { MiniProgramArtifact } from "@mpcore/test";
2
+ import { VitestPluginContext } from "vitest/node";
3
+ //#region src/artifact.d.ts
4
+ interface MpcoreArtifactWatcher {
5
+ artifact: MiniProgramArtifact;
6
+ close: () => void | Promise<void>;
7
+ }
8
+ interface MpcoreArtifactWatchCallbacks {
9
+ onRebuilt: (artifact: MiniProgramArtifact) => Promise<void>;
10
+ onError: (error: unknown) => void;
11
+ }
12
+ interface MpcoreArtifactFactory {
13
+ build: () => MiniProgramArtifact | Promise<MiniProgramArtifact>;
14
+ /** watcher 负责首次构建并返回初始产物,避免 run 与 watch 重复构建。 */
15
+ watch?: (callbacks: MpcoreArtifactWatchCallbacks) => MpcoreArtifactWatcher | Promise<MpcoreArtifactWatcher>;
16
+ }
17
+ declare module 'vitest' {
18
+ interface ProvidedContext {
19
+ mpcoreArtifact: MiniProgramArtifact;
20
+ }
21
+ }
22
+ //#endregion
23
+ //#region src/config.d.ts
24
+ interface MpcoreVitestOptions {
25
+ artifact?: MpcoreArtifactFactory;
26
+ }
27
+ interface MpcoreVitestPlugin {
28
+ config: (config: {
29
+ test?: {
30
+ setupFiles?: string | string[];
31
+ };
32
+ }) => {
33
+ test: {
34
+ setupFiles: string[];
35
+ };
36
+ };
37
+ configureVitest: (context: VitestPluginContext) => Promise<void>;
38
+ name: string;
39
+ }
40
+ /** 配置入口仅依赖 runner 的公开类型,不提前加载测试线程的 expect/test。 */
41
+ declare function mpcoreTest(options?: MpcoreVitestOptions): MpcoreVitestPlugin;
42
+ //#endregion
43
+ export { MpcoreArtifactWatchCallbacks as a, MpcoreArtifactFactory as i, MpcoreVitestPlugin as n, MpcoreArtifactWatcher as o, mpcoreTest as r, MpcoreVitestOptions as t };
@@ -0,0 +1,2 @@
1
+ import { a as MpcoreArtifactWatchCallbacks, i as MpcoreArtifactFactory, n as MpcoreVitestPlugin, o as MpcoreArtifactWatcher, r as mpcoreTest, t as MpcoreVitestOptions } from "./config-D3NKpUBr.mjs";
2
+ export { type MpcoreArtifactFactory, type MpcoreArtifactWatchCallbacks, type MpcoreArtifactWatcher, MpcoreVitestOptions, MpcoreVitestPlugin, mpcoreTest };
@@ -0,0 +1 @@
1
+ export { t as mpcoreTest } from "./config-1SyUfssv.mjs";
package/dist/index.d.mts CHANGED
@@ -1,26 +1,15 @@
1
+ import { a as MpcoreArtifactWatchCallbacks, i as MpcoreArtifactFactory, n as MpcoreVitestPlugin, o as MpcoreArtifactWatcher, r as mpcoreTest, t as MpcoreVitestOptions } from "./config-D3NKpUBr.mjs";
1
2
  import { CreateTestProjectOptions, MiniProgramEmissionSource, MiniProgramNode, MiniProgramTestProject } from "@mpcore/test";
2
3
  export * from "@mpcore/test";
3
4
  //#region src/index.d.ts
4
5
  export interface MpcoreVitestFixture {
5
6
  mpcore: MiniProgramTestProject;
6
7
  }
7
- export interface MpcoreVitestPlugin {
8
- config: (config: {
9
- test?: {
10
- setupFiles?: string | string[];
11
- };
12
- }) => {
13
- test: {
14
- setupFiles: string[];
15
- };
16
- };
17
- name: string;
18
- }
19
- export declare function mpcoreTest(): MpcoreVitestPlugin;
20
- export declare function createMpcoreTest(options: CreateTestProjectOptions): import("vitest").TestAPI<{
8
+ export type MpcoreTestProjectOptions = Omit<CreateTestProjectOptions, 'artifact'> & Partial<Pick<CreateTestProjectOptions, 'artifact'>>;
9
+ export declare function createMpcoreTest(options?: MpcoreTestProjectOptions): import("vitest").TestAPI<{
21
10
  mpcore: MiniProgramTestProject;
22
11
  }>;
23
- export declare function createVitestProject(options: CreateTestProjectOptions): MiniProgramTestProject;
12
+ export declare function createVitestProject(options?: MpcoreTestProjectOptions): MiniProgramTestProject;
24
13
  export declare function registerMpcoreMatchers(): void;
25
14
  declare module 'vitest' {
26
15
  interface Matchers<R, T> {
@@ -31,4 +20,5 @@ declare module 'vitest' {
31
20
  toHaveTextContent: T extends MiniProgramNode ? (value: string | RegExp) => R : never;
32
21
  }
33
22
  }
34
- //#endregion
23
+ //#endregion
24
+ export { type MpcoreArtifactFactory, type MpcoreArtifactWatchCallbacks, type MpcoreArtifactWatcher, type MpcoreVitestOptions, type MpcoreVitestPlugin, mpcoreTest };
package/dist/index.mjs CHANGED
@@ -1,24 +1,19 @@
1
- import { fileURLToPath } from "node:url";
1
+ import { t as mpcoreTest } from "./config-1SyUfssv.mjs";
2
2
  import { createTestProject, mpcoreMatchers } from "@mpcore/test";
3
- import { expect, onTestFinished, test } from "vitest";
3
+ import { expect, inject, onTestFinished, test } from "vitest";
4
4
  export * from "@mpcore/test";
5
5
  //#region src/index.ts
6
- function toList(value) {
7
- if (!value) return [];
8
- return Array.isArray(value) ? value : [value];
9
- }
10
- function mpcoreTest() {
11
- const setupFile = fileURLToPath(new URL("./setup.mjs", import.meta.url));
6
+ function projectOptions(options = {}) {
7
+ const artifact = options.artifact ?? inject("mpcoreArtifact");
8
+ if (!artifact) throw new Error("No mpcore artifact was provided. Configure mpcoreTest({ artifact }) from @mpcore/vitest/config or pass an explicit artifact.");
12
9
  return {
13
- name: "mpcore:vitest",
14
- config(config) {
15
- return { test: { setupFiles: [...toList(config.test?.setupFiles), setupFile] } };
16
- }
10
+ ...options,
11
+ artifact
17
12
  };
18
13
  }
19
14
  function createMpcoreTest(options) {
20
15
  return test.extend({ mpcore: async ({}, use) => {
21
- const project = createTestProject(options);
16
+ const project = createTestProject(projectOptions(options));
22
17
  try {
23
18
  await use(project);
24
19
  } finally {
@@ -27,7 +22,7 @@ function createMpcoreTest(options) {
27
22
  } });
28
23
  }
29
24
  function createVitestProject(options) {
30
- const project = createTestProject(options);
25
+ const project = createTestProject(projectOptions(options));
31
26
  onTestFinished(async () => {
32
27
  await project.close();
33
28
  });
package/dist/setup.mjs CHANGED
@@ -1,6 +1,7 @@
1
- import { mpcoreMatchers } from "@mpcore/test";
1
+ import { mpcoreMatchers, mpcoreSnapshotSerializer } from "@mpcore/test";
2
2
  import { expect } from "vitest";
3
3
  //#region src/setup.ts
4
4
  expect.extend(mpcoreMatchers);
5
+ expect.addSnapshotSerializer(mpcoreSnapshotSerializer);
5
6
  //#endregion
6
7
  export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mpcore/vitest",
3
3
  "type": "module",
4
- "version": "0.2.7",
4
+ "version": "1.1.0",
5
5
  "description": "Vitest integration for @mpcore/test",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "MIT",
@@ -31,6 +31,10 @@
31
31
  "./setup": {
32
32
  "types": "./dist/setup.d.mts",
33
33
  "import": "./dist/setup.mjs"
34
+ },
35
+ "./config": {
36
+ "types": "./dist/config.d.mts",
37
+ "import": "./dist/config.mjs"
34
38
  }
35
39
  },
36
40
  "main": "./dist/index.mjs",
@@ -41,26 +45,33 @@
41
45
  "dist"
42
46
  ],
43
47
  "engines": {
44
- "node": "^20.19.0 || >=22.12.0"
48
+ "node": "^22.18.0 || ^24.11.0 || >=26.0.0"
45
49
  },
46
50
  "peerDependencies": {
47
51
  "vitest": ">=5.0.0"
48
52
  },
49
53
  "dependencies": {
50
- "@mpcore/test": "0.1.15"
54
+ "@mpcore/test": "1.1.0"
51
55
  },
52
56
  "devDependencies": {
53
- "vitest": "~5.0.2"
57
+ "vitest": "~5.0.5"
54
58
  },
55
59
  "publishConfig": {
56
60
  "access": "public",
57
61
  "registry": "https://registry.npmjs.org"
58
62
  },
63
+ "tsd": {
64
+ "compilerOptions": {
65
+ "module": "NodeNext",
66
+ "moduleResolution": "NodeNext"
67
+ }
68
+ },
59
69
  "scripts": {
60
70
  "build": "tsdown",
61
71
  "dev": "tsdown -w",
62
72
  "test": "vitest run",
63
- "test:types": "pnpm build && tsd",
73
+ "test:types": "pnpm build && pnpm run test:types:check",
74
+ "test:types:check": "tsd --files \"test-d/**/*.test-d.ts\"",
64
75
  "typecheck": "tsc --noEmit"
65
76
  }
66
77
  }