diff --git a/__tests__/__prod_snapshots__/base.js.snap b/__tests__/__prod_snapshots__/base.js.snap index 84c5e6b2..146f5669 100644 --- a/__tests__/__prod_snapshots__/base.js.snap +++ b/__tests__/__prod_snapshots__/base.js.snap @@ -8,6 +8,22 @@ exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener= exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false recipe functions cannot return an object that references itself 1`] = `"Maximum call stack size exceeded"`; +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false set drafts revokes sets 1`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false set drafts revokes sets 2`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; @@ -26,6 +42,22 @@ exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener= exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true recipe functions cannot return an object that references itself 1`] = `"Maximum call stack size exceeded"`; +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true set drafts revokes sets 1`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true set drafts revokes sets 2`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; @@ -44,6 +76,22 @@ exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=f exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false recipe functions cannot return an object that references itself 1`] = `"Maximum call stack size exceeded"`; +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false set drafts revokes sets 1`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false set drafts revokes sets 2`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; @@ -62,6 +110,22 @@ exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=t exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true recipe functions cannot return an object that references itself 1`] = `"Maximum call stack size exceeded"`; +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true set drafts revokes sets 1`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true set drafts revokes sets 2`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; @@ -80,6 +144,22 @@ exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=f exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false recipe functions cannot return an object that references itself 1`] = `"Maximum call stack size exceeded"`; +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false set drafts revokes sets 1`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false set drafts revokes sets 2`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; @@ -98,6 +178,22 @@ exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=t exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true recipe functions cannot return an object that references itself 1`] = `"Maximum call stack size exceeded"`; +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true set drafts revokes sets 1`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true set drafts revokes sets 2`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; @@ -116,6 +212,22 @@ exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=fa exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false recipe functions cannot return an object that references itself 1`] = `"Maximum call stack size exceeded"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false set drafts revokes sets 1`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false set drafts revokes sets 2`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; @@ -134,6 +246,22 @@ exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=tr exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true recipe functions cannot return an object that references itself 1`] = `"Maximum call stack size exceeded"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true set drafts revokes sets 1`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true set drafts revokes sets 2`] = `"[Immer] minified error nr: 3. Full error at: https://bit.ly/3cXEKWf"`; diff --git a/__tests__/__prod_snapshots__/manual.js.snap b/__tests__/__prod_snapshots__/manual.js.snap index 40315388..4b055da7 100644 --- a/__tests__/__prod_snapshots__/manual.js.snap +++ b/__tests__/__prod_snapshots__/manual.js.snap @@ -2,6 +2,8 @@ exports[`manual - proxy cannot finishDraft twice 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; +exports[`manual - proxy cannot modify after finish 1`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + exports[`manual - proxy should check arguments 1`] = `"[Immer] minified error nr: 8. Full error at: https://bit.ly/3cXEKWf"`; exports[`manual - proxy should check arguments 2`] = `"[Immer] minified error nr: 8. Full error at: https://bit.ly/3cXEKWf"`; diff --git a/__tests__/__snapshots__/base.js.snap b/__tests__/__snapshots__/base.js.snap index c545cde6..496056c0 100644 --- a/__tests__/__snapshots__/base.js.snap +++ b/__tests__/__snapshots__/base.js.snap @@ -38,6 +38,82 @@ exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener= exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; @@ -76,6 +152,82 @@ exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener= exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; @@ -114,6 +266,82 @@ exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=f exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; @@ -152,6 +380,82 @@ exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=t exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; @@ -190,6 +494,82 @@ exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=f exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; @@ -228,6 +608,82 @@ exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=t exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; @@ -266,43 +722,947 @@ exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=fa exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; -exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true array drafts throws when a non-numeric property is added 1`] = `"[Immer] Immer only supports setting array indices and the 'length' property"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true array drafts throws when a non-numeric property is deleted 1`] = `"[Immer] Immer only supports deleting array indices"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true map drafts revokes map proxies 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true map drafts revokes map proxies 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true recipe functions cannot return a modified child draft 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true recipe functions cannot return an object that references itself 1`] = `"[Immer] Immer forbids circular references"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 1`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 2`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 3`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 4`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 5`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 6`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 7`] = `"Cannot perform 'get' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true revokes the draft once produce returns 8`] = `"Cannot perform 'set' on a proxy that has been revoked"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true set drafts revokes sets 1`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true set drafts revokes sets 2`] = `"[Immer] Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? {}"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true throws when Object.defineProperty() is used on drafts 1`] = `"[Immer] Object.defineProperty() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true throws when Object.setPrototypeOf() is used on a draft 1`] = `"[Immer] Object.setPrototypeOf() cannot be used on an Immer draft"`; + +exports[`base functionality - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true throws when the draft is modified and another object is returned 1`] = `"[Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft."`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=false modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=false:use-listener=false:allow-multi-refs=true modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=false modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=false:use-listener=true:allow-multi-refs=true modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=false modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=true:use-listener=false:allow-multi-refs=true modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=false modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=false:shallow-copy=true:use-listener=true:allow-multi-refs=true modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=false modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=false:use-listener=false:allow-multi-refs=true modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=false modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=false:use-listener=true:allow-multi-refs=true modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=false modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=true:use-listener=false:allow-multi-refs=true modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=false modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true modify deep object 1`] = ` +{ + "map": Map { + "set1" => Set { + { + "a": 2, + }, + { + "b": 2, + }, + }, + "set2" => Set { + { + "c": 3, + }, + }, + }, +} +`; + +exports[`complex nesting map / set / object - auto-freeze=true:shallow-copy=true:use-listener=true:allow-multi-refs=true modify deep object 2`] = ` +[ + { + "op": "remove", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 1, + }, + }, + { + "op": "add", + "path": [ + "map", + "set1", + 0, + ], + "value": { + "a": 2, + }, + }, +] +`; exports[`complex nesting map / set / object modify deep object 1`] = ` { diff --git a/__tests__/base.js b/__tests__/base.js index 6cfc05c2..2bd1b6dc 100644 --- a/__tests__/base.js +++ b/__tests__/base.js @@ -26,21 +26,26 @@ test("immer should have no dependencies", () => { for (const autoFreeze of [true, false]) { for (const useStrictShallowCopy of [true, false]) { for (const useListener of [true, false]) { - const name = `${autoFreeze ? "auto-freeze=true" : "auto-freeze=false"}:${ - useStrictShallowCopy ? "shallow-copy=true" : "shallow-copy=false" - }:${useListener ? "use-listener=true" : "use-listener=false"}` - runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener) + for (const allowMultiRefs of [true, false]) { + const name = `${autoFreeze ? "auto-freeze=true" : "auto-freeze=false"}:${ + useStrictShallowCopy ? "shallow-copy=true" : "shallow-copy=false" + }:${useListener ? "use-listener=true" : "use-listener=false"}:${ + allowMultiRefs ? "allow-multi-refs=true" : "allow-multi-refs=false" + }` + runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener, allowMultiRefs) + } } } } class Foo {} -function runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener) { +function runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener, allowMultiRefs) { const listener = useListener ? function() {} : undefined const {produce, produceWithPatches} = createPatchedImmer({ autoFreeze, - useStrictShallowCopy + useStrictShallowCopy, + allowMultiRefs, }) // When `useListener` is true, append a function to the arguments of every @@ -1033,7 +1038,7 @@ function runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener) { }) }) - it("optimization: does not visit properties of new data structures if autofreeze is disabled and no drafts are unfinalized", () => { + it("optimization: does not visit properties of new data structures if autofreeze and multiref are disabled and no drafts are unfinalized", () => { const newData = {} Object.defineProperty(newData, "x", { enumerable: true, @@ -1046,7 +1051,7 @@ function runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener) { produce({}, d => { d.data = newData }) - if (autoFreeze) { + if (autoFreeze || allowMultiRefs) { expect(run).toThrow("visited!") } else { expect(run).not.toThrow("visited!") @@ -1426,19 +1431,29 @@ function runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener) { // "Upvalues" are variables from a parent scope. it("does not finalize upvalue drafts", () => { produce({a: {}, b: {}}, parent => { - expect(produce({}, () => parent)).toBe(parent) - parent.x // Ensure proxy not revoked. + const producedParent1 = produce({}, () => parent); + expect(()=>parent.x).not.toThrow() // Ensure proxy not revoked. + expect(()=>producedParent1.x).not.toThrow() // Ensure proxy not revoked. + expect(Object.is(parent, producedParent1)).toBe(true) + - expect(produce({}, () => [parent])[0]).toBe(parent) - parent.x // Ensure proxy not revoked. + const producedParent2 = produce({}, () => [parent][0]); + expect(()=>parent.x).not.toThrow() // Ensure proxy not revoked. + expect(()=>producedParent2.x).not.toThrow() // Ensure proxy not revoked. + expect(Object.is(parent, producedParent2)).toBe(true) - expect(produce({}, () => parent.a)).toBe(parent.a) - parent.a.x // Ensure proxy not revoked. + const producedChildA = produce({}, () => parent.a) + expect(()=>parent.a.x).not.toThrow() // Ensure proxy not revoked. + expect(()=>producedChildA.x).not.toThrow() // Ensure proxy not revoked. + expect(Object.is(parent.a, producedChildA)).toBe(true) // Modified parent test parent.c = 1 - expect(produce({}, () => [parent.b])[0]).toBe(parent.b) - parent.b.x // Ensure proxy not revoked. + + const producedChildB = produce({}, () => [parent.b])[0] + expect(()=>parent.b.x).not.toThrow() // Ensure proxy not revoked. + expect(()=>producedChildB.x).not.toThrow() // Ensure proxy not revoked. + expect(Object.is(parent.b, producedChildB)).toBe(true) }) }) @@ -1522,7 +1537,7 @@ function runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener) { expect(world.inc(world).counter.count).toBe(2) }) - it("doesnt recurse into frozen structures if external data is frozen", () => { + it("doesnt recurse into frozen structures if external data is frozen and is not multiref", () => { const frozen = {} Object.defineProperty(frozen, "x", { get() { @@ -1533,11 +1548,14 @@ function runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener) { }) Object.freeze(frozen) - expect(() => { + const expectTest = expect(() => { produce({}, d => { d.x = frozen }) - }).not.toThrow() + }) + + if (allowMultiRefs) expectTest.toThrow("oops") + else expectTest.not.toThrow("oops") }) // See here: https://github.com/mweststrate/immer/issues/89 @@ -1702,11 +1720,12 @@ function runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener) { const next = produce(base, d => { return [d.a, d.a] }) + console.log(base, next); expect(next[0]).toBe(base.a) expect(next[0]).toBe(next[1]) }) - it("cannot return an object that references itself", () => { + if (!allowMultiRefs) it("cannot return an object that references itself", () => { const res = {} res.self = res expect(() => { @@ -2030,7 +2049,7 @@ function runBaseTest(name, autoFreeze, useStrictShallowCopy, useListener) { }) }) - describe(`complex nesting map / set / object`, () => { + describe(`complex nesting map / set / object - ${name}`, () => { const a = {a: 1} const b = {b: 2} const c = {c: 3} diff --git a/__tests__/multiref.ts b/__tests__/multiref.ts new file mode 100644 index 00000000..b5e25ead --- /dev/null +++ b/__tests__/multiref.ts @@ -0,0 +1,358 @@ +import {Immer, enableMapSet} from "../src/immer" +import {inspect} from "util" +import * as v8 from "v8" + +// Implementation note: TypeScript says ES5 doesn't support iterating directly over a Set so I've used Array.from(). +// If the project is moved to a later JS feature set, we can drop the Array.from() and do `for (const value of ref)` instead. + +test("modified circular object", () => { + const immer = new Immer({allowMultiRefs: true}) + const base = {a: 1, b: null} as any + base.b = base + + for (const env of ["production", "development", "testing"]) { + process.env.NODE_ENV = env + expect(() => { + const next = immer.produce(base, (draft: any) => { + draft.a = 2 + }) + expect(next).toEqual({a: 2, b: next}) + }).not.toThrow() + } +}) + +test("unmodified circular object", () => { + const immer = new Immer({allowMultiRefs: true}) + const base = {a: 1, b: null} as any + base.b = base + + for (const env of ["production", "development", "testing"]) { + process.env.NODE_ENV = env + expect(() => { + const next = immer.produce({state: null}, (draft: any) => { + draft.state = base + }) + expect(next.state).toBe(base) + }).not.toThrow() + } +}) + +test("circular object using a draft and a non-draft", () => { + const immer = new Immer({allowMultiRefs: true}) + const baseState: Record = { + someValue: 'abcd', + state1: null, + state2: {state: null}, + state3: {state_: {state: null}}, + state4: {state__: {state_: {state: null}}}, + } + baseState.state1 = baseState + baseState.state2.state = baseState + baseState.state3.state_.state = baseState + baseState.state4.state__.state_.state = baseState + + for (const env of ["production", "development", "testing"]) { + process.env.NODE_ENV = env + v8.setFlagsFromString("--stack-size=2000") + expect(() => { + const next = immer.produce(baseState, (draft: any) => { + draft.state3.state_.state.someValue = 'efgh' + draft.state2.state.someValue = 'ijkl' + }) + + expect(next.someValue).toBe('ijkl') + }).not.toThrow('Maximum call stack size exceeded') + } +}); + +test("replacing a deeply-nested value modified elsewhere does not modify the original object", () => { + + // When failing, produces the following output: + // originalBase: { + // someState: { something: { b: 'b' } }, + // someOtherState: { someState: { something: { b: 'b' } } } + // }, + // base: { + // someState: { something: { b: 'b' } }, + // someOtherState: { someState: { something: { b: 'a modified value' } } } // <----- ⚠ This should be 'b'; we just modified the original state! ⚠ + // }, + // next: { + // someState: { something: { b: 'a modified value' } }, + // someOtherState: { someState: { something: { b: 'a modified value' } } } + // } + + + const immer = new Immer({allowMultiRefs: true}) + + const someState = {something: {b: 'b'}} + const base = {someState, someOtherState: { someState }} + + const originalBaseString = JSON.stringify(base) + + const next = immer.produce(base, draft => { + draft.someState.something.b = 'a modified value' + }) + + let hasError = true + + try { + expect(next.someOtherState.someState).toBe(next.someState) // Make sure multi-ref is working on the surface first + + expect(next.someState.something.b).toBe('a modified value') + expect(base.someState.something.b).not.toBe('a modified value') + expect(next.someOtherState).not.toBe(base.someOtherState) + + expect(next.someOtherState.someState.something.b).toBe('a modified value') + expect(base.someOtherState.someState.something.b).not.toBe('a modified value') + expect(next.someOtherState).not.toBe(base.someOtherState) + hasError = false + } finally { + if (hasError) console.log('Objects for test "replacing a deeply-nested value modified elsewhere does not modify the original object":', + inspect({originalBase: JSON.parse(originalBaseString), base, next}, {depth: 99, colors: true}) + ) + } +}); + +describe("access value & change child's child value", () => { + describe("with object", () => { + const immer = new Immer({allowMultiRefs: true}) + const sameRef = {someNumber: 1, someString: "one"} + const objectOfRefs = {a: sameRef, b: sameRef, c: sameRef, d: sameRef} + + const base = { + objectRef1: objectOfRefs, + objectRef2: objectOfRefs, + objectRef3: objectOfRefs, + objectRef4: objectOfRefs + } + const next = immer.produce(base, draft => { + draft.objectRef2.b.someNumber = 2 + draft.objectRef3.c.someString = "two" + }) + + it("should have kept the Object refs the same", () => { + expect(next.objectRef1).toBe(next.objectRef2), + expect(next.objectRef2).toBe(next.objectRef3), + expect(next.objectRef3).toBe(next.objectRef4) + }) + + it("should have updated the values across everything", () => { + function verifyObjectReference( + ref: {[key: string]: {someNumber: number; someString: string}}, + objectNum: number + ) { + verifySingleReference(ref.a, objectNum, "a") + verifySingleReference(ref.b, objectNum, "b") + verifySingleReference(ref.c, objectNum, "c") + verifySingleReference(ref.d, objectNum, "d") + } + + function verifySingleReference( + ref: {someNumber: number; someString: string}, + objectNum: number, + refKey: string + ) { + //it(`should have updated the values across everything (ref ${refKey.toUpperCase()} in object #${objectNum})`, () => { + expect(ref.someNumber).toBe(2) + expect(ref.someString).toBe("two") + //}) + } + + verifyObjectReference(next.objectRef1, 1) + verifyObjectReference(next.objectRef2, 2) + verifyObjectReference(next.objectRef3, 3) + verifyObjectReference(next.objectRef4, 4) + }); + }) + + describe("with map", () => { + const immer = new Immer({allowMultiRefs: true}) + enableMapSet() + const sameRef = {someNumber: 1, someString: "one"} + const mapOfRefs = new Map([ + ["a", sameRef], + ["b", sameRef], + ["c", sameRef], + ["d", sameRef] + ]) + + const base = { + mapRef1: mapOfRefs, + mapRef2: mapOfRefs, + mapRef3: mapOfRefs, + mapRef4: mapOfRefs + } + const next = immer.produce(base, draft => { + draft.mapRef2.get("b")!.someNumber = 2 + draft.mapRef3.get("c")!.someString = "two" + }) + + it("should have kept the Map refs the same", () => { + expect(next.mapRef1).toBe(next.mapRef2), + expect(next.mapRef2).toBe(next.mapRef3), + expect(next.mapRef3).toBe(next.mapRef4) + }) + + it("should have updated the values across everything", () => { + function verifyMapReference( + ref: Map, + mapNum: number + ) { + verifySingleReference(ref.get("a")!, mapNum, "a") + verifySingleReference(ref.get("b")!, mapNum, "b") + verifySingleReference(ref.get("c")!, mapNum, "c") + verifySingleReference(ref.get("d")!, mapNum, "d") + + //it(`should have the same child refs (map #${mapNum})`, () => { + expect(ref.get("a")).toBe(ref.get("b")), + expect(ref.get("b")).toBe(ref.get("c")), + expect(ref.get("c")).toBe(ref.get("d")) + //}) + } + + function verifySingleReference( + ref: {someNumber: number; someString: string}, + mapNum: number, + refKey: string + ) { + //it(`should have updated the values across everything (ref ${refKey.toUpperCase()} in map #${mapNum})`, () => { + expect(ref.someNumber).toBe(2) + expect(ref.someString).toBe("two") + //}) + } + + verifyMapReference(next.mapRef1, 1) + verifyMapReference(next.mapRef2, 2) + verifyMapReference(next.mapRef3, 3) + verifyMapReference(next.mapRef4, 4) + + }); + }) + + describe("with array", () => { + const immer = new Immer({allowMultiRefs: true}) + const sameRef = {someNumber: 1, someString: "one"} + const arrayOfRefs = [sameRef, sameRef, sameRef, sameRef] + + const base = { + arrayRef1: arrayOfRefs, + arrayRef2: arrayOfRefs, + arrayRef3: arrayOfRefs, + arrayRef4: arrayOfRefs + } + const next = immer.produce(base, draft => { + draft.arrayRef2[1].someNumber = 2 + draft.arrayRef3[2].someString = "two" + }) + + it("should have kept the Array refs the same", () => { + expect(next.arrayRef1).toBe(next.arrayRef2), + expect(next.arrayRef2).toBe(next.arrayRef3), + expect(next.arrayRef3).toBe(next.arrayRef4) + }) + + it("should have updated the values across everything", () => { + function verifyArrayReference( + ref: {someNumber: number; someString: string}[], + arrayNum: number + ) { + let i = 0 + for (const value of ref) { + //it(`should have updated the values across everything (ref #${i++} in array #${arrayNum})`, () => { + verifySingleReference(value) + //}) + } + } + + function verifySingleReference(ref: { + someNumber: number + someString: string + }) { + expect(ref.someNumber).toBe(2) + expect(ref.someString).toBe("two") + } + + verifyArrayReference(next.arrayRef1, 1) + verifyArrayReference(next.arrayRef2, 2) + verifyArrayReference(next.arrayRef3, 3) + verifyArrayReference(next.arrayRef4, 4) + }); + }) + + describe("with set", () => { + const immer = new Immer({allowMultiRefs: true}) + enableMapSet() + const sameRef = {someNumber: 1, someString: "one"} + const setOfRefs = new Set([{sameRef}, {sameRef}, {sameRef}, {sameRef}]) + + const base = { + setRef1: setOfRefs, + setRef2: setOfRefs, + setRef3: setOfRefs, + setRef4: setOfRefs + } + //console.log("base", inspect(base, {depth: 6, colors: true, compact: true})) + + const next = immer.produce(base, draft => { + const set2Values = draft.setRef2.values() + set2Values.next() + set2Values.next().value.sameRef.someNumber = 2 + + const set3Values = draft.setRef3.values() + set3Values.next() + set3Values.next() + set3Values.next().value.sameRef.someString = "two" + }) + + //console.log( + // "next", + // inspect(next, { + // depth: 20, + // colors: true, + // compact: true, + // breakLength: Infinity + // }) + //) + + it("should have kept the Set refs the same", () => { + expect(next.setRef1).toBe(next.setRef2), + expect(next.setRef2).toBe(next.setRef3), + expect(next.setRef3).toBe(next.setRef4) + }) + + it("should have updated the values across everything", () => { + function verifySetReference( + ref: Set<{sameRef: {someNumber: number; someString: string}}>, + setLetter: string + ) { + //it(`should have the same child refs (set ${setLetter.toUpperCase()})`, () => { + let first = ref.values().next().value.sameRef + for (const value of Array.from(ref)) { + expect(value.sameRef).toBe(first) + } + //}) + + let i = 0 + for (const value of Array.from(ref)) { + //it(`should have updated the values across everything (ref #${i++} in set ${setLetter.toUpperCase()})`, () => { + verifySingleReference(value.sameRef) + //}) + } + } + + function verifySingleReference(ref: { + someNumber: number + someString: string + }) { + expect(ref.someNumber).toBe(2) + expect(ref.someString).toBe("two") + } + + verifySetReference(next.setRef1, "a") + verifySetReference(next.setRef2, "b") + verifySetReference(next.setRef3, "c") + verifySetReference(next.setRef4, "d") + + }); + }) +}) diff --git a/src/core/finalize.ts b/src/core/finalize.ts index 6ee69ce6..36fff7bd 100644 --- a/src/core/finalize.ts +++ b/src/core/finalize.ts @@ -15,10 +15,17 @@ import { getPlugin, die, revokeScope, - isFrozen + isFrozen, + createProxy, + enterScope, + markChanged, + latest, } from "../internal" -export function processResult(result: any, scope: ImmerScope) { +export function processResult( + result: any, + scope: ImmerScope +) { scope.unfinalizedDrafts_ = scope.drafts_.length const baseDraft = scope.drafts_![0] const isReplaced = result !== undefined && result !== baseDraft @@ -29,8 +36,7 @@ export function processResult(result: any, scope: ImmerScope) { } if (isDraftable(result)) { // Finalize the result in case it contains (or is) a subset of the draft. - result = finalize(scope, result) - if (!scope.parent_) maybeFreeze(scope, result) + result = enterFinalize(scope, result) } if (scope.patches_) { getPlugin("Patches").generateReplacementPatches_( @@ -42,7 +48,7 @@ export function processResult(result: any, scope: ImmerScope) { } } else { // Finalize the base draft. - result = finalize(scope, baseDraft, []) + result = enterFinalize(scope, baseDraft) } revokeScope(scope) if (scope.patches_) { @@ -51,24 +57,91 @@ export function processResult(result: any, scope: ImmerScope) { return result !== NOTHING ? result : undefined } -function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) { - // Don't recurse in tho recursive data structures - if (isFrozen(value)) return value +// If we have an existingStateMap, enter a second scope while finalize the draft to catch any proxies we create during the process. +// That way, we can catch any deeply-nested objects which weren't drafted in the recipe WITHOUT modifying the original state. +// see the `multiref.ts` test "replacing a deeply-nested value modified elsewhere does not modify the original object" +function enterFinalize( + scope: ImmerScope, + value: any +) { + let secondScope = null; + + try { + if (scope.existingStateMap_) { + secondScope = enterScope(scope.immer_) + secondScope.parent_ = scope + secondScope.existingStateMap_ = scope.existingStateMap_ + if (!isDraft(value)) { + value = createProxy(value, undefined) + } + } + + value = finalize(scope, secondScope, value, []) + } finally { + if (secondScope) { + revokeScope(secondScope) + } + } + + if (!scope.parent_) maybeFreeze(scope, value, false) + return value +} + +function finalizeIfIsDraft( + rootScope: ImmerScope, + secondScope: ImmerScope | null, + value: any, + path?: PatchPath, + encounteredObjects = new WeakSet(), +): any { + if (isDraft(value)) { + return finalize(rootScope, secondScope, value, path, encounteredObjects) + } + return value +} + +function finalize( + rootScope: ImmerScope, + secondScope: ImmerScope | null, + value: any, + path?: PatchPath, + encounteredObjects = new WeakSet(), +): any { + let state: ImmerState | undefined = value[DRAFT_STATE] + + // Never finalize drafts owned by another scope. + if (state && state.scope_ !== rootScope && state.scope_ !== secondScope) return value + + // Don't recurse into recursive data structures + if (isFrozen(value) || encounteredObjects.has(state ? state.base_ : value)) return state ? (state.modified_? state.copy_ : state.base_) : value + encounteredObjects.add(state ? state.base_ : value) + if (state?.copy_) encounteredObjects.add(state.copy_) + - const state: ImmerState = value[DRAFT_STATE] // A plain object, might need freezing, might contain drafts - if (!state) { - each(value, (key, childValue) => - finalizeProperty(rootScope, state, value, key, childValue, path) + if (!state || (!state.modified_ && state.scope_.existingStateMap_)) { + each( + value, + (key, childValue) => + finalizeProperty( + rootScope, + secondScope, + state, + value, + key, + childValue, + path, + undefined, + encounteredObjects + ) ) - return value + + if (!state) return finalizeIfIsDraft(rootScope, secondScope, value, path, encounteredObjects) } - // Never finalize drafts owned by another scope. - if (state.scope_ !== rootScope) return value // Unmodified draft, return the (frozen) original if (!state.modified_) { maybeFreeze(rootScope, state.base_, true) - return state.base_ + return finalizeIfIsDraft(rootScope, secondScope, state.base_, path, encounteredObjects) } // Not finalized yet, let's do that now if (!state.finalized_) { @@ -87,7 +160,17 @@ function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) { isSet = true } each(resultEach, (key, childValue) => - finalizeProperty(rootScope, state, result, key, childValue, path, isSet) + finalizeProperty( + rootScope, + secondScope, + state, + result, + key, + childValue, + path, + isSet, + encounteredObjects + ) ) // everything inside is frozen, we can freeze here maybeFreeze(rootScope, result, false) @@ -101,20 +184,36 @@ function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) { ) } } - return state.copy_ + + return finalizeIfIsDraft(rootScope, secondScope, state.copy_, path, encounteredObjects) } function finalizeProperty( rootScope: ImmerScope, + secondScope: ImmerScope | null, parentState: undefined | ImmerState, targetObject: any, prop: string | number, childValue: any, rootPath?: PatchPath, - targetIsSet?: boolean + targetIsSet?: boolean, + encounteredObjects = new WeakSet() ) { - if (process.env.NODE_ENV !== "production" && childValue === targetObject) - die(5) + if (!rootScope.existingStateMap_) { + if (process.env.NODE_ENV !== "production" && childValue === targetObject) + die(5) + } else { + if (!isDraft(childValue) && isDraftable(childValue)) { + const existingState = rootScope.existingStateMap_.get(childValue) + if (existingState) { + childValue = existingState.draft_ + } else { + childValue = createProxy(childValue, parentState) + } + } + } + + if (isDraft(childValue)) { const path = rootPath && @@ -123,17 +222,26 @@ function finalizeProperty( !has((parentState as Exclude).assigned_!, prop) // Skip deep patches for assigned keys. ? rootPath!.concat(prop) : undefined + // Drafts owned by `scope` are finalized here. - const res = finalize(rootScope, childValue, path) + const state = childValue[DRAFT_STATE] + const res = finalize(rootScope, secondScope, childValue, path, encounteredObjects) set(targetObject, prop, res) + // Drafts from another scope must prevented to be frozen // if we got a draft back from finalize, we're in a nested produce and shouldn't freeze + + if (parentState && rootScope.existingStateMap_ && state.modified_) { + markChanged(parentState) + } + if (isDraft(res)) { rootScope.canAutoFreeze_ = false } else return } else if (targetIsSet) { targetObject.add(childValue) } + // Search new objects for unfinalized drafts. Frozen objects should never contain drafts. if (isDraftable(childValue) && !isFrozen(childValue)) { if (!rootScope.immer_.autoFreeze_ && rootScope.unfinalizedDrafts_ < 1) { @@ -144,7 +252,13 @@ function finalizeProperty( // See add-data.js perf test return } - finalize(rootScope, childValue) + finalize( + rootScope, + secondScope, + childValue, + undefined, + encounteredObjects + ) // Immer deep freezes plain objects, so if there is no parent state, we freeze as well // Per #590, we never freeze symbolic properties. Just to make sure don't accidentally interfere // with other frameworks. @@ -157,9 +271,9 @@ function finalizeProperty( } } -function maybeFreeze(scope: ImmerScope, value: any, deep = false) { +function maybeFreeze(rootScope: ImmerScope, value: any, deep = false) { // we never freeze for a non-root scope; as it would prevent pruning for drafts inside wrapping objects - if (!scope.parent_ && scope.immer_.autoFreeze_ && scope.canAutoFreeze_) { + if (!rootScope.parent_ && rootScope.immer_.autoFreeze_ && rootScope.canAutoFreeze_) { freeze(value, deep) } } diff --git a/src/core/immerClass.ts b/src/core/immerClass.ts index 6c673e0a..f1924fb0 100644 --- a/src/core/immerClass.ts +++ b/src/core/immerClass.ts @@ -34,12 +34,19 @@ interface ProducersFns { export class Immer implements ProducersFns { autoFreeze_: boolean = true useStrictShallowCopy_: boolean = false + allowMultiRefs_: boolean = false - constructor(config?: {autoFreeze?: boolean; useStrictShallowCopy?: boolean}) { + constructor(config?: { + autoFreeze?: boolean + useStrictShallowCopy?: boolean + allowMultiRefs: boolean + }) { if (typeof config?.autoFreeze === "boolean") this.setAutoFreeze(config!.autoFreeze) if (typeof config?.useStrictShallowCopy === "boolean") this.setUseStrictShallowCopy(config!.useStrictShallowCopy) + if (typeof config?.allowMultiRefs === "boolean") + this.setAllowMultiRefs(config!.allowMultiRefs) } /** @@ -132,7 +139,10 @@ export class Immer implements ProducersFns { if (!isDraftable(base)) die(8) if (isDraft(base)) base = current(base) const scope = enterScope(this) - const proxy = createProxy(base, undefined) + const proxy = createProxy( + base, + undefined + ) proxy[DRAFT_STATE].isManual_ = true leaveScope(scope) return proxy as any @@ -144,9 +154,10 @@ export class Immer implements ProducersFns { ): D extends Draft ? T : never { const state: ImmerState = draft && (draft as any)[DRAFT_STATE] if (!state || !state.isManual_) die(9) + const {scope_: scope} = state usePatchesInScope(scope, patchListener) - return processResult(undefined, scope) + return processResult(undefined, scope) as any } /** @@ -167,6 +178,11 @@ export class Immer implements ProducersFns { this.useStrictShallowCopy_ = value } + /** Pass true to allow multiple references to the same object in the same state tree. */ + setAllowMultiRefs(value: boolean) { + this.allowMultiRefs_ = value + } + applyPatches(base: T, patches: Patch[]): T { // If a patch replaces the entire state, take that replacement as base // before applying patches @@ -202,12 +218,20 @@ export function createProxy( ): Drafted { // precondition: createProxy should be guarded by isDraftable, so we know we can safely draft const draft: Drafted = isMap(value) - ? getPlugin("MapSet").proxyMap_(value, parent) + ? getPlugin("MapSet").proxyMap_( + value, + parent + ) : isSet(value) - ? getPlugin("MapSet").proxySet_(value, parent) + ? getPlugin("MapSet").proxySet_( + value, + parent + ) : createProxyProxy(value, parent) const scope = parent ? parent.scope_ : getCurrentScope() + scope.drafts_.push(draft) + return draft } diff --git a/src/core/proxy.ts b/src/core/proxy.ts index 3ce06aa8..a963ba06 100644 --- a/src/core/proxy.ts +++ b/src/core/proxy.ts @@ -17,7 +17,8 @@ import { die, createProxy, ArchType, - ImmerScope + ImmerScope, + isDraft } from "../internal" interface ProxyBaseState extends ImmerBaseState { @@ -53,11 +54,13 @@ export function createProxyProxy( base: T, parent?: ImmerState ): Drafted { + const scope_ = parent ? parent.scope_ : getCurrentScope()! + const isArray = Array.isArray(base) - const state: ProxyState = { + const state: ProxyState = (scope_.existingStateMap_?.get(base) as ProxyState) || { type_: isArray ? ArchType.Array : (ArchType.Object as any), // Track which produce call this is associated with. - scope_: parent ? parent.scope_ : getCurrentScope()!, + scope_: scope_, // True for both shallow and deep changes. modified_: false, // Used during finalization. @@ -74,7 +77,12 @@ export function createProxyProxy( copy_: null, // Called by the `produce` function. revoke_: null as any, - isManual_: false + isManual_: false, + } + + if (parent && state.parent_ !== parent) { + if (state.extraParents_) state.extraParents_.push(parent) + else state.extraParents_ = [parent] } // the traps must target something, a bit like the 'real' base. @@ -90,10 +98,29 @@ export function createProxyProxy( traps = arrayTraps } - const {revoke, proxy} = Proxy.revocable(target, traps) - state.draft_ = proxy as any - state.revoke_ = revoke - return proxy as any + if (state.revoke_) { + let thisHasBeenRevoked = false + const oldRevoke = state.revoke_ + state.revoke_ = () => { + if (thisHasBeenRevoked) return oldRevoke() + thisHasBeenRevoked = true + } + } else { + const {revoke, proxy} = Proxy.revocable(target, traps) + + if (!state.draft_) state.draft_ = proxy as any + state.revoke_ = revoke + } + + return state.draft_ as any +} + +function isScopeDescendedFrom(parent: ImmerScope, child: ImmerScope | undefined) { + while (child) { + if (child === parent) return true + child = child.parent_ + } + return false } /** @@ -116,7 +143,10 @@ export const objectTraps: ProxyHandler = { // Assigned values are never drafted. This catches any drafts we created, too. if (value === peek(state.base_, prop)) { prepareCopy(state) - return (state.copy_![prop as any] = createProxy(value, state)) + return (state.copy_![prop as any] = createProxy( + value, + state + )) } return value }, @@ -164,7 +194,6 @@ export const objectTraps: ProxyHandler = { ) return true - // @ts-ignore state.copy_![prop] = value state.assigned_[prop] = true return true @@ -223,8 +252,7 @@ each(objectTraps, (key, fn) => { arrayTraps.deleteProperty = function(state, prop) { if (process.env.NODE_ENV !== "production" && isNaN(parseInt(prop as any))) die(13) - // @ts-ignore - return arrayTraps.set!.call(this, state, prop, undefined) + return arrayTraps.set!.call(this, state, prop, undefined, undefined) } arrayTraps.set = function(state, prop, value) { if ( @@ -275,18 +303,27 @@ export function markChanged(state: ImmerState) { if (state.parent_) { markChanged(state.parent_) } + if (state.extraParents_) { + for (let i = 0; i < state.extraParents_.length; i++) { + markChanged(state.extraParents_[i]) + } + } } } -export function prepareCopy(state: { - base_: any - copy_: any - scope_: ImmerScope -}) { - if (!state.copy_) { - state.copy_ = shallowCopy( - state.base_, - state.scope_.immer_.useStrictShallowCopy_ - ) +export function prepareCopy(state: ImmerState) { + if (state.copy_) return + + const existing = state.scope_.existingStateMap_?.get(state.base_) + if (existing) { + Object.assign(state, existing) + return } + + state.copy_ = shallowCopy( + state.base_, + state.scope_.immer_.useStrictShallowCopy_ + ) + + state.scope_.existingStateMap_?.set(state.base_, state) } diff --git a/src/core/scope.ts b/src/core/scope.ts index 65eb5eed..36a91299 100644 --- a/src/core/scope.ts +++ b/src/core/scope.ts @@ -19,7 +19,8 @@ export interface ImmerScope { parent_?: ImmerScope patchListener_?: PatchListener immer_: Immer - unfinalizedDrafts_: number + unfinalizedDrafts_: number, + existingStateMap_?: WeakMap } let currentScope: ImmerScope | undefined @@ -39,7 +40,8 @@ function createScope( // Whenever the modified draft contains a draft from another scope, we // need to prevent auto-freezing so the unowned draft can be finalized. canAutoFreeze_: true, - unfinalizedDrafts_: 0 + unfinalizedDrafts_: 0, + existingStateMap_: parent_ ? parent_.existingStateMap_ : (immer_.allowMultiRefs_ ? new WeakMap() : undefined) } } diff --git a/src/plugins/mapset.ts b/src/plugins/mapset.ts index edc628a7..96961820 100644 --- a/src/plugins/mapset.ts +++ b/src/plugins/mapset.ts @@ -14,27 +14,58 @@ import { markChanged, die, ArchType, - each + each, + Objectish, + isDraft } from "../internal" export function enableMapSet() { class DraftMap extends Map { [DRAFT_STATE]: MapState - constructor(target: AnyMap, parent?: ImmerState) { + constructor( + target: AnyMap, + parent?: ImmerState + ) { super() - this[DRAFT_STATE] = { - type_: ArchType.Map, - parent_: parent, - scope_: parent ? parent.scope_ : getCurrentScope()!, - modified_: false, - finalized_: false, - copy_: undefined, - assigned_: undefined, - base_: target, - draft_: this as any, - isManual_: false, - revoked_: false + let revoked = false + const this_ = this + const scope_ = parent ? parent.scope_ : getCurrentScope()! + this[DRAFT_STATE] = new Proxy( + (scope_.existingStateMap_?.get(target) as MapState) || { + type_: ArchType.Map, + parent_: parent, + scope_: parent ? parent.scope_ : getCurrentScope()!, + modified_: false, + finalized_: false, + copy_: undefined, + assigned_: undefined, + base_: target, + draft_: this as any, + isManual_: false, + revoked_: false, + }, + { + get(target, p, receiver) { + if (p === "revoked_") return revoked + if (p === "draft_") return this_ + return Reflect.get(target, p, receiver) + }, + set(target, p, newValue, receiver) { + if (p === "revoked_") { + revoked = newValue + return true + } + if (p === "draft_") return false + return Reflect.set(target, p, newValue, receiver) + } + } + ) + + if (parent && this[DRAFT_STATE].parent_ !== parent) { + if (this[DRAFT_STATE].extraParents_) + this[DRAFT_STATE].extraParents_.push(parent) + else this[DRAFT_STATE].extraParents_ = [parent] } } @@ -46,10 +77,24 @@ export function enableMapSet() { return latest(this[DRAFT_STATE]).has(key) } + isValueChanging(key: any, value: any): boolean { + const state: MapState = this[DRAFT_STATE] + if (!latest(state).has(key)) return true + + const baseValue = latest(state).get(key) + + if (baseValue === value) return false + + if (isDraft(baseValue) && latest(baseValue[DRAFT_STATE]) === value) return false + + return true + } + set(key: any, value: any) { const state: MapState = this[DRAFT_STATE] + const valueState: ImmerState | false = isDraft(value) && value[DRAFT_STATE] assertUnrevoked(state) - if (!latest(state).has(key) || latest(state).get(key) !== value) { + if (this.isValueChanging(key, value)) { prepareMapCopy(state) markChanged(state) state.assigned_!.set(key, true) @@ -158,34 +203,65 @@ export function enableMapSet() { } } - function proxyMap_(target: T, parent?: ImmerState): T { - // @ts-ignore - return new DraftMap(target, parent) + function proxyMap_( + target: T, + parent?: ImmerState + ): T { + return new DraftMap(target, parent) as unknown as T } function prepareMapCopy(state: MapState) { - if (!state.copy_) { - state.assigned_ = new Map() - state.copy_ = new Map(state.base_) - } + if (state.copy_) return + state.assigned_ = new Map() + state.copy_ = new Map(state.base_) + state.scope_.existingStateMap_?.set(state.base_, state) } class DraftSet extends Set { [DRAFT_STATE]: SetState - constructor(target: AnySet, parent?: ImmerState) { + constructor( + target: AnySet, + parent?: ImmerState + ) { super() - this[DRAFT_STATE] = { - type_: ArchType.Set, - parent_: parent, - scope_: parent ? parent.scope_ : getCurrentScope()!, - modified_: false, - finalized_: false, - copy_: undefined, - base_: target, - draft_: this, - drafts_: new Map(), - revoked_: false, - isManual_: false + let revoked = false + const this_ = this + const scope_ = parent ? parent.scope_ : getCurrentScope()! + this[DRAFT_STATE] = new Proxy( + (scope_.existingStateMap_?.get(target) as SetState) || { + type_: ArchType.Set, + parent_: parent, + scope_, + modified_: false, + finalized_: false, + copy_: undefined, + base_: target, + draft_: this, + drafts_: new Map(), + revoked_: false, + isManual_: false + }, + { + get(target, p, receiver) { + if (p === "revoked_") return revoked + if (p === "draft_") return this_ + return Reflect.get(target, p, receiver) + }, + set(target, p, newValue, receiver) { + if (p === "revoked_") { + revoked = newValue + return true + } + if (p === "draft_") return false + return Reflect.set(target, p, newValue, receiver) + } + } + ) + + if (parent && this[DRAFT_STATE].parent_ !== parent) { + if (this[DRAFT_STATE].extraParents_) + this[DRAFT_STATE].extraParents_.push(parent) + else this[DRAFT_STATE].extraParents_ = [parent] } } @@ -275,25 +351,28 @@ export function enableMapSet() { } } } - function proxySet_(target: T, parent?: ImmerState): T { - // @ts-ignore - return new DraftSet(target, parent) + + function proxySet_( + target: T, + parent?: ImmerState + ): T { + return new DraftSet(target, parent) as unknown as T } function prepareSetCopy(state: SetState) { - if (!state.copy_) { - // create drafts for all entries to preserve insertion order - state.copy_ = new Set() - state.base_.forEach(value => { - if (isDraftable(value)) { - const draft = createProxy(value, state) - state.drafts_.set(value, draft) - state.copy_!.add(draft) - } else { - state.copy_!.add(value) - } - }) - } + if (state.copy_) return + // create drafts for all entries to preserve insertion order + state.copy_ = new Set() + state.scope_.existingStateMap_?.set(state.base_, state) + state.base_.forEach(value => { + if (isDraftable(value)) { + const draft = createProxy(value, state) + state.drafts_.set(value, draft) + state.copy_!.add(draft) + } else { + state.copy_!.add(value) + } + }) } function assertUnrevoked(state: any /*ES5State | MapState | SetState*/) { diff --git a/src/types/types-internal.ts b/src/types/types-internal.ts index 5c506252..f21c8554 100644 --- a/src/types/types-internal.ts +++ b/src/types/types-internal.ts @@ -24,6 +24,7 @@ export const enum ArchType { export interface ImmerBaseState { parent_?: ImmerState + extraParents_?: ImmerState[] scope_: ImmerScope modified_: boolean finalized_: boolean diff --git a/src/utils/plugins.ts b/src/utils/plugins.ts index 36cc1d70..d447d9de 100644 --- a/src/utils/plugins.ts +++ b/src/utils/plugins.ts @@ -6,7 +6,8 @@ import { AnyMap, AnySet, ArchType, - die + die, + Objectish } from "../internal" /** Plugin utilities */ @@ -27,8 +28,14 @@ const plugins: { applyPatches_(draft: T, patches: Patch[]): T } MapSet?: { - proxyMap_(target: T, parent?: ImmerState): T - proxySet_(target: T, parent?: ImmerState): T + proxyMap_( + target: T, + parent?: ImmerState + ): T + proxySet_( + target: T, + parent?: ImmerState + ): T } } = {} diff --git a/website/docs/pitfalls.md b/website/docs/pitfalls.md index afa58c2b..01de6fed 100644 --- a/website/docs/pitfalls.md +++ b/website/docs/pitfalls.md @@ -17,6 +17,8 @@ Never reassign the `draft` argument (example: `draft = myCoolNewState`). Instead ### Immer only supports unidirectional trees + + Immer assumes your state to be a unidirectional tree. That is, no object should appear twice in the tree, there should be no circular references. There should be exactly one path from the root to any node of the tree. ### Never explicitly return `undefined` from a producer