From b6d04ff2a12cb644a22dc32b42bcc04cf855afcb Mon Sep 17 00:00:00 2001 From: Brian Cottingham Date: Fri, 12 Aug 2016 12:07:20 -0400 Subject: [PATCH] feature(api): tell consumers when their objects are observable --- src/api/observable.ts | 5 ++-- src/mobx.ts | 2 +- src/types/observableobject.ts | 10 +++++--- test/typescript-tests.ts | 44 +++++++++++++++++++++++++++++++++-- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/api/observable.ts b/src/api/observable.ts index 22fb45372..eb44eb954 100644 --- a/src/api/observable.ts +++ b/src/api/observable.ts @@ -4,6 +4,7 @@ import {computed} from "./computeddecorator"; import {isPlainObject, invariant, deprecated} from "../utils/utils"; import {observableDecorator} from "./observabledecorator"; import {isObservable} from "./isobservable"; +import {IObservableObject} from "../types/observableobject"; import {IObservableArray, ObservableArray} from "../types/observablearray"; /** @@ -15,7 +16,7 @@ export function observable(target: Object, key: string, baseDescriptor?: Propert export function observable(value: T[]): IObservableArray; export function observable(value: () => T, thisArg?: S): IObservableValue; export function observable(value: T): IObservableValue; -export function observable(value: T): T; +export function observable(value: T): T & IObservableObject; export function observable(v: any = undefined, keyOrScope?: string | any) { if (typeof arguments[1] === "string") return observableDecorator.apply(null, arguments); @@ -55,4 +56,4 @@ export function getTypeOfValue(value): ValueType { if (typeof value === "object") return isPlainObject(value) ? ValueType.PlainObject : ValueType.ComplexObject; return ValueType.Reference; // safe default, only refer by reference.. -} \ No newline at end of file +} diff --git a/src/mobx.ts b/src/mobx.ts index b2f4c7053..69bec316e 100644 --- a/src/mobx.ts +++ b/src/mobx.ts @@ -31,7 +31,7 @@ export { IComputedValue } from "./core/com export { asReference, asFlat, asStructure, asMap } from "./types/modifiers"; export { IInterceptable, IInterceptor } from "./types/intercept-utils"; export { IListenable } from "./types/listen-utils"; -export { IObjectWillChange, IObjectChange, isObservableObject } from "./types/observableobject"; +export { IObjectWillChange, IObjectChange, IObservableObject, isObservableObject } from "./types/observableobject"; export { /* 3.0: IValueDidChange, */ IValueWillChange, IObservableValue } from "./types/observablevalue"; export { IObservableArray, IArrayWillChange, IArrayWillSplice, IArrayChange, IArraySplice, isObservableArray, fastArray } from "./types/observablearray"; diff --git a/src/types/observableobject.ts b/src/types/observableobject.ts index 30432a8cd..92d696b55 100644 --- a/src/types/observableobject.ts +++ b/src/types/observableobject.ts @@ -9,6 +9,10 @@ import {hasInterceptors, IInterceptable, registerInterceptor, interceptChange} f import {IListenable, registerListener, hasListeners, notifyListeners} from "./listen-utils"; import {isSpyEnabled, spyReportStart, spyReportEnd} from "../core/spy"; +export interface IObservableObject { + "observable-object": IObservableObject; +} + // In 3.0, change to IObjectDidChange export interface IObjectChange { name: string; @@ -198,11 +202,11 @@ function notifyPropertyAddition(adm, object, name: string, newValue) { spyReportEnd(); } -export function isObservableObject(thing): boolean { +export function isObservableObject(thing: T): thing is T & IObservableObject { if (typeof thing === "object" && thing !== null) { // Initializers run lazily when transpiling to babel, so make sure they are run... runLazyInitializers(thing); - return thing.$mobx instanceof ObservableObjectAdministration; + return (thing as T & {$mobx: any}).$mobx instanceof ObservableObjectAdministration; } return false; -} \ No newline at end of file +} diff --git a/test/typescript-tests.ts b/test/typescript-tests.ts index 0a50326f6..e1409c196 100644 --- a/test/typescript-tests.ts +++ b/test/typescript-tests.ts @@ -2,7 +2,7 @@ /// import { observe, computed, observable, asStructure, autorun, autorunAsync, extendObservable, action, - IObservableArray, IArrayChange, IArraySplice, IObservableValue, isObservable, isObservableObject, + IObservableObject, IObservableArray, IArrayChange, IArraySplice, IObservableValue, isObservable, isObservableObject, extras, Atom, transaction, IObjectChange, spy, useStrict, isAction } from "../lib/mobx"; import * as test from 'tape'; @@ -918,4 +918,44 @@ test("373 - fix isObservable for unused computed", t => { new Bla(); t.end(); -}) \ No newline at end of file +}) + +test("484 - observable objects are IObservableObject", t => { + const needs_observable_object = (o: IObservableObject): any => null; + const o = observable({stuff: "things"}); + + needs_observable_object(o); + t.pass(); + t.end(); +}); + +test("484 - observable objects are still type T", t => { + const o = observable({stuff: "things"}); + o.stuff = "new things"; + t.pass(); + t.end(); +}); + +test("484 - isObservableObject type guard includes type T", t => { + const o = observable({stuff: "things"}); + if(isObservableObject(o)) { + o.stuff = "new things"; + t.pass(); + } else { + t.fail("object should have been observable"); + } + t.end(); +}); + +test("484 - isObservableObject type guard includes type IObservableObject", t => { + const requires_observable_object = (o: IObservableObject): void => null; + const o = observable({stuff: "things"}); + + if(isObservableObject(o)) { + requires_observable_object(o); + t.pass(); + } else { + t.fail("object should have been IObservableObject"); + } + t.end(); +});