Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix diffing object contain readonly symbol key object #10414

1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@

- `[jest-reporters]` Fixes notify reporter on Linux (using notify-send) ([#10393](https://github.com/facebook/jest/pull/10400))
- `[jest-snapshot]` Correctly handles arrays and property matchers in snapshots ([#10404](https://github.com/facebook/jest/pull/10404))
- `[jest-matcher-utils]` Fix diffing object contain readonly symbol key object ([#10414](https://github.com/facebook/jest/pull/10414))
WeiAnAn marked this conversation as resolved.
Show resolved Hide resolved

### Chore & Maintenance

Expand Down
Expand Up @@ -198,3 +198,16 @@ exports[`printDiffOrStringify has no common after clean up chaff one-line 1`] =
Expected: <g>"delete"</>
Received: <r>"insert"</>
`;

exports[`printDiffOrStringify object contain readonly symbol key object 1`] = `
<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Object {</>
<g>- "b": 2,</>
<r>+ "b": 1,</>
<d> Symbol(key): Object {</>
<d> "a": 1,</>
<d> },</>
<d> }</>
`;
Expand Up @@ -113,3 +113,22 @@ test('return same value for built-in object type except array, map and object',
expect(deepCyclicCopyReplaceable(regexp)).toBe(regexp);
expect(deepCyclicCopyReplaceable(set)).toBe(set);
});

test('should copy object symbol key property', () => {
const symbolKey = Symbol.for('key');
expect(deepCyclicCopyReplaceable({[symbolKey]: 1})).toEqual({[symbolKey]: 1});
});

test('should set writable, configurable to true', () => {
const a = {};
Object.defineProperty(a, 'key', {
configurable: false,
enumerable: true,
value: 1,
writable: false,
});
const copied = deepCyclicCopyReplaceable(a);
expect(Object.getOwnPropertyDescriptors(copied)).toEqual({
key: {configurable: true, enumerable: true, value: 1, writable: true},
});
});
Expand Up @@ -50,6 +50,29 @@ describe('printDiffOrStringify', () => {
expect(testDiffOrStringify(expected, received)).toMatchSnapshot();
});

test('object contain readonly symbol key object', () => {
const expected = {b: 2};
const received = {b: 1};
const symbolKey = Symbol.for('key');
Object.defineProperty(expected, symbolKey, {
configurable: true,
enumerable: true,
value: {
a: 1,
},
writable: false,
});
Object.defineProperty(received, symbolKey, {
configurable: true,
enumerable: true,
value: {
a: 1,
},
writable: false,
});
expect(testDiffOrStringify(expected, received)).toMatchSnapshot();
});

describe('MAX_DIFF_STRING_LENGTH', () => {
const lessChange = INVERTED_COLOR('single ');
const less = 'single line';
Expand Down
52 changes: 34 additions & 18 deletions packages/jest-matcher-utils/src/deepCyclicCopyReplaceable.ts
Expand Up @@ -55,25 +55,41 @@ function deepCyclicCopyObject<T>(object: T, cycles: WeakMap<any, any>): T {

cycles.set(object, newObject);

Object.keys(descriptors).forEach(key => {
if (descriptors[key].enumerable) {
descriptors[key] = {
configurable: true,
enumerable: true,
value: deepCyclicCopyReplaceable(
// this accesses the value or getter, depending. We just care about the value anyways, and this allows us to not mess with accessors
// it has the side effect of invoking the getter here though, rather than copying it over
(object as Record<string, unknown>)[key],
cycles,
),
writable: true,
};
} else {
delete descriptors[key];
}
const filterEnumerable = (descriptors: {[x: string]: PropertyDescriptor}) => (
key: string,
) => descriptors[key].enumerable;
const setDescriptor = (object: {[x: string]: any}) => (
newDescriptors: {[x: string]: PropertyDescriptor},
key: string,
) => {
newDescriptors[key] = {
configurable: true,
enumerable: true,
value: deepCyclicCopyReplaceable(
// this accesses the value or getter, depending. We just care about the value anyways, and this allows us to not mess with accessors
// it has the side effect of invoking the getter here though, rather than copying it over
(object as Record<string | symbol, unknown>)[key],
cycles,
),
writable: true,
};
return newDescriptors;
};

const newStringKeyDescriptors = Object.keys(descriptors)
.filter(filterEnumerable(descriptors))
.reduce(setDescriptor(object), {});
const newSymbolKeyDescriptors = Object.getOwnPropertySymbols(descriptors)
//@ts-expect-error because typescript do not support symbol key in object
WeiAnAn marked this conversation as resolved.
Show resolved Hide resolved
.filter(filterEnumerable(descriptors))
//@ts-expect-error because typescript do not support symbol key in object
.reduce(setDescriptor(object), {});

return Object.defineProperties(newObject, {
...newStringKeyDescriptors,
//@ts-expect-error because typescript do not support symbol key in object
...newSymbolKeyDescriptors,
});

return Object.defineProperties(newObject, descriptors);
}

function deepCyclicCopyArray<T>(array: Array<T>, cycles: WeakMap<any, any>): T {
Expand Down