mirror of
https://github.com/denoland/deno.git
synced 2024-10-30 09:08:00 -04:00
parent
57f4e6a864
commit
0eed9b3029
7 changed files with 1800 additions and 0 deletions
198
testing/diff.ts
Normal file
198
testing/diff.ts
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
interface FarthestPoint {
|
||||||
|
y: number;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DiffType = "removed" | "common" | "added";
|
||||||
|
|
||||||
|
export interface DiffResult<T> {
|
||||||
|
type: DiffType;
|
||||||
|
value: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
const REMOVED = 1;
|
||||||
|
const COMMON = 2;
|
||||||
|
const ADDED = 3;
|
||||||
|
|
||||||
|
function createCommon<T>(A: T[], B: T[], reverse?: boolean) {
|
||||||
|
const common = [];
|
||||||
|
if (A.length === 0 || B.length === 0) return [];
|
||||||
|
for (let i = 0; i < Math.min(A.length, B.length); i += 1) {
|
||||||
|
if (
|
||||||
|
A[reverse ? A.length - i - 1 : i] === B[reverse ? B.length - i - 1 : i]
|
||||||
|
) {
|
||||||
|
common.push(A[reverse ? A.length - i - 1 : i]);
|
||||||
|
} else {
|
||||||
|
return common;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return common;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function diff<T>(A: T[], B: T[]): DiffResult<T>[] {
|
||||||
|
function backTrace<T>(
|
||||||
|
A: T[],
|
||||||
|
B: T[],
|
||||||
|
current: FarthestPoint,
|
||||||
|
swapped: boolean
|
||||||
|
) {
|
||||||
|
const M = A.length;
|
||||||
|
const N = B.length;
|
||||||
|
const result = [];
|
||||||
|
let a = M - 1;
|
||||||
|
let b = N - 1;
|
||||||
|
let j = routes[current.id];
|
||||||
|
let type = routes[current.id + diffTypesPtrOffset];
|
||||||
|
while (true) {
|
||||||
|
if (!j && !type) break;
|
||||||
|
const prev = j;
|
||||||
|
if (type === REMOVED) {
|
||||||
|
result.unshift({
|
||||||
|
type: (swapped ? "removed" : "added") as DiffType,
|
||||||
|
value: B[b]
|
||||||
|
});
|
||||||
|
b -= 1;
|
||||||
|
} else if (type === ADDED) {
|
||||||
|
result.unshift({
|
||||||
|
type: (swapped ? "added" : "removed") as DiffType,
|
||||||
|
value: A[a]
|
||||||
|
});
|
||||||
|
a -= 1;
|
||||||
|
} else {
|
||||||
|
result.unshift({ type: "common" as DiffType, value: A[a] });
|
||||||
|
a -= 1;
|
||||||
|
b -= 1;
|
||||||
|
}
|
||||||
|
j = routes[prev];
|
||||||
|
type = routes[prev + diffTypesPtrOffset];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFP(
|
||||||
|
slide: FarthestPoint,
|
||||||
|
down: FarthestPoint,
|
||||||
|
k: number,
|
||||||
|
M: number,
|
||||||
|
N: number
|
||||||
|
): FarthestPoint {
|
||||||
|
if (slide && slide.y === -1 && (down && down.y === -1))
|
||||||
|
return { y: 0, id: 0 };
|
||||||
|
if (
|
||||||
|
(down && down.y === -1) ||
|
||||||
|
k === M ||
|
||||||
|
(slide && slide.y) > (down && down.y) + 1
|
||||||
|
) {
|
||||||
|
const prev = slide.id;
|
||||||
|
ptr++;
|
||||||
|
routes[ptr] = prev;
|
||||||
|
routes[ptr + diffTypesPtrOffset] = ADDED;
|
||||||
|
return { y: slide.y, id: ptr };
|
||||||
|
} else {
|
||||||
|
const prev = down.id;
|
||||||
|
ptr++;
|
||||||
|
routes[ptr] = prev;
|
||||||
|
routes[ptr + diffTypesPtrOffset] = REMOVED;
|
||||||
|
return { y: down.y + 1, id: ptr };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function snake<T>(
|
||||||
|
k: number,
|
||||||
|
slide: FarthestPoint,
|
||||||
|
down: FarthestPoint,
|
||||||
|
offset: number,
|
||||||
|
A: T[],
|
||||||
|
B: T[]
|
||||||
|
) {
|
||||||
|
const M = A.length;
|
||||||
|
const N = B.length;
|
||||||
|
if (k < -N || M < k) return { y: -1 };
|
||||||
|
const fp = createFP(slide, down, k, M, N);
|
||||||
|
while (fp.y + k < M && fp.y < N && A[fp.y + k] === B[fp.y]) {
|
||||||
|
const prev = fp.id;
|
||||||
|
ptr++;
|
||||||
|
fp.id = ptr;
|
||||||
|
fp.y += 1;
|
||||||
|
routes[ptr] = prev;
|
||||||
|
routes[ptr + diffTypesPtrOffset] = COMMON;
|
||||||
|
}
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefixCommon = createCommon(A, B);
|
||||||
|
const suffixCommon = createCommon(
|
||||||
|
A.slice(prefixCommon.length),
|
||||||
|
B.slice(prefixCommon.length),
|
||||||
|
true
|
||||||
|
).reverse();
|
||||||
|
A = suffixCommon.length
|
||||||
|
? A.slice(prefixCommon.length, -suffixCommon.length)
|
||||||
|
: A.slice(prefixCommon.length);
|
||||||
|
B = suffixCommon.length
|
||||||
|
? B.slice(prefixCommon.length, -suffixCommon.length)
|
||||||
|
: B.slice(prefixCommon.length);
|
||||||
|
const swapped = B.length > A.length;
|
||||||
|
[A, B] = swapped ? [B, A] : [A, B];
|
||||||
|
const M = A.length;
|
||||||
|
const N = B.length;
|
||||||
|
if (!M && !N && !suffixCommon.length && !prefixCommon.length) return [];
|
||||||
|
if (!N) {
|
||||||
|
return [
|
||||||
|
...prefixCommon.map(c => ({ type: "common" as DiffType, value: c })),
|
||||||
|
...A.map(a => ({
|
||||||
|
type: (swapped ? "added" : "removed") as DiffType,
|
||||||
|
value: a
|
||||||
|
})),
|
||||||
|
...suffixCommon.map(c => ({ type: "common" as DiffType, value: c }))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
const offset = N;
|
||||||
|
const delta = M - N;
|
||||||
|
const size = M + N + 1;
|
||||||
|
const fp = new Array(size).fill({ y: -1 });
|
||||||
|
// INFO: This buffer is used to save memory and improve performance.
|
||||||
|
// The first half is used to save route and last half is used to save diff type.
|
||||||
|
// This is because, when I kept new uint8array area to save type, performance worsened.
|
||||||
|
const routes = new Uint32Array((M * N + size + 1) * 2);
|
||||||
|
const diffTypesPtrOffset = routes.length / 2;
|
||||||
|
let ptr = 0;
|
||||||
|
let p = -1;
|
||||||
|
while (fp[delta + offset].y < N) {
|
||||||
|
p = p + 1;
|
||||||
|
for (let k = -p; k < delta; ++k) {
|
||||||
|
fp[k + offset] = snake(
|
||||||
|
k,
|
||||||
|
fp[k - 1 + offset],
|
||||||
|
fp[k + 1 + offset],
|
||||||
|
offset,
|
||||||
|
A,
|
||||||
|
B
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (let k = delta + p; k > delta; --k) {
|
||||||
|
fp[k + offset] = snake(
|
||||||
|
k,
|
||||||
|
fp[k - 1 + offset],
|
||||||
|
fp[k + 1 + offset],
|
||||||
|
offset,
|
||||||
|
A,
|
||||||
|
B
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fp[delta + offset] = snake(
|
||||||
|
delta,
|
||||||
|
fp[delta - 1 + offset],
|
||||||
|
fp[delta + 1 + offset],
|
||||||
|
offset,
|
||||||
|
A,
|
||||||
|
B
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
...prefixCommon.map(c => ({ type: "common" as DiffType, value: c })),
|
||||||
|
...backTrace(A, B, fp[delta + offset], swapped),
|
||||||
|
...suffixCommon.map(c => ({ type: "common" as DiffType, value: c }))
|
||||||
|
];
|
||||||
|
}
|
110
testing/diff_test.ts
Normal file
110
testing/diff_test.ts
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import diff from "./diff.ts";
|
||||||
|
import { test, assertEqual } from "./mod.ts";
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "empty",
|
||||||
|
fn() {
|
||||||
|
assertEqual(diff([], []), []);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: '"a" vs "b"',
|
||||||
|
fn() {
|
||||||
|
assertEqual(diff(["a"], ["b"]), [
|
||||||
|
{ type: "removed", value: "a" },
|
||||||
|
{ type: "added", value: "b" }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: '"a" vs "a"',
|
||||||
|
fn() {
|
||||||
|
assertEqual(diff(["a"], ["a"]), [{ type: "common", value: "a" }]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: '"a" vs ""',
|
||||||
|
fn() {
|
||||||
|
assertEqual(diff(["a"], []), [{ type: "removed", value: "a" }]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: '"" vs "a"',
|
||||||
|
fn() {
|
||||||
|
assertEqual(diff([], ["a"]), [{ type: "added", value: "a" }]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: '"a" vs "a, b"',
|
||||||
|
fn() {
|
||||||
|
assertEqual(diff(["a"], ["a", "b"]), [
|
||||||
|
{ type: "common", value: "a" },
|
||||||
|
{ type: "added", value: "b" }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: '"strength" vs "string"',
|
||||||
|
fn() {
|
||||||
|
assertEqual(diff(Array.from("strength"), Array.from("string")), [
|
||||||
|
{ type: "common", value: "s" },
|
||||||
|
{ type: "common", value: "t" },
|
||||||
|
{ type: "common", value: "r" },
|
||||||
|
{ type: "removed", value: "e" },
|
||||||
|
{ type: "added", value: "i" },
|
||||||
|
{ type: "common", value: "n" },
|
||||||
|
{ type: "common", value: "g" },
|
||||||
|
{ type: "removed", value: "t" },
|
||||||
|
{ type: "removed", value: "h" }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: '"strength" vs ""',
|
||||||
|
fn() {
|
||||||
|
assertEqual(diff(Array.from("strength"), Array.from("")), [
|
||||||
|
{ type: "removed", value: "s" },
|
||||||
|
{ type: "removed", value: "t" },
|
||||||
|
{ type: "removed", value: "r" },
|
||||||
|
{ type: "removed", value: "e" },
|
||||||
|
{ type: "removed", value: "n" },
|
||||||
|
{ type: "removed", value: "g" },
|
||||||
|
{ type: "removed", value: "t" },
|
||||||
|
{ type: "removed", value: "h" }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: '"" vs "strength"',
|
||||||
|
fn() {
|
||||||
|
assertEqual(diff(Array.from(""), Array.from("strength")), [
|
||||||
|
{ type: "added", value: "s" },
|
||||||
|
{ type: "added", value: "t" },
|
||||||
|
{ type: "added", value: "r" },
|
||||||
|
{ type: "added", value: "e" },
|
||||||
|
{ type: "added", value: "n" },
|
||||||
|
{ type: "added", value: "g" },
|
||||||
|
{ type: "added", value: "t" },
|
||||||
|
{ type: "added", value: "h" }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: '"abc", "c" vs "abc", "bcd", "c"',
|
||||||
|
fn() {
|
||||||
|
assertEqual(diff(["abc", "c"], ["abc", "bcd", "c"]), [
|
||||||
|
{ type: "common", value: "abc" },
|
||||||
|
{ type: "added", value: "bcd" },
|
||||||
|
{ type: "common", value: "c" }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
529
testing/format.ts
Normal file
529
testing/format.ts
Normal file
|
@ -0,0 +1,529 @@
|
||||||
|
// This file is ported from pretty-format@24.0.0
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Refs = any[];
|
||||||
|
export type Optional<T> = { [K in keyof T]?: T[K] };
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
callToJSON: boolean;
|
||||||
|
escapeRegex: boolean;
|
||||||
|
escapeString: boolean;
|
||||||
|
indent: number;
|
||||||
|
maxDepth: number;
|
||||||
|
min: boolean;
|
||||||
|
printFunctionName: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
callToJSON: boolean;
|
||||||
|
escapeRegex: boolean;
|
||||||
|
escapeString: boolean;
|
||||||
|
indent: string;
|
||||||
|
maxDepth: number;
|
||||||
|
min: boolean;
|
||||||
|
printFunctionName: boolean;
|
||||||
|
spacingInner: string;
|
||||||
|
spacingOuter: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Printer = (
|
||||||
|
val: any,
|
||||||
|
config: Config,
|
||||||
|
indentation: string,
|
||||||
|
depth: number,
|
||||||
|
refs: Refs,
|
||||||
|
hasCalledToJSON?: boolean
|
||||||
|
) => string;
|
||||||
|
|
||||||
|
const toString = Object.prototype.toString;
|
||||||
|
const toISOString = Date.prototype.toISOString;
|
||||||
|
const errorToString = Error.prototype.toString;
|
||||||
|
const regExpToString = RegExp.prototype.toString;
|
||||||
|
const symbolToString = Symbol.prototype.toString;
|
||||||
|
|
||||||
|
const DEFAULT_OPTIONS: Options = {
|
||||||
|
callToJSON: true,
|
||||||
|
escapeRegex: false,
|
||||||
|
escapeString: true,
|
||||||
|
indent: 2,
|
||||||
|
maxDepth: Infinity,
|
||||||
|
min: false,
|
||||||
|
printFunctionName: true
|
||||||
|
};
|
||||||
|
|
||||||
|
interface BasicValueOptions {
|
||||||
|
printFunctionName: boolean;
|
||||||
|
escapeRegex: boolean;
|
||||||
|
escapeString: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicitly comparing typeof constructor to function avoids undefined as name
|
||||||
|
* when mock identity-obj-proxy returns the key as the value for any key.
|
||||||
|
*/
|
||||||
|
const getConstructorName = (val: new (...args: any[]) => any) =>
|
||||||
|
(typeof val.constructor === "function" && val.constructor.name) || "Object";
|
||||||
|
|
||||||
|
/* global window */
|
||||||
|
/** Is val is equal to global window object? Works even if it does not exist :) */
|
||||||
|
const isWindow = (val: any) => typeof window !== "undefined" && val === window;
|
||||||
|
|
||||||
|
const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/;
|
||||||
|
|
||||||
|
function isToStringedArrayType(toStringed: string): boolean {
|
||||||
|
return (
|
||||||
|
toStringed === "[object Array]" ||
|
||||||
|
toStringed === "[object ArrayBuffer]" ||
|
||||||
|
toStringed === "[object DataView]" ||
|
||||||
|
toStringed === "[object Float32Array]" ||
|
||||||
|
toStringed === "[object Float64Array]" ||
|
||||||
|
toStringed === "[object Int8Array]" ||
|
||||||
|
toStringed === "[object Int16Array]" ||
|
||||||
|
toStringed === "[object Int32Array]" ||
|
||||||
|
toStringed === "[object Uint8Array]" ||
|
||||||
|
toStringed === "[object Uint8ClampedArray]" ||
|
||||||
|
toStringed === "[object Uint16Array]" ||
|
||||||
|
toStringed === "[object Uint32Array]"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function printNumber(val: number): string {
|
||||||
|
return Object.is(val, -0) ? "-0" : String(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
function printFunction(val: () => void, printFunctionName: boolean): string {
|
||||||
|
if (!printFunctionName) {
|
||||||
|
return "[Function]";
|
||||||
|
}
|
||||||
|
return "[Function " + (val.name || "anonymous") + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
function printSymbol(val: symbol): string {
|
||||||
|
return symbolToString.call(val).replace(SYMBOL_REGEXP, "Symbol($1)");
|
||||||
|
}
|
||||||
|
|
||||||
|
function printError(val: Error): string {
|
||||||
|
return "[" + errorToString.call(val) + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first port of call for printing an object, handles most of the
|
||||||
|
* data-types in JS.
|
||||||
|
*/
|
||||||
|
function printBasicValue(
|
||||||
|
val: any,
|
||||||
|
{ printFunctionName, escapeRegex, escapeString }: BasicValueOptions
|
||||||
|
): string | null {
|
||||||
|
if (val === true || val === false) {
|
||||||
|
return "" + val;
|
||||||
|
}
|
||||||
|
if (val === undefined) {
|
||||||
|
return "undefined";
|
||||||
|
}
|
||||||
|
if (val === null) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeOf = typeof val;
|
||||||
|
|
||||||
|
if (typeOf === "number") {
|
||||||
|
return printNumber(val);
|
||||||
|
}
|
||||||
|
if (typeOf === "string") {
|
||||||
|
if (escapeString) {
|
||||||
|
return '"' + val.replace(/"|\\/g, "\\$&") + '"';
|
||||||
|
}
|
||||||
|
return '"' + val + '"';
|
||||||
|
}
|
||||||
|
if (typeOf === "function") {
|
||||||
|
return printFunction(val, printFunctionName);
|
||||||
|
}
|
||||||
|
if (typeOf === "symbol") {
|
||||||
|
return printSymbol(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toStringed = toString.call(val);
|
||||||
|
|
||||||
|
if (toStringed === "[object WeakMap]") {
|
||||||
|
return "WeakMap {}";
|
||||||
|
}
|
||||||
|
if (toStringed === "[object WeakSet]") {
|
||||||
|
return "WeakSet {}";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
toStringed === "[object Function]" ||
|
||||||
|
toStringed === "[object GeneratorFunction]"
|
||||||
|
) {
|
||||||
|
return printFunction(val, printFunctionName);
|
||||||
|
}
|
||||||
|
if (toStringed === "[object Symbol]") {
|
||||||
|
return printSymbol(val);
|
||||||
|
}
|
||||||
|
if (toStringed === "[object Date]") {
|
||||||
|
return isNaN(+val) ? "Date { NaN }" : toISOString.call(val);
|
||||||
|
}
|
||||||
|
if (toStringed === "[object Error]") {
|
||||||
|
return printError(val);
|
||||||
|
}
|
||||||
|
if (toStringed === "[object RegExp]") {
|
||||||
|
if (escapeRegex) {
|
||||||
|
// https://github.com/benjamingr/RegExp.escape/blob/master/polyfill.js
|
||||||
|
return regExpToString.call(val).replace(/[\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||||
|
}
|
||||||
|
return regExpToString.call(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val instanceof Error) {
|
||||||
|
return printError(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles more complex objects ( such as objects with circular references.
|
||||||
|
* maps and sets etc )
|
||||||
|
*/
|
||||||
|
function printComplexValue(
|
||||||
|
val: any,
|
||||||
|
config: Config,
|
||||||
|
indentation: string,
|
||||||
|
depth: number,
|
||||||
|
refs: Refs,
|
||||||
|
hasCalledToJSON?: boolean
|
||||||
|
): string {
|
||||||
|
if (refs.indexOf(val) !== -1) {
|
||||||
|
return "[Circular]";
|
||||||
|
}
|
||||||
|
refs = refs.slice();
|
||||||
|
refs.push(val);
|
||||||
|
|
||||||
|
const hitMaxDepth = ++depth > config.maxDepth;
|
||||||
|
const { min, callToJSON } = config;
|
||||||
|
|
||||||
|
if (
|
||||||
|
callToJSON &&
|
||||||
|
!hitMaxDepth &&
|
||||||
|
val.toJSON &&
|
||||||
|
typeof val.toJSON === "function" &&
|
||||||
|
!hasCalledToJSON
|
||||||
|
) {
|
||||||
|
return printer(val.toJSON(), config, indentation, depth, refs, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toStringed = toString.call(val);
|
||||||
|
if (toStringed === "[object Arguments]") {
|
||||||
|
return hitMaxDepth
|
||||||
|
? "[Arguments]"
|
||||||
|
: (min ? "" : "Arguments ") +
|
||||||
|
"[" +
|
||||||
|
printListItems(val, config, indentation, depth, refs, printer) +
|
||||||
|
"]";
|
||||||
|
}
|
||||||
|
if (isToStringedArrayType(toStringed)) {
|
||||||
|
return hitMaxDepth
|
||||||
|
? "[" + val.constructor.name + "]"
|
||||||
|
: (min ? "" : val.constructor.name + " ") +
|
||||||
|
"[" +
|
||||||
|
printListItems(val, config, indentation, depth, refs, printer) +
|
||||||
|
"]";
|
||||||
|
}
|
||||||
|
if (toStringed === "[object Map]") {
|
||||||
|
return hitMaxDepth
|
||||||
|
? "[Map]"
|
||||||
|
: "Map {" +
|
||||||
|
printIteratorEntries(
|
||||||
|
val.entries(),
|
||||||
|
config,
|
||||||
|
indentation,
|
||||||
|
depth,
|
||||||
|
refs,
|
||||||
|
printer,
|
||||||
|
" => "
|
||||||
|
) +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
if (toStringed === "[object Set]") {
|
||||||
|
return hitMaxDepth
|
||||||
|
? "[Set]"
|
||||||
|
: "Set {" +
|
||||||
|
printIteratorValues(
|
||||||
|
val.values(),
|
||||||
|
config,
|
||||||
|
indentation,
|
||||||
|
depth,
|
||||||
|
refs,
|
||||||
|
printer
|
||||||
|
) +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid failure to serialize global window object in jsdom test environment.
|
||||||
|
// For example, not even relevant if window is prop of React element.
|
||||||
|
return hitMaxDepth || isWindow(val)
|
||||||
|
? "[" + getConstructorName(val) + "]"
|
||||||
|
: (min ? "" : getConstructorName(val) + " ") +
|
||||||
|
"{" +
|
||||||
|
printObjectProperties(val, config, indentation, depth, refs, printer) +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
function printer(
|
||||||
|
val: any,
|
||||||
|
config: Config,
|
||||||
|
indentation: string,
|
||||||
|
depth: number,
|
||||||
|
refs: Refs,
|
||||||
|
hasCalledToJSON?: boolean
|
||||||
|
): string {
|
||||||
|
const basicResult = printBasicValue(val, config);
|
||||||
|
if (basicResult !== null) {
|
||||||
|
return basicResult;
|
||||||
|
}
|
||||||
|
return printComplexValue(
|
||||||
|
val,
|
||||||
|
config,
|
||||||
|
indentation,
|
||||||
|
depth,
|
||||||
|
refs,
|
||||||
|
hasCalledToJSON
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getConfig = (options: Options): Config => ({
|
||||||
|
...options,
|
||||||
|
indent: options.min ? "" : createIndent(options.indent),
|
||||||
|
spacingInner: options.min ? " " : "\n",
|
||||||
|
spacingOuter: options.min ? "" : "\n"
|
||||||
|
});
|
||||||
|
|
||||||
|
function createIndent(indent: number): string {
|
||||||
|
return new Array(indent + 1).join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
const getKeysOfEnumerableProperties = (object: {}) => {
|
||||||
|
const keys: Array<string | symbol> = Object.keys(object).sort();
|
||||||
|
|
||||||
|
if (Object.getOwnPropertySymbols) {
|
||||||
|
Object.getOwnPropertySymbols(object).forEach(symbol => {
|
||||||
|
if (Object.getOwnPropertyDescriptor(object, symbol)!.enumerable) {
|
||||||
|
keys.push(symbol);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return entries (for example, of a map)
|
||||||
|
* with spacing, indentation, and comma
|
||||||
|
* without surrounding punctuation (for example, braces)
|
||||||
|
*/
|
||||||
|
function printIteratorEntries(
|
||||||
|
iterator: any,
|
||||||
|
config: Config,
|
||||||
|
indentation: string,
|
||||||
|
depth: number,
|
||||||
|
refs: Refs,
|
||||||
|
printer: Printer,
|
||||||
|
// Too bad, so sad that separator for ECMAScript Map has been ' => '
|
||||||
|
// What a distracting diff if you change a data structure to/from
|
||||||
|
// ECMAScript Object or Immutable.Map/OrderedMap which use the default.
|
||||||
|
separator: string = ": "
|
||||||
|
): string {
|
||||||
|
let result = "";
|
||||||
|
let current = iterator.next();
|
||||||
|
|
||||||
|
if (!current.done) {
|
||||||
|
result += config.spacingOuter;
|
||||||
|
|
||||||
|
const indentationNext = indentation + config.indent;
|
||||||
|
|
||||||
|
while (!current.done) {
|
||||||
|
const name = printer(
|
||||||
|
current.value[0],
|
||||||
|
config,
|
||||||
|
indentationNext,
|
||||||
|
depth,
|
||||||
|
refs
|
||||||
|
);
|
||||||
|
const value = printer(
|
||||||
|
current.value[1],
|
||||||
|
config,
|
||||||
|
indentationNext,
|
||||||
|
depth,
|
||||||
|
refs
|
||||||
|
);
|
||||||
|
|
||||||
|
result += indentationNext + name + separator + value;
|
||||||
|
|
||||||
|
current = iterator.next();
|
||||||
|
|
||||||
|
if (!current.done) {
|
||||||
|
result += "," + config.spacingInner;
|
||||||
|
} else if (!config.min) {
|
||||||
|
result += ",";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += config.spacingOuter + indentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return values (for example, of a set)
|
||||||
|
* with spacing, indentation, and comma
|
||||||
|
* without surrounding punctuation (braces or brackets)
|
||||||
|
*/
|
||||||
|
function printIteratorValues(
|
||||||
|
iterator: Iterator<any>,
|
||||||
|
config: Config,
|
||||||
|
indentation: string,
|
||||||
|
depth: number,
|
||||||
|
refs: Refs,
|
||||||
|
printer: Printer
|
||||||
|
): string {
|
||||||
|
let result = "";
|
||||||
|
let current = iterator.next();
|
||||||
|
|
||||||
|
if (!current.done) {
|
||||||
|
result += config.spacingOuter;
|
||||||
|
|
||||||
|
const indentationNext = indentation + config.indent;
|
||||||
|
|
||||||
|
while (!current.done) {
|
||||||
|
result +=
|
||||||
|
indentationNext +
|
||||||
|
printer(current.value, config, indentationNext, depth, refs);
|
||||||
|
|
||||||
|
current = iterator.next();
|
||||||
|
|
||||||
|
if (!current.done) {
|
||||||
|
result += "," + config.spacingInner;
|
||||||
|
} else if (!config.min) {
|
||||||
|
result += ",";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += config.spacingOuter + indentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return items (for example, of an array)
|
||||||
|
* with spacing, indentation, and comma
|
||||||
|
* without surrounding punctuation (for example, brackets)
|
||||||
|
*/
|
||||||
|
function printListItems(
|
||||||
|
list: any,
|
||||||
|
config: Config,
|
||||||
|
indentation: string,
|
||||||
|
depth: number,
|
||||||
|
refs: Refs,
|
||||||
|
printer: Printer
|
||||||
|
): string {
|
||||||
|
let result = "";
|
||||||
|
|
||||||
|
if (list.length) {
|
||||||
|
result += config.spacingOuter;
|
||||||
|
|
||||||
|
const indentationNext = indentation + config.indent;
|
||||||
|
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
result +=
|
||||||
|
indentationNext +
|
||||||
|
printer(list[i], config, indentationNext, depth, refs);
|
||||||
|
|
||||||
|
if (i < list.length - 1) {
|
||||||
|
result += "," + config.spacingInner;
|
||||||
|
} else if (!config.min) {
|
||||||
|
result += ",";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += config.spacingOuter + indentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return properties of an object
|
||||||
|
* with spacing, indentation, and comma
|
||||||
|
* without surrounding punctuation (for example, braces)
|
||||||
|
*/
|
||||||
|
function printObjectProperties(
|
||||||
|
val: {},
|
||||||
|
config: Config,
|
||||||
|
indentation: string,
|
||||||
|
depth: number,
|
||||||
|
refs: Refs,
|
||||||
|
printer: Printer
|
||||||
|
): string {
|
||||||
|
let result = "";
|
||||||
|
const keys = getKeysOfEnumerableProperties(val);
|
||||||
|
|
||||||
|
if (keys.length) {
|
||||||
|
result += config.spacingOuter;
|
||||||
|
|
||||||
|
const indentationNext = indentation + config.indent;
|
||||||
|
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const key = keys[i];
|
||||||
|
const name = printer(key, config, indentationNext, depth, refs);
|
||||||
|
const value = printer(
|
||||||
|
val[key as keyof typeof val],
|
||||||
|
config,
|
||||||
|
indentationNext,
|
||||||
|
depth,
|
||||||
|
refs
|
||||||
|
);
|
||||||
|
|
||||||
|
result += indentationNext + name + ": " + value;
|
||||||
|
|
||||||
|
if (i < keys.length - 1) {
|
||||||
|
result += "," + config.spacingInner;
|
||||||
|
} else if (!config.min) {
|
||||||
|
result += ",";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result += config.spacingOuter + indentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a presentation string of your `val` object
|
||||||
|
* @param val any potential JavaScript object
|
||||||
|
* @param options Custom settings
|
||||||
|
*/
|
||||||
|
export function format(val: any, options: Optional<Options> = {}): string {
|
||||||
|
const opts = Object.keys(DEFAULT_OPTIONS).reduce(
|
||||||
|
(acc: Options, k: keyof Options) => {
|
||||||
|
const opt = options[k];
|
||||||
|
if (typeof opt === "undefined") {
|
||||||
|
return { ...acc, [k]: DEFAULT_OPTIONS[k] };
|
||||||
|
}
|
||||||
|
return { ...acc, [k]: opt };
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
) as Options;
|
||||||
|
const basicResult = printBasicValue(val, opts);
|
||||||
|
if (basicResult !== null) {
|
||||||
|
return basicResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return printComplexValue(val, getConfig(opts), "", 0, []);
|
||||||
|
}
|
793
testing/format_test.ts
Normal file
793
testing/format_test.ts
Normal file
|
@ -0,0 +1,793 @@
|
||||||
|
// This file is ported from pretty-format@24.0.0
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { test, assertEqual } from "./mod.ts";
|
||||||
|
import { format } from "./format.ts";
|
||||||
|
|
||||||
|
function returnArguments(..._args: Array<unknown>) {
|
||||||
|
return arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
function MyObject(value: unknown) {
|
||||||
|
// @ts-ignore
|
||||||
|
this.name = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyArray<T> extends Array<T> {}
|
||||||
|
|
||||||
|
const createVal = () => [
|
||||||
|
{
|
||||||
|
id: "8658c1d0-9eda-4a90-95e1-8001e8eb6036",
|
||||||
|
text: "Add alternative serialize API for pretty-format plugins",
|
||||||
|
type: "ADD_TODO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "8658c1d0-9eda-4a90-95e1-8001e8eb6036",
|
||||||
|
type: "TOGGLE_TODO"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const createExpected = () =>
|
||||||
|
[
|
||||||
|
"Array [",
|
||||||
|
" Object {",
|
||||||
|
' "id": "8658c1d0-9eda-4a90-95e1-8001e8eb6036",',
|
||||||
|
' "text": "Add alternative serialize API for pretty-format plugins",',
|
||||||
|
' "type": "ADD_TODO",',
|
||||||
|
" },",
|
||||||
|
" Object {",
|
||||||
|
' "id": "8658c1d0-9eda-4a90-95e1-8001e8eb6036",',
|
||||||
|
' "type": "TOGGLE_TODO",',
|
||||||
|
" },",
|
||||||
|
"]"
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints empty arguments",
|
||||||
|
fn() {
|
||||||
|
const val = returnArguments();
|
||||||
|
assertEqual(format(val), "Arguments []");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints empty arguments",
|
||||||
|
fn() {
|
||||||
|
const val: any[] = [];
|
||||||
|
assertEqual(format(val), "Array []");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an array with items",
|
||||||
|
fn() {
|
||||||
|
const val = [1, 2, 3];
|
||||||
|
assertEqual(format(val), "Array [\n 1,\n 2,\n 3,\n]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a empty typed array",
|
||||||
|
fn() {
|
||||||
|
const val = new Uint32Array(0);
|
||||||
|
assertEqual(format(val), "Uint32Array []");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a typed array with items",
|
||||||
|
fn() {
|
||||||
|
const val = new Uint32Array(3);
|
||||||
|
assertEqual(format(val), "Uint32Array [\n 0,\n 0,\n 0,\n]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an array buffer",
|
||||||
|
fn() {
|
||||||
|
const val = new ArrayBuffer(3);
|
||||||
|
assertEqual(format(val), "ArrayBuffer []");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a nested array",
|
||||||
|
fn() {
|
||||||
|
const val = [[1, 2, 3]];
|
||||||
|
assertEqual(
|
||||||
|
format(val),
|
||||||
|
"Array [\n Array [\n 1,\n 2,\n 3,\n ],\n]"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints true",
|
||||||
|
fn() {
|
||||||
|
const val = true;
|
||||||
|
assertEqual(format(val), "true");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints false",
|
||||||
|
fn() {
|
||||||
|
const val = false;
|
||||||
|
assertEqual(format(val), "false");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an error",
|
||||||
|
fn() {
|
||||||
|
const val = new Error();
|
||||||
|
assertEqual(format(val), "[Error]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a typed error with a message",
|
||||||
|
fn() {
|
||||||
|
const val = new TypeError("message");
|
||||||
|
assertEqual(format(val), "[TypeError: message]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a function constructor",
|
||||||
|
fn() {
|
||||||
|
// tslint:disable-next-line:function-constructor
|
||||||
|
const val = new Function();
|
||||||
|
assertEqual(format(val), "[Function anonymous]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an anonymous callback function",
|
||||||
|
fn() {
|
||||||
|
let val;
|
||||||
|
function f(cb: () => void) {
|
||||||
|
val = cb;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:no-empty
|
||||||
|
f(() => {});
|
||||||
|
assertEqual(format(val), "[Function anonymous]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an anonymous assigned function",
|
||||||
|
fn() {
|
||||||
|
// tslint:disable-next-line:no-empty
|
||||||
|
const val = () => {};
|
||||||
|
const formatted = format(val);
|
||||||
|
assertEqual(
|
||||||
|
formatted === "[Function anonymous]" || formatted === "[Function val]",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a named function",
|
||||||
|
fn() {
|
||||||
|
// tslint:disable-next-line:no-empty
|
||||||
|
const val = function named() {};
|
||||||
|
assertEqual(format(val), "[Function named]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a named generator function",
|
||||||
|
fn() {
|
||||||
|
const val = function* generate() {
|
||||||
|
yield 1;
|
||||||
|
yield 2;
|
||||||
|
yield 3;
|
||||||
|
};
|
||||||
|
assertEqual(format(val), "[Function generate]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "can customize function names",
|
||||||
|
fn() {
|
||||||
|
// tslint:disable-next-line:no-empty
|
||||||
|
const val = function named() {};
|
||||||
|
assertEqual(
|
||||||
|
format(val, {
|
||||||
|
printFunctionName: false
|
||||||
|
}),
|
||||||
|
"[Function]"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints Infinity",
|
||||||
|
fn() {
|
||||||
|
const val = Infinity;
|
||||||
|
assertEqual(format(val), "Infinity");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints -Infinity",
|
||||||
|
fn() {
|
||||||
|
const val = -Infinity;
|
||||||
|
assertEqual(format(val), "-Infinity");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an empty map",
|
||||||
|
fn() {
|
||||||
|
const val = new Map();
|
||||||
|
assertEqual(format(val), "Map {}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a map with values",
|
||||||
|
fn() {
|
||||||
|
const val = new Map();
|
||||||
|
val.set("prop1", "value1");
|
||||||
|
val.set("prop2", "value2");
|
||||||
|
assertEqual(
|
||||||
|
format(val),
|
||||||
|
'Map {\n "prop1" => "value1",\n "prop2" => "value2",\n}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a map with non-string keys",
|
||||||
|
fn() {
|
||||||
|
const val = new Map<any, any>([
|
||||||
|
[false, "boolean"],
|
||||||
|
["false", "string"],
|
||||||
|
[0, "number"],
|
||||||
|
["0", "string"],
|
||||||
|
[null, "null"],
|
||||||
|
["null", "string"],
|
||||||
|
[undefined, "undefined"],
|
||||||
|
["undefined", "string"],
|
||||||
|
[Symbol("description"), "symbol"],
|
||||||
|
["Symbol(description)", "string"],
|
||||||
|
[["array", "key"], "array"],
|
||||||
|
[{ key: "value" }, "object"]
|
||||||
|
]);
|
||||||
|
const expected = [
|
||||||
|
"Map {",
|
||||||
|
' false => "boolean",',
|
||||||
|
' "false" => "string",',
|
||||||
|
' 0 => "number",',
|
||||||
|
' "0" => "string",',
|
||||||
|
' null => "null",',
|
||||||
|
' "null" => "string",',
|
||||||
|
' undefined => "undefined",',
|
||||||
|
' "undefined" => "string",',
|
||||||
|
' Symbol(description) => "symbol",',
|
||||||
|
' "Symbol(description)" => "string",',
|
||||||
|
" Array [",
|
||||||
|
' "array",',
|
||||||
|
' "key",',
|
||||||
|
' ] => "array",',
|
||||||
|
" Object {",
|
||||||
|
' "key": "value",',
|
||||||
|
' } => "object",',
|
||||||
|
"}"
|
||||||
|
].join("\n");
|
||||||
|
assertEqual(format(val), expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints NaN",
|
||||||
|
fn() {
|
||||||
|
const val = NaN;
|
||||||
|
assertEqual(format(val), "NaN");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints null",
|
||||||
|
fn() {
|
||||||
|
const val = null;
|
||||||
|
assertEqual(format(val), "null");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a positive number",
|
||||||
|
fn() {
|
||||||
|
const val = 123;
|
||||||
|
assertEqual(format(val), "123");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a negative number",
|
||||||
|
fn() {
|
||||||
|
const val = -123;
|
||||||
|
assertEqual(format(val), "-123");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints zero",
|
||||||
|
fn() {
|
||||||
|
const val = 0;
|
||||||
|
assertEqual(format(val), "0");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints negative zero",
|
||||||
|
fn() {
|
||||||
|
const val = -0;
|
||||||
|
assertEqual(format(val), "-0");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a date",
|
||||||
|
fn() {
|
||||||
|
const val = new Date(10e11);
|
||||||
|
assertEqual(format(val), "2001-09-09T01:46:40.000Z");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an invalid date",
|
||||||
|
fn() {
|
||||||
|
const val = new Date(Infinity);
|
||||||
|
assertEqual(format(val), "Date { NaN }");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an empty object",
|
||||||
|
fn() {
|
||||||
|
const val = {};
|
||||||
|
assertEqual(format(val), "Object {}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an object with properties",
|
||||||
|
fn() {
|
||||||
|
const val = { prop1: "value1", prop2: "value2" };
|
||||||
|
assertEqual(
|
||||||
|
format(val),
|
||||||
|
'Object {\n "prop1": "value1",\n "prop2": "value2",\n}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an object with properties and symbols",
|
||||||
|
fn() {
|
||||||
|
const val: any = {};
|
||||||
|
val[Symbol("symbol1")] = "value2";
|
||||||
|
val[Symbol("symbol2")] = "value3";
|
||||||
|
val.prop = "value1";
|
||||||
|
assertEqual(
|
||||||
|
format(val),
|
||||||
|
'Object {\n "prop": "value1",\n Symbol(symbol1): "value2",\n Symbol(symbol2): "value3",\n}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name:
|
||||||
|
"prints an object without non-enumerable properties which have string key",
|
||||||
|
fn() {
|
||||||
|
const val: any = {
|
||||||
|
enumerable: true
|
||||||
|
};
|
||||||
|
const key = "non-enumerable";
|
||||||
|
Object.defineProperty(val, key, {
|
||||||
|
enumerable: false,
|
||||||
|
value: false
|
||||||
|
});
|
||||||
|
assertEqual(format(val), 'Object {\n "enumerable": true,\n}');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name:
|
||||||
|
"prints an object without non-enumerable properties which have symbol key",
|
||||||
|
fn() {
|
||||||
|
const val: any = {
|
||||||
|
enumerable: true
|
||||||
|
};
|
||||||
|
const key = Symbol("non-enumerable");
|
||||||
|
Object.defineProperty(val, key, {
|
||||||
|
enumerable: false,
|
||||||
|
value: false
|
||||||
|
});
|
||||||
|
assertEqual(format(val), 'Object {\n "enumerable": true,\n}');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an object with sorted properties",
|
||||||
|
fn() {
|
||||||
|
const val = { b: 1, a: 2 };
|
||||||
|
assertEqual(format(val), 'Object {\n "a": 2,\n "b": 1,\n}');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints regular expressions from constructors",
|
||||||
|
fn() {
|
||||||
|
const val = new RegExp("regexp");
|
||||||
|
assertEqual(format(val), "/regexp/");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints regular expressions from literals",
|
||||||
|
fn() {
|
||||||
|
const val = /regexp/gi;
|
||||||
|
assertEqual(format(val), "/regexp/gi");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints regular expressions {escapeRegex: false}",
|
||||||
|
fn() {
|
||||||
|
const val = /regexp\d/gi;
|
||||||
|
assertEqual(format(val), "/regexp\\d/gi");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints regular expressions {escapeRegex: true}",
|
||||||
|
fn() {
|
||||||
|
const val = /regexp\d/gi;
|
||||||
|
assertEqual(format(val, { escapeRegex: true }), "/regexp\\\\d/gi");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "escapes regular expressions nested inside object",
|
||||||
|
fn() {
|
||||||
|
const obj = { test: /regexp\d/gi };
|
||||||
|
assertEqual(
|
||||||
|
format(obj, { escapeRegex: true }),
|
||||||
|
'Object {\n "test": /regexp\\\\d/gi,\n}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints an empty set",
|
||||||
|
fn() {
|
||||||
|
const val = new Set();
|
||||||
|
assertEqual(format(val), "Set {}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a set with values",
|
||||||
|
fn() {
|
||||||
|
const val = new Set();
|
||||||
|
val.add("value1");
|
||||||
|
val.add("value2");
|
||||||
|
assertEqual(format(val), 'Set {\n "value1",\n "value2",\n}');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a string",
|
||||||
|
fn() {
|
||||||
|
const val = "string";
|
||||||
|
assertEqual(format(val), '"string"');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints and escape a string",
|
||||||
|
fn() {
|
||||||
|
const val = "\"'\\";
|
||||||
|
assertEqual(format(val), '"\\"\'\\\\"');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "doesn't escape string with {excapeString: false}",
|
||||||
|
fn() {
|
||||||
|
const val = "\"'\\n";
|
||||||
|
assertEqual(format(val, { escapeString: false }), '""\'\\n"');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a string with escapes",
|
||||||
|
fn() {
|
||||||
|
assertEqual(format('"-"'), '"\\"-\\""');
|
||||||
|
assertEqual(format("\\ \\\\"), '"\\\\ \\\\\\\\"');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a multiline string",
|
||||||
|
fn() {
|
||||||
|
const val = ["line 1", "line 2", "line 3"].join("\n");
|
||||||
|
assertEqual(format(val), '"' + val + '"');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a multiline string as value of object property",
|
||||||
|
fn() {
|
||||||
|
const polyline = {
|
||||||
|
props: {
|
||||||
|
id: "J",
|
||||||
|
points: ["0.5,0.460", "0.5,0.875", "0.25,0.875"].join("\n")
|
||||||
|
},
|
||||||
|
type: "polyline"
|
||||||
|
};
|
||||||
|
const val = {
|
||||||
|
props: {
|
||||||
|
children: polyline
|
||||||
|
},
|
||||||
|
type: "svg"
|
||||||
|
};
|
||||||
|
assertEqual(
|
||||||
|
format(val),
|
||||||
|
[
|
||||||
|
"Object {",
|
||||||
|
' "props": Object {',
|
||||||
|
' "children": Object {',
|
||||||
|
' "props": Object {',
|
||||||
|
' "id": "J",',
|
||||||
|
' "points": "0.5,0.460',
|
||||||
|
"0.5,0.875",
|
||||||
|
'0.25,0.875",',
|
||||||
|
" },",
|
||||||
|
' "type": "polyline",',
|
||||||
|
" },",
|
||||||
|
" },",
|
||||||
|
' "type": "svg",',
|
||||||
|
"}"
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a symbol",
|
||||||
|
fn() {
|
||||||
|
const val = Symbol("symbol");
|
||||||
|
assertEqual(format(val), "Symbol(symbol)");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints undefined",
|
||||||
|
fn() {
|
||||||
|
const val = undefined;
|
||||||
|
assertEqual(format(val), "undefined");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a WeakMap",
|
||||||
|
fn() {
|
||||||
|
const val = new WeakMap();
|
||||||
|
assertEqual(format(val), "WeakMap {}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints a WeakSet",
|
||||||
|
fn() {
|
||||||
|
const val = new WeakSet();
|
||||||
|
assertEqual(format(val), "WeakSet {}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints deeply nested objects",
|
||||||
|
fn() {
|
||||||
|
const val = { prop: { prop: { prop: "value" } } };
|
||||||
|
assertEqual(
|
||||||
|
format(val),
|
||||||
|
'Object {\n "prop": Object {\n "prop": Object {\n "prop": "value",\n },\n },\n}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints circular references",
|
||||||
|
fn() {
|
||||||
|
const val: any = {};
|
||||||
|
val.prop = val;
|
||||||
|
assertEqual(format(val), 'Object {\n "prop": [Circular],\n}');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints parallel references",
|
||||||
|
fn() {
|
||||||
|
const inner = {};
|
||||||
|
const val = { prop1: inner, prop2: inner };
|
||||||
|
assertEqual(
|
||||||
|
format(val),
|
||||||
|
'Object {\n "prop1": Object {},\n "prop2": Object {},\n}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "default implicit: 2 spaces",
|
||||||
|
fn() {
|
||||||
|
assertEqual(format(createVal()), createExpected());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "default explicit: 2 spaces",
|
||||||
|
fn() {
|
||||||
|
assertEqual(format(createVal(), { indent: 2 }), createExpected());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tests assume that no strings in val contain multiple adjacent spaces!
|
||||||
|
test({
|
||||||
|
name: "non-default: 0 spaces",
|
||||||
|
fn() {
|
||||||
|
const indent = 0;
|
||||||
|
assertEqual(
|
||||||
|
format(createVal(), { indent }),
|
||||||
|
createExpected().replace(/ {2}/g, " ".repeat(indent))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "non-default: 4 spaces",
|
||||||
|
fn() {
|
||||||
|
const indent = 4;
|
||||||
|
assertEqual(
|
||||||
|
format(createVal(), { indent }),
|
||||||
|
createExpected().replace(/ {2}/g, " ".repeat(indent))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "can customize the max depth",
|
||||||
|
fn() {
|
||||||
|
const v = [
|
||||||
|
{
|
||||||
|
"arguments empty": returnArguments(),
|
||||||
|
"arguments non-empty": returnArguments("arg"),
|
||||||
|
"array literal empty": [],
|
||||||
|
"array literal non-empty": ["item"],
|
||||||
|
"extended array empty": new MyArray(),
|
||||||
|
"map empty": new Map(),
|
||||||
|
"map non-empty": new Map([["name", "value"]]),
|
||||||
|
"object literal empty": {},
|
||||||
|
"object literal non-empty": { name: "value" },
|
||||||
|
// @ts-ignore
|
||||||
|
"object with constructor": new MyObject("value"),
|
||||||
|
"object without constructor": Object.create(null),
|
||||||
|
"set empty": new Set(),
|
||||||
|
"set non-empty": new Set(["value"])
|
||||||
|
}
|
||||||
|
];
|
||||||
|
assertEqual(
|
||||||
|
format(v, { maxDepth: 2 }),
|
||||||
|
[
|
||||||
|
"Array [",
|
||||||
|
" Object {",
|
||||||
|
' "arguments empty": [Arguments],',
|
||||||
|
' "arguments non-empty": [Arguments],',
|
||||||
|
' "array literal empty": [Array],',
|
||||||
|
' "array literal non-empty": [Array],',
|
||||||
|
' "extended array empty": [MyArray],',
|
||||||
|
' "map empty": [Map],',
|
||||||
|
' "map non-empty": [Map],',
|
||||||
|
' "object literal empty": [Object],',
|
||||||
|
' "object literal non-empty": [Object],',
|
||||||
|
' "object with constructor": [MyObject],',
|
||||||
|
' "object without constructor": [Object],',
|
||||||
|
' "set empty": [Set],',
|
||||||
|
' "set non-empty": [Set],',
|
||||||
|
" },",
|
||||||
|
"]"
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints objects with no constructor",
|
||||||
|
fn() {
|
||||||
|
assertEqual(format(Object.create(null)), "Object {}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "prints identity-obj-proxy with string constructor",
|
||||||
|
fn() {
|
||||||
|
const obj = Object.create(null);
|
||||||
|
obj.constructor = "constructor";
|
||||||
|
const expected = [
|
||||||
|
"Object {", // Object instead of undefined
|
||||||
|
' "constructor": "constructor",',
|
||||||
|
"}"
|
||||||
|
].join("\n");
|
||||||
|
assertEqual(format(obj), expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "calls toJSON and prints its return value",
|
||||||
|
fn() {
|
||||||
|
assertEqual(
|
||||||
|
format({
|
||||||
|
toJSON: () => ({ value: false }),
|
||||||
|
value: true
|
||||||
|
}),
|
||||||
|
'Object {\n "value": false,\n}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "calls toJSON and prints an internal representation.",
|
||||||
|
fn() {
|
||||||
|
assertEqual(
|
||||||
|
format({
|
||||||
|
toJSON: () => "[Internal Object]",
|
||||||
|
value: true
|
||||||
|
}),
|
||||||
|
'"[Internal Object]"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "calls toJSON only on functions",
|
||||||
|
fn() {
|
||||||
|
assertEqual(
|
||||||
|
format({
|
||||||
|
toJSON: false,
|
||||||
|
value: true
|
||||||
|
}),
|
||||||
|
'Object {\n "toJSON": false,\n "value": true,\n}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "does not call toJSON recursively",
|
||||||
|
fn() {
|
||||||
|
assertEqual(
|
||||||
|
format({
|
||||||
|
toJSON: () => ({ toJSON: () => ({ value: true }) }),
|
||||||
|
value: false
|
||||||
|
}),
|
||||||
|
'Object {\n "toJSON": [Function toJSON],\n}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "calls toJSON on Sets",
|
||||||
|
fn() {
|
||||||
|
const set = new Set([1]);
|
||||||
|
(set as any).toJSON = () => "map";
|
||||||
|
assertEqual(format(set), '"map"');
|
||||||
|
}
|
||||||
|
});
|
75
testing/pretty.ts
Normal file
75
testing/pretty.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
import { equal } from "./mod.ts";
|
||||||
|
import { red, green, white, gray, bold } from "../colors/mod.ts";
|
||||||
|
import diff, { DiffType, DiffResult } from "./diff.ts";
|
||||||
|
import { format } from "./format.ts";
|
||||||
|
|
||||||
|
const CAN_NOT_DISPLAY = "[Cannot display]";
|
||||||
|
|
||||||
|
function createStr(v: unknown): string {
|
||||||
|
try {
|
||||||
|
return format(v);
|
||||||
|
} catch (e) {
|
||||||
|
return red(CAN_NOT_DISPLAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createColor(diffType: DiffType) {
|
||||||
|
switch (diffType) {
|
||||||
|
case "added":
|
||||||
|
return (s: string) => green(bold(s));
|
||||||
|
case "removed":
|
||||||
|
return (s: string) => red(bold(s));
|
||||||
|
default:
|
||||||
|
return white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSign(diffType: DiffType) {
|
||||||
|
switch (diffType) {
|
||||||
|
case "added":
|
||||||
|
return "+ ";
|
||||||
|
case "removed":
|
||||||
|
return "- ";
|
||||||
|
default:
|
||||||
|
return " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMessage(diffResult: ReadonlyArray<DiffResult<string>>) {
|
||||||
|
const messages = [];
|
||||||
|
messages.push("");
|
||||||
|
messages.push("");
|
||||||
|
messages.push(
|
||||||
|
` ${gray(bold("[Diff]"))} ${red(bold("Left"))} / ${green(bold("Right"))}`
|
||||||
|
);
|
||||||
|
messages.push("");
|
||||||
|
messages.push("");
|
||||||
|
diffResult.forEach((result: DiffResult<string>) => {
|
||||||
|
const c = createColor(result.type);
|
||||||
|
messages.push(c(`${createSign(result.type)}${result.value}`));
|
||||||
|
});
|
||||||
|
messages.push("");
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertEqual(actual: unknown, expected: unknown) {
|
||||||
|
if (equal(actual, expected)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let message = "";
|
||||||
|
const actualString = createStr(actual);
|
||||||
|
const expectedString = createStr(expected);
|
||||||
|
try {
|
||||||
|
const diffResult = diff(
|
||||||
|
actualString.split("\n"),
|
||||||
|
expectedString.split("\n")
|
||||||
|
);
|
||||||
|
message = buildMessage(diffResult).join("\n");
|
||||||
|
} catch (e) {
|
||||||
|
message = `\n${red(CAN_NOT_DISPLAY)} + \n\n`;
|
||||||
|
}
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
92
testing/pretty_test.ts
Normal file
92
testing/pretty_test.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
import { test, assert } from "./mod.ts";
|
||||||
|
import { red, green, white, gray, bold } from "../colors/mod.ts";
|
||||||
|
import { assertEqual } from "./pretty.ts";
|
||||||
|
|
||||||
|
const createHeader = () => [
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
` ${gray(bold("[Diff]"))} ${red(bold("Left"))} / ${green(bold("Right"))}`,
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
];
|
||||||
|
|
||||||
|
const added = (s: string) => green(bold(s));
|
||||||
|
const removed = (s: string) => red(bold(s));
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "pass case",
|
||||||
|
fn() {
|
||||||
|
assertEqual({ a: 10 }, { a: 10 });
|
||||||
|
assertEqual(true, true);
|
||||||
|
assertEqual(10, 10);
|
||||||
|
assertEqual("abc", "abc");
|
||||||
|
assertEqual({ a: 10, b: { c: "1" } }, { a: 10, b: { c: "1" } });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "failed with number",
|
||||||
|
fn() {
|
||||||
|
assert.throws(
|
||||||
|
() => assertEqual(1, 2),
|
||||||
|
Error,
|
||||||
|
[...createHeader(), removed(`- 1`), added(`+ 2`), ""].join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "failed with number vs string",
|
||||||
|
fn() {
|
||||||
|
assert.throws(
|
||||||
|
() => assertEqual(1, "1"),
|
||||||
|
Error,
|
||||||
|
[...createHeader(), removed(`- 1`), added(`+ "1"`)].join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "failed with array",
|
||||||
|
fn() {
|
||||||
|
assert.throws(
|
||||||
|
() => assertEqual([1, "2", 3], ["1", "2", 3]),
|
||||||
|
Error,
|
||||||
|
[
|
||||||
|
...createHeader(),
|
||||||
|
white(" Array ["),
|
||||||
|
removed(`- 1,`),
|
||||||
|
added(`+ "1",`),
|
||||||
|
white(' "2",'),
|
||||||
|
white(" 3,"),
|
||||||
|
white(" ]"),
|
||||||
|
""
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "failed with object",
|
||||||
|
fn() {
|
||||||
|
assert.throws(
|
||||||
|
() => assertEqual({ a: 1, b: "2", c: 3 }, { a: 1, b: 2, c: [3] }),
|
||||||
|
Error,
|
||||||
|
[
|
||||||
|
...createHeader(),
|
||||||
|
white(" Object {"),
|
||||||
|
white(` "a": 1,`),
|
||||||
|
added(`+ "b": 2,`),
|
||||||
|
added(`+ "c": Array [`),
|
||||||
|
added(`+ 3,`),
|
||||||
|
added(`+ ],`),
|
||||||
|
removed(`- "b": "2",`),
|
||||||
|
removed(`- "c": 3,`),
|
||||||
|
white(" }"),
|
||||||
|
""
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,6 +1,9 @@
|
||||||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
import { test, assert, assertEqual, equal } from "./mod.ts";
|
import { test, assert, assertEqual, equal } from "./mod.ts";
|
||||||
|
import "./format_test.ts";
|
||||||
|
import "./diff_test.ts";
|
||||||
|
import "./pretty_test.ts";
|
||||||
|
|
||||||
test(function testingEqual() {
|
test(function testingEqual() {
|
||||||
assert(equal("world", "world"));
|
assert(equal("world", "world"));
|
||||||
|
|
Loading…
Reference in a new issue