Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add global snapshot serializer #8137

Merged
merged 7 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 18 additions & 21 deletions packages/rspack-test-tools/etc/test-tools.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,6 @@ export function createWatchNewIncrementalCase(name: string, src: string, dist: s
export class DefaultsConfigProcessor<T extends ECompilerType> extends SimpleTaskProcessor<T> {
constructor(_defaultsConfigOptions: IDefaultsConfigProcessorOptions<T>);
// (undocumented)
static addSnapshotSerializer(expectImpl: jest.Expect): void;
// (undocumented)
after(context: ITestContext): Promise<void>;
// (undocumented)
afterAll(context: ITestContext): Promise<void>;
Expand Down Expand Up @@ -249,13 +247,6 @@ export class DiagnosticProcessor<T extends ECompilerType> extends BasicProcessor
protected _diagnosticOptions: IDiagnosticProcessorOptions<T>;
}

// @public (undocumented)
class Diff {
constructor(value: string);
// (undocumented)
value: string;
}

// @public (undocumented)
export class DiffComparator {
constructor(options: IDiffComparatorOptions);
Expand Down Expand Up @@ -346,8 +337,6 @@ export enum EEsmMode {
export class ErrorProcessor<T extends ECompilerType> extends SimpleTaskProcessor<T> {
constructor(_errorOptions: IErrorProcessorOptions<T>);
// (undocumented)
static addSnapshotSerializer(expectImpl: jest.Expect): void;
// (undocumented)
check(env: ITestEnv, context: ITestContext): Promise<void>;
// (undocumented)
compiler(context: ITestContext): Promise<void>;
Expand Down Expand Up @@ -605,7 +594,7 @@ export interface IDefaultsConfigProcessorOptions<T extends ECompilerType> {
// (undocumented)
cwd?: string;
// (undocumented)
diff: (diff: jest.JestMatchers<Diff>, defaults: jest.JestMatchers<TCompilerOptions<T>>) => Promise<void>;
diff: (diff: jest.JestMatchers<RspackTestDiff>, defaults: jest.JestMatchers<TCompilerOptions<T>>) => Promise<void>;
// (undocumented)
name: string;
// (undocumented)
Expand Down Expand Up @@ -695,7 +684,7 @@ export interface IErrorProcessorOptions<T extends ECompilerType> {
// (undocumented)
build?: (context: ITestContext, compiler: TCompiler<T>) => Promise<void>;
// (undocumented)
check?: (stats: TStatsDiagnostics) => Promise<void>;
check?: (stats: RspackStatsDiagnostics) => Promise<void>;
// (undocumented)
compilerType: T;
// (undocumented)
Expand Down Expand Up @@ -1150,6 +1139,22 @@ export class RspackDiffConfigPlugin implements RspackPluginInstance {
name: string;
}

// @public (undocumented)
class RspackStatsDiagnostics {
constructor(errors: StatsError[], warnings: StatsError[]);
// (undocumented)
errors: StatsError[];
// (undocumented)
warnings: StatsError[];
}

// @public (undocumented)
class RspackTestDiff {
constructor(value: string);
// (undocumented)
value: string;
}

// @public (undocumented)
export class SimpleTaskProcessor<T extends ECompilerType> implements ITestProcessor {
constructor(_options: ISimpleProcessorOptions<T>);
Expand Down Expand Up @@ -1190,8 +1195,6 @@ export class SnapshotProcessor<T extends ECompilerType> extends BasicProcessor<T
export class StatsAPIProcessor<T extends ECompilerType> extends SimpleTaskProcessor<T> {
constructor(_statsAPIOptions: IStatsAPIProcessorOptions<T>);
// (undocumented)
static addSnapshotSerializer(expectImpl: jest.Expect): void;
// (undocumented)
check(env: ITestEnv, context: ITestContext): Promise<void>;
// (undocumented)
compiler(context: ITestContext): Promise<void>;
Expand Down Expand Up @@ -1445,12 +1448,6 @@ export type TStatsAPICaseConfig = Omit<IStatsAPIProcessorOptions<ECompilerType.R
description: string;
};

// @public (undocumented)
type TStatsDiagnostics = {
errors: StatsError[];
warnings: StatsError[];
};

// @public (undocumented)
export type TTestConfig<T extends ECompilerType> = {
documentType?: EDocumentType;
Expand Down
1 change: 1 addition & 0 deletions packages/rspack-test-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"jsdom": "^25.0.0",
"memfs": "4.8.1",
"mkdirp": "0.5.6",
"path-serializer": "0.1.2",
"pretty-format": "29.7.0",
"rimraf": "3.0.2",
"strip-ansi": "6.0.1",
Expand Down
1 change: 0 additions & 1 deletion packages/rspack-test-tools/src/case/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export function createErrorCase(
testConfig: string
) {
if (!addedSerializer) {
ErrorProcessor.addSnapshotSerializer(expect);
addedSerializer = true;
}
const caseConfig = require(testConfig);
Expand Down
1 change: 0 additions & 1 deletion packages/rspack-test-tools/src/case/stats-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export function createStatsAPICase(
testConfig: string
) {
if (!addedSerializer) {
StatsAPIProcessor.addSnapshotSerializer(expect);
addedSerializer = true;
}
const caseConfig: TStatsAPICaseConfig = require(testConfig);
Expand Down
23 changes: 23 additions & 0 deletions packages/rspack-test-tools/src/helper/expect/char.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const normalizeCRLF = (str: string): string => {
return str.replace(/\r\n?/g, "\n");
};

export const normalizeCLR = (str: string): string => {
return (
str
.replace(/\u001b\[1m\u001b\[([0-9;]*)m/g, "<CLR=$1,BOLD>")
.replace(/\u001b\[1m/g, "<CLR=BOLD>")
.replace(/\u001b\[39m\u001b\[22m/g, "</CLR>")
.replace(/\u001b\[([0-9;]*)m/g, "<CLR=$1>")
// CHANGE: The time unit display in Rspack is second
.replace(/[.0-9]+(<\/CLR>)?(\s?s)/g, "X$1$2")
);
};

export const normalizeColor = (str: string): string => {
return str.replace(/\u001b\[[0-9;]*m/g, "");
};

export const normalizeSlash = (str: string): string => {
return str.replace(/(\\)+/g, "/");
};
36 changes: 36 additions & 0 deletions packages/rspack-test-tools/src/helper/expect/diff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const CURRENT_CWD = process.cwd();

const quoteMeta = (str: string) => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&");
const cwdRegExp = new RegExp(
`${quoteMeta(CURRENT_CWD)}((?:\\\\)?(?:[a-zA-Z.\\-_]+\\\\)*)`,
"g"
);
const escapedCwd = JSON.stringify(CURRENT_CWD).slice(1, -1);
const escapedCwdRegExp = new RegExp(
`${quoteMeta(escapedCwd)}((?:\\\\\\\\)?(?:[a-zA-Z.\\-_]+\\\\\\\\)*)`,
"g"
);

export const normalizeDiff = (diff: { value: string }) => {
let normalizedStr: string = diff.value;
if (CURRENT_CWD.startsWith("/")) {
normalizedStr = normalizedStr.replace(
new RegExp(quoteMeta(CURRENT_CWD), "g"),
"<cwd>"
);
} else {
normalizedStr = normalizedStr.replace(
cwdRegExp,
(_, g) => `<cwd>${g.replace(/\\/g, "/")}`
);
normalizedStr = normalizedStr.replace(
escapedCwdRegExp,
(_, g) => `<cwd>${g.replace(/\\\\/g, "/")}`
);
}
normalizedStr = normalizedStr.replace(
/@@ -\d+,\d+ \+\d+,\d+ @@/g,
"@@ ... @@"
);
return normalizedStr;
};
59 changes: 59 additions & 0 deletions packages/rspack-test-tools/src/helper/expect/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import prettyFormat from "pretty-format";

const ERROR_STACK_PATTERN = /(│.* at ).*/g;

const prettyFormatOptions = {
escapeRegex: false,
printFunctionName: false,
plugins: [
{
test(val: any) {
return typeof val === "string";
},
print(val: any) {
return `"${val
.replace(/\\/gm, "/")
.replace(/"/gm, '\\"')
.replace(/\r?\n/gm, "\\n")}"`;
}
}
]
};

function cleanErrorStack(message: string) {
return message.replace(ERROR_STACK_PATTERN, "$1xxx");
}

function cleanError(err: Error) {
const result: Partial<Record<keyof Error, any>> = {};
for (const key of Object.getOwnPropertyNames(err)) {
result[key as keyof Error] = err[key as keyof Error];
}

if (result.message) {
result.message = cleanErrorStack(err.message);
}

if (result.stack) {
result.stack = cleanErrorStack(result.stack);
}

return result;
}

export function normalizeDignostics(received: {
errors: Error[];
warnings: Error[];
}): string {
return prettyFormat(
{
errors: received.errors.map(e => cleanError(e)),
warnings: received.warnings.map(e => cleanError(e))
},
prettyFormatOptions
).trim();
}

export function normalizeError(received: Error): string {
return prettyFormat(cleanError(received), prettyFormatOptions).trim();
}
30 changes: 30 additions & 0 deletions packages/rspack-test-tools/src/helper/expect/placeholder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import path from "node:path";
const { createSnapshotSerializer } = require("path-serializer");

const placeholderSerializer = createSnapshotSerializer({
workspace: path.resolve(__dirname, "../../../../../"),
replace: [
{
match: path.resolve(__dirname, "../../../rspack"),
mark: "rspack"
},
{
match: path.resolve(__dirname, "../../"),
mark: "test_tools"
},
{
match: /:\d+:\d+-\d+:\d+/g,
mark: "line_col_range"
},
{
match: /:\d+:\d+/g,
mark: "line_col"
}
],
features: {
addDoubleQuotes: false,
ansiDoubleQuotes: false
}
});

export const normalizePlaceholder = placeholderSerializer.serialize;
12 changes: 12 additions & 0 deletions packages/rspack-test-tools/src/helper/expect/rspack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const normalizeStats = (stats: { value: string }): string => {
return (
stats.value
// CHANGE: Remove potential line break and "|" caused by long text
.replace(/((ERROR|WARNING)([\s\S](?!╭|├))*?)(\n {2}│ )/g, "$1")
// CHANGE: Update the regular expression to replace the 'Rspack' version string
.replace(/Rspack [^ )]+(\)?) compiled/g, "Rspack x.x.x$1 compiled")
.replace(/(\w)\\(\w)/g, "$1/$2")
.replace(/, additional resolving: X ms/g, "")
.replace(/Unexpected identifier '.+?'/g, "Unexpected identifier")
);
};
73 changes: 71 additions & 2 deletions packages/rspack-test-tools/src/helper/setup-expect.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,81 @@
// @ts-nocheck

import { normalizeCLR, normalizeCRLF, normalizeSlash } from "./expect/char";
import { normalizeDiff } from "./expect/diff";
import { normalizeDignostics, normalizeError } from "./expect/error";
import { normalizePlaceholder } from "./expect/placeholder";
import { normalizeStats } from "./expect/rspack";
import { toBeTypeOf } from "./expect/to-be-typeof";
import { toEndWith } from "./expect/to-end-with";
import { toMatchFileSnapshot } from "./expect/to-match-file-snapshot";
const { normalizePaths } = require("jest-serializer-path");

expect.extend({
// CHANGE: new test matcher for `rspack-test-tools`
// @ts-ignore
toMatchFileSnapshot,
toBeTypeOf,
toEndWith
});

const pipes = [
normalizeSlash,
normalizeCLR,
normalizeCRLF,
normalizePlaceholder,
normalizePaths
];

const serialize = (
str: string,
extra: Array<(str: string) => string> = []
): string =>
[...pipes, ...extra].reduce((res, transform) => transform(res), str);

expect.addSnapshotSerializer({
test(received) {
return typeof received === "string";
},
print(received) {
return serialize((received as string).trim());
}
});

// for diff
expect.addSnapshotSerializer({
test(received) {
return received?.constructor?.name === "RspackTestDiff";
},
print(received, next) {
return next(normalizeDiff(received as { value: string }));
}
});

// for errors
expect.addSnapshotSerializer({
test(received) {
return received?.constructor?.name === "RspackStatsDiagnostics";
},
print(received, next) {
return next(
normalizeDignostics(received as { errors: Error[]; warnings: Error[] })
);
}
});

expect.addSnapshotSerializer({
test(received) {
return typeof received?.message === "string";
},
print(received, next) {
return next(normalizeError(received as Error));
}
});

// for stats
expect.addSnapshotSerializer({
test(received) {
return received?.constructor?.name === "RspackStats";
},
print(received, next) {
return next(normalizeStats(received as { value: string }));
}
});
Loading
Loading