Skip to content

Commit

Permalink
feat: obsolete snapshots get flagged and -u cleans them up (#12)
Browse files Browse the repository at this point in the history
Signed-off-by: Chapman Pendery <[email protected]>
  • Loading branch information
cpendery authored Mar 5, 2024
1 parent b770b3f commit b1b8c07
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 21 deletions.
32 changes: 23 additions & 9 deletions src/reporter/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type TestSummary = {
failed: number;
written: number;
updated: number;
obsolete: number;
};
};

Expand Down Expand Up @@ -71,8 +72,8 @@ export class BaseReporter {
endTest(test: TestCase, result: TestResult): void {
if (!this.isTTY) this.currentTest += 1;
}
end(rootSuite: Suite): number {
const summary = this._generateSummary(rootSuite);
end(rootSuite: Suite, obsoleteSnapshots: number): number {
const summary = this._generateSummary(rootSuite, obsoleteSnapshots);

this._printFailures(summary);
this._printSummary(summary);
Expand All @@ -81,17 +82,26 @@ export class BaseReporter {
).length;
}

private _generateSummary(rootSuite: Suite): TestSummary {
private _generateSummary(
rootSuite: Suite,
obsoleteSnapshots: number
): TestSummary {
let didNotRun = 0;
let skipped = 0;
let expected = 0;
const unexpected: TestCase[] = [];
const flaky: TestCase[] = [];
const snapshots = { written: 0, updated: 0, failed: 0, passed: 0 };
const snapshots = {
written: 0,
updated: 0,
failed: 0,
passed: 0,
obsolete: obsoleteSnapshots,
};

rootSuite.allTests().forEach((test) => {
test.snapshots().forEach((snapshot) => {
switch (snapshot) {
switch (snapshot.result) {
case "passed":
snapshots.passed++;
break;
Expand Down Expand Up @@ -185,21 +195,25 @@ export class BaseReporter {
if (snapshots.written > 0) {
snapshotTokens.push(chalk.green(`${snapshots.written} written`));
}
if (snapshots.obsolete > 0) {
snapshotTokens.push(chalk.yellow(`${snapshots.obsolete} obsolete`));
}

const snapshotTotal =
snapshots.passed +
snapshots.failed +
snapshots.written +
snapshots.updated;
const snapshotErrorPostfix =
snapshots.failed > 0
snapshots.updated +
snapshots.obsolete;
const snapshotPostfix =
snapshots.failed > 0 || snapshots.obsolete > 0
? chalk.dim(
"(Inspect your code changes or use the `-u` flag to update them.)"
)
: "";
if (snapshotTotal !== 0) {
process.stdout.write(
` snapshots: ${snapshotTokens.join(", ")}, ${snapshotTotal} total ${snapshotErrorPostfix}\n\n`
` snapshots: ${snapshotTokens.join(", ")}, ${snapshotTotal} total ${snapshotPostfix}\n\n`
);
} else {
process.stdout.write("\n");
Expand Down
4 changes: 2 additions & 2 deletions src/reporter/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export class ListReporter extends BaseReporter {
this._updateOrAppendLine(row, line, prefix);
}

override end(rootSuite: Suite): number {
return super.end(rootSuite);
override end(rootSuite: Suite, obsoleteSnapshots: number): number {
return super.end(rootSuite, obsoleteSnapshots);
}

private _resultIcon(status: TestStatus): string {
Expand Down
27 changes: 26 additions & 1 deletion src/runner/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { BaseReporter } from "../reporter/base.js";
import { TestCase } from "../test/testcase.js";
import { cacheFolderName, executableName } from "../utils/constants.js";
import { supportsColor } from "chalk";
import { cleanSnapshot } from "../test/matchers/toMatchSnapshot.js";

/* eslint-disable no-var */

Expand Down Expand Up @@ -131,6 +132,28 @@ const checkShellSupport = (shells: Shell[]) => {
}
};

const cleanSnapshots = async (
tests: TestCase[],
{ updateSnapshot }: ExecutionOptions
) => {
const snapshotFiles = tests.reduce((snapshots, test) => {
const source = test.filePath() ?? "";
const snapshotNames = test.snapshots().map((snapshot) => snapshot.name);
snapshots.set(source, [...(snapshots.get(source) ?? []), ...snapshotNames]);
return snapshots;
}, new Map<string, string[]>());

let unusedSnapshots = 0;
for (const snapshotFile of snapshotFiles.keys()) {
unusedSnapshots += await cleanSnapshot(
snapshotFile,
new Set(snapshotFiles.get(snapshotFile)),
updateSnapshot
);
}
return unusedSnapshots;
};

export const run = async (options: ExecutionOptions) => {
checkNodeVersion();

Expand Down Expand Up @@ -216,6 +239,8 @@ export const run = async (options: ExecutionOptions) => {
} catch {
/* empty */
}
const failures = reporter.end(rootSuite);
const obsoleteSnapshots = await cleanSnapshots(allTests, options);
const failures = reporter.end(rootSuite, obsoleteSnapshots);

process.exit(failures);
};
12 changes: 7 additions & 5 deletions src/runner/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import workerpool from "workerpool";
import { Suite } from "../test/suite.js";
import { spawn } from "../terminal/term.js";
import { defaultShell } from "../terminal/shell.js";
import { TestCase, TestStatus } from "../test/testcase.js";
import { Snapshot, TestCase, TestStatus } from "../test/testcase.js";
import { expect } from "../test/test.js";
import { SnapshotStatus } from "../test/matchers/toMatchSnapshot.js";
import { BaseReporter } from "../reporter/base.js";
import { poll } from "../utils/poll.js";
import { flushSnapshotExecutionCache } from "../test/matchers/toMatchSnapshot.js";
Expand All @@ -20,7 +19,7 @@ type WorkerResult = {
stderr?: string;
status: TestStatus;
duration: number;
snapshots: SnapshotStatus[];
snapshots: Snapshot[];
};

type WorkerExecutionOptions = {
Expand Down Expand Up @@ -103,7 +102,7 @@ export async function runTestWorker(
pool: workerpool.Pool,
reporter: BaseReporter
): Promise<WorkerResult> {
const snapshots: SnapshotStatus[] = [];
const snapshots: Snapshot[] = [];
if (test.expectedStatus === "skipped") {
reporter.startTest(test, {
status: "pending",
Expand Down Expand Up @@ -152,7 +151,10 @@ export async function runTestWorker(
reportStarted = true;
startTime = payload.startTime;
} else if (payload.snapshotResult) {
snapshots.push(payload.snapshotResult);
snapshots.push({
name: payload.snapshotName,
result: payload.snapshotResult,
});
}
},
}
Expand Down
39 changes: 37 additions & 2 deletions src/test/matchers/toMatchSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,38 @@ const updateSnapshot = async (
await fh.close();
};

export const cleanSnapshot = async (
testPath: string,
retainedSnapshots: Set<string>,
updateSnapshot: boolean
): Promise<number> => {
const snapPath = snapshotPath(testPath);
if (!fs.existsSync(snapPath)) return retainedSnapshots.size;
const snapshots = require(snapPath);
const unusedSnapshots = Object.keys(snapshots).filter(
(snapshot) => !retainedSnapshots.has(snapshot)
);

if (!updateSnapshot) return unusedSnapshots.length;
unusedSnapshots.forEach((unusedSnapshot) => {
delete snapshots[unusedSnapshot];
});

await fsAsync.writeFile(
snapPath,
"// TUI Test Snapshot v1\n\n" +
Object.keys(snapshots)
.sort()
.map(
(snapshotName) =>
`exports[\`${snapshotName}\`] = String.raw\`\n${snapshots[snapshotName].trim()}\n\`;\n\n`
)
.join("")
);

return unusedSnapshots.length;
};

const generateSnapshot = (terminal: Terminal, includeColors: boolean) => {
const { view, shifts } = terminal.serialize();
if (shifts.size === 0 || !includeColors) {
Expand Down Expand Up @@ -108,14 +140,17 @@ export async function toMatchSnapshot(
const snapshotEmpty = existingSnapshot == null;

if (!workpool.isMainThread) {
const snapshotResult = snapshotEmpty
const snapshotResult: SnapshotStatus = snapshotEmpty
? "written"
: snapshotShouldUpdate
? "updated"
: snapshotsDifferent
? "failed"
: "passed";
workpool.workerEmit({ snapshotResult, testName });
workpool.workerEmit({
snapshotResult,
snapshotName: snapshotPostfixTestName,
});
}

if (snapshotEmpty || snapshotShouldUpdate) {
Expand Down
9 changes: 7 additions & 2 deletions src/test/testcase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,16 @@ export type TestStatus =
| "skipped"
| "flaky";

export type Snapshot = {
name: string;
result: SnapshotStatus;
};

export type TestResult = {
status: TestStatus;
error?: string;
duration: number;
snapshots: SnapshotStatus[];
snapshots: Snapshot[];
stdout?: string;
stderr?: string;
};
Expand Down Expand Up @@ -64,7 +69,7 @@ export class TestCase {
return "unexpected";
}

snapshots(): SnapshotStatus[] {
snapshots(): Snapshot[] {
return this.results.at(-1)?.snapshots ?? [];
}

Expand Down

0 comments on commit b1b8c07

Please sign in to comment.