diff --git a/__tests__/core/model.test.ts b/__tests__/core/model.test.ts index 95d542c4f..4979c01b9 100644 --- a/__tests__/core/model.test.ts +++ b/__tests__/core/model.test.ts @@ -85,6 +85,18 @@ describe("Model instantiation", () => { expect(Model.properties).toEqual({}) }) + test("Model should not mutate properties object", () => { + const properties = { + prop1: "prop1", + prop2: 2 + } + const Model = types.model("name", properties) + + expect(properties).toEqual({ + prop1: "prop1", + prop2: 2 + }) + }) }) describe("Model identifier", () => { test("If no identifier attribute is provided, the identifierAttribute should be undefined.", () => { diff --git a/src/types/complex-types/model.ts b/src/types/complex-types/model.ts index d1a25f621..b7b08e7fa 100644 --- a/src/types/complex-types/model.ts +++ b/src/types/complex-types/model.ts @@ -81,20 +81,20 @@ export interface ModelPropertiesDeclaration { */ export type ModelPropertiesDeclarationToProperties = T extends { [k: string]: IAnyType } // optimization to reduce nesting - ? T - : { - [K in keyof T]: T[K] extends IAnyType // keep IAnyType check on the top to reduce nesting - ? T[K] - : T[K] extends string - ? IType - : T[K] extends number - ? IType - : T[K] extends boolean - ? IType - : T[K] extends Date - ? IType - : never - } + ? T + : { + [K in keyof T]: T[K] extends IAnyType // keep IAnyType check on the top to reduce nesting + ? T[K] + : T[K] extends string + ? IType + : T[K] extends number + ? IType + : T[K] extends boolean + ? IType + : T[K] extends Date + ? IType + : never + } /** * Checks if a value is optional (undefined, any or unknown). @@ -138,13 +138,13 @@ type WithAdditionalProperties = T extends Record ? Record = keyof P extends never ? // When there are no props, we want to prevent passing in any object. We have two objects we want to allow: - // 1. The empty object - // 2. An instance of this model - // - // The `IStateTreeNode` interface allows both. For (1), these props are optional so an empty object is allowed. - // For (2), an instance will contain these two props, including the "secret" `$stateTreeNodeType` prop. TypeScript's - // excess property checking will then ensure no other props are passed in. - IStateTreeNode + // 1. The empty object + // 2. An instance of this model + // + // The `IStateTreeNode` interface allows both. For (1), these props are optional so an empty object is allowed. + // For (2), an instance will contain these two props, including the "secret" `$stateTreeNodeType` prop. TypeScript's + // excess property checking will then ensure no other props are passed in. + IStateTreeNode : _CustomOrOther>> /** @hidden */ @@ -183,10 +183,10 @@ export interface IModelType< CustomC = _NotCustomized, CustomS = _NotCustomized > extends IType< - ModelCreationType2, - ModelSnapshotType2, - ModelInstanceType - > { + ModelCreationType2, + ModelSnapshotType2, + ModelInstanceType +> { readonly properties: PROPS named(newName: string): IModelType @@ -225,7 +225,7 @@ export interface IModelType< /** * Any model type. */ -export interface IAnyModelType extends IModelType {} +export interface IAnyModelType extends IModelType { } /** @hidden */ export type ExtractProps = T extends IModelType @@ -324,7 +324,7 @@ function toPropertiesObject(declaredProps: ModelPropertiesDeclaration): ModelPro } return props - }, declaredProps as any) + }, { ...declaredProps } as any) } /** @@ -332,19 +332,18 @@ function toPropertiesObject(declaredProps: ModelPropertiesDeclaration): ModelPro * @hidden */ export class ModelType< - PROPS extends ModelProperties, - OTHERS, - CustomC, - CustomS, - MT extends IModelType - > + PROPS extends ModelProperties, + OTHERS, + CustomC, + CustomS, + MT extends IModelType +> extends ComplexType< ModelCreationType2, ModelSnapshotType2, ModelInstanceType > - implements IModelType -{ + implements IModelType { readonly flags = TypeFlags.Object /* @@ -438,8 +437,8 @@ export class ModelType< const actionInvoker = createActionInvoker(self as any, name, boundAction) actions[name] = actionInvoker - // See #646, allow models to be mocked - ;(!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(self, name, actionInvoker) + // See #646, allow models to be mocked + ; (!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(self, name, actionInvoker) }) } @@ -514,7 +513,7 @@ export class ModelType< } else if (typeof descriptor.value === "function") { // this is a view function, merge as is! // See #646, allow models to be mocked - ;(!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(self, key, descriptor.value) + ; (!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(self, key, descriptor.value) } else { throw new MstError(`A view member should either be a function or getter based property`) } @@ -644,7 +643,7 @@ export class ModelType< try { // TODO: FIXME, make sure the observable ref is used! const atom = getAtom(node.storedValue, name) - ;(atom as any).reportObserved() + ; (atom as any).reportObserved() } catch (e) { throw new MstError(`${name} property is declared twice`) } @@ -668,14 +667,14 @@ export class ModelType< if (!(patch.op === "replace" || patch.op === "add")) { throw new MstError(`object does not support operation ${patch.op}`) } - ;(node.storedValue as any)[subpath] = patch.value + ; (node.storedValue as any)[subpath] = patch.value } applySnapshot(node: this["N"], snapshot: this["C"]): void { typecheckInternal(this, snapshot) const preProcessedSnapshot = this.applySnapshotPreProcessor(snapshot) this.forAllProps((name) => { - ;(node.storedValue as any)[name] = preProcessedSnapshot[name] + ; (node.storedValue as any)[name] = preProcessedSnapshot[name] }) } @@ -731,7 +730,7 @@ export class ModelType< } removeChild(node: this["N"], subpath: string) { - ;(node.storedValue as any)[subpath] = undefined + ; (node.storedValue as any)[subpath] = undefined } } ModelType.prototype.applySnapshot = action(ModelType.prototype.applySnapshot) @@ -782,32 +781,32 @@ export function compose(name: string, A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType): IModelType>>>, _CustomJoin>>>> // prettier-ignore export function compose(A: - IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType): IModelType>>>, _CustomJoin>>>> + IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType): IModelType>>>, _CustomJoin>>>> // prettier-ignore export function compose(name: string, A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType): IModelType>>>>, _CustomJoin>>>>> + extends ModelProperties, OF, FCF, FSF>(name: string, A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType): IModelType>>>>, _CustomJoin>>>>> // prettier-ignore export function compose(A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType): IModelType>>>>, _CustomJoin>>>>> + extends ModelProperties, OF, FCF, FSF>(A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType): IModelType>>>>, _CustomJoin>>>>> // prettier-ignore export function compose(name: string, A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType): IModelType>>>>>, _CustomJoin>>>>>> + extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG>(name: string, A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType): IModelType>>>>>, _CustomJoin>>>>>> // prettier-ignore export function compose(A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType): IModelType>>>>>, _CustomJoin>>>>>> + extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG>(A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType): IModelType>>>>>, _CustomJoin>>>>>> // prettier-ignore export function compose(name: string, A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType, H: IModelType): IModelType>>>>>>, _CustomJoin>>>>>>> + extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG, PH extends ModelProperties, OH, FCH, FSH>(name: string, A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType, H: IModelType): IModelType>>>>>>, _CustomJoin>>>>>>> // prettier-ignore export function compose(A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType, H: IModelType): IModelType>>>>>>, _CustomJoin>>>>>>> + extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG, PH extends ModelProperties, OH, FCH, FSH>(A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType, H: IModelType): IModelType>>>>>>, _CustomJoin>>>>>>> // prettier-ignore export function compose(name: string, A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType, H: IModelType, I: IModelType): IModelType>>>>>>>, _CustomJoin>>>>>>>> + extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG, PH extends ModelProperties, OH, FCH, FSH, PI extends ModelProperties, OI, FCI, FSI>(name: string, A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType, H: IModelType, I: IModelType): IModelType>>>>>>>, _CustomJoin>>>>>>>> // prettier-ignore export function compose(A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType, H: IModelType, I: IModelType): IModelType>>>>>>>, _CustomJoin>>>>>>>> + extends ModelProperties, OF, FCF, FSF, PG extends ModelProperties, OG, FCG, FSG, PH extends ModelProperties, OH, FCH, FSH, PI extends ModelProperties, OI, FCI, FSI>(A: IModelType, B: IModelType, C: IModelType, D: IModelType, E: IModelType, F: IModelType, G: IModelType, H: IModelType, I: IModelType): IModelType>>>>>>>, _CustomJoin>>>>>>>> /** * `types.compose` - Composes a new model from one or more existing model types.