Skip to content

Commit 358c980

Browse files
committed
chore: add hook case creator
1 parent ea79a64 commit 358c980

File tree

10 files changed

+282
-257
lines changed

10 files changed

+282
-257
lines changed

packages/rspack-test-tools/etc/api.md

+35-1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ export function createErrorCase(name: string, src: string, dist: string, testCon
9898
// @public (undocumented)
9999
export function createHashCase(name: string, src: string, dist: string): void;
100100

101+
// @public (undocumented)
102+
export function createHookCase(name: string, src: string, dist: string, source: string): void;
103+
101104
// @public (undocumented)
102105
export function createHotCase(name: string, src: string, dist: string, target: TCompilerOptions<ECompilerType.Rspack>["target"]): void;
103106

@@ -260,12 +263,43 @@ export function escapeSep(str: string): string;
260263
export function formatCode(name: string, raw: string, options: IFormatCodeOptions): string;
261264

262265
// @public (undocumented)
263-
export function getSimpleProcessorRunner(src: string, dist: string, env: ITestEnv): (name: string, processor: ITestProcessor) => Promise<void>;
266+
export function getSimpleProcessorRunner(src: string, dist: string, options?: {
267+
env?: () => ITestEnv;
268+
context?: (src: string, dist: string) => ITestContext;
269+
}): (name: string, processor: ITestProcessor) => Promise<void>;
270+
271+
// @public (undocumented)
272+
export class HookCasesContext extends TestContext {
273+
constructor(src: string, testName: string, options: TTestContextOptions);
274+
// @internal (undocumented)
275+
_addSnapshot(content: unknown, name: string, group: string | number): void;
276+
// @internal (undocumented)
277+
collectSnapshots(options?: {
278+
diff: {};
279+
}): Promise<void>;
280+
// (undocumented)
281+
protected count: number;
282+
// (undocumented)
283+
protected options: TTestContextOptions;
284+
// (undocumented)
285+
protected promises: Promise<void>[];
286+
snapped(cb: (...args: unknown[]) => Promise<unknown>, prefix?: string): (this: any, ...args: unknown[]) => Promise<unknown>;
287+
// (undocumented)
288+
protected snapshots: Record<string | number, Array<[string | Buffer, string]>>;
289+
// (undocumented)
290+
protected snapshotsList: Array<string | number>;
291+
// (undocumented)
292+
protected src: string;
293+
// (undocumented)
294+
protected testName: string;
295+
}
264296

265297
// @public (undocumented)
266298
export class HookTaskProcessor extends SnapshotProcessor<ECompilerType.Rspack> {
267299
constructor(hookOptions: IHookProcessorOptions<ECompilerType.Rspack>);
268300
// (undocumented)
301+
check(env: ITestEnv, context: HookCasesContext): Promise<void>;
302+
// (undocumented)
269303
config(context: ITestContext): Promise<void>;
270304
// (undocumented)
271305
protected hookOptions: IHookProcessorOptions<ECompilerType.Rspack>;

packages/rspack-test-tools/src/case/compiler.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@ export function createCompilerCase(
1616
testConfig: string
1717
) {
1818
const caseConfig: TCompilerCaseConfig = require(testConfig);
19-
20-
const runner = getSimpleProcessorRunner(src, dist, {
21-
it,
22-
beforeEach,
23-
afterEach
24-
});
19+
const runner = getSimpleProcessorRunner(src, dist);
2520

2621
it(caseConfig.description, async () => {
2722
await runner(

packages/rspack-test-tools/src/case/error.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,7 @@ export function createErrorCase(
2222
addedSerializer = true;
2323
}
2424
const caseConfig = require(testConfig);
25-
const runner = getSimpleProcessorRunner(src, dist, {
26-
it,
27-
beforeEach,
28-
afterEach
29-
});
25+
const runner = getSimpleProcessorRunner(src, dist);
3026

3127
it(caseConfig.description, async () => {
3228
await runner(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import path from "path";
2+
import { getSimpleProcessorRunner } from "../test/simple";
3+
import { HookCasesContext, HookTaskProcessor } from "../processor";
4+
import { BasicRunnerFactory } from "../runner";
5+
import { ECompilerType, TCompilerOptions } from "../type";
6+
import createLazyTestEnv from "../helper/legacy/createLazyTestEnv";
7+
8+
export function createHookCase(
9+
name: string,
10+
src: string,
11+
dist: string,
12+
source: string
13+
) {
14+
const caseConfig = require(path.join(src, "test.js"));
15+
const testName = path.basename(
16+
name.slice(0, name.indexOf(path.extname(name)))
17+
);
18+
const runner = getSimpleProcessorRunner(source, dist, {
19+
env: () => env,
20+
context: () =>
21+
new HookCasesContext(src, testName, {
22+
src: source,
23+
dist: dist,
24+
runnerFactory: BasicRunnerFactory
25+
})
26+
});
27+
28+
it(caseConfig.description, async () => {
29+
await runner(
30+
name,
31+
new HookTaskProcessor({
32+
name,
33+
compilerType: ECompilerType.Rspack,
34+
findBundle: function (
35+
i: number,
36+
options: TCompilerOptions<ECompilerType.Rspack>
37+
) {
38+
return ["main.js"];
39+
},
40+
snapshot: path.join(src, "output.snap.txt"),
41+
...caseConfig
42+
})
43+
);
44+
});
45+
const env = createLazyTestEnv(10000);
46+
}

packages/rspack-test-tools/src/case/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export * from "./hot-step";
1313
export * from "./compiler";
1414
export * from "./stats-api";
1515
export * from "./error";
16+
export * from "./hook";

packages/rspack-test-tools/src/case/stats-api.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,7 @@ export function createStatsAPICase(
2525
addedSerializer = true;
2626
}
2727
const caseConfig: TStatsAPICaseConfig = require(testConfig);
28-
const runner = getSimpleProcessorRunner(src, dist, {
29-
it,
30-
beforeEach,
31-
afterEach
32-
});
28+
const runner = getSimpleProcessorRunner(src, dist);
3329

3430
it(caseConfig.description, async () => {
3531
await runner(

packages/rspack-test-tools/src/processor/hook.ts

+178-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,176 @@
1-
import { ISnapshotProcessorOptions, SnapshotProcessor } from ".";
2-
import { ECompilerType, ITestContext, TCompilerOptions } from "../type";
1+
import path from "path";
2+
import {
3+
ECompilerType,
4+
ITestContext,
5+
ITestEnv,
6+
TCompilerOptions
7+
} from "../type";
8+
import { Source } from "webpack-sources";
9+
import { Compilation, Compiler } from "@rspack/core";
10+
import { format as prettyFormat, PrettyFormatOptions } from "pretty-format";
11+
import { getSerializers } from "jest-snapshot";
12+
import { TTestContextOptions, TestContext } from "../test/context";
13+
import { ISnapshotProcessorOptions, SnapshotProcessor } from "./snapshot";
14+
15+
const pathSerializer = require("jest-serializer-path");
16+
const normalizePaths = pathSerializer.normalizePaths;
17+
const srcDir = path.resolve(__dirname, "../../tests/fixtures");
18+
const distDir = path.resolve(__dirname, "../../tests/js/hook");
19+
20+
const sourceSerializer = {
21+
test(val: unknown) {
22+
return val instanceof Source;
23+
},
24+
print(val: Source) {
25+
return val.source();
26+
}
27+
};
28+
29+
const internalSerializer = {
30+
test(val: unknown) {
31+
return val instanceof Compiler || val instanceof Compilation;
32+
},
33+
print(val: Compiler | Compilation) {
34+
return JSON.stringify(`${val.constructor.name}(internal ignored)`);
35+
}
36+
};
37+
38+
const testPathSerializer = {
39+
test(val: unknown) {
40+
return typeof val === "string";
41+
},
42+
print(val: string) {
43+
return JSON.stringify(
44+
normalizePaths(
45+
// @ts-ignore
46+
val
47+
.replaceAll(srcDir, "<HOOK_SRC_DIR>")
48+
.replaceAll(distDir, "<HOOK_DIST_DIR>")
49+
)
50+
);
51+
}
52+
};
53+
54+
const escapeRegex = true;
55+
const printFunctionName = false;
56+
const normalizeNewlines = (str: string) => str.replace(/\r\n|\r/g, "\n");
57+
const serialize = (val: unknown, indent = 2, formatOverrides = {}) =>
58+
normalizeNewlines(
59+
prettyFormat(val, {
60+
escapeRegex,
61+
indent,
62+
plugins: [
63+
...getSerializers(),
64+
sourceSerializer,
65+
internalSerializer,
66+
testPathSerializer
67+
] as PrettyFormatOptions["plugins"],
68+
printFunctionName,
69+
...formatOverrides
70+
})
71+
);
72+
73+
export class HookCasesContext extends TestContext {
74+
protected promises: Promise<void>[] = [];
75+
protected count: number = 0;
76+
protected snapshots: Record<
77+
string | number,
78+
Array<[string | Buffer, string]>
79+
> = {};
80+
protected snapshotsList: Array<string | number> = [];
81+
82+
constructor(
83+
protected src: string,
84+
protected testName: string,
85+
protected options: TTestContextOptions
86+
) {
87+
super(options);
88+
this.snapped = this.snapped.bind(this);
89+
}
90+
91+
/**
92+
* Snapshot function arguments and return value.
93+
* Generated snapshot is located in the same directory with the test source.
94+
* @example
95+
* compiler.hooks.compilation("name", context.snapped((...args) => { ... }))
96+
*/
97+
snapped(cb: (...args: unknown[]) => Promise<unknown>, prefix = "") {
98+
// eslint-disable-next-line
99+
let context = this;
100+
return function SNAPPED_HOOK(this: any, ...args: unknown[]) {
101+
let group = prefix ? prefix : context.count++;
102+
context._addSnapshot(args, "input", group);
103+
let output = cb.apply(this, args);
104+
if (output && typeof output.then === "function") {
105+
let resolve;
106+
context.promises.push(new Promise(r => (resolve = r)));
107+
return output
108+
.then((o: unknown) => {
109+
context._addSnapshot(o, "output (Promise resolved)", group);
110+
return o;
111+
})
112+
.catch((o: unknown) => {
113+
context._addSnapshot(o, "output (Promise rejected)", group);
114+
return o;
115+
})
116+
.finally(resolve);
117+
}
118+
context._addSnapshot(output, "output", group);
119+
return output;
120+
};
121+
}
122+
123+
/**
124+
* @internal
125+
*/
126+
_addSnapshot(content: unknown, name: string, group: string | number) {
127+
content = Buffer.isBuffer(content)
128+
? content
129+
: serialize(content, undefined, {
130+
escapeString: true,
131+
printBasicPrototype: true
132+
}).replace(/\r\n/g, "\n");
133+
(this.snapshots[group] = this.snapshots[group] || []).push([
134+
content as Buffer | string,
135+
name
136+
]);
137+
if (!this.snapshotsList.includes(group)) {
138+
this.snapshotsList.push(group);
139+
}
140+
}
141+
142+
/**
143+
* @internal
144+
*/
145+
async collectSnapshots(
146+
options = {
147+
diff: {}
148+
}
149+
) {
150+
await Promise.allSettled(this.promises);
151+
if (!this.snapshotsList.length) return;
152+
153+
let snapshots = this.snapshotsList.reduce((acc, group, index) => {
154+
let block = this.snapshots[group || index].reduce(
155+
(acc, [content, name]) => {
156+
name = `## ${name || `test: ${index}`}\n\n`;
157+
let block = "```javascript\n" + content + "\n```\n";
158+
return (acc += name + block + "\n");
159+
},
160+
""
161+
);
162+
group = Number.isInteger(group) ? `Group: ${index}` : group;
163+
group = `# ${group}\n\n`;
164+
return (acc += group + block);
165+
}, "");
166+
167+
// @ts-ignore
168+
expect(snapshots).toMatchFileSnapshot(
169+
path.join(this.src, "hooks.snap.txt"),
170+
options
171+
);
172+
}
173+
}
3174

4175
interface IHookProcessorOptions<T extends ECompilerType>
5176
extends ISnapshotProcessorOptions<T> {
@@ -44,4 +215,9 @@ export class HookTaskProcessor extends SnapshotProcessor<ECompilerType.Rspack> {
44215
compiler.mergeOptions(this.hookOptions.options(context));
45216
}
46217
}
218+
219+
async check(env: ITestEnv, context: HookCasesContext) {
220+
await (context as any).collectSnapshots();
221+
await super.check(env, context);
222+
}
47223
}

packages/rspack-test-tools/src/test/simple.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ITestEnv, ITestProcessor } from "../type";
1+
import { ITestContext, ITestEnv, ITestProcessor } from "../type";
22
import { TestContext } from "./context";
33

44
const CONTEXT_MAP: Map<
@@ -9,14 +9,18 @@ const CONTEXT_MAP: Map<
99
export function getSimpleProcessorRunner(
1010
src: string,
1111
dist: string,
12-
env: ITestEnv
12+
options: {
13+
env?: () => ITestEnv;
14+
context?: (src: string, dist: string) => ITestContext;
15+
} = {}
1316
) {
17+
const createEnv = options.env || (() => ({ it, beforeEach, afterEach }));
18+
const createContext =
19+
options.context ||
20+
((src: string, dist: string) => new TestContext({ src, dist }));
1421
const key = `src: ${src}, dist: ${dist}`;
1522
if (!CONTEXT_MAP.has(key)) {
16-
const context = new TestContext({
17-
src,
18-
dist
19-
});
23+
const context = createContext(src, dist);
2024
const runner = async function run(name: string, processor: ITestProcessor) {
2125
try {
2226
await processor.beforeAll?.(context);
@@ -27,8 +31,8 @@ export function getSimpleProcessorRunner(
2731
} catch (e: unknown) {
2832
context.emitError(name, e as Error);
2933
} finally {
30-
await processor.run?.(env, context);
31-
await processor.check?.(env, context);
34+
await processor.run?.(createEnv(), context);
35+
await processor.check?.(createEnv(), context);
3236
await processor.after?.(context);
3337
await processor.afterAll?.(context);
3438
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const path = require("path");
2+
const { createHookCase, describeByWalk } = require("../dist");
3+
const source = path.resolve(__dirname, "./fixtures");
4+
5+
describeByWalk(__filename, (name, src, dist) => {
6+
createHookCase(name, src, dist, source);
7+
});

0 commit comments

Comments
 (0)