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: #768 immerable field being lost during patch value cloning #771

Merged
merged 1 commit into from Mar 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
56 changes: 54 additions & 2 deletions __tests__/empty.ts
Expand Up @@ -2,9 +2,11 @@ import {
produce,
produceWithPatches,
setUseProxies,
enableAllPlugins
enableAllPlugins,
immerable,
applyPatches
} from "../src/immer"
import {DRAFT_STATE} from "../src/internal"
import {DRAFT_STATE, Patch} from "../src/internal"

enableAllPlugins()

Expand Down Expand Up @@ -149,3 +151,53 @@ function createBaseState() {
}
return data
}

describe("#768", () => {
class Stock {
[immerable] = true

constructor(public price: number) {}

pushPrice(price: number) {
this.price = price
}
}

type State = {
stock: Stock
}

test("bla", () => {
// Set up conditions to produce the error
const errorProducingPatch = [
{
op: "replace",
path: ["stock"],
value: new Stock(200)
}
] as Patch[]

// Start with modified state
const state = {
stock: new Stock(100)
}

expect(state.stock.price).toEqual(100)
expect(state.stock[immerable]).toBeTruthy()
// Use patch to "replace" stocks
debugger
const resetState: State = applyPatches(state, errorProducingPatch)
expect(state.stock.price).toEqual(100)
expect(resetState.stock.price).toEqual(200)
expect(resetState.stock[immerable]).toBeTruthy()

// Problems come in when resetState is modified
const updatedState = produce(resetState, draft => {
draft.stock.pushPrice(300)
})
expect(state.stock.price).toEqual(100)
expect(updatedState.stock.price).toEqual(300)
expect(updatedState.stock[immerable]).toBeTruthy()
expect(resetState.stock.price).toEqual(200)
})
})
3 changes: 2 additions & 1 deletion __tests__/tsconfig.json
Expand Up @@ -3,6 +3,7 @@
"compilerOptions": {
"lib": ["es2015"],
"strict": true,
"noUnusedLocals": false
"noUnusedLocals": false,
"target": "ES5"
}
}
6 changes: 3 additions & 3 deletions src/core/immerClass.ts
Expand Up @@ -185,7 +185,7 @@ export class Immer implements ProducersFns {
this.useProxies_ = value
}

applyPatches(base: Objectish, patches: Patch[]) {
applyPatches<T extends Objectish>(base: Objectish, patches: Patch[]): T {
// If a patch replaces the entire state, take that replacement as base
// before applying patches
let i: number
Expand All @@ -200,12 +200,12 @@ export class Immer implements ProducersFns {
const applyPatchesImpl = getPlugin("Patches").applyPatches_
if (isDraft(base)) {
// N.B: never hits if some patch a replacement, patches are never drafts
return applyPatchesImpl(base, patches)
return applyPatchesImpl(base, patches) as any
}
// Otherwise, produce a copy of the base state.
return this.produce(base, (draft: Drafted) =>
applyPatchesImpl(draft, patches.slice(i + 1))
)
) as any
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/plugins/patches.ts
@@ -1,3 +1,4 @@
import {immerable} from "../immer"
import {
ImmerState,
Patch,
Expand Down Expand Up @@ -287,6 +288,7 @@ export function enablePatches() {
if (isSet(obj)) return new Set(Array.from(obj).map(deepClonePatchValue))
const cloned = Object.create(Object.getPrototypeOf(obj))
for (const key in obj) cloned[key] = deepClonePatchValue(obj[key])
if (has(obj, immerable)) cloned[immerable] = obj[immerable]
return cloned
}

Expand Down