diff options
Diffstat (limited to 'tests/unit/console_test.ts')
-rw-r--r-- | tests/unit/console_test.ts | 2411 |
1 files changed, 2411 insertions, 0 deletions
diff --git a/tests/unit/console_test.ts b/tests/unit/console_test.ts new file mode 100644 index 000000000..2f24b2c4e --- /dev/null +++ b/tests/unit/console_test.ts @@ -0,0 +1,2411 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +// TODO(ry) The unit test functions in this module are too coarse. They should +// be broken up into smaller bits. + +// TODO(ry) These tests currently strip all the ANSI colors out. We don't have a +// good way to control whether we produce color output or not since +// std/fmt/colors auto determines whether to put colors in or not. We need +// better infrastructure here so we can properly test the colors. + +import { + assert, + assertEquals, + assertStringIncludes, + assertThrows, +} from "./test_util.ts"; +import { stripColor } from "@test_util/std/fmt/colors.ts"; + +const customInspect = Symbol.for("Deno.customInspect"); +const { + Console, + cssToAnsi: cssToAnsi_, + inspectArgs, + parseCss: parseCss_, + parseCssColor: parseCssColor_, + // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol +} = Deno[Deno.internal]; + +function stringify(...args: unknown[]): string { + return stripColor(inspectArgs(args).replace(/\n$/, "")); +} + +interface Css { + backgroundColor: [number, number, number] | string | null; + color: [number, number, number] | string | null; + fontWeight: string | null; + fontStyle: string | null; + textDecorationColor: [number, number, number] | null; + textDecorationLine: string[]; +} + +const DEFAULT_CSS: Css = { + backgroundColor: null, + color: null, + fontWeight: null, + fontStyle: null, + textDecorationColor: null, + textDecorationLine: [], +}; + +function parseCss(cssString: string): Css { + return parseCss_(cssString); +} + +function parseCssColor(colorString: string): [number, number, number] | null { + return parseCssColor_(colorString); +} + +/** ANSI-fy the CSS, replace "\x1b" with "_". */ +function cssToAnsiEsc(css: Css, prevCss: Css | null = null): string { + return cssToAnsi_(css, prevCss).replaceAll("\x1b", "_"); +} + +// test cases from web-platform-tests +// via https://github.com/web-platform-tests/wpt/blob/master/console/console-is-a-namespace.any.js +Deno.test(function consoleShouldBeANamespace() { + const prototype1 = Object.getPrototypeOf(console); + const prototype2 = Object.getPrototypeOf(prototype1); + + assertEquals(Object.getOwnPropertyNames(prototype1).length, 0); + assertEquals(prototype2, Object.prototype); +}); + +Deno.test(function consoleHasRightInstance() { + assert(console instanceof Console); + assertEquals({} instanceof Console, false); +}); + +Deno.test(function consoleTestAssertShouldNotThrowError() { + mockConsole((console) => { + console.assert(true); + let hasThrown = undefined; + try { + console.assert(false); + hasThrown = false; + } catch { + hasThrown = true; + } + assertEquals(hasThrown, false); + }); +}); + +Deno.test(function consoleTestStringifyComplexObjects() { + assertEquals(stringify("foo"), "foo"); + assertEquals(stringify(["foo", "bar"]), `[ "foo", "bar" ]`); + assertEquals(stringify({ foo: "bar" }), `{ foo: "bar" }`); +}); + +Deno.test( + function consoleTestStringifyComplexObjectsWithEscapedSequences() { + assertEquals( + stringify( + ["foo\b", "foo\f", "foo\n", "foo\r", "foo\t", "foo\v", "foo\0"], + ), + `[ + "foo\\b", "foo\\f", + "foo\\n", "foo\\r", + "foo\\t", "foo\\v", + "foo\\x00" +]`, + ); + assertEquals( + stringify( + [ + Symbol(), + Symbol(""), + Symbol("foo\b"), + Symbol("foo\f"), + Symbol("foo\n"), + Symbol("foo\r"), + Symbol("foo\t"), + Symbol("foo\v"), + Symbol("foo\0"), + ], + ), + `[ + Symbol(), + Symbol(""), + Symbol("foo\\b"), + Symbol("foo\\f"), + Symbol("foo\\n"), + Symbol("foo\\r"), + Symbol("foo\\t"), + Symbol("foo\\v"), + Symbol("foo\\x00") +]`, + ); + assertEquals( + stringify( + { "foo\b": "bar\n", "bar\r": "baz\t", "qux\0": "qux\0" }, + ), + `{ "foo\\b": "bar\\n", "bar\\r": "baz\\t", "qux\\x00": "qux\\x00" }`, + ); + assertEquals( + stringify( + { + [Symbol("foo\b")]: `Symbol("foo\n")`, + [Symbol("bar\n")]: `Symbol("bar\n")`, + [Symbol("bar\r")]: `Symbol("bar\r")`, + [Symbol("baz\t")]: `Symbol("baz\t")`, + [Symbol("qux\0")]: `Symbol("qux\0")`, + }, + ), + `{ + [Symbol("foo\\b")]: 'Symbol("foo\\n")', + [Symbol("bar\\n")]: 'Symbol("bar\\n")', + [Symbol("bar\\r")]: 'Symbol("bar\\r")', + [Symbol("baz\\t")]: 'Symbol("baz\\t")', + [Symbol("qux\\x00")]: 'Symbol("qux\\x00")' +}`, + ); + assertEquals( + stringify(new Set(["foo\n", "foo\r", "foo\0"])), + `Set(3) { "foo\\n", "foo\\r", "foo\\x00" }`, + ); + }, +); + +Deno.test(function consoleTestStringifyQuotes() { + assertEquals(stringify(["\\"]), `[ "\\\\" ]`); + assertEquals(stringify(['\\,"']), `[ '\\\\,"' ]`); + assertEquals(stringify([`\\,",'`]), `[ \`\\\\,",'\` ]`); + assertEquals(stringify(["\\,\",',`"]), `[ "\\\\,\\",',\`" ]`); +}); + +Deno.test(function consoleTestStringifyLongStrings() { + const veryLongString = "a".repeat(200); + // If we stringify an object containing the long string, it gets abbreviated. + let actual = stringify({ veryLongString }); + assert(actual.includes("...")); + assert(actual.length < 200); + // However if we stringify the string itself, we get it exactly. + actual = stringify(veryLongString); + assertEquals(actual, veryLongString); +}); + +Deno.test(function consoleTestStringifyCircular() { + class Base { + a = 1; + m1() {} + } + + class Extended extends Base { + b = 2; + m2() {} + } + + // deno-lint-ignore no-explicit-any + const nestedObj: any = { + num: 1, + bool: true, + str: "a", + method() {}, + async asyncMethod() {}, + *generatorMethod() {}, + un: undefined, + nu: null, + arrowFunc: () => {}, + extendedClass: new Extended(), + nFunc: new Function(), + extendedCstr: Extended, + }; + + const circularObj = { + num: 2, + bool: false, + str: "b", + method() {}, + un: undefined, + nu: null, + nested: nestedObj, + emptyObj: {}, + arr: [1, "s", false, null, nestedObj], + baseClass: new Base(), + }; + + nestedObj.o = circularObj; + const nestedObjExpected = `<ref *1> { + num: 1, + bool: true, + str: "a", + method: [Function: method], + asyncMethod: [AsyncFunction: asyncMethod], + generatorMethod: [GeneratorFunction: generatorMethod], + un: undefined, + nu: null, + arrowFunc: [Function: arrowFunc], + extendedClass: Extended { a: 1, b: 2 }, + nFunc: [Function: anonymous], + extendedCstr: [class Extended extends Base], + o: { + num: 2, + bool: false, + str: "b", + method: [Function: method], + un: undefined, + nu: null, + nested: [Circular *1], + emptyObj: {}, + arr: [ 1, "s", false, null, [Circular *1] ], + baseClass: Base { a: 1 } + } +}`; + + assertEquals(stringify(1), "1"); + assertEquals(stringify(-0), "-0"); + assertEquals(stringify(1n), "1n"); + assertEquals(stringify("s"), "s"); + assertEquals(stringify(false), "false"); + assertEquals(stringify(new Number(1)), "[Number: 1]"); + assertEquals(stringify(new Number(-0)), "[Number: -0]"); + assertEquals(stringify(Object(1n)), "[BigInt: 1n]"); + assertEquals(stringify(new Boolean(true)), "[Boolean: true]"); + assertEquals(stringify(new String("deno")), `[String: "deno"]`); + assertEquals(stringify(/[0-9]*/), "/[0-9]*/"); + assertEquals( + stringify(new Date("2018-12-10T02:26:59.002Z")), + "2018-12-10T02:26:59.002Z", + ); + assertEquals(stringify(new Set([1, 2, 3])), "Set(3) { 1, 2, 3 }"); + assertEquals( + stringify(new Set([1, 2, 3]).values()), + "[Set Iterator] { 1, 2, 3 }", + ); + assertEquals( + stringify(new Set([1, 2, 3]).entries()), + "[Set Entries] { [ 1, 1 ], [ 2, 2 ], [ 3, 3 ] }", + ); + assertEquals( + stringify( + new Map([ + [1, "one"], + [2, "two"], + ]), + ), + `Map(2) { 1 => "one", 2 => "two" }`, + ); + assertEquals( + stringify(new Map([[1, "one"], [2, "two"]]).values()), + `[Map Iterator] { "one", "two" }`, + ); + assertEquals( + stringify(new Map([[1, "one"], [2, "two"]]).entries()), + `[Map Entries] { [ 1, "one" ], [ 2, "two" ] }`, + ); + assertEquals(stringify(new WeakSet()), "WeakSet { <items unknown> }"); + assertEquals(stringify(new WeakMap()), "WeakMap { <items unknown> }"); + assertEquals(stringify(Symbol(1)), `Symbol("1")`); + assertEquals(stringify(Object(Symbol(1))), `[Symbol: Symbol("1")]`); + assertEquals(stringify(null), "null"); + assertEquals(stringify(undefined), "undefined"); + assertEquals(stringify(new Extended()), "Extended { a: 1, b: 2 }"); + assertEquals( + stringify(function f() {}), + "[Function: f]", + ); + assertEquals( + stringify(async function af() {}), + "[AsyncFunction: af]", + ); + assertEquals( + stringify(function* gf() {}), + "[GeneratorFunction: gf]", + ); + assertEquals( + stringify(async function* agf() {}), + "[AsyncGeneratorFunction: agf]", + ); + assertEquals( + stringify(new Uint8Array([1, 2, 3])), + "Uint8Array(3) [ 1, 2, 3 ]", + ); + assertEquals(stringify(Uint8Array.prototype), "TypedArray {}"); + assertEquals( + stringify({ a: { b: { c: { d: new Set([1]) } } } }), + `{ + a: { + b: { c: { d: Set(1) { 1 } } } + } +}`, + ); + assertEquals(stringify(nestedObj), nestedObjExpected); + assertEquals( + stringify(JSON), + "Object [JSON] {}", + ); + assertEquals( + stringify(new Console(() => {})), + `Object [console] { + log: [Function: log], + debug: [Function: debug], + info: [Function: info], + dir: [Function: dir], + dirxml: [Function: dir], + warn: [Function: warn], + error: [Function: error], + assert: [Function: assert], + count: [Function: count], + countReset: [Function: countReset], + table: [Function: table], + time: [Function: time], + timeLog: [Function: timeLog], + timeEnd: [Function: timeEnd], + group: [Function: group], + groupCollapsed: [Function: group], + groupEnd: [Function: groupEnd], + clear: [Function: clear], + trace: [Function: trace], + profile: [Function: profile], + profileEnd: [Function: profileEnd], + timeStamp: [Function: timeStamp], + indentLevel: 0, + [Symbol(isConsoleInstance)]: true +}`, + ); + assertEquals( + stringify({ str: 1, [Symbol.for("sym")]: 2, [Symbol.toStringTag]: "TAG" }), + `Object [TAG] { + str: 1, + [Symbol(sym)]: 2, + [Symbol(Symbol.toStringTag)]: "TAG" +}`, + ); + // test inspect is working the same + assertEquals(stripColor(Deno.inspect(nestedObj)), nestedObjExpected); +}); + +Deno.test(function consoleTestStringifyMultipleCircular() { + const y = { a: { b: {} }, foo: { bar: {} } }; + y.a.b = y.a; + y.foo.bar = y.foo; + assertEquals( + stringify(y), + "{\n" + + " a: <ref *1> { b: [Circular *1] },\n" + + " foo: <ref *2> { bar: [Circular *2] }\n" + + "}", + ); +}); + +Deno.test(function consoleTestStringifyFunctionWithPrototypeRemoved() { + const f = function f() {}; + Reflect.setPrototypeOf(f, null); + assertEquals(stringify(f), "[Function (null prototype): f]"); + const af = async function af() {}; + Reflect.setPrototypeOf(af, null); + assertEquals(stringify(af), "[AsyncFunction (null prototype): af]"); + const gf = function* gf() {}; + Reflect.setPrototypeOf(gf, null); + assertEquals(stringify(gf), "[GeneratorFunction (null prototype): gf]"); + const agf = async function* agf() {}; + Reflect.setPrototypeOf(agf, null); + assertEquals( + stringify(agf), + "[AsyncGeneratorFunction (null prototype): agf]", + ); +}); + +Deno.test(function consoleTestStringifyFunctionWithProperties() { + const f = () => "test"; + f.x = () => "foo"; + f.y = 3; + f.z = () => "baz"; + f.b = function bar() {}; + f.a = new Map(); + assertEquals( + stringify({ f }), + `{ + f: [Function: f] { + x: [Function (anonymous)], + y: 3, + z: [Function (anonymous)], + b: [Function: bar], + a: Map(0) {} + } +}`, + ); + + const t = () => {}; + t.x = f; + f.s = f; + f.t = t; + assertEquals( + stringify({ f }), + `{ + f: <ref *1> [Function: f] { + x: [Function (anonymous)], + y: 3, + z: [Function (anonymous)], + b: [Function: bar], + a: Map(0) {}, + s: [Circular *1], + t: [Function: t] { x: [Circular *1] } + } +}`, + ); + + assertEquals( + stringify(Array), + `[Function: Array]`, + ); + + assertEquals( + stripColor(Deno.inspect(Array, { showHidden: true })), + `<ref *1> [Function: Array] { + [length]: 1, + [name]: "Array", + [prototype]: Object(0) [ + [length]: 0, + [constructor]: [Circular *1], + [at]: [Function: at] { [length]: 1, [name]: "at" }, + [concat]: [Function: concat] { [length]: 1, [name]: "concat" }, + [copyWithin]: [Function: copyWithin] { [length]: 2, [name]: "copyWithin" }, + [fill]: [Function: fill] { [length]: 1, [name]: "fill" }, + [find]: [Function: find] { [length]: 1, [name]: "find" }, + [findIndex]: [Function: findIndex] { [length]: 1, [name]: "findIndex" }, + [findLast]: [Function: findLast] { [length]: 1, [name]: "findLast" }, + [findLastIndex]: [Function: findLastIndex] { [length]: 1, [name]: "findLastIndex" }, + [lastIndexOf]: [Function: lastIndexOf] { [length]: 1, [name]: "lastIndexOf" }, + [pop]: [Function: pop] { [length]: 0, [name]: "pop" }, + [push]: [Function: push] { [length]: 1, [name]: "push" }, + [reverse]: [Function: reverse] { [length]: 0, [name]: "reverse" }, + [shift]: [Function: shift] { [length]: 0, [name]: "shift" }, + [unshift]: [Function: unshift] { [length]: 1, [name]: "unshift" }, + [slice]: [Function: slice] { [length]: 2, [name]: "slice" }, + [sort]: [Function: sort] { [length]: 1, [name]: "sort" }, + [splice]: [Function: splice] { [length]: 2, [name]: "splice" }, + [includes]: [Function: includes] { [length]: 1, [name]: "includes" }, + [indexOf]: [Function: indexOf] { [length]: 1, [name]: "indexOf" }, + [join]: [Function: join] { [length]: 1, [name]: "join" }, + [keys]: [Function: keys] { [length]: 0, [name]: "keys" }, + [entries]: [Function: entries] { [length]: 0, [name]: "entries" }, + [values]: [Function: values] { [length]: 0, [name]: "values" }, + [forEach]: [Function: forEach] { [length]: 1, [name]: "forEach" }, + [filter]: [Function: filter] { [length]: 1, [name]: "filter" }, + [flat]: [Function: flat] { [length]: 0, [name]: "flat" }, + [flatMap]: [Function: flatMap] { [length]: 1, [name]: "flatMap" }, + [map]: [Function: map] { [length]: 1, [name]: "map" }, + [every]: [Function: every] { [length]: 1, [name]: "every" }, + [some]: [Function: some] { [length]: 1, [name]: "some" }, + [reduce]: [Function: reduce] { [length]: 1, [name]: "reduce" }, + [reduceRight]: [Function: reduceRight] { [length]: 1, [name]: "reduceRight" }, + [toReversed]: [Function: toReversed] { [length]: 0, [name]: "toReversed" }, + [toSorted]: [Function: toSorted] { [length]: 1, [name]: "toSorted" }, + [toSpliced]: [Function: toSpliced] { [length]: 2, [name]: "toSpliced" }, + [with]: [Function: with] { [length]: 2, [name]: "with" }, + [toLocaleString]: [Function: toLocaleString] { [length]: 0, [name]: "toLocaleString" }, + [toString]: [Function: toString] { [length]: 0, [name]: "toString" }, + [Symbol(Symbol.iterator)]: [Function: values] { [length]: 0, [name]: "values" }, + [Symbol(Symbol.unscopables)]: [Object: null prototype] { + at: true, + copyWithin: true, + entries: true, + fill: true, + find: true, + findIndex: true, + findLast: true, + findLastIndex: true, + flat: true, + flatMap: true, + includes: true, + keys: true, + toReversed: true, + toSorted: true, + toSpliced: true, + values: true + } + ], + [isArray]: [Function: isArray] { [length]: 1, [name]: "isArray" }, + [from]: [Function: from] { [length]: 1, [name]: "from" }, + [of]: [Function: of] { [length]: 0, [name]: "of" }, + [fromAsync]: [Function: fromAsync] { [length]: 1, [name]: "fromAsync" }, + [Symbol(Symbol.species)]: [Getter] +}`, + ); +}); + +Deno.test(function consoleTestStringifyWithDepth() { + // deno-lint-ignore no-explicit-any + const nestedObj: any = { a: { b: { c: { d: { e: { f: 42 } } } } } }; + assertEquals( + stripColor(inspectArgs([nestedObj], { depth: 3 })), + "{\n a: { b: { c: { d: [Object] } } }\n}", + ); + assertEquals( + stripColor(inspectArgs([nestedObj], { depth: 4 })), + "{\n a: {\n b: { c: { d: { e: [Object] } } }\n }\n}", + ); + assertEquals( + stripColor(inspectArgs([nestedObj], { depth: 0 })), + "{ a: [Object] }", + ); + assertEquals( + stripColor(inspectArgs([nestedObj])), + "{\n a: {\n b: { c: { d: { e: [Object] } } }\n }\n}", + ); + // test inspect is working the same way + assertEquals( + stripColor(Deno.inspect(nestedObj, { depth: 4 })), + "{\n a: {\n b: { c: { d: { e: [Object] } } }\n }\n}", + ); +}); + +Deno.test(function consoleTestStringifyLargeObject() { + const obj = { + a: 2, + o: { + a: "1", + b: "2", + c: "3", + d: "4", + e: "5", + f: "6", + g: 10, + asd: 2, + asda: 3, + x: { a: "asd", x: 3 }, + }, + }; + assertEquals( + stringify(obj), + `{ + a: 2, + o: { + a: "1", + b: "2", + c: "3", + d: "4", + e: "5", + f: "6", + g: 10, + asd: 2, + asda: 3, + x: { a: "asd", x: 3 } + } +}`, + ); +}); + +Deno.test(function consoleTestStringifyIterable() { + const shortArray = [1, 2, 3, 4, 5]; + assertEquals(stringify(shortArray), "[ 1, 2, 3, 4, 5 ]"); + + const longArray = new Array(200).fill(0); + assertEquals( + stringify(longArray), + `[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ... 100 more items +]`, + ); + + const obj = { a: "a", longArray }; + assertEquals( + stringify(obj), + `{ + a: "a", + longArray: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ... 100 more items + ] +}`, + ); + + const shortMap = new Map([ + ["a", 0], + ["b", 1], + ]); + assertEquals(stringify(shortMap), `Map(2) { "a" => 0, "b" => 1 }`); + + const longMap = new Map(); + for (const key of Array(200).keys()) { + longMap.set(`${key}`, key); + } + assertEquals( + stringify(longMap), + `Map(200) { + "0" => 0, + "1" => 1, + "2" => 2, + "3" => 3, + "4" => 4, + "5" => 5, + "6" => 6, + "7" => 7, + "8" => 8, + "9" => 9, + "10" => 10, + "11" => 11, + "12" => 12, + "13" => 13, + "14" => 14, + "15" => 15, + "16" => 16, + "17" => 17, + "18" => 18, + "19" => 19, + "20" => 20, + "21" => 21, + "22" => 22, + "23" => 23, + "24" => 24, + "25" => 25, + "26" => 26, + "27" => 27, + "28" => 28, + "29" => 29, + "30" => 30, + "31" => 31, + "32" => 32, + "33" => 33, + "34" => 34, + "35" => 35, + "36" => 36, + "37" => 37, + "38" => 38, + "39" => 39, + "40" => 40, + "41" => 41, + "42" => 42, + "43" => 43, + "44" => 44, + "45" => 45, + "46" => 46, + "47" => 47, + "48" => 48, + "49" => 49, + "50" => 50, + "51" => 51, + "52" => 52, + "53" => 53, + "54" => 54, + "55" => 55, + "56" => 56, + "57" => 57, + "58" => 58, + "59" => 59, + "60" => 60, + "61" => 61, + "62" => 62, + "63" => 63, + "64" => 64, + "65" => 65, + "66" => 66, + "67" => 67, + "68" => 68, + "69" => 69, + "70" => 70, + "71" => 71, + "72" => 72, + "73" => 73, + "74" => 74, + "75" => 75, + "76" => 76, + "77" => 77, + "78" => 78, + "79" => 79, + "80" => 80, + "81" => 81, + "82" => 82, + "83" => 83, + "84" => 84, + "85" => 85, + "86" => 86, + "87" => 87, + "88" => 88, + "89" => 89, + "90" => 90, + "91" => 91, + "92" => 92, + "93" => 93, + "94" => 94, + "95" => 95, + "96" => 96, + "97" => 97, + "98" => 98, + "99" => 99, + ... 100 more items +}`, + ); + + const shortSet = new Set([1, 2, 3]); + assertEquals(stringify(shortSet), `Set(3) { 1, 2, 3 }`); + const longSet = new Set(); + for (const key of Array(200).keys()) { + longSet.add(key); + } + assertEquals( + stringify(longSet), + `Set(200) { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + ... 100 more items +}`, + ); + + const withEmptyEl = Array(10); + withEmptyEl.fill(0, 4, 6); + assertEquals( + stringify(withEmptyEl), + `[ <4 empty items>, 0, 0, <4 empty items> ]`, + ); + + const emptyArray = Array(5000); + assertEquals( + stringify(emptyArray), + `[ <5000 empty items> ]`, + ); + + assertEquals( + stringify(Array(1)), + `[ <1 empty item> ]`, + ); + + assertEquals( + stringify([, , 1]), + `[ <2 empty items>, 1 ]`, + ); + + assertEquals( + stringify([1, , , 1]), + `[ 1, <2 empty items>, 1 ]`, + ); + + const withEmptyElAndMoreItems = Array(500); + withEmptyElAndMoreItems.fill(0, 50, 80); + withEmptyElAndMoreItems.fill(2, 100, 120); + withEmptyElAndMoreItems.fill(3, 140, 160); + withEmptyElAndMoreItems.fill(4, 180); + assertEquals( + stringify(withEmptyElAndMoreItems), + `[ + <50 empty items>, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, <20 empty items>, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + <20 empty items>, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, <20 empty items>, 4, 4, + 4, 4, 4, 4, + 4, 4, 4, 4, + 4, 4, 4, 4, + 4, 4, 4, 4, + 4, 4, 4, 4, + 4, 4, 4, 4, + ... 294 more items +]`, + ); + + const lWithEmptyEl = Array(200); + lWithEmptyEl.fill(0, 50, 80); + assertEquals( + stringify(lWithEmptyEl), + `[ + <50 empty items>, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, <120 empty items> +]`, + ); +}); + +Deno.test(function consoleTestStringifyIterableWhenGrouped() { + const withOddNumberOfEls = new Float64Array( + [ + 2.1, + 2.01, + 2.001, + 2.0001, + 2.00001, + 2.000001, + 2.0000001, + 2.00000001, + 2.000000001, + 2.0000000001, + 2, + ], + ); + assertEquals( + stringify(withOddNumberOfEls), + `Float64Array(11) [ + 2.1, 2.01, + 2.001, 2.0001, + 2.00001, 2.000001, + 2.0000001, 2.00000001, + 2.000000001, 2.0000000001, + 2 +]`, + ); + const withEvenNumberOfEls = new Float64Array( + [ + 2.1, + 2.01, + 2.001, + 2.0001, + 2.00001, + 2.000001, + 2.0000001, + 2.00000001, + 2.000000001, + 2.0000000001, + 2, + 2, + ], + ); + assertEquals( + stringify(withEvenNumberOfEls), + `Float64Array(12) [ + 2.1, 2.01, + 2.001, 2.0001, + 2.00001, 2.000001, + 2.0000001, 2.00000001, + 2.000000001, 2.0000000001, + 2, 2 +]`, + ); + const withThreeColumns = [ + 2, + 2.1, + 2.11, + 2, + 2.111, + 2.1111, + 2, + 2.1, + 2.11, + 2, + 2.1, + ]; + assertEquals( + stringify(withThreeColumns), + `[ + 2, 2.1, 2.11, + 2, 2.111, 2.1111, + 2, 2.1, 2.11, + 2, 2.1 +]`, + ); +}); + +Deno.test(function consoleTestIteratorValueAreNotConsumed() { + const setIterator = new Set([1, 2, 3]).values(); + assertEquals( + stringify(setIterator), + "[Set Iterator] { 1, 2, 3 }", + ); + assertEquals([...setIterator], [1, 2, 3]); +}); + +Deno.test(function consoleTestWeakSetAndWeakMapWithShowHidden() { + assertEquals( + stripColor(Deno.inspect(new WeakSet([{}]), { showHidden: true })), + "WeakSet { {} }", + ); + assertEquals( + stripColor(Deno.inspect(new WeakMap([[{}, "foo"]]), { showHidden: true })), + 'WeakMap { {} => "foo" }', + ); +}); + +Deno.test(async function consoleTestStringifyPromises() { + const pendingPromise = new Promise((_res, _rej) => {}); + assertEquals(stringify(pendingPromise), "Promise { <pending> }"); + + const resolvedPromise = new Promise((res, _rej) => { + res("Resolved!"); + }); + assertEquals(stringify(resolvedPromise), `Promise { "Resolved!" }`); + + let rejectedPromise; + try { + rejectedPromise = new Promise((_, rej) => { + rej(Error("Whoops")); + }); + await rejectedPromise; + } catch (_err) { + // pass + } + const strLines = stringify(rejectedPromise).split("\n"); + assertEquals(strLines[0], "Promise {"); + assertEquals(strLines[1], " <rejected> Error: Whoops"); +}); + +Deno.test(function consoleTestWithCustomInspector() { + class A { + [customInspect]( + inspect: unknown, + options: Deno.InspectOptions, + ): string { + assertEquals(typeof inspect, "function"); + assertEquals(typeof options, "object"); + return "b"; + } + } + + assertEquals(stringify(new A()), "b"); +}); + +Deno.test(function consoleTestWithCustomInspectorUsingInspectFunc() { + class A { + [customInspect]( + inspect: (v: unknown, opts?: Deno.InspectOptions) => string, + ): string { + return "b " + inspect({ c: 1 }); + } + } + + assertEquals(stringify(new A()), "b { c: 1 }"); +}); + +Deno.test(function consoleTestWithConstructorError() { + const obj = new Proxy({}, { + getOwnPropertyDescriptor(_target, name) { + if (name == "constructor") { + throw "yikes"; + } + return undefined; + }, + }); + assertEquals(Deno.inspect(obj), "{}"); +}); + +Deno.test(function consoleTestWithCustomInspectorError() { + class A { + [customInspect](): never { + throw new Error("BOOM"); + } + } + + const a = new A(); + assertThrows( + () => stringify(a), + Error, + "BOOM", + "Custom inspect won't attempt to parse if user defined function throws", + ); + assertThrows( + () => stringify(a), + Error, + "BOOM", + "Inspect should fail and maintain a clear CTX_STACK", + ); +}); + +Deno.test(function consoleTestWithCustomInspectFunction() { + function a() {} + Object.assign(a, { + [customInspect]() { + return "b"; + }, + }); + + assertEquals(stringify(a), "b"); +}); + +Deno.test(function consoleTestWithIntegerFormatSpecifier() { + assertEquals(stringify("%i"), "%i"); + assertEquals(stringify("%i", 42.0), "42"); + assertEquals(stringify("%i", 42), "42"); + assertEquals(stringify("%i", "42"), "NaN"); + assertEquals(stringify("%i", 1.5), "1"); + assertEquals(stringify("%i", -0.5), "0"); + assertEquals(stringify("%i", ""), "NaN"); + assertEquals(stringify("%i", Symbol()), "NaN"); + assertEquals(stringify("%i %d", 42, 43), "42 43"); + assertEquals(stringify("%d %i", 42), "42 %i"); + assertEquals(stringify("%d", 12345678901234567890123), "1"); + assertEquals( + stringify("%i", 12345678901234567890123n), + "12345678901234567890123n", + ); +}); + +Deno.test(function consoleTestWithFloatFormatSpecifier() { + assertEquals(stringify("%f"), "%f"); + assertEquals(stringify("%f", 42.0), "42"); + assertEquals(stringify("%f", 42), "42"); + assertEquals(stringify("%f", "42"), "NaN"); + assertEquals(stringify("%f", 1.5), "1.5"); + assertEquals(stringify("%f", -0.5), "-0.5"); + assertEquals(stringify("%f", Math.PI), "3.141592653589793"); + assertEquals(stringify("%f", ""), "NaN"); + assertEquals(stringify("%f", Symbol("foo")), "NaN"); + assertEquals(stringify("%f", 5n), "NaN"); + assertEquals(stringify("%f %f", 42, 43), "42 43"); + assertEquals(stringify("%f %f", 42), "42 %f"); +}); + +Deno.test(function consoleTestWithStringFormatSpecifier() { + assertEquals(stringify("%s"), "%s"); + assertEquals(stringify("%s", undefined), "undefined"); + assertEquals(stringify("%s", "foo"), "foo"); + assertEquals(stringify("%s", 42), "42"); + assertEquals(stringify("%s", "42"), "42"); + assertEquals(stringify("%s %s", 42, 43), "42 43"); + assertEquals(stringify("%s %s", 42), "42 %s"); + assertEquals(stringify("%s", Symbol("foo")), "Symbol(foo)"); +}); + +Deno.test(function consoleTestWithObjectFormatSpecifier() { + assertEquals(stringify("%o"), "%o"); + assertEquals(stringify("%o", 42), "42"); + assertEquals(stringify("%o", "foo"), `"foo"`); + assertEquals(stringify("o: %o, a: %O", {}, []), "o: {}, a: []"); + assertEquals(stringify("%o", { a: 42 }), "{ a: 42 }"); + assertEquals( + stringify("%o", { a: { b: { c: { d: new Set([1]) } } } }), + "{\n a: {\n b: { c: { d: Set(1) { 1 } } }\n }\n}", + ); +}); + +Deno.test(function consoleTestWithStyleSpecifier() { + assertEquals(stringify("%cfoo%cbar"), "%cfoo%cbar"); + assertEquals(stringify("%cfoo%cbar", ""), "foo%cbar"); + assertEquals(stripColor(stringify("%cfoo%cbar", "", "color: red")), "foobar"); +}); + +Deno.test(function consoleParseCssColor() { + assertEquals(parseCssColor("inherit"), null); + assertEquals(parseCssColor("black"), [0, 0, 0]); + assertEquals(parseCssColor("darkmagenta"), [139, 0, 139]); + assertEquals(parseCssColor("slateblue"), [106, 90, 205]); + assertEquals(parseCssColor("#ffaa00"), [255, 170, 0]); + assertEquals(parseCssColor("#ffAA00"), [255, 170, 0]); + assertEquals(parseCssColor("#fa0"), [255, 170, 0]); + assertEquals(parseCssColor("#FA0"), [255, 170, 0]); + assertEquals(parseCssColor("#18d"), [17, 136, 221]); + assertEquals(parseCssColor("#18D"), [17, 136, 221]); + assertEquals(parseCssColor("#1188DD"), [17, 136, 221]); + assertEquals(parseCssColor("rgb(100, 200, 50)"), [100, 200, 50]); + assertEquals(parseCssColor("rgb(+100.3, -200, .5)"), [100, 0, 1]); + assertEquals(parseCssColor("hsl(75, 60%, 40%)"), [133, 163, 41]); + + assertEquals(parseCssColor("rgb(100,200,50)"), [100, 200, 50]); + assertEquals( + parseCssColor("rgb( \t\n100 \t\n, \t\n200 \t\n, \t\n50 \t\n)"), + [100, 200, 50], + ); +}); + +Deno.test(function consoleParseCss() { + assertEquals( + parseCss("background-color: inherit"), + { ...DEFAULT_CSS, backgroundColor: "inherit" }, + ); + assertEquals( + parseCss("color: inherit"), + { ...DEFAULT_CSS, color: "inherit" }, + ); + assertEquals( + parseCss("background-color: red"), + { ...DEFAULT_CSS, backgroundColor: "red" }, + ); + assertEquals(parseCss("color: blue"), { ...DEFAULT_CSS, color: "blue" }); + assertEquals( + parseCss("font-weight: bold"), + { ...DEFAULT_CSS, fontWeight: "bold" }, + ); + assertEquals( + parseCss("font-style: italic"), + { ...DEFAULT_CSS, fontStyle: "italic" }, + ); + assertEquals( + parseCss("font-style: oblique"), + { ...DEFAULT_CSS, fontStyle: "italic" }, + ); + assertEquals( + parseCss("text-decoration-color: green"), + { ...DEFAULT_CSS, textDecorationColor: [0, 128, 0] }, + ); + assertEquals( + parseCss("text-decoration-line: underline overline line-through"), + { + ...DEFAULT_CSS, + textDecorationLine: ["underline", "overline", "line-through"], + }, + ); + assertEquals( + parseCss("text-decoration: yellow underline"), + { + ...DEFAULT_CSS, + textDecorationColor: [255, 255, 0], + textDecorationLine: ["underline"], + }, + ); + + assertEquals( + parseCss("color:red;font-weight:bold;"), + { ...DEFAULT_CSS, color: "red", fontWeight: "bold" }, + ); + assertEquals( + parseCss( + " \t\ncolor \t\n: \t\nred \t\n; \t\nfont-weight \t\n: \t\nbold \t\n; \t\n", + ), + { ...DEFAULT_CSS, color: "red", fontWeight: "bold" }, + ); + assertEquals( + parseCss("color: red; font-weight: bold, font-style: italic"), + { ...DEFAULT_CSS, color: "red" }, + ); +}); + +Deno.test(function consoleCssToAnsi() { + assertEquals( + cssToAnsiEsc({ ...DEFAULT_CSS, backgroundColor: "inherit" }), + "_[49m", + ); + assertEquals( + cssToAnsiEsc({ ...DEFAULT_CSS, backgroundColor: "foo" }), + "_[49m", + ); + assertEquals( + cssToAnsiEsc({ ...DEFAULT_CSS, backgroundColor: "black" }), + "_[40m", + ); + assertEquals( + cssToAnsiEsc({ ...DEFAULT_CSS, color: "inherit" }), + "_[39m", + ); + assertEquals( + cssToAnsiEsc({ ...DEFAULT_CSS, color: "blue" }), + "_[34m", + ); + assertEquals( + cssToAnsiEsc({ ...DEFAULT_CSS, backgroundColor: [200, 201, 202] }), + "_[48;2;200;201;202m", + ); + assertEquals( + cssToAnsiEsc({ ...DEFAULT_CSS, color: [203, 204, 205] }), + "_[38;2;203;204;205m", + ); + assertEquals(cssToAnsiEsc({ ...DEFAULT_CSS, fontWeight: "bold" }), "_[1m"); + assertEquals(cssToAnsiEsc({ ...DEFAULT_CSS, fontStyle: "italic" }), "_[3m"); + assertEquals( + cssToAnsiEsc({ ...DEFAULT_CSS, textDecorationColor: [206, 207, 208] }), + "_[58;2;206;207;208m", + ); + assertEquals( + cssToAnsiEsc({ ...DEFAULT_CSS, textDecorationLine: ["underline"] }), + "_[4m", + ); + assertEquals( + cssToAnsiEsc( + { ...DEFAULT_CSS, textDecorationLine: ["overline", "line-through"] }, + ), + "_[9m_[53m", + ); + assertEquals( + cssToAnsiEsc( + { ...DEFAULT_CSS, color: [203, 204, 205], fontWeight: "bold" }, + ), + "_[38;2;203;204;205m_[1m", + ); + assertEquals( + cssToAnsiEsc( + { ...DEFAULT_CSS, color: [0, 0, 0], fontWeight: "bold" }, + { ...DEFAULT_CSS, color: [203, 204, 205], fontStyle: "italic" }, + ), + "_[38;2;0;0;0m_[1m_[23m", + ); +}); + +Deno.test(function consoleTestWithVariousOrInvalidFormatSpecifier() { + assertEquals(stringify("%s:%s"), "%s:%s"); + assertEquals(stringify("%i:%i"), "%i:%i"); + assertEquals(stringify("%d:%d"), "%d:%d"); + assertEquals(stringify("%%s%s", "foo"), "%sfoo"); + assertEquals(stringify("%s:%s", undefined), "undefined:%s"); + assertEquals(stringify("%s:%s", "foo", "bar"), "foo:bar"); + assertEquals(stringify("%s:%s", "foo", "bar", "baz"), "foo:bar baz"); + assertEquals(stringify("%%%s%%", "hi"), "%hi%"); + assertEquals(stringify("%d:%d", 12), "12:%d"); + assertEquals(stringify("%i:%i", 12), "12:%i"); + assertEquals(stringify("%f:%f", 12), "12:%f"); + assertEquals(stringify("o: %o, a: %o", {}), "o: {}, a: %o"); + assertEquals(stringify("abc%", 1), "abc% 1"); +}); + +Deno.test(function consoleTestCallToStringOnLabel() { + const methods = ["count", "countReset", "time", "timeLog", "timeEnd"]; + mockConsole((console) => { + for (const method of methods) { + let hasCalled = false; + console[method]({ + toString() { + hasCalled = true; + }, + }); + assertEquals(hasCalled, true); + } + }); +}); + +Deno.test(function consoleTestError() { + class MyError extends Error { + constructor(errStr: string) { + super(errStr); + this.name = "MyError"; + } + } + try { + throw new MyError("This is an error"); + } catch (e) { + assert( + stringify(e) + .split("\n")[0] // error has been caught + .includes("MyError: This is an error"), + ); + } +}); + +Deno.test(function consoleTestClear() { + mockConsole((console, out) => { + console.clear(); + assertEquals(out.toString(), "\x1b[1;1H" + "\x1b[0J"); + }); +}); + +// Test bound this issue +Deno.test(function consoleDetachedLog() { + mockConsole((console) => { + const log = console.log; + const dir = console.dir; + const dirxml = console.dirxml; + const debug = console.debug; + const info = console.info; + const warn = console.warn; + const error = console.error; + const consoleAssert = console.assert; + const consoleCount = console.count; + const consoleCountReset = console.countReset; + const consoleTable = console.table; + const consoleTime = console.time; + const consoleTimeLog = console.timeLog; + const consoleTimeEnd = console.timeEnd; + const consoleGroup = console.group; + const consoleGroupEnd = console.groupEnd; + const consoleClear = console.clear; + log("Hello world"); + dir("Hello world"); + dirxml("Hello world"); + debug("Hello world"); + info("Hello world"); + warn("Hello world"); + error("Hello world"); + consoleAssert(true); + consoleCount("Hello world"); + consoleCountReset("Hello world"); + consoleTable({ test: "Hello world" }); + consoleTime("Hello world"); + consoleTimeLog("Hello world"); + consoleTimeEnd("Hello world"); + consoleGroup("Hello world"); + consoleGroupEnd(); + consoleClear(); + }); +}); + +class StringBuffer { + chunks: string[] = []; + add(x: string) { + this.chunks.push(x); + } + toString(): string { + return this.chunks.join(""); + } +} + +type ConsoleExamineFunc = ( + // deno-lint-ignore no-explicit-any + csl: any, + out: StringBuffer, + err?: StringBuffer, + both?: StringBuffer, +) => void; + +function mockConsole(f: ConsoleExamineFunc) { + const out = new StringBuffer(); + const err = new StringBuffer(); + const both = new StringBuffer(); + const csl = new Console( + (x: string, level: number, printsNewLine: boolean) => { + const content = x + (printsNewLine ? "\n" : ""); + const buf = level > 1 ? err : out; + buf.add(content); + both.add(content); + }, + ); + f(csl, out, err, both); +} + +// console.group test +Deno.test(function consoleGroup() { + mockConsole((console, out) => { + console.group("1"); + console.log("2"); + console.group("3"); + console.log("4"); + console.groupEnd(); + console.groupEnd(); + console.log("5"); + console.log("6"); + + assertEquals( + out.toString(), + `1 + 2 + 3 + 4 +5 +6 +`, + ); + }); +}); + +// console.group with console.warn test +Deno.test(function consoleGroupWarn() { + mockConsole((console, _out, _err, both) => { + assert(both); + console.warn("1"); + console.group(); + console.warn("2"); + console.group(); + console.warn("3"); + console.groupEnd(); + console.warn("4"); + console.groupEnd(); + console.warn("5"); + + console.warn("6"); + console.warn("7"); + assertEquals( + both.toString(), + `1 + 2 + 3 + 4 +5 +6 +7 +`, + ); + }); +}); + +// console.table test +Deno.test(function consoleTable() { + mockConsole((console, out) => { + console.table({ a: "test", b: 1 }); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬────────┐ +│ (idx) │ Values │ +├───────┼────────┤ +│ a │ "test" │ +│ b │ 1 │ +└───────┴────────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table({ a: { b: 10 }, b: { b: 20, c: 30 } }, ["c"]); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬────┐ +│ (idx) │ c │ +├───────┼────┤ +│ a │ │ +│ b │ 30 │ +└───────┴────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table([[1, 1], [234, 2.34], [56789, 56.789]]); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬───────┬────────┐ +│ (idx) │ 0 │ 1 │ +├───────┼───────┼────────┤ +│ 0 │ 1 │ 1 │ +│ 1 │ 234 │ 2.34 │ +│ 2 │ 56789 │ 56.789 │ +└───────┴───────┴────────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table([1, 2, [3, [4]], [5, 6], [[7], [8]]]); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬───────┬───────┬────────┐ +│ (idx) │ 0 │ 1 │ Values │ +├───────┼───────┼───────┼────────┤ +│ 0 │ │ │ 1 │ +│ 1 │ │ │ 2 │ +│ 2 │ 3 │ [ 4 ] │ │ +│ 3 │ 5 │ 6 │ │ +│ 4 │ [ 7 ] │ [ 8 ] │ │ +└───────┴───────┴───────┴────────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table(new Set([1, 2, 3, "test"])); + assertEquals( + stripColor(out.toString()), + `\ +┌────────────┬────────┐ +│ (iter idx) │ Values │ +├────────────┼────────┤ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +│ 3 │ "test" │ +└────────────┴────────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table( + new Map([ + [1, "one"], + [2, "two"], + ]), + ); + assertEquals( + stripColor(out.toString()), + `\ +┌────────────┬─────┬────────┐ +│ (iter idx) │ Key │ Values │ +├────────────┼─────┼────────┤ +│ 0 │ 1 │ "one" │ +│ 1 │ 2 │ "two" │ +└────────────┴─────┴────────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table({ + a: true, + b: { c: { d: 10 }, e: [1, 2, [5, 6]] }, + f: "test", + g: new Set([1, 2, 3, "test"]), + h: new Map([[1, "one"]]), + }); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬───────────┬────────────────────┬────────┐ +│ (idx) │ c │ e │ Values │ +├───────┼───────────┼────────────────────┼────────┤ +│ a │ │ │ true │ +│ b │ { d: 10 } │ [ 1, 2, [ 5, 6 ] ] │ │ +│ f │ │ │ "test" │ +│ g │ │ │ │ +│ h │ │ │ │ +└───────┴───────────┴────────────────────┴────────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table([ + 1, + "test", + false, + { a: 10 }, + ["test", { b: 20, c: "test" }], + ]); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬────────┬──────────────────────┬────┬────────┐ +│ (idx) │ 0 │ 1 │ a │ Values │ +├───────┼────────┼──────────────────────┼────┼────────┤ +│ 0 │ │ │ │ 1 │ +│ 1 │ │ │ │ "test" │ +│ 2 │ │ │ │ false │ +│ 3 │ │ │ 10 │ │ +│ 4 │ "test" │ { b: 20, c: "test" } │ │ │ +└───────┴────────┴──────────────────────┴────┴────────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table([]); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┐ +│ (idx) │ +├───────┤ +└───────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table({}); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┐ +│ (idx) │ +├───────┤ +└───────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table(new Set()); + assertEquals( + stripColor(out.toString()), + `\ +┌────────────┐ +│ (iter idx) │ +├────────────┤ +└────────────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table(new Map()); + assertEquals( + stripColor(out.toString()), + `\ +┌────────────┐ +│ (iter idx) │ +├────────────┤ +└────────────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table("test"); + assertEquals(out.toString(), "test\n"); + }); + mockConsole((console, out) => { + console.table(["Hello", "你好", "Amapá"]); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬─────────┐ +│ (idx) │ Values │ +├───────┼─────────┤ +│ 0 │ "Hello" │ +│ 1 │ "你好" │ +│ 2 │ "Amapá" │ +└───────┴─────────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table([ + [1, 2], + [3, 4], + ]); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬───┬───┐ +│ (idx) │ 0 │ 1 │ +├───────┼───┼───┤ +│ 0 │ 1 │ 2 │ +│ 1 │ 3 │ 4 │ +└───────┴───┴───┘ +`, + ); + }); + mockConsole((console, out) => { + console.table({ 1: { a: 4, b: 5 }, 2: null, 3: { b: 6, c: 7 } }, ["b"]); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬───┐ +│ (idx) │ b │ +├───────┼───┤ +│ 1 │ 5 │ +│ 2 │ │ +│ 3 │ 6 │ +└───────┴───┘ +`, + ); + }); + mockConsole((console, out) => { + console.table([{ a: 0 }, { a: 1, b: 1 }, { a: 2 }, { a: 3, b: 3 }]); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬───┬───┐ +│ (idx) │ a │ b │ +├───────┼───┼───┤ +│ 0 │ 0 │ │ +│ 1 │ 1 │ 1 │ +│ 2 │ 2 │ │ +│ 3 │ 3 │ 3 │ +└───────┴───┴───┘ +`, + ); + }); + mockConsole((console, out) => { + console.table( + [{ a: 0 }, { a: 1, c: 1 }, { a: 2 }, { a: 3, c: 3 }], + ["a", "b", "c"], + ); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬───┬───┬───┐ +│ (idx) │ a │ b │ c │ +├───────┼───┼───┼───┤ +│ 0 │ 0 │ │ │ +│ 1 │ 1 │ │ 1 │ +│ 2 │ 2 │ │ │ +│ 3 │ 3 │ │ 3 │ +└───────┴───┴───┴───┘ +`, + ); + }); +}); + +// console.log(Error) test +Deno.test(function consoleLogShouldNotThrowError() { + mockConsole((console) => { + let result = 0; + try { + console.log(new Error("foo")); + result = 1; + } catch (_e) { + result = 2; + } + assertEquals(result, 1); + }); + + // output errors to the console should not include "Uncaught" + mockConsole((console, out) => { + console.log(new Error("foo")); + assertEquals(out.toString().includes("Uncaught"), false); + }); +}); + +Deno.test(function consoleLogShouldNotThrowErrorWhenInvalidCssColorsAreGiven() { + mockConsole((console, out) => { + console.log("%cfoo", "color: foo; background-color: bar;"); + assertEquals(stripColor(out.toString()), "foo\n"); + }); +}); + +// console.log(Invalid Date) test +Deno.test(function consoleLogShouldNotThrowErrorWhenInvalidDateIsPassed() { + mockConsole((console, out) => { + const invalidDate = new Date("test"); + console.log(invalidDate); + assertEquals(stripColor(out.toString()), "Invalid Date\n"); + }); +}); + +// console.log(new Proxy(new Set(), {})) +Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedSet() { + mockConsole((console, out) => { + const proxiedSet = new Proxy(new Set([1, 2]), {}); + console.log(proxiedSet); + assertEquals(stripColor(out.toString()), "Set(2) { 1, 2 }\n"); + }); +}); + +// console.log(new Proxy(new Map(), {})) +Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedMap() { + mockConsole((console, out) => { + const proxiedMap = new Proxy(new Map([[1, 1], [2, 2]]), {}); + console.log(proxiedMap); + assertEquals(stripColor(out.toString()), "Map(2) { 1 => 1, 2 => 2 }\n"); + }); +}); + +// console.log(new Proxy(new Uint8Array(), {})) +Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedTypedArray() { + mockConsole((console, out) => { + const proxiedUint8Array = new Proxy(new Uint8Array([1, 2]), {}); + console.log(proxiedUint8Array); + assertEquals(stripColor(out.toString()), "Uint8Array(2) [ 1, 2 ]\n"); + }); +}); + +// console.log(new Proxy(new RegExp(), {})) +Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedRegExp() { + mockConsole((console, out) => { + const proxiedRegExp = new Proxy(/aaaa/, {}); + console.log(proxiedRegExp); + assertEquals(stripColor(out.toString()), "/aaaa/\n"); + }); +}); + +// console.log(new Proxy(new Date(), {})) +Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedDate() { + mockConsole((console, out) => { + const proxiedDate = new Proxy(new Date("2022-09-24T15:59:39.529Z"), {}); + console.log(proxiedDate); + assertEquals(stripColor(out.toString()), "2022-09-24T15:59:39.529Z\n"); + }); +}); + +// console.log(new Proxy(new Error(), {})) +Deno.test(function consoleLogShouldNotThrowErrorWhenInputIsProxiedError() { + mockConsole((console, out) => { + const proxiedError = new Proxy(new Error("message"), {}); + console.log(proxiedError); + assertStringIncludes(stripColor(out.toString()), "Error: message\n"); + }); +}); + +// console.dir test +Deno.test(function consoleDir() { + mockConsole((console, out) => { + console.dir("DIR"); + assertEquals(out.toString(), "DIR\n"); + }); + mockConsole((console, out) => { + console.dir("DIR", { indentLevel: 2 }); + assertEquals(out.toString(), " DIR\n"); + }); +}); + +// console.dir test +Deno.test(function consoleDirXml() { + mockConsole((console, out) => { + console.dirxml("DIRXML"); + assertEquals(out.toString(), "DIRXML\n"); + }); + mockConsole((console, out) => { + console.dirxml("DIRXML", { indentLevel: 2 }); + assertEquals(out.toString(), " DIRXML\n"); + }); +}); + +// console.trace test +Deno.test(function consoleTrace() { + mockConsole((console, _out, err) => { + console.trace("%s", "custom message"); + assert(err); + assert(err.toString().includes("Trace: custom message")); + }); +}); + +Deno.test(function inspectString() { + assertEquals( + stripColor(Deno.inspect("\0")), + `"\\x00"`, + ); + assertEquals( + stripColor(Deno.inspect("\x1b[2J")), + `"\\x1b[2J"`, + ); +}); + +Deno.test(function inspectGetters() { + assertEquals( + stripColor(Deno.inspect({ + get foo() { + return 0; + }, + })), + "{ foo: [Getter] }", + ); + + assertEquals( + stripColor(Deno.inspect({ + get foo() { + return 0; + }, + }, { getters: true })), + "{ foo: [Getter: 0] }", + ); + + assertEquals( + Deno.inspect({ + get foo() { + throw new Error("bar"); + }, + }, { getters: true }), + "{ foo: [Getter: <Inspection threw (bar)>] }", + ); +}); + +Deno.test(function inspectPrototype() { + class A {} + assertEquals(Deno.inspect(A.prototype), "{}"); +}); + +Deno.test(function inspectSorted() { + assertEquals( + stripColor(Deno.inspect({ b: 2, a: 1 }, { sorted: true })), + "{ a: 1, b: 2 }", + ); + assertEquals( + stripColor(Deno.inspect(new Set(["b", "a"]), { sorted: true })), + `Set(2) { "a", "b" }`, + ); + assertEquals( + stripColor(Deno.inspect( + new Map([ + ["b", 2], + ["a", 1], + ]), + { sorted: true }, + )), + `Map(2) { "a" => 1, "b" => 2 }`, + ); +}); + +Deno.test(function inspectTrailingComma() { + assertEquals( + stripColor(Deno.inspect( + [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + ], + { trailingComma: true }, + )), + `[ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", +]`, + ); + assertEquals( + stripColor(Deno.inspect( + { + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: 1, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: 2, + }, + { trailingComma: true }, + )), + `{ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: 1, + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: 2, +}`, + ); + assertEquals( + stripColor(Deno.inspect( + new Set([ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + ]), + { trailingComma: true }, + )), + `Set(2) { + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", +}`, + ); + assertEquals( + stripColor(Deno.inspect( + new Map([ + ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1], + ["bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 2], + ]), + { trailingComma: true }, + )), + `Map(2) { + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" => 1, + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" => 2, +}`, + ); +}); + +Deno.test(function inspectCompact() { + assertEquals( + stripColor(Deno.inspect({ a: 1, b: 2 }, { compact: false })), + `{ + a: 1, + b: 2 +}`, + ); +}); + +Deno.test(function inspectIterableLimit() { + assertEquals( + stripColor(Deno.inspect(["a", "b", "c"], { iterableLimit: 2 })), + `[ "a", "b", ... 1 more item ]`, + ); + assertEquals( + stripColor(Deno.inspect(new Set(["a", "b", "c"]), { iterableLimit: 2 })), + `Set(3) { "a", "b", ... 1 more item }`, + ); + assertEquals( + stripColor(Deno.inspect( + new Map([ + ["a", 1], + ["b", 2], + ["c", 3], + ]), + { iterableLimit: 2 }, + )), + `Map(3) { "a" => 1, "b" => 2, ... 1 more item }`, + ); +}); + +Deno.test(function inspectProxy() { + assertEquals( + stripColor(Deno.inspect( + new Proxy([1, 2, 3], {}), + )), + "[ 1, 2, 3 ]", + ); + assertEquals( + stripColor(Deno.inspect( + new Proxy({ key: "value" }, {}), + )), + `{ key: "value" }`, + ); + assertEquals( + stripColor(Deno.inspect( + new Proxy({}, { + get(_target, key) { + if (key === Symbol.toStringTag) { + return "MyProxy"; + } else { + return 5; + } + }, + getOwnPropertyDescriptor() { + return { + enumerable: true, + configurable: true, + value: 5, + }; + }, + ownKeys() { + return ["prop1", "prop2"]; + }, + }), + )), + `Object [MyProxy] { prop1: 5, prop2: 5 }`, + ); + assertEquals( + stripColor(Deno.inspect( + new Proxy([1, 2, 3], { get() {} }), + { showProxy: true }, + )), + "Proxy [ [ 1, 2, 3 ], { get: [Function: get] } ]", + ); + assertEquals( + stripColor(Deno.inspect( + new Proxy({ a: 1 }, { + set(): boolean { + return false; + }, + }), + { showProxy: true }, + )), + "Proxy [ { a: 1 }, { set: [Function: set] } ]", + ); + assertEquals( + stripColor(Deno.inspect( + new Proxy([1, 2, 3, 4, 5, 6, 7], { get() {} }), + { showProxy: true }, + )), + `Proxy [ + [ + 1, 2, 3, 4, + 5, 6, 7 + ], + { get: [Function: get] } +]`, + ); + assertEquals( + stripColor(Deno.inspect( + new Proxy(function fn() {}, { get() {} }), + { showProxy: true }, + )), + "Proxy [ [Function: fn], { get: [Function: get] } ]", + ); +}); + +Deno.test(function inspectError() { + const error1 = new Error("This is an error"); + const error2 = new Error("This is an error", { + cause: new Error("This is a cause error"), + }); + + assertStringIncludes( + stripColor(Deno.inspect(error1)), + "Error: This is an error", + ); + assertStringIncludes( + stripColor(Deno.inspect(error2)), + "Error: This is an error", + ); + assertStringIncludes( + stripColor(Deno.inspect(error2)), + "Caused by Error: This is a cause error", + ); +}); + +Deno.test(function inspectErrorCircular() { + const error1 = new Error("This is an error"); + const error2 = new Error("This is an error", { + cause: new Error("This is a cause error"), + }); + error1.cause = error1; + assert(error2.cause instanceof Error); + error2.cause.cause = error2; + + assertStringIncludes( + stripColor(Deno.inspect(error1)), + "Error: This is an error", + ); + assertStringIncludes( + stripColor(Deno.inspect(error2)), + "<ref *1> Error: This is an error", + ); + assertStringIncludes( + stripColor(Deno.inspect(error2)), + "Caused by Error: This is a cause error", + ); + assertStringIncludes( + stripColor(Deno.inspect(error2)), + "Caused by [Circular *1]", + ); +}); + +Deno.test(function inspectColors() { + assertEquals(Deno.inspect(1), "1"); + assertStringIncludes(Deno.inspect(1, { colors: true }), "\x1b["); +}); + +Deno.test(function inspectEmptyArray() { + const arr: string[] = []; + + assertEquals( + Deno.inspect(arr, { + compact: false, + trailingComma: true, + }), + "[]", + ); +}); + +Deno.test(function inspectDeepEmptyArray() { + const obj = { + arr: [], + }; + + assertEquals( + Deno.inspect(obj, { + compact: false, + trailingComma: true, + }), + `{ + arr: [], +}`, + ); +}); + +Deno.test(function inspectEmptyMap() { + const map = new Map(); + + assertEquals( + Deno.inspect(map, { + compact: false, + trailingComma: true, + }), + "Map(0) {}", + ); +}); + +Deno.test(function inspectEmptySet() { + const set = new Set(); + + assertEquals( + Deno.inspect(set, { + compact: false, + trailingComma: true, + }), + "Set(0) {}", + ); +}); + +Deno.test(function inspectEmptyUint8Array() { + const typedArray = new Uint8Array(0); + + assertEquals( + Deno.inspect(typedArray, { + compact: false, + trailingComma: true, + }), + "Uint8Array(0) []", + ); +}); + +Deno.test(function inspectLargeArrayBuffer() { + const arrayBuffer = new ArrayBuffer(2 ** 32 + 1); + assertEquals( + Deno.inspect(arrayBuffer), + `ArrayBuffer { + [Uint8Contents]: <00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... 4294967197 more bytes>, + byteLength: 4294967297 +}`, + ); + structuredClone(arrayBuffer, { transfer: [arrayBuffer] }); + assertEquals( + Deno.inspect(arrayBuffer), + "ArrayBuffer { (detached), byteLength: 0 }", + ); + + const sharedArrayBuffer = new SharedArrayBuffer(2 ** 32 + 1); + assertEquals( + Deno.inspect(sharedArrayBuffer), + `SharedArrayBuffer { + [Uint8Contents]: <00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... 4294967197 more bytes>, + byteLength: 4294967297 +}`, + ); +}); + +Deno.test(function inspectStringAbbreviation() { + const LONG_STRING = + "This is a really long string which will be abbreviated with ellipsis."; + const obj = { + str: LONG_STRING, + }; + const arr = [LONG_STRING]; + + assertEquals( + Deno.inspect(obj, { strAbbreviateSize: 10 }), + '{ str: "This is a "... 59 more characters }', + ); + + assertEquals( + Deno.inspect(arr, { strAbbreviateSize: 10 }), + '[ "This is a "... 59 more characters ]', + ); +}); + +Deno.test(async function inspectAggregateError() { + try { + await Promise.any([]); + } catch (err) { + assertEquals( + Deno.inspect(err).trimEnd(), + "AggregateError: All promises were rejected", + ); + } +}); + +Deno.test(function inspectWithPrototypePollution() { + const originalExec = RegExp.prototype.exec; + try { + RegExp.prototype.exec = () => { + throw Error(); + }; + Deno.inspect("foo"); + } finally { + RegExp.prototype.exec = originalExec; + } +}); + +Deno.test(function inspectPromiseLike() { + assertEquals( + Deno.inspect(Object.create(Promise.prototype)), + "Promise {}", + ); +}); + +Deno.test(function inspectorMethods() { + console.timeStamp("test"); + console.profile("test"); + console.profileEnd("test"); +}); + +Deno.test(function inspectQuotesOverride() { + assertEquals( + // @ts-ignore - 'quotes' is an internal option + Deno.inspect("foo", { quotes: ["'", '"', "`"] }), + "'foo'", + ); + assertEquals( + // @ts-ignore - 'quotes' is an internal option + Deno.inspect("'foo'", { quotes: ["'", '"', "`"] }), + `"'foo'"`, + ); +}); + +Deno.test(function inspectAnonymousFunctions() { + assertEquals(Deno.inspect(() => {}), "[Function (anonymous)]"); + assertEquals(Deno.inspect(function () {}), "[Function (anonymous)]"); + assertEquals(Deno.inspect(async () => {}), "[AsyncFunction (anonymous)]"); + assertEquals( + Deno.inspect(async function () {}), + "[AsyncFunction (anonymous)]", + ); + assertEquals( + Deno.inspect(function* () {}), + "[GeneratorFunction (anonymous)]", + ); + assertEquals( + Deno.inspect(async function* () {}), + "[AsyncGeneratorFunction (anonymous)]", + ); +}); + +Deno.test(function inspectBreakLengthOption() { + assertEquals( + Deno.inspect("123456789\n".repeat(3), { breakLength: 34 }), + `"123456789\\n123456789\\n123456789\\n"`, + ); + assertEquals( + Deno.inspect("123456789\n".repeat(3), { breakLength: 33 }), + `"123456789\\n" + + "123456789\\n" + + "123456789\\n"`, + ); +}); + +Deno.test(function inspectEscapeSequencesFalse() { + assertEquals( + Deno.inspect("foo\nbar", { escapeSequences: true }), + '"foo\\nbar"', + ); // default behavior + assertEquals( + Deno.inspect("foo\nbar", { escapeSequences: false }), + '"foo\nbar"', + ); +}); |