From c433477752476d8de64bdf99a88b26d469f1c05e Mon Sep 17 00:00:00 2001 From: isaacs Date: Sat, 30 Sep 2023 10:20:02 -0700 Subject: [PATCH] tcompare: include known non-enumerable Error properties Fix: https://github.com/tapjs/tapjs/issues/933 --- src/tcompare/src/format.ts | 14 ++++++-- .../tap-snapshots/test/format.tsx.test.cjs | 18 ++++++++++ src/tcompare/test/format.tsx | 33 +++++++++++++++++-- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/tcompare/src/format.ts b/src/tcompare/src/format.ts index 68cd26520..0d9c3cdb0 100644 --- a/src/tcompare/src/format.ts +++ b/src/tcompare/src/format.ts @@ -822,8 +822,16 @@ export class Format { } getPojoKeys(obj: any = this.object): PropertyKey[] { + const keys: PropertyKey[] = [] + // always include known non-enumerable properties of Error objects + if (obj instanceof Error) { + const known = ['errors', 'cause'] + for (const prop of known) { + const desc = Object.getOwnPropertyDescriptor(obj, prop) + if (desc && !desc.enumerable) keys.push(prop) + } + } if (this.options.includeEnumerable) { - const keys: PropertyKey[] = [] // optimized fast path, for/in over enumerable string keys only for (const i in obj) { keys.push(i) @@ -835,9 +843,9 @@ export class Format { return keys } - const keys: PropertyKey[] = this.#getPojoKeys(obj).concat( + keys.push(...this.#getPojoKeys(obj).concat( this.#getPojoKeys(obj, true) - ) + )) if (!this.options.includeGetters) { return keys } diff --git a/src/tcompare/tap-snapshots/test/format.tsx.test.cjs b/src/tcompare/tap-snapshots/test/format.tsx.test.cjs index 451bde392..26c363023 100644 --- a/src/tcompare/tap-snapshots/test/format.tsx.test.cjs +++ b/src/tcompare/tap-snapshots/test/format.tsx.test.cjs @@ -33,6 +33,24 @@ exports[`test/format.tsx > TAP > error without name/message > {} > tight > must new Object(undefined) ` +exports[`test/format.tsx > TAP > format aggregate errors and causes > must match snapshot 1`] = ` +AggregateError: aggregated errors { + "errors": Array [ + Error: xyz { + "cause": "something", + [Symbol.for(hsmusic.sugar.index)]: 1, + }, + AggregateError: agg agg { + "errors": Array [ + TypeError: blzhr { + [Symbol.for(hsmusic.sugar.index)]: "something", + }, + ], + }, + ], +} +` + exports[`test/format.tsx > TAP > format iterable > must match snapshot 1`] = ` And [ And [ diff --git a/src/tcompare/test/format.tsx b/src/tcompare/test/format.tsx index 0c1f5c6c4..c67a4db44 100644 --- a/src/tcompare/test/format.tsx +++ b/src/tcompare/test/format.tsx @@ -318,9 +318,12 @@ t.test('invalid iterator', t => { // looks like an array t.equal(f.isArray(), true) // until you try to format it - t.equal(f.print(), `Object { + t.equal( + f.print(), + `Object { [Symbol.iterator]: Function [Symbol.iterator](), -}`) +}` + ) // then it realizes it's actually not t.equal(f.isArray(), false) t.end() @@ -455,9 +458,33 @@ t.test('formatting jsx', t => { } t.matchSnapshot( format(lookAlike), - `children=${format(children).replace(/\n/g, ' ').replace(/ +/g, ' ').replace(/, ([}\]])/g, '$1').replace(/([\[{]) /g, '$1')}` + `children=${format(children) + .replace(/\n/g, ' ') + .replace(/ +/g, ' ') + .replace(/, ([}\]])/g, '$1') + .replace(/([\[{]) /g, '$1')}` ) } t.end() }) + +t.test('format aggregate errors and causes', t => { + const sym = Symbol.for('hsmusic.sugar.index') + const errors = [ + Object.assign(new Error('xyz', { cause: 'something' }), { + [sym]: 1, + }), + new AggregateError( + [ + Object.assign(new TypeError('blzhr'), { + [sym]: 'something', + }), + ], + 'agg agg' + ), + ] + const er = new AggregateError(errors, 'aggregated errors') + t.matchSnapshot(format(er)) + t.end() +})