Skip to content

Commit

Permalink
Merge pull request #782 from mobxjs/eladnava-symbol-toprimitive-support
Browse files Browse the repository at this point in the history
Add support for `toPrimitive` and `valueOf` on computed and observable values
  • Loading branch information
mweststrate committed Jan 19, 2017
2 parents 9537b6a + aa6a00c commit c7449df
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
@@ -1,8 +1,9 @@
# 3.0.2

* Fixed issue where MobX failed on environments where `Map` is not defined, #779 by @dirtyrolf
* MobX can now be compiled on windows as well! #772 by @madarauchiha
* MobX can now be compiled on windows as well! #772 by @madarauchiha #GoodnessSquad
* Added documentation on how Flow typings can be used, #766 by @wietsevenema
* Added support for `Symbol.toPrimitive()` and `valueOf()`, see #773 by @eladnava #GoodnessSquad

Re-introduced _structural comparison_. Seems we couldn't part from it yet :). So the following things have been added:

Expand Down
8 changes: 7 additions & 1 deletion src/core/computedvalue.ts
Expand Up @@ -2,7 +2,7 @@ import {IObservable, reportObserved, propagateMaybeChanged, propagateChangeConfi
import {IDerivation, IDerivationState, trackDerivedFunction, clearObserving, untrackedStart, untrackedEnd, shouldCompute, CaughtException, isCaughtException} from "./derivation";
import {globalState} from "./globalstate";
import {allowStateChangesStart, allowStateChangesEnd, createAction} from "./action";
import {createInstanceofPredicate, getNextId, valueDidChange, invariant, Lambda, unique, joinStrings} from "../utils/utils";
import {createInstanceofPredicate, getNextId, valueDidChange, invariant, Lambda, unique, joinStrings, primitiveSymbol, toPrimitive} from "../utils/utils";
import {isSpyEnabled, spyReport} from "../core/spy";
import {autorun} from "../api/autorun";
import {IValueDidChange} from "../types/observablevalue";
Expand Down Expand Up @@ -183,6 +183,10 @@ export class ComputedValue<T> implements IObservable, IComputedValue<T>, IDeriva
return `${this.name}[${this.derivation.toString()}]`;
}

valueOf(): T {
return toPrimitive(this.get());
};

whyRun() {
const isTracking = Boolean(globalState.trackingDerivation);
const observing = unique(this.isComputing ? this.newObserving! : this.observing).map((dep: any) => dep.name);
Expand Down Expand Up @@ -213,4 +217,6 @@ WhyRun? computation '${this.name}':
}
}

ComputedValue.prototype[primitiveSymbol()] = ComputedValue.prototype.valueOf;

export const isComputedValue = createInstanceofPredicate("ComputedValue", ComputedValue);
10 changes: 9 additions & 1 deletion src/types/observablevalue.ts
@@ -1,6 +1,6 @@
import {BaseAtom} from "../core/atom";
import {checkIfStateModificationsAreAllowed} from "../core/derivation";
import {Lambda, getNextId, createInstanceofPredicate} from "../utils/utils";
import {Lambda, getNextId, createInstanceofPredicate, primitiveSymbol, toPrimitive} from "../utils/utils";
import {hasInterceptors, IInterceptable, IInterceptor, registerInterceptor, interceptChange} from "./intercept-utils";
import {IListenable, registerListener, hasListeners, notifyListeners} from "./listen-utils";
import {isSpyEnabled, spyReportStart, spyReportEnd, spyReport} from "../core/spy";
Expand All @@ -27,6 +27,8 @@ export interface IObservableValue<T> {
observe(listener: (change: IValueDidChange<T>) => void, fireImmediately?: boolean): Lambda;
}

declare var Symbol;

export class ObservableValue<T> extends BaseAtom implements IObservableValue<T>, IInterceptable<IValueWillChange<T>>, IListenable {
hasUnreportedChange = false;
interceptors;
Expand Down Expand Up @@ -117,6 +119,12 @@ export class ObservableValue<T> extends BaseAtom implements IObservableValue<T>,
toString() {
return `${this.name}[${this.value}]`;
}

valueOf(): T {
return toPrimitive(this.get());
}
}

ObservableValue.prototype[primitiveSymbol()] = ObservableValue.prototype.valueOf;

export const isObservableValue = createInstanceofPredicate("ObservableValue", ObservableValue);
10 changes: 10 additions & 0 deletions src/utils/utils.ts
Expand Up @@ -227,6 +227,16 @@ export function isES6Map(thing): boolean {
return false;
}

declare var Symbol;

export function primitiveSymbol() {
return (typeof Symbol === "function" && Symbol.toPrimitive) || "@@toPrimitive";
}

export function toPrimitive(value) {
return value === null ? null : typeof value === "object" ? ("" + value) : value;
}

import {globalState} from "../core/globalstate";
import {isObservableArray} from "../types/observablearray";
import {isObservableMap} from "../types/observablemap";
Expand Down
2 changes: 1 addition & 1 deletion test/makereactive.js
Expand Up @@ -574,7 +574,7 @@ test("boxed value json", t => {
t.deepEqual(a.get().x, 1);
a.set(3);
t.deepEqual(a.get(), 3);
t.equal("" + a, 'ObservableValue@3[3]');
t.equal("" + a, '3');
t.equal(a.toJSON(), 3);
t.end();
})
Expand Down
35 changes: 35 additions & 0 deletions test/observables.js
Expand Up @@ -1662,6 +1662,41 @@ test('603 - transaction should not kill reactions', t => {

})

test('#561 test toPrimitive() of observable objects', function(t) {
if (typeof Symbol !== "undefined" && Symbol.toPrimitive) {
var x = observable(3);

t.equal(x.valueOf(), 3);
t.equal(x[Symbol.toPrimitive](), 3);

t.equal(+x, 3);
t.equal(++x, 4);

var y = observable(3);

t.equal(y + 7, 10);

var z = computed(() => ({ a: 3 }));
t.equal(3 + z, "3[object Object]");
} else {
var x = observable(3);

t.equal(x.valueOf(), 3);
t.equal(x["@@toPrimitive"](), 3);

t.equal(+x, 3);
t.equal(++x, 4);

var y = observable(3);

t.equal(y + 7, 10);

var z = computed(() => ({ a: 3 }));
t.equal("3" + (z["@@toPrimitive"]()), "3[object Object]");
}
t.end()
});

test('observables should not fail when ES6 Map is missing', t => {
const globalMapFunction = global.Map;
global.Map = undefined;
Expand Down

0 comments on commit c7449df

Please sign in to comment.