diff --git a/__tests__/regressions.js b/__tests__/regressions.js index 618ae286..8590b1a0 100644 --- a/__tests__/regressions.js +++ b/__tests__/regressions.js @@ -274,6 +274,32 @@ function runBaseTest(name, useProxies, autoFreeze, useListener) { ]) }) + for (const modified of [false, true]) { + test.each(["default", NaN])( + `assigning an inherited value creates an own property (modified: ${modified}, value: %s)`, + value => { + const proto = {[immerable]: true, name: value} + const state = Object.create(proto) + + const [newState, patches, inversePatches] = produceWithPatches( + state, + draft => { + if (modified) draft.changed = true + draft.name = value + } + ) + + expect(newState).not.toBe(state) + expect(Object.hasOwnProperty.call(state, "name")).toBe(false) + expect(Object.hasOwnProperty.call(newState, "name")).toBe(true) + expect(newState.name).toBe(value) + expect(Object.getPrototypeOf(newState)).toBe(proto) + expect(patches).toContainEqual({op: "add", path: ["name"], value}) + expect(inversePatches).toContainEqual({op: "remove", path: ["name"]}) + } + ) + } + test("Nested and chained produce calls throw 'Cannot perform 'get' on a proxy that has been revoked' error", () => { const state = { foo: { diff --git a/src/core/proxy.ts b/src/core/proxy.ts index 70e95ad5..6bf0db31 100644 --- a/src/core/proxy.ts +++ b/src/core/proxy.ts @@ -179,8 +179,7 @@ export const objectTraps: ProxyHandler = { return true } if (!state.modified_) { - // the last check is because we need to be able to distinguish setting a non-existing to undefined (which is a change) - // from setting an existing property with value undefined to undefined (which is not a change) + // Assigning an inherited value creates an own property, even when the value is unchanged. const current = peek(latest(state), prop) // special case, if we assigning the original value to a draft, we can ignore the assignment const currentState: ProxyObjectState = current?.[DRAFT_STATE] @@ -189,21 +188,16 @@ export const objectTraps: ProxyHandler = { state.assigned_!.set(prop, false) return true } - if ( - is(value, current) && - (value !== undefined || has(state.base_, prop, state.type_)) - ) - return true + if (is(value, current) && has(state.base_, prop, state.type_)) return true prepareCopy(state) markChanged(state) } if ( - (state.copy_![prop] === value && - // special case: handle new props with value 'undefined' - (value !== undefined || has(state.copy_, prop, state.type_))) || - // special case: NaN - (Number.isNaN(value) && Number.isNaN(state.copy_![prop])) + (state.copy_![prop] === value || + // special case: NaN + (Number.isNaN(value) && Number.isNaN(state.copy_![prop]))) && + has(state.copy_, prop, state.type_) ) return true