diff --git a/akita/__tests__/deepFreeze.spec.ts b/akita/__tests__/deepFreeze.spec.ts new file mode 100644 index 00000000..f27615c3 --- /dev/null +++ b/akita/__tests__/deepFreeze.spec.ts @@ -0,0 +1,79 @@ +import { Store } from '../src/store'; +import { StoreConfig } from '../src/storeConfig'; +import { deepFreeze } from '../src/deepFreeze'; + +class SpecialObject { + specialString: string = 'special'; + specialNumber: number = 2; + + constructor(params: Partial) { + Object.assign(this, params); + } +} + +class ComplexState { + propertyString: string = ''; + propertyNumber: number = 1; + specialObject: SpecialObject = new SpecialObject({}); + + constructor(params: Partial) { + Object.assign(this, params); + } +} + +@StoreConfig({ + name: 'complexState' +}) +class ComplexStore extends Store { + constructor() { + super(new ComplexState({})); + } +} + +const complexStore = new ComplexStore(); + +describe('store with no custom deepFreeze', () => { + it('should use default function', () => { + expect(complexStore.deepFreeze).toEqual(deepFreeze); + }); + + it('should freeze all properties', () => { + expect(Object.isFrozen(complexStore._value().propertyNumber)).toBeTruthy(); + expect(Object.isFrozen(complexStore._value().propertyString)).toBeTruthy(); + expect(Object.isFrozen(complexStore._value().specialObject)).toBeTruthy(); + expect(Object.isFrozen(complexStore._value().specialObject.specialNumber)).toBeTruthy(); + expect(Object.isFrozen(complexStore._value().specialObject.specialString)).toBeTruthy(); + }); +}); + +function deepFreezeCustom(o: ComplexState) { + Object.freeze(o); + + return o; +} + +@StoreConfig({ + name: 'complexState2', + deepFreezeFunction: deepFreezeCustom +}) +class ComplexStoreCustom extends Store { + constructor() { + super(new ComplexState({})); + } +} + +const complexStoreCustom = new ComplexStoreCustom(); + +describe('store with custom deepFreeze', () => { + it('should use custom function', () => { + expect(complexStoreCustom.deepFreeze).toEqual(deepFreezeCustom); + }); + + it('should not freeze all properties', () => { + expect(Object.isFrozen(complexStoreCustom._value().propertyNumber)).toBeTruthy(); + expect(Object.isFrozen(complexStoreCustom._value().propertyString)).toBeTruthy(); + expect(Object.isFrozen(complexStoreCustom._value().specialObject)).toBeFalsy(); + expect(Object.isFrozen(complexStoreCustom._value().specialObject.specialNumber)).toBeTruthy(); + expect(Object.isFrozen(complexStoreCustom._value().specialObject.specialString)).toBeTruthy(); + }); +}); diff --git a/akita/src/store.ts b/akita/src/store.ts index 186e9444..004e62cd 100755 --- a/akita/src/store.ts +++ b/akita/src/store.ts @@ -133,6 +133,11 @@ export class Store { return this.config.idKey || this.options.idKey || DEFAULT_ID_KEY; } + // @internal + get deepFreeze() { + return this.config.deepFreezeFunction || this.options.deepFreezeFunction || deepFreeze; + } + // @internal get cacheConfig() { return this.config.cache || this.options.cache; @@ -140,7 +145,7 @@ export class Store { // @internal _setState(newStateFn: (state: Readonly) => S, _dispatchAction = true) { - this.storeValue = __DEV__ ? deepFreeze(newStateFn(this._value())) : newStateFn(this._value()); + this.storeValue = __DEV__ ? this.deepFreeze(newStateFn(this._value())) : newStateFn(this._value()); if (!this.store) { this.store = new BehaviorSubject(this.storeValue); diff --git a/akita/src/storeConfig.ts b/akita/src/storeConfig.ts index 5f1b19ec..bcdb836c 100644 --- a/akita/src/storeConfig.ts +++ b/akita/src/storeConfig.ts @@ -4,6 +4,7 @@ export type StoreConfigOptions = { idKey?: string; storeName?: string; cache?: { ttl: number }; + deepFreezeFunction?: (o: any) => any; }; export type UpdatableStoreConfigOptions = {