Skip to content

Commit

Permalink
tcompare: include symbols in formatting/comparison
Browse files Browse the repository at this point in the history
Fix: #930
  • Loading branch information
isaacs committed Sep 29, 2023
1 parent 8344411 commit 86bc4d0
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 45 deletions.
85 changes: 65 additions & 20 deletions src/tcompare/src/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,27 @@ export class Format {
this.memo += 'null'
}

#printSymbol(sym: symbol): string {
const keyFor = Symbol.keyFor(sym)
const s = String(sym)
if (s.startsWith('Symbol(Symbol.')) {
// check to see if it's a key on the Symbol global.
// return Symbol.iterator, not Symbol(Symbol.iterator)
const symKey = s.substring(
'Symbol(Symbol.'.length,
s.length - 1
)
if (
symKey &&
Symbol[symKey as keyof SymbolConstructor] === sym
) {
return `Symbol.${symKey}`
}
}
return keyFor ? 'Symbol.for' + s.substring('Symbol'.length) : s
}
printSymbol(): void {
this.memo += this.object.toString()
this.memo += this.#printSymbol(this.object)
}

printBigInt(): void {
Expand Down Expand Up @@ -483,7 +502,9 @@ export class Format {
return this.parent && this.parent.isMap()
? this.style.mapKeyStart() +
this.parent.child(this.key, { isKey: true }, Format).print()
: JSON.stringify(this.key)
: typeof this.key === 'string'
? JSON.stringify(this.key)
: `[${this.#printSymbol(this.key)}]`
}

printCircular(seen: Format): void {
Expand Down Expand Up @@ -787,29 +808,51 @@ export class Format {
this.printPojoBody()
}

getPojoKeys(obj: any = this.object): string[] {
#getPojoKeys(obj: any, symbols = false): PropertyKey[] {
// fast path, own string props only
if (!symbols) {
return Object.keys(obj)
}
// get all enumerable symbols
const keys: symbol[] = Object.getOwnPropertySymbols(obj)
return keys.filter(
k => Object.getOwnPropertyDescriptor(obj, k)?.enumerable
)
}

getPojoKeys(obj: any = this.object): PropertyKey[] {
if (this.options.includeEnumerable) {
const keys = []
const keys: PropertyKey[] = []
// optimized fast path, for/in over enumerable string keys only
for (const i in obj) {
keys.push(i)
}
// walk up proto chain collecting all strings and symbols
for (let p = obj; p; p = Object.getPrototypeOf(p)) {
keys.push(...this.#getPojoKeys(p, true))
}
return keys
}

const keys: PropertyKey[] = this.#getPojoKeys(obj).concat(
this.#getPojoKeys(obj, true)
)
if (!this.options.includeGetters) {
return keys
} else if (this.options.includeGetters) {
const own = new Set(Object.keys(obj))
const proto = Object.getPrototypeOf(obj)
if (proto) {
const desc = Object.getOwnPropertyDescriptors(proto)
for (const [name, prop] of Object.entries(desc)) {
if (prop.enumerable && typeof prop.get === 'function') {
// public wrappers around internal things are worth showing
own.add(name)
}
}

const own = new Set(keys)
const proto = Object.getPrototypeOf(obj)
if (proto) {
const desc = Object.getOwnPropertyDescriptors(proto)
for (const [name, prop] of Object.entries(desc)) {
if (prop.enumerable && typeof prop.get === 'function') {
// public wrappers around internal things are worth showing
own.add(name)
}
}
return Array.from(own)
} else {
return Object.keys(obj)
}
return Array.from(own)
}

printPojo(): void {
Expand Down Expand Up @@ -847,16 +890,18 @@ export class Format {
}
}

getPojoEntries(obj: any): [string, any][] {
const ent: [string, any][] = this.getPojoKeys(obj).map(k => {
getPojoEntries(obj: any): [PropertyKey, any][] {
const ent: [PropertyKey, any][] = this.getPojoKeys(obj).map(k => {
try {
return [k, obj[k]]
} catch {
return [k, undefined]
}
})
return this.sort
? ent.sort((a, b) => a[0].localeCompare(b[0], 'en'))
? ent.sort((a, b) =>
String(a[0]).localeCompare(String(b[0]), 'en')
)
: ent
}

Expand Down
6 changes: 4 additions & 2 deletions src/tcompare/src/has.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ export class Has extends Same {
objKeys.push(k)
}
}
const ent: [string, any][] = objKeys
const ent: [PropertyKey, any][] = objKeys
.filter(k => expSet.has(k))
.map(k => [k, obj[k]])

return this.sort
? ent.sort((a, b) => a[0].localeCompare(b[0], 'en'))
? ent.sort((a, b) =>
String(a[0]).localeCompare(String(b[0]), 'en')
)
: ent
}

Expand Down
2 changes: 1 addition & 1 deletion src/tcompare/src/same.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ export class Same extends Format {
printPojoEmpty() {
// both are empty and not a simple mismatch, nothing to do
}
getPojoKeys(obj: any = this.object): string[] {
getPojoKeys(obj: any = this.object): PropertyKey[] {
const fromSuper = super.getPojoKeys(obj)
if (obj === this.expect) {
return fromSuper
Expand Down
64 changes: 57 additions & 7 deletions src/tcompare/tap-snapshots/test/format.tsx.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ exports[`test/format.tsx > TAP > formatting jsx > children=Array ["hello", <div>

exports[`test/format.tsx > TAP > formatting jsx > children=Array [<span> number is not a react node </span>, 7] 1`] = `
Object {
"$$typeof": Symbol(react.element),
"$$typeof": Symbol.for(react.element),
"type": "div",
"key": null,
"ref": null,
"props": Object {
"children": Array [
Object {
"$$typeof": Symbol(react.element),
"$$typeof": Symbol.for(react.element),
"type": "image",
"key": null,
"ref": null,
Expand Down Expand Up @@ -110,14 +110,14 @@ exports[`test/format.tsx > TAP > formatting jsx > children=Array [Array ["hello"

exports[`test/format.tsx > TAP > formatting jsx > children=Array [Object {"invalid": "not a react node"}] 1`] = `
Object {
"$$typeof": Symbol(react.element),
"$$typeof": Symbol.for(react.element),
"type": "div",
"key": null,
"ref": null,
"props": Object {
"children": Array [
Object {
"$$typeof": Symbol(react.element),
"$$typeof": Symbol.for(react.element),
"type": "image",
"key": null,
"ref": null,
Expand Down Expand Up @@ -152,14 +152,14 @@ exports[`test/format.tsx > TAP > formatting jsx > children=Array [Set {"hello",

exports[`test/format.tsx > TAP > formatting jsx > children=Object {"invalid": "not a react node"} 1`] = `
Object {
"$$typeof": Symbol(react.element),
"$$typeof": Symbol.for(react.element),
"type": "div",
"key": null,
"ref": null,
"props": Object {
"children": Array [
Object {
"$$typeof": Symbol(react.element),
"$$typeof": Symbol.for(react.element),
"type": "image",
"key": null,
"ref": null,
Expand Down Expand Up @@ -4076,6 +4076,8 @@ Hidden {
"raw": 1,
"value": 1,
"baseValue": 0,
[Symbol(_value)]: 1,
[Symbol(_baseValue)]: 0,
}
`

Expand All @@ -4088,6 +4090,8 @@ Null Object {
exports[`test/format.tsx > TAP > hidden props and getters > enumerable inherited getters shown 1`] = `
Hidden {
"raw": 1,
[Symbol(_value)]: 1,
[Symbol(_baseValue)]: 0,
"value": 1,
}
`
Expand All @@ -4101,6 +4105,8 @@ Null Object {
exports[`test/format.tsx > TAP > hidden props and getters > own props only 1`] = `
Hidden {
"raw": 1,
[Symbol(_value)]: 1,
[Symbol(_baseValue)]: 0,
}
`

Expand All @@ -4111,7 +4117,9 @@ Null Object {
`

exports[`test/format.tsx > TAP > invalid iterator > must match snapshot 1`] = `
Object {}
Object {
[Symbol.iterator]: Function [Symbol.iterator](),
}
`

exports[`test/format.tsx > TAP > locale sorting > must match snapshot 1`] = `
Expand Down Expand Up @@ -4148,6 +4156,28 @@ Minipass {
"_maxListeners": undefined,
"writable": false,
"readable": true,
[Symbol(kCapture)]: false,
[Symbol(flowing)]: false,
[Symbol(paused)]: false,
[Symbol(pipes)]: Array [],
[Symbol(buffer)]: Array [
Buffer <6865 6c6c 6f hello>,
],
[Symbol(objectMode)]: false,
[Symbol(encoding)]: null,
[Symbol(async)]: false,
[Symbol(decoder)]: null,
[Symbol(EOF)]: true,
[Symbol(emittedEnd)]: false,
[Symbol(emittingEnd)]: false,
[Symbol(closed)]: false,
[Symbol(emittedError)]: null,
[Symbol(bufferLength)]: 5,
[Symbol(destroyed)]: false,
[Symbol(signal)]: undefined,
[Symbol(aborted)]: false,
[Symbol(dataListeners)]: 0,
[Symbol(discarded)]: false,
}
`

Expand All @@ -4159,5 +4189,25 @@ Minipass {
"writable": true,
"readable": true,
"pipe": null,
[Symbol(kCapture)]: false,
[Symbol(flowing)]: false,
[Symbol(paused)]: false,
[Symbol(pipes)]: Array [],
[Symbol(buffer)]: Array [],
[Symbol(objectMode)]: false,
[Symbol(encoding)]: null,
[Symbol(async)]: false,
[Symbol(decoder)]: null,
[Symbol(EOF)]: false,
[Symbol(emittedEnd)]: false,
[Symbol(emittingEnd)]: false,
[Symbol(closed)]: false,
[Symbol(emittedError)]: null,
[Symbol(bufferLength)]: 0,
[Symbol(destroyed)]: false,
[Symbol(signal)]: undefined,
[Symbol(aborted)]: false,
[Symbol(dataListeners)]: 0,
[Symbol(discarded)]: false,
}
`
2 changes: 1 addition & 1 deletion src/tcompare/tap-snapshots/test/match.ts.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ exports[`test/match.ts > TAP > symbology > must match snapshot 6`] = `
+++ actual
@@ -1,3 +1,3 @@
Object {
- "a": Symbol(a),
- "a": Symbol.for(a),
+ "a": "Symbol(a)",
}
Expand Down
16 changes: 9 additions & 7 deletions src/tcompare/tap-snapshots/test/same.tsx.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -469,8 +469,10 @@ exports[`test/same.tsx > TAP > iterables match one another > must match snapshot
exports[`test/same.tsx > TAP > iterator that doesnt play nice > must match snapshot 1`] = `
--- expected
+++ actual
@@ -1,1 +1,5 @@
-Object {}
@@ -1,3 +1,5 @@
-Object {
- [Symbol.iterator]: GeneratorFunction [Symbol.iterator](),
-}
+Array [
+ 1,
+ 2,
Expand Down Expand Up @@ -711,7 +713,7 @@ exports[`test/same.tsx > TAP > react > array nesting relevant when not using rea
- [
- [
- {
- "$$typeof": Symbol(react.element),
- "$$typeof": Symbol.for(react.element),
- "type": "img",
- "key": null,
- "ref": null,
Expand All @@ -724,7 +726,7 @@ exports[`test/same.tsx > TAP > react > array nesting relevant when not using rea
- ],
- ],
- ],
+ "$$typeof": Symbol(react.element),
+ "$$typeof": Symbol.for(react.element),
+ "type": "img",
+ "props": {
+ "src": "foo",
Expand All @@ -747,7 +749,7 @@ exports[`test/same.tsx > TAP > react > array nesting relevant when not using rea
- Array [
- Array [
- Object {
- "$$typeof": Symbol(react.element),
- "$$typeof": Symbol.for(react.element),
- "type": "img",
- "key": null,
- "ref": null,
Expand All @@ -760,7 +762,7 @@ exports[`test/same.tsx > TAP > react > array nesting relevant when not using rea
- ],
- ],
- ],
+ "$$typeof": Symbol(react.element),
+ "$$typeof": Symbol.for(react.element),
+ "type": "img",
+ "props": Object {
+ "src": "foo",
Expand Down Expand Up @@ -1131,7 +1133,7 @@ exports[`test/same.tsx > TAP > symbology > must match snapshot 6`] = `
+++ actual
@@ -1,3 +1,3 @@
Object {
- "a": Symbol(a),
- "a": Symbol.for(a),
+ "a": "Symbol(a)",
}
Expand Down
9 changes: 8 additions & 1 deletion src/tcompare/tap-snapshots/test/strict.ts.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,13 @@ exports[`test/strict.ts > TAP > symbology > must match snapshot 3`] = `
`

exports[`test/strict.ts > TAP > symbology > must match snapshot 4`] = `
--- expected
+++ actual
@@ -1,3 +1,3 @@
Object {
- "a": Symbol.for(a),
+ "a": Symbol(a),
}
`

Expand Down Expand Up @@ -601,7 +608,7 @@ exports[`test/strict.ts > TAP > symbology > must match snapshot 8`] = `
+++ actual
@@ -1,3 +1,3 @@
Object {
- "a": Symbol(a),
- "a": Symbol.for(a),
+ "a": "Symbol(a)",
}
Expand Down
4 changes: 3 additions & 1 deletion src/tcompare/test/format.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,9 @@ 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()
Expand Down
Loading

0 comments on commit 86bc4d0

Please sign in to comment.