assert: add assert.Snapshot
PR-URL: https://github.com/nodejs/node/pull/44095 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
parent
818bd6cb4d
commit
8f9d1ab5ec
@ -2006,6 +2006,32 @@ argument, then `error` is assumed to be omitted and the string will be used for
|
|||||||
example in [`assert.throws()`][] carefully if using a string as the second
|
example in [`assert.throws()`][] carefully if using a string as the second
|
||||||
argument gets considered.
|
argument gets considered.
|
||||||
|
|
||||||
|
## `assert.snapshot(value, name)`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
> Stability: 1 - Experimental
|
||||||
|
|
||||||
|
* `value` {any} the value to snapshot
|
||||||
|
* `name` {string} the name of snapshot.
|
||||||
|
* Returns: {Promise}
|
||||||
|
|
||||||
|
reads a snapshot from a file, and compares `value` to the snapshot.
|
||||||
|
`value` is serialized with [`util.inspect()`][]
|
||||||
|
If the value is not strictly equal to the snapshot,
|
||||||
|
`assert.snapshot()` will return a rejected `Promise`
|
||||||
|
with an [`AssertionError`][].
|
||||||
|
|
||||||
|
If the snapshot file does not exist, the snapshot is written.
|
||||||
|
|
||||||
|
In case it is needed to force a snapshot update,
|
||||||
|
use [`--update-assert-snapshot`][];
|
||||||
|
|
||||||
|
By default, a snapshot is read and written to a file,
|
||||||
|
using the same name as the main entrypoint with `.snapshot` as the extension.
|
||||||
|
|
||||||
## `assert.strictEqual(actual, expected[, message])`
|
## `assert.strictEqual(actual, expected[, message])`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
@ -2442,6 +2468,7 @@ argument.
|
|||||||
[Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript
|
[Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript
|
||||||
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring
|
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring
|
||||||
[`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality
|
[`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality
|
||||||
|
[`--update-assert-snapshot`]: cli.md#--update-assert-snapshot
|
||||||
[`===` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality
|
[`===` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality
|
||||||
[`==` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality
|
[`==` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality
|
||||||
[`AssertionError`]: #class-assertassertionerror
|
[`AssertionError`]: #class-assertassertionerror
|
||||||
@ -2473,5 +2500,6 @@ argument.
|
|||||||
[`process.on('exit')`]: process.md#event-exit
|
[`process.on('exit')`]: process.md#event-exit
|
||||||
[`tracker.calls()`]: #trackercallsfn-exact
|
[`tracker.calls()`]: #trackercallsfn-exact
|
||||||
[`tracker.verify()`]: #trackerverify
|
[`tracker.verify()`]: #trackerverify
|
||||||
|
[`util.inspect()`]: util.md#utilinspectobject-options
|
||||||
[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
|
[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
|
||||||
[prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
|
[prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
|
||||||
|
@ -1488,6 +1488,14 @@ occurs. One of the following modes can be chosen:
|
|||||||
If a rejection happens during the command line entry point's ES module static
|
If a rejection happens during the command line entry point's ES module static
|
||||||
loading phase, it will always raise it as an uncaught exception.
|
loading phase, it will always raise it as an uncaught exception.
|
||||||
|
|
||||||
|
### `--update-assert-snapshot`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
Force updating snapshot files for [`assert.snapshot()`][]
|
||||||
|
|
||||||
### `--use-bundled-ca`, `--use-openssl-ca`
|
### `--use-bundled-ca`, `--use-openssl-ca`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
@ -1849,6 +1857,7 @@ Node.js options that are allowed are:
|
|||||||
* `--trace-warnings`
|
* `--trace-warnings`
|
||||||
* `--track-heap-objects`
|
* `--track-heap-objects`
|
||||||
* `--unhandled-rejections`
|
* `--unhandled-rejections`
|
||||||
|
* `--update-assert-snapshot`
|
||||||
* `--use-bundled-ca`
|
* `--use-bundled-ca`
|
||||||
* `--use-largepages`
|
* `--use-largepages`
|
||||||
* `--use-openssl-ca`
|
* `--use-openssl-ca`
|
||||||
@ -2224,6 +2233,7 @@ done
|
|||||||
[`NO_COLOR`]: https://no-color.org
|
[`NO_COLOR`]: https://no-color.org
|
||||||
[`SlowBuffer`]: buffer.md#class-slowbuffer
|
[`SlowBuffer`]: buffer.md#class-slowbuffer
|
||||||
[`YoungGenerationSizeFromSemiSpaceSize`]: https://chromium.googlesource.com/v8/v8.git/+/refs/tags/10.3.129/src/heap/heap.cc#328
|
[`YoungGenerationSizeFromSemiSpaceSize`]: https://chromium.googlesource.com/v8/v8.git/+/refs/tags/10.3.129/src/heap/heap.cc#328
|
||||||
|
[`assert.snapshot()`]: assert.md#assertsnapshotvalue-name
|
||||||
[`dns.lookup()`]: dns.md#dnslookuphostname-options-callback
|
[`dns.lookup()`]: dns.md#dnslookuphostname-options-callback
|
||||||
[`dns.setDefaultResultOrder()`]: dns.md#dnssetdefaultresultorderorder
|
[`dns.setDefaultResultOrder()`]: dns.md#dnssetdefaultresultorderorder
|
||||||
[`dnsPromises.lookup()`]: dns.md#dnspromiseslookuphostname-options
|
[`dnsPromises.lookup()`]: dns.md#dnspromiseslookuphostname-options
|
||||||
|
@ -705,6 +705,13 @@ A special type of error that can be triggered whenever Node.js detects an
|
|||||||
exceptional logic violation that should never occur. These are raised typically
|
exceptional logic violation that should never occur. These are raised typically
|
||||||
by the `node:assert` module.
|
by the `node:assert` module.
|
||||||
|
|
||||||
|
<a id="ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED"></a>
|
||||||
|
|
||||||
|
### `ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED`
|
||||||
|
|
||||||
|
An attempt was made to use `assert.snapshot()` in an environment that
|
||||||
|
does not support snapshots, such as the REPL, or when using `node --eval`.
|
||||||
|
|
||||||
<a id="ERR_ASYNC_CALLBACK"></a>
|
<a id="ERR_ASYNC_CALLBACK"></a>
|
||||||
|
|
||||||
### `ERR_ASYNC_CALLBACK`
|
### `ERR_ASYNC_CALLBACK`
|
||||||
|
@ -1052,6 +1052,9 @@ assert.doesNotMatch = function doesNotMatch(string, regexp, message) {
|
|||||||
|
|
||||||
assert.CallTracker = CallTracker;
|
assert.CallTracker = CallTracker;
|
||||||
|
|
||||||
|
const snapshot = require('internal/assert/snapshot');
|
||||||
|
assert.snapshot = snapshot;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose a strict only variant of assert.
|
* Expose a strict only variant of assert.
|
||||||
* @param {...any} args
|
* @param {...any} args
|
||||||
|
129
lib/internal/assert/snapshot.js
Normal file
129
lib/internal/assert/snapshot.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {
|
||||||
|
ArrayPrototypeJoin,
|
||||||
|
ArrayPrototypeMap,
|
||||||
|
ArrayPrototypeSlice,
|
||||||
|
RegExp,
|
||||||
|
SafeMap,
|
||||||
|
SafeSet,
|
||||||
|
StringPrototypeSplit,
|
||||||
|
StringPrototypeReplace,
|
||||||
|
Symbol,
|
||||||
|
} = primordials;
|
||||||
|
|
||||||
|
const { codes: { ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED } } = require('internal/errors');
|
||||||
|
const AssertionError = require('internal/assert/assertion_error');
|
||||||
|
const { inspect } = require('internal/util/inspect');
|
||||||
|
const { getOptionValue } = require('internal/options');
|
||||||
|
const { validateString } = require('internal/validators');
|
||||||
|
const { once } = require('events');
|
||||||
|
const { createReadStream, createWriteStream } = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const kUpdateSnapshot = getOptionValue('--update-assert-snapshot');
|
||||||
|
const kInitialSnapshot = Symbol('kInitialSnapshot');
|
||||||
|
const kDefaultDelimiter = '\n#*#*#*#*#*#*#*#*#*#*#*#\n';
|
||||||
|
const kDefaultDelimiterRegex = new RegExp(kDefaultDelimiter.replaceAll('*', '\\*').replaceAll('\n', '\r?\n'), 'g');
|
||||||
|
const kKeyDelimiter = /:\r?\n/g;
|
||||||
|
|
||||||
|
function getSnapshotPath() {
|
||||||
|
if (process.mainModule) {
|
||||||
|
const { dir, name } = path.parse(process.mainModule.filename);
|
||||||
|
return path.join(dir, `${name}.snapshot`);
|
||||||
|
}
|
||||||
|
if (!process.argv[1]) {
|
||||||
|
throw new ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED();
|
||||||
|
}
|
||||||
|
const { dir, name } = path.parse(process.argv[1]);
|
||||||
|
return path.join(dir, `${name}.snapshot`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSource() {
|
||||||
|
return createReadStream(getSnapshotPath(), { encoding: 'utf8' });
|
||||||
|
}
|
||||||
|
|
||||||
|
let _target;
|
||||||
|
function getTarget() {
|
||||||
|
_target ??= createWriteStream(getSnapshotPath(), { encoding: 'utf8' });
|
||||||
|
return _target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeName(name) {
|
||||||
|
validateString(name, 'name');
|
||||||
|
return StringPrototypeReplace(`${name}`, kKeyDelimiter, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
let writtenNames;
|
||||||
|
let snapshotValue;
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
async function writeSnapshot({ name, value }) {
|
||||||
|
const target = getTarget();
|
||||||
|
if (counter > 1) {
|
||||||
|
target.write(kDefaultDelimiter);
|
||||||
|
}
|
||||||
|
writtenNames = writtenNames || new SafeSet();
|
||||||
|
if (writtenNames.has(name)) {
|
||||||
|
throw new AssertionError({ message: `Snapshot "${name}" already used` });
|
||||||
|
}
|
||||||
|
writtenNames.add(name);
|
||||||
|
const drained = target.write(`${name}:\n${value}`);
|
||||||
|
await drained || once(target, 'drain');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSnapshot() {
|
||||||
|
if (snapshotValue !== undefined) {
|
||||||
|
return snapshotValue;
|
||||||
|
}
|
||||||
|
if (kUpdateSnapshot) {
|
||||||
|
snapshotValue = kInitialSnapshot;
|
||||||
|
return kInitialSnapshot;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const source = getSource();
|
||||||
|
let data = '';
|
||||||
|
for await (const line of source) {
|
||||||
|
data += line;
|
||||||
|
}
|
||||||
|
snapshotValue = new SafeMap(
|
||||||
|
ArrayPrototypeMap(
|
||||||
|
StringPrototypeSplit(data, kDefaultDelimiterRegex),
|
||||||
|
(item) => {
|
||||||
|
const arr = StringPrototypeSplit(item, kKeyDelimiter);
|
||||||
|
return [
|
||||||
|
arr[0],
|
||||||
|
ArrayPrototypeJoin(ArrayPrototypeSlice(arr, 1), ':\n'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === 'ENOENT') {
|
||||||
|
snapshotValue = kInitialSnapshot;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return snapshotValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function snapshot(input, name) {
|
||||||
|
const snapshot = await getSnapshot();
|
||||||
|
counter = counter + 1;
|
||||||
|
name = serializeName(name);
|
||||||
|
|
||||||
|
const value = inspect(input);
|
||||||
|
if (snapshot === kInitialSnapshot) {
|
||||||
|
await writeSnapshot({ name, value });
|
||||||
|
} else if (snapshot.has(name)) {
|
||||||
|
const expected = snapshot.get(name);
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
assert.strictEqual(value, expected);
|
||||||
|
} else {
|
||||||
|
throw new AssertionError({ message: `Snapshot "${name}" does not exist`, actual: inspect(snapshot) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = snapshot;
|
@ -936,6 +936,8 @@ module.exports = {
|
|||||||
E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError);
|
E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError);
|
||||||
E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError);
|
E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError);
|
||||||
E('ERR_ASSERTION', '%s', Error);
|
E('ERR_ASSERTION', '%s', Error);
|
||||||
|
E('ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED',
|
||||||
|
'Snapshot is not supported in this context ', TypeError);
|
||||||
E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError);
|
E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError);
|
||||||
E('ERR_ASYNC_TYPE', 'Invalid name for async "type": %s', TypeError);
|
E('ERR_ASYNC_TYPE', 'Invalid name for async "type": %s', TypeError);
|
||||||
E('ERR_BROTLI_INVALID_PARAM', '%s is not a valid Brotli parameter', RangeError);
|
E('ERR_BROTLI_INVALID_PARAM', '%s is not a valid Brotli parameter', RangeError);
|
||||||
|
@ -622,6 +622,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
|||||||
&EnvironmentOptions::force_repl);
|
&EnvironmentOptions::force_repl);
|
||||||
AddAlias("-i", "--interactive");
|
AddAlias("-i", "--interactive");
|
||||||
|
|
||||||
|
AddOption("--update-assert-snapshot",
|
||||||
|
"update assert snapshot files",
|
||||||
|
&EnvironmentOptions::update_assert_snapshot,
|
||||||
|
kAllowedInEnvironment);
|
||||||
|
|
||||||
AddOption("--napi-modules", "", NoOp{}, kAllowedInEnvironment);
|
AddOption("--napi-modules", "", NoOp{}, kAllowedInEnvironment);
|
||||||
|
|
||||||
AddOption("--tls-keylog",
|
AddOption("--tls-keylog",
|
||||||
|
@ -136,6 +136,7 @@ class EnvironmentOptions : public Options {
|
|||||||
bool preserve_symlinks = false;
|
bool preserve_symlinks = false;
|
||||||
bool preserve_symlinks_main = false;
|
bool preserve_symlinks_main = false;
|
||||||
bool prof_process = false;
|
bool prof_process = false;
|
||||||
|
bool update_assert_snapshot = false;
|
||||||
#if HAVE_INSPECTOR
|
#if HAVE_INSPECTOR
|
||||||
std::string cpu_prof_dir;
|
std::string cpu_prof_dir;
|
||||||
static const uint64_t kDefaultCpuProfInterval = 1000;
|
static const uint64_t kDefaultCpuProfInterval = 1000;
|
||||||
|
3
test/fixtures/assert-snapshot/basic.mjs
vendored
Normal file
3
test/fixtures/assert-snapshot/basic.mjs
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
|
||||||
|
await assert.snapshot("test", "name");
|
4
test/fixtures/assert-snapshot/multiple.mjs
vendored
Normal file
4
test/fixtures/assert-snapshot/multiple.mjs
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
|
||||||
|
await assert.snapshot("test", "name");
|
||||||
|
await assert.snapshot("test", "another name");
|
4
test/fixtures/assert-snapshot/non-existing-name.mjs
vendored
Normal file
4
test/fixtures/assert-snapshot/non-existing-name.mjs
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
|
||||||
|
await assert.snapshot("test", "another name");
|
||||||
|
await assert.snapshot("test", "non existing");
|
5
test/fixtures/assert-snapshot/non-existing-name.snapshot
vendored
Normal file
5
test/fixtures/assert-snapshot/non-existing-name.snapshot
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
another name:
|
||||||
|
'test'
|
||||||
|
#*#*#*#*#*#*#*#*#*#*#*#
|
||||||
|
name:
|
||||||
|
'test'
|
11
test/fixtures/assert-snapshot/random.mjs
vendored
Normal file
11
test/fixtures/assert-snapshot/random.mjs
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
|
||||||
|
function random() {
|
||||||
|
return `Random Value: ${Math.random()}`;
|
||||||
|
}
|
||||||
|
function transform(value) {
|
||||||
|
return value.replaceAll(/Random Value: \d+\.+\d+/g, 'Random Value: *');
|
||||||
|
}
|
||||||
|
|
||||||
|
await assert.snapshot(transform(random()), 'random1');
|
||||||
|
await assert.snapshot(transform(random()), 'random2');
|
5
test/fixtures/assert-snapshot/random.snapshot
vendored
Normal file
5
test/fixtures/assert-snapshot/random.snapshot
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
random1:
|
||||||
|
'Random Value: *'
|
||||||
|
#*#*#*#*#*#*#*#*#*#*#*#
|
||||||
|
random2:
|
||||||
|
'Random Value: *'
|
11
test/fixtures/assert-snapshot/serialize.mjs
vendored
Normal file
11
test/fixtures/assert-snapshot/serialize.mjs
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
|
||||||
|
function fn() {
|
||||||
|
this.should.be.a.fn();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await assert.snapshot(fn, 'function');
|
||||||
|
await assert.snapshot({ foo: "bar" }, 'object');
|
||||||
|
await assert.snapshot(new Set([1, 2, 3]), 'set');
|
||||||
|
await assert.snapshot(new Map([['one', 1], ['two', 2]]), 'map');
|
11
test/fixtures/assert-snapshot/serialize.snapshot
vendored
Normal file
11
test/fixtures/assert-snapshot/serialize.snapshot
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
function:
|
||||||
|
[Function: fn]
|
||||||
|
#*#*#*#*#*#*#*#*#*#*#*#
|
||||||
|
object:
|
||||||
|
{ foo: 'bar' }
|
||||||
|
#*#*#*#*#*#*#*#*#*#*#*#
|
||||||
|
set:
|
||||||
|
Set(3) { 1, 2, 3 }
|
||||||
|
#*#*#*#*#*#*#*#*#*#*#*#
|
||||||
|
map:
|
||||||
|
Map(2) { 'one' => 1, 'two' => 2 }
|
3
test/fixtures/assert-snapshot/single.mjs
vendored
Normal file
3
test/fixtures/assert-snapshot/single.mjs
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
|
||||||
|
await assert.snapshot("test", "snapshot");
|
3
test/fixtures/assert-snapshot/value-changed.mjs
vendored
Normal file
3
test/fixtures/assert-snapshot/value-changed.mjs
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
|
||||||
|
await assert.snapshot("changed", "snapshot");
|
2
test/fixtures/assert-snapshot/value-changed.snapshot
vendored
Normal file
2
test/fixtures/assert-snapshot/value-changed.snapshot
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
snapshot:
|
||||||
|
'original'
|
133
test/parallel/test-assert-snapshot.mjs
Normal file
133
test/parallel/test-assert-snapshot.mjs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import { spawnPromisified } from '../common/index.mjs';
|
||||||
|
import * as fixtures from '../common/fixtures.mjs';
|
||||||
|
import tmpdir from '../common/tmpdir.js';
|
||||||
|
import assert from 'node:assert';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { execPath } from 'node:process';
|
||||||
|
import { writeFile, readFile, unlink } from 'node:fs/promises';
|
||||||
|
import { describe, it } from 'node:test';
|
||||||
|
|
||||||
|
tmpdir.refresh();
|
||||||
|
|
||||||
|
function getSnapshotPath(filename) {
|
||||||
|
const { name, dir } = path.parse(filename);
|
||||||
|
return path.join(tmpdir.path, dir, `${name}.snapshot`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function spawnTmpfile(content = '', filename, extraFlags = []) {
|
||||||
|
const header = filename.endsWith('.mjs') ?
|
||||||
|
'import assert from \'node:assert\';' :
|
||||||
|
'const assert = require(\'node:assert\');';
|
||||||
|
await writeFile(path.join(tmpdir.path, filename), `${header}\n${content}`);
|
||||||
|
const { stdout, stderr, code } = await spawnPromisified(
|
||||||
|
execPath,
|
||||||
|
['--no-warnings', ...extraFlags, filename],
|
||||||
|
{ cwd: tmpdir.path });
|
||||||
|
|
||||||
|
const snapshotPath = getSnapshotPath(filename);
|
||||||
|
const snapshot = await readFile(snapshotPath, 'utf8').catch((err) => err);
|
||||||
|
return { stdout, stderr, code, snapshot, snapshotPath, filename };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function spawnFixture(filename) {
|
||||||
|
const { dir, base, name } = path.parse(filename);
|
||||||
|
const { stdout, stderr, code } = await spawnPromisified(execPath, ['--no-warnings', base], { cwd: dir });
|
||||||
|
|
||||||
|
const snapshotPath = path.join(dir, `${name}.snapshot`);
|
||||||
|
const snapshot = await readFile(snapshotPath, 'utf8').catch((err) => err);
|
||||||
|
return { stdout, stderr, code, snapshot, snapshotPath };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('assert.snapshot', { concurrency: true }, () => {
|
||||||
|
it('should write snapshot', async () => {
|
||||||
|
const { stderr, code, snapshot, snapshotPath } = await spawnFixture(fixtures.path('assert-snapshot/basic.mjs'));
|
||||||
|
await unlink(snapshotPath);
|
||||||
|
assert.strictEqual(stderr, '');
|
||||||
|
assert.strictEqual(code, 0);
|
||||||
|
assert.match(snapshot, /^name:\r?\n'test'$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should write multiple snapshots', async () => {
|
||||||
|
const { stderr, code, snapshot, snapshotPath } = await spawnFixture(fixtures.path('assert-snapshot/multiple.mjs'));
|
||||||
|
await unlink(snapshotPath);
|
||||||
|
assert.strictEqual(stderr, '');
|
||||||
|
assert.strictEqual(code, 0);
|
||||||
|
assert.match(snapshot, /^name:\n'test'\r?\n#\*#\*#\*#\*#\*#\*#\*#\*#\*#\*#\*#\r?\nanother name:\r?\n'test'$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should succeed running multiple times', async () => {
|
||||||
|
let result = await spawnFixture(fixtures.path('assert-snapshot/single.mjs'), false);
|
||||||
|
assert.strictEqual(result.stderr, '');
|
||||||
|
assert.strictEqual(result.code, 0);
|
||||||
|
assert.match(result.snapshot, /^snapshot:\r?\n'test'$/);
|
||||||
|
|
||||||
|
result = await spawnFixture(fixtures.path('assert-snapshot/single.mjs'));
|
||||||
|
await unlink(result.snapshotPath);
|
||||||
|
assert.strictEqual(result.stderr, '');
|
||||||
|
assert.strictEqual(result.code, 0);
|
||||||
|
assert.match(result.snapshot, /^snapshot:\r?\n'test'$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when name is not provided', async () => {
|
||||||
|
for (const name of [1, undefined, null, {}, function() {}]) {
|
||||||
|
await assert.rejects(() => assert.snapshot('', name), {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
name: 'TypeError',
|
||||||
|
message: /^The "name" argument must be of type string/
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when value does not match snapshot', async () => {
|
||||||
|
const { code, stderr, snapshot } = await spawnFixture(fixtures.path('assert-snapshot/value-changed.mjs'));
|
||||||
|
assert.match(stderr, /AssertionError \[ERR_ASSERTION\]/);
|
||||||
|
assert.strictEqual(code, 1);
|
||||||
|
assert.match(snapshot, /^snapshot:\r?\n'original'$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when snapshot does not contain a named snapshot', async () => {
|
||||||
|
const { code, stderr, snapshot } = await spawnFixture(fixtures.path('assert-snapshot/non-existing-name.mjs'));
|
||||||
|
assert.match(stderr, /AssertionError \[ERR_ASSERTION\]/);
|
||||||
|
assert.match(stderr, /Snapshot "non existing" does not exist/);
|
||||||
|
assert.strictEqual(code, 1);
|
||||||
|
assert.match(snapshot, /^another name:\r?\n'test'\r?\n#\*#\*#\*#\*#\*#\*#\*#\*#\*#\*#\*#\r?\nname:\r?\n'test'$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should snapshot a random replaced value', async () => {
|
||||||
|
const originalSnapshot = await readFile(fixtures.path('assert-snapshot/random.snapshot'), 'utf8');
|
||||||
|
const { stderr, code, snapshot } = await spawnFixture(fixtures.path('assert-snapshot/random.mjs'));
|
||||||
|
assert.strictEqual(stderr, '');
|
||||||
|
assert.strictEqual(code, 0);
|
||||||
|
assert.strictEqual(snapshot, originalSnapshot);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should serialize values', async () => {
|
||||||
|
const originalSnapshot = await readFile(fixtures.path('assert-snapshot/serialize.snapshot'), 'utf8');
|
||||||
|
const { stderr, code, snapshot } = await spawnFixture(fixtures.path('assert-snapshot/serialize.mjs'));
|
||||||
|
assert.strictEqual(stderr, '');
|
||||||
|
assert.strictEqual(code, 0);
|
||||||
|
assert.strictEqual(snapshot, originalSnapshot);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should override snapshot when passing --update-assert-snapshot', async () => {
|
||||||
|
const filename = 'updated.mjs';
|
||||||
|
await writeFile(getSnapshotPath(filename), 'snapshot:\n\'test\'');
|
||||||
|
const { stderr, code, snapshot } = await spawnTmpfile('await assert.snapshot(\'changed\', \'snapshot\');',
|
||||||
|
filename, ['--update-assert-snapshot']);
|
||||||
|
assert.strictEqual(stderr, '');
|
||||||
|
assert.strictEqual(code, 0);
|
||||||
|
assert.match(snapshot, /^snapshot:\r?\n'changed'$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('snapshot file should have the name of the module - esm', async () => {
|
||||||
|
const filename = 'name.mjs';
|
||||||
|
const { snapshotPath } = await spawnTmpfile('await assert.snapshot("test");', filename);
|
||||||
|
assert.match(snapshotPath, /name\.snapshot$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('snapshot file should have the name of the module - common js', async () => {
|
||||||
|
const filename = 'name.js';
|
||||||
|
const { snapshotPath } = await spawnTmpfile('assert.snapshot("test").then(() => process.exit());', filename);
|
||||||
|
assert.match(snapshotPath, /name\.snapshot$/);
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user