fix: drop getters and setters when diffing objects for error#9757
fix: drop getters and setters when diffing objects for error#9757SimenB merged 14 commits intojestjs:masterfrom
Conversation
| ### Features | ||
|
|
||
| ### Fixes | ||
|
|
There was a problem hiding this comment.
CI is failing on master due to this missing newline, snuck it in here
|
|
||
| if (!('set' in descriptor)) { | ||
| if ('set' in descriptor) { | ||
| descriptor.set = undefined; |
There was a problem hiding this comment.
not sure if setting to undefined or deleting it makes the most sense
| if ('set' in descriptor) { | ||
| descriptor.set = undefined; | ||
| } else { | ||
| descriptor.writable = true; |
There was a problem hiding this comment.
This should be outside a condition? You could have a frozen object with a setter on it, right?
There was a problem hiding this comment.
We cannot set writable if there's a getter or a setter. So thanks for the question, we still failed if there was a getter but no setter 🙂 fixed and added a test
Object.defineProperty({}, 'foo', {set(_){}, writable: true}) // Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
Object.defineProperty({}, 'foo', {get(){}, writable: true}) // Invalid property descriptor. Cannot both specify accessors and a value or writable attributeThere was a problem hiding this comment.
Or did you have another example in mind?
There was a problem hiding this comment.
Added some more tests. With both getter and setter, only getter and only setter, both wrapped in Object.freeze and not.
|
I've tried a bit to construct another failure case with a |
Codecov Report
@@ Coverage Diff @@
## master #9757 +/- ##
=======================================
Coverage 64.90% 64.90%
=======================================
Files 288 288
Lines 12195 12190 -5
Branches 3022 3020 -2
=======================================
- Hits 7915 7912 -3
+ Misses 3639 3638 -1
+ Partials 641 640 -1
Continue to review full report at Codecov.
|
| }); | ||
| Object.getOwnPropertySymbols(this.object).forEach(key => { | ||
| const descriptor = Object.getOwnPropertyDescriptor(this.object, key); | ||
| if ((descriptor as PropertyDescriptor).enumerable) { |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
| configurable: true, | ||
| enumerable: true, | ||
| value: deepCyclicCopyReplaceable( | ||
| (object as Record<string, unknown>)[key], |
|
Hmm. I must be doing something wrong. I checked out your PR and removed all the implementation changes, but the tests still pass. What am I doing wrong? I have both |
|
@gaearon my bad, I simplified the tests so much that they didn't actually fail on master anymore 😬 Should be fixed now. I also deleted a test that verified getters weren't called (as we explicitly want that now, as per your suggestion) |
| } | ||
|
|
||
| descriptor.configurable = true; | ||
| descriptors[key] = { |
There was a problem hiding this comment.
Can the enumerable check be here instead?
if (descriptors[key].enumerable) {
descriptors[key] = ...
} else {
delete descriptors[key];
}There was a problem hiding this comment.
At least if I understood your comment correcly
diff --git i/packages/jest-matcher-utils/src/Replaceable.ts w/packages/jest-matcher-utils/src/Replaceable.ts
index fc8c7343c..dd9e04f32 100644
--- i/packages/jest-matcher-utils/src/Replaceable.ts
+++ w/packages/jest-matcher-utils/src/Replaceable.ts
@@ -35,12 +35,6 @@ export default class Replaceable {
Object.entries(this.object).forEach(([key, value]) => {
cb(value, key, this.object);
});
- Object.getOwnPropertySymbols(this.object).forEach(key => {
- const descriptor = Object.getOwnPropertyDescriptor(this.object, key);
- if (descriptor?.enumerable) {
- cb(this.object[key], key, this.object);
- }
- });
} else {
this.object.forEach(cb);
}
diff --git i/packages/jest-matcher-utils/src/deepCyclicCopyReplaceable.ts w/packages/jest-matcher-utils/src/deepCyclicCopyReplaceable.ts
index ad98131dd..67271d5d8 100644
--- i/packages/jest-matcher-utils/src/deepCyclicCopyReplaceable.ts
+++ w/packages/jest-matcher-utils/src/deepCyclicCopyReplaceable.ts
@@ -56,15 +56,19 @@ function deepCyclicCopyObject<T>(object: T, cycles: WeakMap<any, any>): T {
cycles.set(object, newObject);
Object.keys(descriptors).forEach(key => {
- descriptors[key] = {
- configurable: true,
- enumerable: true,
- value: deepCyclicCopyReplaceable(
- (object as Record<string, unknown>)[key],
- cycles,
- ),
- writable: true,
- };
+ if (descriptors[key].enumerable) {
+ descriptors[key] = descriptors[key] = {
+ configurable: true,
+ enumerable: true,
+ value: deepCyclicCopyReplaceable(
+ (object as Record<string, unknown>)[key],
+ cycles,
+ ),
+ writable: true,
+ };
+ } else {
+ delete descriptors[key];
+ }
});
return Object.defineProperties(newObject, descriptors);There was a problem hiding this comment.
You'd still need the Object.getOwnPropertySymbols call but you can lose the if (descriptor?.enumerable) { check because we would've not copied those over in the first place.
|
I think our longer term aspiration should be that let i = 0;
expect({
get a() {
i++;
return i;
},
}).toEqual({
a: 1
});should pass. But that's out of scope for sure. (I verified it fails on 24.) |
|
@jeysal just noticed the discussion you had going in the comments 😅 I'm not able to make the tests pass unless I use the current form of the code. Could you experiment with it? I like copying over the Although I guess it doesn't really matter if we call the getter here or in |
| expect(deepCyclicCopyReplaceable(fn)).toBe(fn); | ||
| }); | ||
|
|
||
| test('does not execute getters/setters, but copies them', () => { |
There was a problem hiding this comment.
So it's not just in the comments - we invoke the getters on purpose now, so this test doesn't make sense anymore
My concern with this is — are you going to call it on the right object later? What if the getter reads from |
| expect(cb.mock.calls[2]).toEqual([3, symbolKey, object]); | ||
| }); | ||
|
|
||
| test('object forEach do not iterate none enumerable symbol key', () => { |
Yeah, it's called by |
|
As long as the getter is called on the original object, seems fine to me. Let's add a test for that? |
|
Let's keep it as is now - I don't think copying the getter rather than calling it is observable to the user anyways |
…pshots * upstream/master: (225 commits) docs: add CLA link to contributing docs (jestjs#9789) chore: roll new version of docs v25.3.0 chore: update changelog for release chore(jest-types): correct type testRegex for ProjectConfig (jestjs#9780) feat(circus): enable writing async test event handlers (jestjs#9397) feat: enable all babel syntax plugins (jestjs#9774) chore: add helper for getting Jest's config in e2e tests (jestjs#9770) feat: pass ESM options to transformers (jestjs#9597) chore: replace `any`s with `unknown`s (jestjs#9626) feat: pass ESM options to Babel (jestjs#9766) chore(website): add copy button the code blocks (jestjs#9750) chore: bump istanbul-reports for new uncovered lines design (jestjs#9758) chore: correct CHANGELOG.md (jestjs#9763) chore(jest-types): expose type `CacheKeyOptions` for `getCacheK… (jestjs#9762) docs: Fix simple typo, seperated -> separated (jestjs#9760) v25.2.7 chore: update changelog for release fix: drop getters and setters when diffing objects for error (jestjs#9757) chore(jest-types): correct return type of shouldRunTestSuite fo… (jestjs#9753) ...
|
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |


Summary
Fixes #9745. Not sure if it makes sense to drop the fix from #9575? I can check if it's
writeableat the same time I now check for a setter, which makes the test added at that time pass./cc @WeiAnAn
Test plan
Unit test added.