1
1
"use strict"
2
- import { Immer , nothing , original , isDraft } from "../src/index"
3
- import { shallowCopy } from "../src/common"
2
+ import { Immer , nothing , original , isDraft , immerable } from "../src/index"
3
+ import { each , shallowCopy , isEnumerable } from "../src/common"
4
4
import deepFreeze from "deep-freeze"
5
5
import cloneDeep from "lodash.clonedeep"
6
6
import * as lodash from "lodash"
@@ -259,7 +259,9 @@ function runBaseTest(name, useProxies, autoFreeze, useListener) {
259
259
produce ( [ ] , d => {
260
260
d . x = 3
261
261
} )
262
- } ) . toThrow ( / d o e s n o t s u p p o r t / )
262
+ } ) . toThrow (
263
+ "Immer only supports setting array indices and the 'length' property"
264
+ )
263
265
} )
264
266
265
267
it ( "throws when a non-numeric property is deleted" , ( ) => {
@@ -269,11 +271,86 @@ function runBaseTest(name, useProxies, autoFreeze, useListener) {
269
271
produce ( baseState , d => {
270
272
delete d . x
271
273
} )
272
- } ) . toThrow ( / d o e s n o t s u p p o r t / )
274
+ } ) . toThrow ( "Immer only supports deleting array indices" )
273
275
} )
274
276
}
275
277
} )
276
278
279
+ it ( "supports `immerable` symbol on constructor" , ( ) => {
280
+ class One { }
281
+ One [ immerable ] = true
282
+ const baseState = new One ( )
283
+ const nextState = produce ( baseState , draft => {
284
+ expect ( draft ) . not . toBe ( baseState )
285
+ draft . foo = true
286
+ } )
287
+ expect ( nextState ) . not . toBe ( baseState )
288
+ expect ( nextState . foo ) . toBeTruthy ( )
289
+ } )
290
+
291
+ it ( "preserves symbol properties" , ( ) => {
292
+ const test = Symbol ( "test" )
293
+ const baseState = { [ test ] : true }
294
+ const nextState = produce ( baseState , s => {
295
+ expect ( s [ test ] ) . toBeTruthy ( )
296
+ s . foo = true
297
+ } )
298
+ expect ( nextState ) . toEqual ( {
299
+ [ test ] : true ,
300
+ foo : true
301
+ } )
302
+ } )
303
+
304
+ it ( "preserves non-enumerable properties" , ( ) => {
305
+ const baseState = { }
306
+ Object . defineProperty ( baseState , "foo" , {
307
+ value : true ,
308
+ enumerable : false
309
+ } )
310
+ const nextState = produce ( baseState , s => {
311
+ expect ( s . foo ) . toBeTruthy ( )
312
+ expect ( isEnumerable ( s , "foo" ) ) . toBeFalsy ( )
313
+ s . bar = true
314
+ } )
315
+ expect ( nextState . foo ) . toBeTruthy ( )
316
+ expect ( isEnumerable ( nextState , "foo" ) ) . toBeFalsy ( )
317
+ } )
318
+
319
+ it ( "throws on computed properties" , ( ) => {
320
+ const baseState = { }
321
+ Object . defineProperty ( baseState , "foo" , {
322
+ get : ( ) => { } ,
323
+ enumerable : true
324
+ } )
325
+ expect ( ( ) => {
326
+ produce ( baseState , s => {
327
+ // Proxies only throw once a change is made.
328
+ if ( useProxies ) {
329
+ s . modified = true
330
+ }
331
+ } )
332
+ } ) . toThrowError ( "Immer drafts cannot have computed properties" )
333
+ } )
334
+
335
+ it ( "allows inherited computed properties" , ( ) => {
336
+ const proto = { }
337
+ Object . defineProperty ( proto , "foo" , {
338
+ get ( ) {
339
+ return this . bar
340
+ } ,
341
+ set ( val ) {
342
+ this . bar = val
343
+ }
344
+ } )
345
+ const baseState = Object . create ( proto )
346
+ produce ( baseState , s => {
347
+ expect ( s . bar ) . toBeUndefined ( )
348
+ s . foo = { }
349
+ expect ( s . bar ) . toBeDefined ( )
350
+ expect ( s . foo ) . toBe ( s . bar )
351
+ } )
352
+ } )
353
+
277
354
it ( "can rename nested objects (no changes)" , ( ) => {
278
355
const nextState = produce ( { obj : { } } , s => {
279
356
s . foo = s . obj
@@ -429,7 +506,9 @@ function runBaseTest(name, useProxies, autoFreeze, useListener) {
429
506
value : 2
430
507
} )
431
508
} )
432
- } ) . toThrow ( / d o e s n o t s u p p o r t / )
509
+ } ) . toThrow (
510
+ "Object.defineProperty() cannot be used on an Immer draft"
511
+ )
433
512
} )
434
513
435
514
it ( "should handle constructor correctly" , ( ) => {
@@ -604,7 +683,7 @@ function runBaseTest(name, useProxies, autoFreeze, useListener) {
604
683
it ( "throws when Object.setPrototypeOf() is used on a draft" , ( ) => {
605
684
produce ( { } , draft => {
606
685
expect ( ( ) => Object . setPrototypeOf ( draft , Array ) ) . toThrow (
607
- / d o e s n o t s u p p o r t /
686
+ "Object.setPrototypeOf() cannot be used on an Immer draft"
608
687
)
609
688
} )
610
689
} )
@@ -905,12 +984,19 @@ function runBaseTest(name, useProxies, autoFreeze, useListener) {
905
984
}
906
985
907
986
function testObjectTypes ( produce ) {
987
+ class Foo {
988
+ constructor ( foo ) {
989
+ this . foo = foo
990
+ this [ immerable ] = true
991
+ }
992
+ }
908
993
const values = {
909
994
"empty object" : { } ,
910
995
"plain object" : { a : 1 , b : 2 } ,
911
996
"object (no prototype)" : Object . create ( null ) ,
912
997
"empty array" : [ ] ,
913
- "plain array" : [ 1 , 2 ]
998
+ "plain array" : [ 1 , 2 ] ,
999
+ "class instance (draftable)" : new Foo ( 1 )
914
1000
}
915
1001
for ( const name in values ) {
916
1002
const value = values [ name ]
@@ -976,7 +1062,7 @@ function testLiteralTypes(produce) {
976
1062
"boxed string" : new String ( "" ) ,
977
1063
"boxed boolean" : new Boolean ( ) ,
978
1064
"date object" : new Date ( ) ,
979
- "class instance" : new Foo ( )
1065
+ "class instance (not draftable) " : new Foo ( )
980
1066
}
981
1067
for ( const name in values ) {
982
1068
describe ( name , ( ) => {
@@ -1006,12 +1092,11 @@ function testLiteralTypes(produce) {
1006
1092
}
1007
1093
1008
1094
function enumerableOnly ( x ) {
1009
- const copy = shallowCopy ( x )
1010
- for ( const key in copy ) {
1011
- const value = copy [ key ]
1095
+ const copy = Array . isArray ( x ) ? x . slice ( ) : Object . assign ( { } , x )
1096
+ each ( copy , ( prop , value ) => {
1012
1097
if ( value && typeof value === "object" ) {
1013
- copy [ key ] = enumerableOnly ( value )
1098
+ copy [ prop ] = enumerableOnly ( value )
1014
1099
}
1015
- }
1100
+ } )
1016
1101
return copy
1017
1102
}
0 commit comments