Skip to content

Commit 28036ff

Browse files
committed
feat: on-demand re-run
1 parent 6dfe4b2 commit 28036ff

3 files changed

Lines changed: 87 additions & 19 deletions

File tree

packages/scaffold/src/constant.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ export const TESTER_BASE_PATH = `${BASE_DIR}/test`;
88
export const TESTER_PROFILE_DIR = `${TESTER_BASE_PATH}/profile`;
99
export const TESTER_DATA_DIR = `${TESTER_BASE_PATH}/data`;
1010
export const TESTER_PLUGIN_DIR = `${TESTER_BASE_PATH}/resource`;
11-
export const TESTER_PLUGIN_TESTS_DIR = `${TESTER_PLUGIN_DIR}/tests`;
11+
export const TESTER_PLUGIN_TESTS_DIR = `${TESTER_PLUGIN_DIR}/content/units`;
1212
export const TESTER_PLUGIN_REF = "zotero-plugin-scaffold-test-runner";
1313
export const TESTER_PLUGIN_ID = "[email protected]";

packages/scaffold/src/core/tester/test-runner-plugin.ts

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { BuildContext, BuildResult } from "esbuild";
22
import type { Context } from "../..//types/index.js";
33
import { context } from "esbuild";
44
import { copy, outputFile, outputJSON, pathExists } from "fs-extra/esm";
5+
import { resolve } from "pathe";
56
import { glob } from "tinyglobby";
67
import { CACHE_DIR, TESTER_PLUGIN_DIR } from "../../constant.js";
78
import { saveResource } from "../../utils/file.js";
@@ -38,7 +39,7 @@ export class TestRunnerPlugin {
3839
const esbuildResult = await this.esbuildContext?.rebuild();
3940

4041
// get affected tests
41-
const tests = this.getAffectedTests(changedFile, esbuildResult?.metafile);
42+
const tests = findImpactedTests(changedFile, esbuildResult?.metafile);
4243

4344
// this.generateTestPage
4445
// mocha setup
@@ -144,24 +145,33 @@ export class TestRunnerPlugin {
144145
const html = generateHtml(setupCode, testFiles);
145146
await outputFile(`${TESTER_PLUGIN_DIR}/content/index.xhtml`, html);
146147
}
148+
}
147149

148-
/**
149-
* 给定源码路径,查找导入了该文件的所有 output file
150-
*/
151-
private getAffectedTests(changedFile: string, metafile: BuildResult["metafile"]): string[] {
152-
if (!metafile)
153-
return [];
154-
155-
const affectedEntries = new Set<string>();
156-
157-
for (const [path, info] of Object.entries(metafile.outputs)) {
158-
for (const imp of info.imports) {
159-
if (imp.path === changedFile) {
160-
affectedEntries.add(path);
161-
}
162-
}
150+
/**
151+
* Determines which test files are impacted by a given changed file based on the esbuild metafile.
152+
*
153+
* This function analyzes the build metadata to find test files that depend on the changed file
154+
* either directly as an entry point or indirectly as an input. It is useful in a watch mode setup
155+
* to selectively rerun only the affected tests.
156+
*
157+
* @param {string} changedFilePath - The file path of the changed source file.
158+
* @param {BuildResult["metafile"]} buildMetadata - The esbuild metafile containing dependency information.
159+
* @returns {string[]} An array of impacted test file paths that need to be re-executed.
160+
*/
161+
export function findImpactedTests(changedFilePath: string, buildMetadata: BuildResult["metafile"]): string[] {
162+
if (!buildMetadata)
163+
return [];
164+
165+
const resolvedChangedFile = resolve(changedFilePath);
166+
const impactedTestFiles = new Set<string>();
167+
168+
for (const [outputFilePath, outputInfo] of Object.entries(buildMetadata.outputs)) {
169+
const testFilePath = outputFilePath.replace(`${TESTER_PLUGIN_DIR}/content/`, "");
170+
// const resolvedEntryPoint = outputInfo.entryPoint ? resolve(outputInfo.entryPoint) : null;
171+
172+
if (Object.keys(outputInfo.inputs).some(inputPath => resolve(inputPath) === resolvedChangedFile)) {
173+
impactedTestFiles.add(testFilePath);
163174
}
164-
165-
return [...affectedEntries];
166175
}
176+
return Array.from(impactedTestFiles);
167177
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { Metafile } from "esbuild";
2+
import { describe, expect, it, vi } from "vitest";
3+
import { findImpactedTests } from "../../src/core/tester/test-runner-plugin.js";
4+
5+
// Mock resolve function
6+
vi.mock("path", async () => {
7+
const actual = await vi.importActual<typeof import("path")>("path");
8+
return { ...actual, resolve: (p: string) => p };
9+
});
10+
11+
describe("findImpactedTests", () => {
12+
const mockMetafileOutputs = {
13+
outputs: {
14+
".scaffold/test/resource/content/units/test1.spec.js": {
15+
entryPoint: "test/test1.spec.ts",
16+
inputs: {
17+
"test/test1.spec.ts": {},
18+
"src/moduleA.ts": {},
19+
"src/moduleB.ts": {},
20+
},
21+
},
22+
".scaffold/test/resource/content/units/test2.spec.js": {
23+
entryPoint: "test/test2.spec.ts",
24+
inputs: {
25+
"test/test2.spec.ts": {},
26+
"src/moduleC.ts": {},
27+
},
28+
},
29+
".scaffold/test/resource/content/units/test3.spec.js": {
30+
entryPoint: "test/test3.spec.ts",
31+
inputs: {
32+
"test/test3.spec.ts": {},
33+
"src/moduleC.ts": {},
34+
},
35+
},
36+
},
37+
} as unknown as Metafile;
38+
39+
it("returns affected test file when a test file itself is changed", () => {
40+
const result = findImpactedTests("test/test1.spec.ts", mockMetafileOutputs);
41+
expect(result).toEqual(["units/test1.spec.js"]);
42+
});
43+
44+
it("returns affected test files when a source file is changed", () => {
45+
const result = findImpactedTests("src/moduleA.ts", mockMetafileOutputs);
46+
expect(result).toEqual(["units/test1.spec.js"]);
47+
});
48+
49+
it("returns multiple affected test files when multiple tests depend on the changed file", () => {
50+
const result = findImpactedTests("src/moduleC.ts", mockMetafileOutputs);
51+
expect(result).toEqual(["units/test2.spec.js", "units/test3.spec.js"]);
52+
});
53+
54+
it("returns an empty array if no test file is affected", () => {
55+
const result = findImpactedTests("src/unrelated.ts", mockMetafileOutputs);
56+
expect(result).toEqual([]);
57+
});
58+
});

0 commit comments

Comments
 (0)