From f7abbfac0044995578d4a3b3603465c756a799fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Weslley=20Ara=C3=BAjo?= <46850407+wellwelwel@users.noreply.github.com> Date: Sun, 19 May 2024 06:47:01 -0300 Subject: [PATCH] fix(assert): improve multi-depth logs (#265) --- fixtures/sintax/big-int.ts | 3 + src/@types/assert.ts | 12 ++ .../{parseAsssetion.ts => parse-assertion.ts} | 66 +++++--- src/modules/assert-promise.ts | 7 +- src/modules/assert.ts | 7 +- src/polyfills/object.ts | 27 ++++ test/unit/assert.result-type.test.ts | 143 ++++++++++++++++++ 7 files changed, 234 insertions(+), 31 deletions(-) create mode 100644 fixtures/sintax/big-int.ts create mode 100644 src/@types/assert.ts rename src/helpers/{parseAsssetion.ts => parse-assertion.ts} (66%) create mode 100644 src/polyfills/object.ts create mode 100644 test/unit/assert.result-type.test.ts diff --git a/fixtures/sintax/big-int.ts b/fixtures/sintax/big-int.ts new file mode 100644 index 00000000..6ad353d7 --- /dev/null +++ b/fixtures/sintax/big-int.ts @@ -0,0 +1,3 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: BigInt literals are not available when targeting lower than ES2020 +export const bigIntValue = 987456321456987456321n; diff --git a/src/@types/assert.ts b/src/@types/assert.ts new file mode 100644 index 00000000..b236286b --- /dev/null +++ b/src/@types/assert.ts @@ -0,0 +1,12 @@ +/* c8 ignore start */ + +export type ParseAssertionOptions = { + message?: string | Error; + defaultMessage?: string; + actual?: string; + expected?: string; + throw?: boolean; + hideDiff?: boolean; +}; + +/* c8 ignore stop */ diff --git a/src/helpers/parseAsssetion.ts b/src/helpers/parse-assertion.ts similarity index 66% rename from src/helpers/parseAsssetion.ts rename to src/helpers/parse-assertion.ts index 6db1f7db..fc1a34dd 100644 --- a/src/helpers/parseAsssetion.ts +++ b/src/helpers/parse-assertion.ts @@ -6,17 +6,44 @@ import { format } from './format.js'; import { hr } from './hr.js'; import { findFile } from './find-file.js'; import { each } from '../configs/each.js'; +import { fromEntries, entries } from '../polyfills/object.js'; +/* c8 ignore next */ +import { ParseAssertionOptions } from '../@types/assert.js'; +import { nodeVersion } from './get-runtime.js'; + +export const parseResultType = (type?: unknown): string => { + const recurse = (value: unknown): unknown => { + if (typeof value === 'undefined') return 'undefined'; + + if ( + typeof value === 'function' || + typeof value === 'bigint' || + value instanceof RegExp + ) + return String(value); + + if (Array.isArray(value)) return value.map(recurse); + + /* c8 ignore start */ + if (value !== null && typeof value === 'object') { + if (!nodeVersion || nodeVersion >= 12) + return Object.fromEntries( + Object.entries(value).map(([key, val]) => [key, recurse(val)]) + ); + + return fromEntries( + entries(value).map(([key, val]) => [key, recurse(val)]) + ); + } + /* c8 ignore stop */ + + return value; + }; -/* c8 ignore start */ -export type ParseAssertionOptions = { - message?: string | Error; - defaultMessage?: string; - actual?: string; - expected?: string; - throw?: boolean; - hideDiff?: boolean; + const result = recurse(type); + + return typeof result === 'string' ? result : JSON.stringify(result, null, 2); }; -/* c8 ignore stop */ export const parseAssertion = async ( cb: () => void | Promise, @@ -78,26 +105,19 @@ export const parseAssertion = async ( console.log(`${format.dim(' Operator')} ${operator}${EOL}`); if (!options?.hideDiff) { + const splitActual = parseResultType(actual).split('\n'); + const splitExpected = parseResultType(expected).split('\n'); + console.log(format.dim(` ${options?.actual || 'Actual'}:`)); - console.log( - format.bold( - typeof actual === 'function' || actual instanceof RegExp - ? ` ${String(actual)}` - : ` ${format.fail(JSON.stringify(actual))}` - ) + splitActual.forEach((line) => + console.log(` ${format.bold(format.fail(line))}`) ); console.log( `${EOL} ${format.dim(`${options?.expected || 'Expected'}:`)}` ); - console.log( - format.bold( - `${ - typeof expected === 'function' || expected instanceof RegExp - ? ` ${String(expected)}` - : ` ${format.success(JSON.stringify(expected))}` - }` - ) + splitExpected.forEach((line) => + console.log(` ${format.bold(format.success(line))}`) ); } diff --git a/src/modules/assert-promise.ts b/src/modules/assert-promise.ts index a5154bbb..21b95fdd 100644 --- a/src/modules/assert-promise.ts +++ b/src/modules/assert-promise.ts @@ -1,9 +1,8 @@ import * as nodeAssert from 'node:assert'; -import { - parseAssertion, - ParseAssertionOptions, -} from '../helpers/parseAsssetion.js'; +import { parseAssertion } from '../helpers/parse-assertion.js'; import { nodeVersion } from '../helpers/get-runtime.js'; +/* c8 ignore next */ +import { ParseAssertionOptions } from '../@types/assert.js'; const ok = async ( value: unknown, diff --git a/src/modules/assert.ts b/src/modules/assert.ts index a9e5e3a0..a356cfc9 100644 --- a/src/modules/assert.ts +++ b/src/modules/assert.ts @@ -1,9 +1,8 @@ import * as nodeAssert from 'node:assert'; -import { - parseAssertion, - ParseAssertionOptions, -} from '../helpers/parseAsssetion.js'; +import { parseAssertion } from '../helpers/parse-assertion.js'; import { nodeVersion } from '../helpers/get-runtime.js'; +/* c8 ignore next */ +import { ParseAssertionOptions } from '../@types/assert.js'; const ok = ( value: unknown, diff --git a/src/polyfills/object.ts b/src/polyfills/object.ts new file mode 100644 index 00000000..b7d7d368 --- /dev/null +++ b/src/polyfills/object.ts @@ -0,0 +1,27 @@ +/* c8 ignore start */ + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const entries = (obj: { [key: string]: any }): [string, unknown][] => { + const ownProps = Object.keys(obj); + let i = ownProps.length; + const resArray = new Array(i); + + // benchmark `while` outperformed `for` + while (i--) resArray[i] = [ownProps[i], obj[ownProps[i]]]; + + return resArray; +}; + +export const fromEntries = ( + entries: [string, unknown][] +): Record => { + return entries.reduce( + (acc, [key, value]) => { + acc[key] = value; + return acc; + }, + {} as Record + ); +}; + +/* c8 ignore stop */ diff --git a/test/unit/assert.result-type.test.ts b/test/unit/assert.result-type.test.ts new file mode 100644 index 00000000..d5e872dc --- /dev/null +++ b/test/unit/assert.result-type.test.ts @@ -0,0 +1,143 @@ +import { test, assert, describe } from '../../src/index.js'; +import { parseResultType } from '../../src/helpers/parse-assertion.js'; +import { nodeVersion } from '../../src/helpers/get-runtime.js'; + +describe('Assert: Parse Result Type', { background: false, icon: '🔬' }); +test(async () => { + assert.deepStrictEqual( + parseResultType(), + 'undefined', + 'Undefined (Implicit)' + ); + assert.deepStrictEqual( + parseResultType(undefined), + 'undefined', + 'Undefined (Explicit)' + ); + assert.deepStrictEqual(parseResultType(null), 'null', 'Null'); + assert.deepStrictEqual(parseResultType(true), 'true', 'True'); + assert.deepStrictEqual(parseResultType(false), 'false', 'False'); + assert.deepStrictEqual(parseResultType('string'), 'string', 'String'); + assert.deepStrictEqual( + parseResultType(` + Multi + + Line + `), + ` + Multi + + Line + `, + 'String (Multi Line/Table)' + ); + assert.deepStrictEqual(parseResultType(123), '123', 'Number'); + if (!nodeVersion || nodeVersion >= 10) { + const module = await import('../../fixtures/sintax/big-int.js'); + assert.deepStrictEqual( + parseResultType(module.bigIntValue), + '987456321456987456321', + 'Big Int' + ); + } + assert.deepStrictEqual(parseResultType(/123/), '/123/', 'Regex'); + + assert(/=>/.test(parseResultType(() => {})), 'Anonymous Function'); + assert( + /=>/.test(parseResultType((a: number) => a)), + 'Anonymous Function (Param)' + ); + assert( + /=>/.test(parseResultType((a: number, b: number) => a + b)), + 'Anonymous Function (Params)' + ); + + assert( + /function/.test( + parseResultType(function () { + return; + }) + ), + 'Function' + ); + assert( + /function/.test( + parseResultType(function (a: number) { + return a; + }) + ), + 'Function (Param)' + ); + assert( + /function/.test( + parseResultType(function (a: number, b: number) { + return a + b; + }) + ), + 'Function (Params)' + ); + + assert.deepStrictEqual( + parseResultType({ a: true }), + `{ + "a": true +}`, + 'Object' + ); + assert.deepStrictEqual(parseResultType({}), '{}', 'Object (Empty)'); + assert.deepStrictEqual( + parseResultType({ a: { b: 123 }, c: [/123/gi] }), + `{ + "a": { + "b": 123 + }, + "c": [ + "/123/gi" + ] +}`, + 'Object (Complex)' + ); +}); +assert.deepStrictEqual( + parseResultType([1]), + `[ + 1 +]`, + 'Array' +); +assert.deepStrictEqual(parseResultType([]), `[]`, 'Array (Empty)'); +assert.deepStrictEqual( + parseResultType([ + 1, + true, + undefined, + /123/gm, + { a: { b: [{ c: undefined }] } }, + [[[[/[^0-9]/]]]], + ]), + `[ + 1, + true, + "undefined", + "/123/gm", + { + "a": { + "b": [ + { + "c": "undefined" + } + ] + } + }, + [ + [ + [ + [ + "/[^0-9]/" + ] + ] + ] + ] +]`, + 'Array Complex' +);