2022-04-20 11:46:21 +00:00
|
|
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
2022-05-06 13:05:27 +00:00
|
|
|
import { fromFileUrl, parse, resolve, toFileUrl } from "../path/mod.ts";
|
2022-04-20 11:46:21 +00:00
|
|
|
import { ensureFile, ensureFileSync } from "../fs/mod.ts";
|
|
|
|
import { bold, green, red } from "../fmt/colors.ts";
|
2022-05-06 13:05:27 +00:00
|
|
|
import { assert, AssertionError, equal } from "./asserts.ts";
|
2022-04-20 11:46:21 +00:00
|
|
|
import { buildMessage, diff, diffstr } from "./_diff.ts";
|
|
|
|
|
|
|
|
const CAN_NOT_DISPLAY = "[Cannot display]";
|
|
|
|
const SNAPSHOT_DIR = "__snapshots__";
|
2022-05-06 13:05:27 +00:00
|
|
|
const SNAPSHOT_EXT = "snap";
|
2022-04-20 11:46:21 +00:00
|
|
|
|
2022-05-06 13:05:27 +00:00
|
|
|
export type SnapshotMode = "assert" | "update";
|
|
|
|
|
|
|
|
export type SnapshotOptions<T = unknown> = {
|
|
|
|
/**
|
|
|
|
* Snapshot output directory. Snapshot files will be written to this directory.
|
|
|
|
* This can be relative to the test directory or an absolute path.
|
|
|
|
*
|
|
|
|
* If both `dir` and `path` are specified, the `dir` option will be ignored and
|
|
|
|
* the `path` option will be handled as normal.
|
|
|
|
*/
|
|
|
|
dir?: string;
|
|
|
|
/**
|
|
|
|
* Snapshot mode. Defaults to `assert`, unless the `-u` or `--update` flag is
|
|
|
|
* passed, in which case this will be set to `update`. This option takes higher
|
|
|
|
* priority than the update flag. If the `--update` flag is passed, it will be
|
|
|
|
* ignored if the `mode` option is set.
|
|
|
|
*/
|
|
|
|
mode?: SnapshotMode;
|
|
|
|
/**
|
|
|
|
* Failure message to log when the assertion fails. Specifying this option will
|
|
|
|
* cause the diff not to be logged.
|
|
|
|
*/
|
|
|
|
msg?: string;
|
|
|
|
/**
|
|
|
|
* Name of the snapshot to use in the snapshot file.
|
|
|
|
*/
|
|
|
|
name?: string;
|
|
|
|
/**
|
|
|
|
* Snapshot output path. The shapshot will be written to this file. This can be
|
|
|
|
* a path relative to the test directory or an absolute path.
|
|
|
|
*
|
|
|
|
* If both `dir` and `path` are specified, the `dir` option will be ignored and
|
|
|
|
* the `path` option will be handled as normal.
|
|
|
|
*/
|
|
|
|
path?: string;
|
|
|
|
/**
|
|
|
|
* Function to use when serializing the snapshot.
|
|
|
|
*/
|
|
|
|
serializer?: (actual: T) => string;
|
2022-04-20 11:46:21 +00:00
|
|
|
};
|
|
|
|
|
2022-05-06 13:05:27 +00:00
|
|
|
function getErrorMessage(message: string, options: SnapshotOptions) {
|
|
|
|
return typeof options.msg === "string" ? options.msg : message;
|
|
|
|
}
|
2022-04-20 11:46:21 +00:00
|
|
|
|
2022-05-06 13:05:27 +00:00
|
|
|
/**
|
|
|
|
* Default serializer for `assertSnapshot`.
|
|
|
|
*/
|
|
|
|
export function serialize(actual: unknown): string;
|
|
|
|
export function serialize<T>(actual: T): string;
|
|
|
|
export function serialize(actual: unknown): string {
|
2022-04-26 03:54:45 +00:00
|
|
|
return Deno.inspect(actual, {
|
|
|
|
depth: Infinity,
|
|
|
|
sorted: true,
|
|
|
|
trailingComma: true,
|
|
|
|
compact: false,
|
|
|
|
iterableLimit: Infinity,
|
|
|
|
strAbbreviateSize: Infinity,
|
2022-05-03 12:21:11 +00:00
|
|
|
}).replace(/\\n/g, "\n");
|
2022-04-26 03:54:45 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 11:46:21 +00:00
|
|
|
/**
|
2022-05-06 13:05:27 +00:00
|
|
|
* Converts a string to a valid JavaScript string which can be wrapped in backticks.
|
|
|
|
*
|
|
|
|
* @example
|
2022-04-20 11:46:21 +00:00
|
|
|
*
|
2022-05-06 13:05:27 +00:00
|
|
|
* "special characters (\ ` $) will be escaped" -> "special characters (\\ \` \$) will be escaped"
|
|
|
|
*/
|
|
|
|
function escapeStringForJs(str: string) {
|
|
|
|
return str
|
|
|
|
.replace(/\\/g, "\\\\")
|
|
|
|
.replace(/`/g, "\\`")
|
|
|
|
.replace(/\$/g, "\\$");
|
|
|
|
}
|
|
|
|
|
|
|
|
let _mode: SnapshotMode;
|
|
|
|
/**
|
|
|
|
* Get the snapshot mode.
|
2022-04-20 11:46:21 +00:00
|
|
|
*/
|
2022-05-06 13:05:27 +00:00
|
|
|
function getMode(options: SnapshotOptions) {
|
|
|
|
if (options.mode) {
|
|
|
|
return options.mode;
|
|
|
|
} else if (_mode) {
|
|
|
|
return _mode;
|
|
|
|
} else {
|
|
|
|
_mode = Deno.args.some((arg) => arg === "--update" || arg === "-u")
|
|
|
|
? "update"
|
|
|
|
: "assert";
|
|
|
|
return _mode;
|
2022-04-20 11:46:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-05-06 13:05:27 +00:00
|
|
|
* Return `true` when snapshot mode is `update`.
|
2022-04-20 11:46:21 +00:00
|
|
|
*/
|
2022-05-06 13:05:27 +00:00
|
|
|
function getIsUpdate(options: SnapshotOptions) {
|
|
|
|
return getMode(options) === "update";
|
|
|
|
}
|
|
|
|
|
|
|
|
class AssertSnapshotContext {
|
|
|
|
static contexts = new Map<string, AssertSnapshotContext>();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an instance of `AssertSnapshotContext`. This will be retrieved from
|
|
|
|
* a cache if an instance was already created for a given snapshot file path.
|
|
|
|
*/
|
|
|
|
static fromOptions(
|
|
|
|
testContext: Deno.TestContext,
|
|
|
|
options: SnapshotOptions,
|
|
|
|
): AssertSnapshotContext {
|
|
|
|
let path: string;
|
|
|
|
const testFilePath = fromFileUrl(testContext.origin);
|
|
|
|
const { dir, base } = parse(testFilePath);
|
|
|
|
if (options.path) {
|
|
|
|
path = resolve(dir, options.path);
|
|
|
|
} else if (options.dir) {
|
|
|
|
path = resolve(dir, options.dir, `${base}.${SNAPSHOT_EXT}`);
|
|
|
|
} else {
|
|
|
|
path = resolve(dir, SNAPSHOT_DIR, `${base}.${SNAPSHOT_EXT}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
let context = this.contexts.get(path);
|
|
|
|
if (context) {
|
|
|
|
return context;
|
|
|
|
}
|
|
|
|
|
|
|
|
context = new this(toFileUrl(path));
|
|
|
|
this.contexts.set(path, context);
|
|
|
|
return context;
|
|
|
|
}
|
|
|
|
|
2022-05-27 12:27:13 +00:00
|
|
|
#teardownRegistered = false;
|
|
|
|
#currentSnapshots: Map<string, string | undefined> | undefined;
|
|
|
|
#updatedSnapshots = new Map<string, string>();
|
|
|
|
#snapshotCounts = new Map<string, number>();
|
|
|
|
#snapshotsUpdated = new Array<string>();
|
|
|
|
#snapshotFileUrl: URL;
|
|
|
|
snapshotUpdateQueue = new Array<string>();
|
2022-05-06 13:05:27 +00:00
|
|
|
|
|
|
|
constructor(snapshotFileUrl: URL) {
|
2022-05-27 12:27:13 +00:00
|
|
|
this.#snapshotFileUrl = snapshotFileUrl;
|
2022-05-06 13:05:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Asserts that `this.#currentSnapshots` has been initialized and then returns it.
|
|
|
|
*
|
|
|
|
* Should only be called when `this.#currentSnapshots` has already been initialized.
|
|
|
|
*/
|
2022-05-27 12:27:13 +00:00
|
|
|
#getCurrentSnapshotsInitialized() {
|
2022-05-06 13:05:27 +00:00
|
|
|
assert(
|
2022-05-27 12:27:13 +00:00
|
|
|
this.#currentSnapshots,
|
2022-05-06 13:05:27 +00:00
|
|
|
"Snapshot was not initialized. This is a bug in `assertSnapshot`.",
|
|
|
|
);
|
2022-05-27 12:27:13 +00:00
|
|
|
return this.#currentSnapshots;
|
2022-05-06 13:05:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Write updates to the snapshot file and log statistics.
|
|
|
|
*/
|
2022-05-27 12:27:13 +00:00
|
|
|
#teardown = () => {
|
2022-05-06 13:05:27 +00:00
|
|
|
const buf = ["export const snapshot = {};"];
|
2022-05-27 12:27:13 +00:00
|
|
|
const currentSnapshots = this.#getCurrentSnapshotsInitialized();
|
2022-05-06 13:05:27 +00:00
|
|
|
this.snapshotUpdateQueue.forEach((name) => {
|
2022-05-27 12:27:13 +00:00
|
|
|
const updatedSnapshot = this.#updatedSnapshots.get(name);
|
2022-05-06 13:05:27 +00:00
|
|
|
const currentSnapshot = currentSnapshots.get(name);
|
|
|
|
let formattedSnapshot: string;
|
|
|
|
if (typeof updatedSnapshot === "string") {
|
|
|
|
formattedSnapshot = updatedSnapshot;
|
|
|
|
} else if (typeof currentSnapshot === "string") {
|
|
|
|
formattedSnapshot = currentSnapshot;
|
|
|
|
} else {
|
|
|
|
// This occurs when `assertSnapshot` is called in "assert" mode but
|
|
|
|
// the snapshot doesn't exist and `assertSnapshot` is also called in
|
|
|
|
// "update" mode. In this case, we have nothing to write to the
|
|
|
|
// snapshot file so we can just exit early
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
formattedSnapshot = escapeStringForJs(formattedSnapshot);
|
|
|
|
formattedSnapshot = formattedSnapshot.includes("\n")
|
|
|
|
? `\n${formattedSnapshot}\n`
|
|
|
|
: formattedSnapshot;
|
|
|
|
const formattedName = escapeStringForJs(name);
|
|
|
|
buf.push(`\nsnapshot[\`${formattedName}\`] = \`${formattedSnapshot}\`;`);
|
|
|
|
});
|
2022-05-27 12:27:13 +00:00
|
|
|
const snapshotFilePath = fromFileUrl(this.#snapshotFileUrl);
|
2022-05-06 13:05:27 +00:00
|
|
|
ensureFileSync(snapshotFilePath);
|
|
|
|
Deno.writeTextFileSync(snapshotFilePath, buf.join("\n") + "\n");
|
|
|
|
|
|
|
|
const contexts = Array.from(AssertSnapshotContext.contexts.values());
|
|
|
|
if (contexts[contexts.length - 1] === this) {
|
|
|
|
let updated = 0;
|
|
|
|
for (const context of contexts) {
|
|
|
|
updated += context.getUpdatedCount();
|
|
|
|
}
|
|
|
|
if (updated > 0) {
|
|
|
|
console.log(
|
|
|
|
green(bold(`\n > ${updated} snapshots updated.`)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns `this.#currentSnapshots` and if necessary, tries to initialize it by reading existing
|
|
|
|
* snapshots from the snapshot file. If the snapshot mode is `update` and the snapshot file does
|
|
|
|
* not exist then it will be created.
|
|
|
|
*/
|
2022-05-27 12:27:13 +00:00
|
|
|
async #readSnapshotFile(options: SnapshotOptions) {
|
|
|
|
if (this.#currentSnapshots) {
|
|
|
|
return this.#currentSnapshots;
|
2022-05-06 13:05:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (getIsUpdate(options)) {
|
2022-05-27 12:27:13 +00:00
|
|
|
await ensureFile(fromFileUrl(this.#snapshotFileUrl));
|
2022-04-20 11:46:21 +00:00
|
|
|
}
|
2022-05-06 13:05:27 +00:00
|
|
|
|
|
|
|
try {
|
2022-05-27 12:27:13 +00:00
|
|
|
const snapshotFileUrl = this.#snapshotFileUrl.toString();
|
2022-05-06 13:05:27 +00:00
|
|
|
const { snapshot } = await import(snapshotFileUrl);
|
2022-05-27 12:27:13 +00:00
|
|
|
this.#currentSnapshots = typeof snapshot === "undefined"
|
2022-05-06 13:05:27 +00:00
|
|
|
? new Map()
|
|
|
|
: new Map(
|
|
|
|
Object.entries(snapshot).map(([name, snapshot]) => {
|
|
|
|
if (typeof snapshot !== "string") {
|
|
|
|
throw new AssertionError(
|
|
|
|
getErrorMessage(
|
|
|
|
`Corrupt snapshot:\n\t(${name})\n\t${snapshotFileUrl}`,
|
|
|
|
options,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return [
|
|
|
|
name,
|
|
|
|
snapshot.includes("\n") ? snapshot.slice(1, -1) : snapshot,
|
|
|
|
];
|
|
|
|
}),
|
|
|
|
);
|
2022-05-27 12:27:13 +00:00
|
|
|
return this.#currentSnapshots;
|
2022-05-06 13:05:27 +00:00
|
|
|
} catch (error) {
|
|
|
|
if (
|
|
|
|
error instanceof TypeError &&
|
|
|
|
error.message.startsWith("Module not found")
|
|
|
|
) {
|
|
|
|
throw new AssertionError(
|
|
|
|
getErrorMessage(
|
|
|
|
"Missing snapshot file.",
|
|
|
|
options,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a teardown function which writes the snapshot file to disk and logs the number
|
|
|
|
* of snapshots updated after all tests have run.
|
|
|
|
*
|
|
|
|
* This method can safely be called more than once and will only register the teardown
|
|
|
|
* function once.
|
|
|
|
*/
|
|
|
|
public registerTeardown() {
|
2022-05-27 12:27:13 +00:00
|
|
|
if (!this.#teardownRegistered) {
|
|
|
|
globalThis.addEventListener("unload", this.#teardown);
|
|
|
|
this.#teardownRegistered = true;
|
2022-05-06 13:05:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the number of snapshots which have been created with the same name and increments
|
|
|
|
* the count by 1.
|
|
|
|
*/
|
|
|
|
public getCount(snapshotName: string) {
|
2022-05-27 12:27:13 +00:00
|
|
|
let count = this.#snapshotCounts.get(snapshotName) || 0;
|
|
|
|
this.#snapshotCounts.set(snapshotName, ++count);
|
2022-05-06 13:05:27 +00:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get an existing snapshot by name or returns `undefined` if the snapshot does not exist.
|
|
|
|
*/
|
|
|
|
public async getSnapshot(snapshotName: string, options: SnapshotOptions) {
|
2022-05-27 12:27:13 +00:00
|
|
|
const snapshots = await this.#readSnapshotFile(options);
|
2022-05-06 13:05:27 +00:00
|
|
|
return snapshots.get(snapshotName);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update a snapshot by name. Updates will be written to the snapshot file when all tests
|
|
|
|
* have run. If the snapshot does not exist, it will be created.
|
|
|
|
*
|
|
|
|
* Should only be called when mode is `update`.
|
|
|
|
*/
|
|
|
|
public updateSnapshot(snapshotName: string, snapshot: string) {
|
2022-05-27 12:27:13 +00:00
|
|
|
if (!this.#snapshotsUpdated.includes(snapshotName)) {
|
|
|
|
this.#snapshotsUpdated.push(snapshotName);
|
2022-05-06 13:05:27 +00:00
|
|
|
}
|
2022-05-27 12:27:13 +00:00
|
|
|
const currentSnapshots = this.#getCurrentSnapshotsInitialized();
|
2022-05-06 13:05:27 +00:00
|
|
|
if (!currentSnapshots.has(snapshotName)) {
|
|
|
|
currentSnapshots.set(snapshotName, undefined);
|
|
|
|
}
|
2022-05-27 12:27:13 +00:00
|
|
|
this.#updatedSnapshots.set(snapshotName, snapshot);
|
2022-05-06 13:05:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the number of updated snapshots.
|
|
|
|
*/
|
|
|
|
public getUpdatedCount() {
|
2022-05-27 12:27:13 +00:00
|
|
|
return this.#snapshotsUpdated.length;
|
2022-05-06 13:05:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a snapshot to the update queue.
|
|
|
|
*
|
|
|
|
* Tracks the order in which snapshots were created so that they can be written to
|
|
|
|
* the snapshot file in the correct order.
|
|
|
|
*
|
|
|
|
* Should be called with each snapshot, regardless of the mode, as a future call to
|
|
|
|
* `assertSnapshot` could cause updates to be written to the snapshot file if the
|
|
|
|
* `update` mode is passed in the options.
|
|
|
|
*/
|
|
|
|
public pushSnapshotToUpdateQueue(snapshotName: string) {
|
|
|
|
this.snapshotUpdateQueue.push(snapshotName);
|
|
|
|
}
|
2022-05-30 13:21:05 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if exist snapshot
|
|
|
|
*/
|
|
|
|
public hasSnapshot(snapshotName: string): boolean {
|
|
|
|
return this.#currentSnapshots
|
|
|
|
? this.#currentSnapshots.has(snapshotName)
|
|
|
|
: false;
|
|
|
|
}
|
2022-04-20 11:46:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Make an assertion that `actual` matches a snapshot. If the snapshot and `actual` do
|
|
|
|
* not a match, then throw.
|
|
|
|
*
|
|
|
|
* Type parameter can be specified to ensure values under comparison have the same type.
|
|
|
|
* For example:
|
|
|
|
* ```ts
|
|
|
|
* import { assertSnapshot } from "./snapshot.ts";
|
|
|
|
*
|
|
|
|
* Deno.test("snapshot", async (test) => {
|
|
|
|
* await assertSnapshot<number>(test, 2);
|
|
|
|
* });
|
|
|
|
* ```
|
|
|
|
*/
|
2022-05-06 13:05:27 +00:00
|
|
|
export async function assertSnapshot<T>(
|
2022-07-02 09:24:42 +00:00
|
|
|
context: Deno.TestContext,
|
2022-05-06 13:05:27 +00:00
|
|
|
actual: T,
|
|
|
|
options: SnapshotOptions<T>,
|
2022-04-20 11:46:21 +00:00
|
|
|
): Promise<void>;
|
|
|
|
export async function assertSnapshot<T>(
|
|
|
|
context: Deno.TestContext,
|
|
|
|
actual: T,
|
2022-05-06 13:05:27 +00:00
|
|
|
message?: string,
|
2022-04-20 11:46:21 +00:00
|
|
|
): Promise<void>;
|
|
|
|
export async function assertSnapshot(
|
|
|
|
context: Deno.TestContext,
|
|
|
|
actual: unknown,
|
2022-05-06 13:05:27 +00:00
|
|
|
msgOrOpts?: string | SnapshotOptions<unknown>,
|
2022-04-20 11:46:21 +00:00
|
|
|
): Promise<void> {
|
2022-05-06 13:05:27 +00:00
|
|
|
const options = getOptions();
|
|
|
|
const assertSnapshotContext = AssertSnapshotContext.fromOptions(
|
|
|
|
context,
|
|
|
|
options,
|
|
|
|
);
|
|
|
|
const testName = getTestName(context, options);
|
|
|
|
const count = assertSnapshotContext.getCount(testName);
|
|
|
|
const name = `${testName} ${count}`;
|
|
|
|
const snapshot = await assertSnapshotContext.getSnapshot(
|
|
|
|
name,
|
|
|
|
options,
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSnapshotContext.pushSnapshotToUpdateQueue(name);
|
|
|
|
const _serialize = options.serializer || serialize;
|
|
|
|
const _actual = _serialize(actual);
|
|
|
|
if (getIsUpdate(options)) {
|
|
|
|
assertSnapshotContext.registerTeardown();
|
|
|
|
if (!equal(_actual, snapshot)) {
|
|
|
|
assertSnapshotContext.updateSnapshot(name, _actual);
|
2022-04-20 11:46:21 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-05-30 13:21:05 +00:00
|
|
|
if (
|
|
|
|
!assertSnapshotContext.hasSnapshot(name) ||
|
|
|
|
typeof snapshot === "undefined"
|
|
|
|
) {
|
2022-05-06 13:05:27 +00:00
|
|
|
throw new AssertionError(
|
|
|
|
getErrorMessage(`Missing snapshot: ${name}`, options),
|
|
|
|
);
|
2022-04-20 11:46:21 +00:00
|
|
|
}
|
2022-05-06 13:05:27 +00:00
|
|
|
if (equal(_actual, snapshot)) {
|
2022-04-20 11:46:21 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
let message = "";
|
|
|
|
try {
|
2022-05-06 13:05:27 +00:00
|
|
|
const stringDiff = !_actual.includes("\n");
|
2022-04-20 11:46:21 +00:00
|
|
|
const diffResult = stringDiff
|
2022-05-06 13:05:27 +00:00
|
|
|
? diffstr(_actual, snapshot)
|
|
|
|
: diff(_actual.split("\n"), snapshot.split("\n"));
|
2022-04-20 11:46:21 +00:00
|
|
|
const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n");
|
|
|
|
message = `Snapshot does not match:\n${diffMsg}`;
|
|
|
|
} catch {
|
|
|
|
message = `Snapshot does not match:\n${red(CAN_NOT_DISPLAY)} \n\n`;
|
|
|
|
}
|
2022-05-06 13:05:27 +00:00
|
|
|
throw new AssertionError(
|
|
|
|
getErrorMessage(message, options),
|
|
|
|
);
|
2022-04-20 11:46:21 +00:00
|
|
|
}
|
|
|
|
|
2022-05-06 13:05:27 +00:00
|
|
|
function getOptions(): SnapshotOptions {
|
|
|
|
if (typeof msgOrOpts === "object" && msgOrOpts !== null) {
|
|
|
|
return msgOrOpts;
|
2022-04-20 11:46:21 +00:00
|
|
|
}
|
2022-05-06 13:05:27 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
msg: msgOrOpts,
|
|
|
|
};
|
2022-04-20 11:46:21 +00:00
|
|
|
}
|
2022-05-06 13:05:27 +00:00
|
|
|
function getTestName(
|
|
|
|
context: Deno.TestContext,
|
|
|
|
options?: SnapshotOptions,
|
|
|
|
): string {
|
|
|
|
if (options && options.name) {
|
|
|
|
return options.name;
|
|
|
|
} else if (context.parent) {
|
2022-04-20 11:46:21 +00:00
|
|
|
return `${getTestName(context.parent)} > ${context.name}`;
|
|
|
|
}
|
|
|
|
return context.name;
|
|
|
|
}
|
|
|
|
}
|
2022-07-02 09:24:42 +00:00
|
|
|
|
|
|
|
export function createAssertSnapshot<T>(
|
|
|
|
options: SnapshotOptions<T>,
|
|
|
|
baseAssertSnapshot: typeof assertSnapshot = assertSnapshot,
|
|
|
|
): typeof assertSnapshot {
|
|
|
|
return async function _assertSnapshot(
|
|
|
|
context: Deno.TestContext,
|
|
|
|
actual: T,
|
|
|
|
messageOrOptions?: string | SnapshotOptions<T>,
|
|
|
|
): Promise<void> {
|
|
|
|
const mergedOptions: SnapshotOptions<T> = {
|
|
|
|
...options,
|
|
|
|
...(typeof messageOrOptions === "string"
|
|
|
|
? {
|
|
|
|
msg: messageOrOptions,
|
|
|
|
}
|
|
|
|
: messageOrOptions),
|
|
|
|
};
|
|
|
|
|
|
|
|
await baseAssertSnapshot(context, actual, mergedOptions);
|
|
|
|
};
|
|
|
|
}
|