import { primordials } from "ext:core/mod.js";
import { inspect } from "ext:deno_node/internal/util/inspect.mjs";
import { isError } from "ext:deno_node/internal/util.mjs";
import { isErrorStackTraceLimitWritable } from "ext:deno_node/internal/errors.ts";
import * as colors from "ext:deno_node/internal/util/colors.ts";
import {
myersDiff,
printMyersDiff,
printSimpleMyersDiff,
} from "ext:deno_node/internal/assert/myers_diff.js";
import { validateObject } from "ext:deno_node/internal/validators.mjs";
import * as io from "ext:deno_io/12_io.js";
function getConsoleWidth() {
try {
return Deno.consoleSize().columns;
} catch {
return 80;
}
}
const {
ArrayPrototypeJoin,
ArrayPrototypePop,
ArrayPrototypeSlice,
Error,
ErrorCaptureStackTrace,
ObjectAssign,
ObjectDefineProperty,
ObjectGetPrototypeOf,
ObjectPrototypeHasOwnProperty,
String,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeSplit,
} = primordials;
const kReadableOperator = {
deepStrictEqual: "Expected values to be strictly deep-equal:",
partialDeepStrictEqual:
"Expected values to be partially and strictly deep-equal:",
strictEqual: "Expected values to be strictly equal:",
strictEqualObject: 'Expected "actual" to be reference-equal to "expected":',
deepEqual: "Expected values to be loosely deep-equal:",
notDeepStrictEqual: 'Expected "actual" not to be strictly deep-equal to:',
notStrictEqual: 'Expected "actual" to be strictly unequal to:',
notStrictEqualObject:
'Expected "actual" not to be reference-equal to "expected":',
notDeepEqual: 'Expected "actual" not to be loosely deep-equal to:',
notIdentical: "Values have same structure but are not reference-equal:",
notDeepEqualUnequal: "Expected values not to be loosely deep-equal:",
};
const kMaxShortStringLength = 12;
const kMaxLongStringLength = 512;
const kMethodsWithCustomMessageDiff = [
"deepStrictEqual",
"strictEqual",
"partialDeepStrictEqual",
];
function copyError(source) {
const target = ObjectAssign(
{ __proto__: ObjectGetPrototypeOf(source) },
source,
);
ObjectDefineProperty(target, "message", {
__proto__: null,
value: source.message,
});
if (ObjectPrototypeHasOwnProperty(source, "cause")) {
let { cause } = source;
if (isError(cause)) {
cause = copyError(cause);
}
ObjectDefineProperty(target, "cause", { __proto__: null, value: cause });
}
return target;
}
function inspectValue(val) {
return inspect(val, {
compact: false,
customInspect: false,
depth: 1000,
maxArrayLength: Infinity,
showHidden: false,
showProxy: false,
sorted: true,
getters: true,
});
}
function getErrorMessage(operator, message) {
return message || kReadableOperator[operator];
}
function checkOperator(actual, expected, operator) {
if (
operator === "strictEqual" &&
((typeof actual === "object" &&
actual !== null &&
typeof expected === "object" &&
expected !== null) ||
(typeof actual === "function" && typeof expected === "function"))
) {
operator = "strictEqualObject";
}
return operator;
}
function getColoredMyersDiff(actual, expected) {
const header =
`${colors.green}actual${colors.white} ${colors.red}expected${colors.white}`;
const skipped = false;
const diff = myersDiff(
StringPrototypeSplit(actual, ""),
StringPrototypeSplit(expected, ""),
);
let message = printSimpleMyersDiff(diff);
if (skipped) {
message += "...";
}
return { message, header, skipped };
}
function getStackedDiff(actual, expected) {
const isStringComparison = typeof actual === "string" &&
typeof expected === "string";
let message =
`\n${colors.green}+${colors.white} ${actual}\n${colors.red}- ${colors.white}${expected}`;
const stringsLen = actual.length + expected.length;
const maxTerminalLength = io.stderr.isTerminal() ? getConsoleWidth() : 80;
const showIndicator = isStringComparison && (stringsLen <= maxTerminalLength);
if (showIndicator) {
let indicatorIdx = -1;
for (let i = 0; i < actual.length; i++) {
if (actual[i] !== expected[i]) {
if (i >= 3) {
indicatorIdx = i;
}
break;
}
}
if (indicatorIdx !== -1) {
message += `\n${StringPrototypeRepeat(" ", indicatorIdx + 2)}^`;
}
}
return { message };
}
function getSimpleDiff(originalActual, actual, originalExpected, expected) {
let stringsLen = actual.length + expected.length;
if (typeof originalActual === "string") {
stringsLen -= 2;
}
if (typeof originalExpected === "string") {
stringsLen -= 2;
}
if (
stringsLen <= kMaxShortStringLength &&
(originalActual !== 0 || originalExpected !== 0)
) {
return { message: `${actual} !== ${expected}`, header: "" };
}
const isStringComparison = typeof originalActual === "string" &&
typeof originalExpected === "string";
if (isStringComparison && colors.hasColors) {
return getColoredMyersDiff(actual, expected);
}
return getStackedDiff(actual, expected);
}
function isSimpleDiff(actual, inspectedActual, expected, inspectedExpected) {
if (inspectedActual.length > 1 || inspectedExpected.length > 1) {
return false;
}
return typeof actual !== "object" || actual === null ||
typeof expected !== "object" || expected === null;
}
function createErrDiff(
actual,
expected,
operator,
customMessage,
diffType = "simple",
) {
operator = checkOperator(actual, expected, operator);
let skipped = false;
let message = "";
const inspectedActual = inspectValue(actual);
const inspectedExpected = inspectValue(expected);
const inspectedSplitActual = StringPrototypeSplit(inspectedActual, "\n");
const inspectedSplitExpected = StringPrototypeSplit(inspectedExpected, "\n");
const showSimpleDiff = isSimpleDiff(
actual,
inspectedSplitActual,
expected,
inspectedSplitExpected,
);
let header =
`${colors.green}+ actual${colors.white} ${colors.red}- expected${colors.white}`;
if (showSimpleDiff) {
const simpleDiff = getSimpleDiff(
actual,
inspectedSplitActual[0],
expected,
inspectedSplitExpected[0],
);
message = simpleDiff.message;
if (typeof simpleDiff.header !== "undefined") {
header = simpleDiff.header;
}
if (simpleDiff.skipped) {
skipped = true;
}
} else if (inspectedActual === inspectedExpected) {
operator = "notIdentical";
if (inspectedSplitActual.length > 50 && diffType !== "full") {
message = `${
ArrayPrototypeJoin(
ArrayPrototypeSlice(inspectedSplitActual, 0, 50),
"\n",
)
}\n...}`;
skipped = true;
} else {
message = ArrayPrototypeJoin(inspectedSplitActual, "\n");
}
header = "";
} else {
const checkCommaDisparity = actual != null && typeof actual === "object";
const diff = myersDiff(
inspectedSplitActual,
inspectedSplitExpected,
checkCommaDisparity,
);
const myersDiffMessage = printMyersDiff(diff, operator);
message = myersDiffMessage.message;
if (operator === "partialDeepStrictEqual") {
header = `${colors.gray}${
colors.hasColors ? "" : "+ "
}actual${colors.white} ${colors.red}- expected${colors.white}`;
}
if (myersDiffMessage.skipped) {
skipped = true;
}
}
const headerMessage = `${
getErrorMessage(operator, customMessage)
}\n${header}`;
const skippedMessage = skipped ? "\n... Skipped lines" : "";
return `${headerMessage}${skippedMessage}\n${message}\n`;
}
function addEllipsis(string) {
const lines = StringPrototypeSplit(string, "\n", 11);
if (lines.length > 10) {
lines.length = 10;
return `${ArrayPrototypeJoin(lines, "\n")}\n...`;
} else if (string.length > kMaxLongStringLength) {
return `${StringPrototypeSlice(string, kMaxLongStringLength)}...`;
}
return string;
}
export class AssertionError extends Error {
constructor(options) {
validateObject(options, "options");
const {
message,
operator,
stackStartFn,
details,
stackStartFunction,
diff = "simple",
} = options;
let {
actual,
expected,
} = options;
const limit = Error.stackTraceLimit;
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
if (message != null) {
if (kMethodsWithCustomMessageDiff.includes(operator)) {
super(createErrDiff(actual, expected, operator, message, diff));
} else {
super(String(message));
}
} else {
colors.refresh();
if (
typeof actual === "object" && actual !== null &&
typeof expected === "object" && expected !== null &&
"stack" in actual && actual instanceof Error &&
"stack" in expected && expected instanceof Error
) {
actual = copyError(actual);
expected = copyError(expected);
}
if (kMethodsWithCustomMessageDiff.includes(operator)) {
super(createErrDiff(actual, expected, operator, message, diff));
} else if (
operator === "notDeepStrictEqual" ||
operator === "notStrictEqual"
) {
let base = kReadableOperator[operator];
const res = StringPrototypeSplit(inspectValue(actual), "\n");
if (
operator === "notStrictEqual" &&
((typeof actual === "object" && actual !== null) ||
typeof actual === "function")
) {
base = kReadableOperator.notStrictEqualObject;
}
if (res.length > 50 && diff !== "full") {
res[46] = `${colors.blue}...${colors.white}`;
while (res.length > 47) {
ArrayPrototypePop(res);
}
}
if (res.length === 1) {
super(`${base}${res[0].length > 5 ? "\n\n" : " "}${res[0]}`);
} else {
super(`${base}\n\n${ArrayPrototypeJoin(res, "\n")}\n`);
}
} else {
let res = inspectValue(actual);
let other = inspectValue(expected);
const knownOperator = kReadableOperator[operator];
if (operator === "notDeepEqual" && res === other) {
res = `${knownOperator}\n\n${res}`;
if (res.length > 1024 && diff !== "full") {
res = `${StringPrototypeSlice(res, 0, 1021)}...`;
}
super(res);
} else {
if (res.length > kMaxLongStringLength && diff !== "full") {
res = `${StringPrototypeSlice(res, 0, 509)}...`;
}
if (other.length > kMaxLongStringLength && diff !== "full") {
other = `${StringPrototypeSlice(other, 0, 509)}...`;
}
if (operator === "deepEqual") {
res = `${knownOperator}\n\n${res}\n\nshould loosely deep-equal\n\n`;
} else {
const newOp = kReadableOperator[`${operator}Unequal`];
if (newOp) {
res = `${newOp}\n\n${res}\n\nshould not loosely deep-equal\n\n`;
} else {
other = ` ${operator} ${other}`;
}
}
super(`${res}${other}`);
}
}
}
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit;
this.generatedMessage = !message;
ObjectDefineProperty(this, "name", {
__proto__: null,
value: "AssertionError [ERR_ASSERTION]",
enumerable: false,
writable: true,
configurable: true,
});
this.code = "ERR_ASSERTION";
if (details) {
this.actual = undefined;
this.expected = undefined;
this.operator = undefined;
for (let i = 0; i < details.length; i++) {
this["message " + i] = details[i].message;
this["actual " + i] = details[i].actual;
this["expected " + i] = details[i].expected;
this["operator " + i] = details[i].operator;
this["stack trace " + i] = details[i].stack;
}
} else {
this.actual = actual;
this.expected = expected;
this.operator = operator;
}
ErrorCaptureStackTrace(this, stackStartFn || stackStartFunction);
this.stack; this.name = "AssertionError";
this.diff = diff;
}
toString() {
return `${this.name} [${this.code}]: ${this.message}`;
}
[inspect.custom](_recurseTimes, ctx) {
const tmpActual = this.actual;
const tmpExpected = this.expected;
if (typeof this.actual === "string") {
this.actual = addEllipsis(this.actual);
}
if (typeof this.expected === "string") {
this.expected = addEllipsis(this.expected);
}
const result = inspect(this, {
...ctx,
customInspect: false,
depth: 0,
});
this.actual = tmpActual;
this.expected = tmpExpected;
return result;
}
}
export default AssertionError;