From 5642cd5f5afbde55cede2a4762305423899699b2 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 29 Sep 2016 10:27:36 +0200 Subject: [PATCH 1/4] Implemented `toJS` in such a way that it only recurses into observables --- src/api/tojs.ts | 61 ++++++++- src/mobx.ts | 2 +- test/api.js | 1 + test/observables.js | 304 ------------------------------------------- test/tojs-legacy.js | 311 ++++++++++++++++++++++++++++++++++++++++++++ test/tojs.js | 306 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 675 insertions(+), 310 deletions(-) create mode 100644 test/tojs-legacy.js create mode 100644 test/tojs.js diff --git a/src/api/tojs.ts b/src/api/tojs.ts index b5ed76ca9..0f3f0dc99 100644 --- a/src/api/tojs.ts +++ b/src/api/tojs.ts @@ -1,12 +1,63 @@ import {isObservableArray} from "../types/observablearray"; +import {isObservableObject} from "../types/observableobject"; import {isObservableMap} from "../types/observablemap"; import {isObservableValue} from "../types/observablevalue"; +import {isObservable} from "../api/isobservable"; import {deprecated} from "../utils/utils"; /** * Basically, a deep clone, so that no reactive property will exist anymore. */ export function toJS(source, detectCycles: boolean = true, __alreadySeen: [any, any][] = null) { + // TODO: in 3.0, detectCycles should default to false? + // optimization: using ES6 map would be more efficient! + // optimization: lift this function outside toJS, this makes recursion expensive + function cache(value) { + if (detectCycles) + __alreadySeen.push([source, value]); + return value; + } + if (isObservable(source)) { + if (detectCycles && __alreadySeen === null) + __alreadySeen = []; + if (detectCycles && source !== null && typeof source === "object") { + for (let i = 0, l = __alreadySeen.length; i < l; i++) + if (__alreadySeen[i][0] === source) + return __alreadySeen[i][1]; + } + + if (isObservableArray(source)) { + const res = cache([]); + const toAdd = source.map(value => toJS(value, detectCycles, __alreadySeen)); + res.length = toAdd.length; + for (let i = 0, l = toAdd.length; i < l; i++) + res[i] = toAdd[i]; + return res; + } + if (isObservableObject(source)) { + const res = cache({}); + for (let key in source) + res[key] = toJS(source[key], detectCycles, __alreadySeen); + return res; + } + if (isObservableMap(source)) { + const res = cache({}); + source.forEach( + (value, key) => res[key] = toJS(value, detectCycles, __alreadySeen) + ); + return res; + } + if (isObservableValue(source)) + return toJS(source.get(), detectCycles, __alreadySeen); + } + return source; +} + +/** + * Basically, a deep clone, so that no reactive property will exist anymore. + */ +export function toJSlegacy(source, detectCycles: boolean = true, __alreadySeen: [any, any][] = null) { + deprecated("toJSlegacy is deprecated and will be removed in the next major. Use `toJS` instead. See #566"); // optimization: using ES6 map would be more efficient! function cache(value) { if (detectCycles) @@ -28,7 +79,7 @@ export function toJS(source, detectCycles: boolean = true, __alreadySeen: [any, return source; if (Array.isArray(source) || isObservableArray(source)) { const res = cache([]); - const toAdd = source.map(value => toJS(value, detectCycles, __alreadySeen)); + const toAdd = source.map(value => toJSlegacy(value, detectCycles, __alreadySeen)); res.length = toAdd.length; for (let i = 0, l = toAdd.length; i < l; i++) res[i] = toAdd[i]; @@ -37,16 +88,16 @@ export function toJS(source, detectCycles: boolean = true, __alreadySeen: [any, if (isObservableMap(source)) { const res = cache({}); source.forEach( - (value, key) => res[key] = toJS(value, detectCycles, __alreadySeen) + (value, key) => res[key] = toJSlegacy(value, detectCycles, __alreadySeen) ); return res; } if (isObservableValue(source)) - return toJS(source.get(), detectCycles, __alreadySeen); + return toJSlegacy(source.get(), detectCycles, __alreadySeen); if (typeof source === "object") { const res = cache({}); for (let key in source) - res[key] = toJS(source[key], detectCycles, __alreadySeen); + res[key] = toJSlegacy(source[key], detectCycles, __alreadySeen); return res; } return source; @@ -54,5 +105,5 @@ export function toJS(source, detectCycles: boolean = true, __alreadySeen: [any, export function toJSON(source, detectCycles: boolean = true, __alreadySeen: [any, any][] = null) { deprecated("toJSON is deprecated. Use toJS instead"); - return toJS.apply(null, arguments); + return toJSlegacy.apply(null, arguments); } \ No newline at end of file diff --git a/src/mobx.ts b/src/mobx.ts index 430cf8e9e..ad6b23a6b 100644 --- a/src/mobx.ts +++ b/src/mobx.ts @@ -48,7 +48,7 @@ export { autorun, autorunAsync, autorunUntil, when, reaction } from "./api/auto export { action, isAction, runInAction } from "./api/action"; export { expr } from "./api/expr"; -export { toJSON, toJS } from "./api/tojs"; +export { toJSON, toJS, toJSlegacy } from "./api/tojs"; export { ITransformer, createTransformer } from "./api/createtransformer"; export { whyRun } from "./api/whyrun"; diff --git a/test/api.js b/test/api.js index 39e31f757..e33ff694c 100644 --- a/test/api.js +++ b/test/api.js @@ -39,6 +39,7 @@ test('correct api should be exposed', function(t) { 'runInAction', 'spy', 'toJS', + 'toJSlegacy', 'toJSON', 'transaction', 'untracked', diff --git a/test/observables.js b/test/observables.js index f57f92b96..86836a4d6 100644 --- a/test/observables.js +++ b/test/observables.js @@ -862,310 +862,6 @@ test('expr2', function(t) { t.end(); }) -test('json1', function(t) { - var todos = observable([ - { - title: "write blog" - }, - { - title: "improve coverge" - } - ]); - - var output; - mobx.autorun(function() { - output = todos.map(function(todo) { return todo.title; }).join(", "); - }); - - todos[1].title = "improve coverage"; // prints: write blog, improve coverage - t.equal(output, "write blog, improve coverage"); - todos.push({ title: "take a nap" }); // prints: write blog, improve coverage, take a nap - t.equal(output, "write blog, improve coverage, take a nap"); - - t.end(); -}) - -test('json2', function(t) { - var source = { - todos: [ - { - title: "write blog", - tags: ["react","frp"], - details: { - url: "somewhere" - } - }, - { - title: "do the dishes", - tags: ["mweh"], - details: { - url: "here" - } - } - ] - }; - - var o = mobx.observable(JSON.parse(JSON.stringify(source))); - - t.deepEqual(mobx.toJS(o), source); - - var analyze = observable(function() { - return [ - o.todos.length, - o.todos[1].details.url - ] - }); - - var alltags = observable(function() { - return o.todos.map(function(todo) { - return todo.tags.join(","); - }).join(","); - }); - - var ab = []; - var tb = []; - - m.observe(analyze, function(d) { ab.push(d); }, true); - m.observe(alltags, function(d) { tb.push(d); }, true); - - o.todos[0].details.url = "boe"; - o.todos[1].details.url = "ba"; - o.todos[0].tags[0] = "reactjs"; - o.todos[1].tags.push("pff"); - - t.deepEqual(mobx.toJS(o), { - "todos": [ - { - "title": "write blog", - "tags": [ - "reactjs", - "frp" - ], - "details": { - "url": "boe" - } - }, - { - "title": "do the dishes", - "tags": [ - "mweh", "pff" - ], - "details": { - "url": "ba" - } - } - ] - }); - t.deepEqual(ab, [ [ 2, 'here' ], [ 2, 'ba' ] ]); - t.deepEqual(tb, [ 'react,frp,mweh', 'reactjs,frp,mweh', 'reactjs,frp,mweh,pff' ]); - ab = []; - tb = []; - - o.todos.push(mobx.observable({ - title: "test", - tags: ["x"] - })); - - t.deepEqual(mobx.toJSON(o), { - "todos": [ - { - "title": "write blog", - "tags": [ - "reactjs", - "frp" - ], - "details": { - "url": "boe" - } - }, - { - "title": "do the dishes", - "tags": [ - "mweh", "pff" - ], - "details": { - "url": "ba" - } - }, - { - title: "test", - tags: ["x"] - } - ] - }); - t.deepEqual(ab, [[3, "ba"]]); - t.deepEqual(tb, ["reactjs,frp,mweh,pff,x"]); - ab = []; - tb = []; - - o.todos[1] = mobx.observable({ - title: "clean the attic", - tags: ["needs sabbatical"], - details: { - url: "booking.com" - } - }); - t.deepEqual(JSON.parse(JSON.stringify(o)), { - "todos": [ - { - "title": "write blog", - "tags": [ - "reactjs", - "frp" - ], - "details": { - "url": "boe" - } - }, - { - "title": "clean the attic", - "tags": [ - "needs sabbatical" - ], - "details": { - "url": "booking.com" - } - }, - { - title: "test", - tags: ["x"] - } - ] - }); - t.deepEqual(ab, [[3, "booking.com"]]); - t.deepEqual(tb, ["reactjs,frp,needs sabbatical,x"]); - ab = []; - tb = []; - - o.todos[1].details = mobx.observable({ url: "google" }); - o.todos[1].tags = ["foo", "bar"]; - t.deepEqual(mobx.toJSON(o, false), { - "todos": [ - { - "title": "write blog", - "tags": [ - "reactjs", - "frp" - ], - "details": { - "url": "boe" - } - }, - { - "title": "clean the attic", - "tags": [ - "foo", "bar" - ], - "details": { - "url": "google" - } - }, - { - title: "test", - tags: ["x"] - } - ] - }); - t.deepEqual(mobx.toJSON(o, true), mobx.toJSON(o, false)); - t.deepEqual(ab, [[3, "google"]]); - t.deepEqual(tb, ["reactjs,frp,foo,bar,x"]); - - t.end(); -}) - -test('toJS handles dates', t => { - var a = observable({ - d: new Date() - }); - - var b = mobx.toJS(a); - t.equal(b.d instanceof Date, true) - t.equal(a.d === b.d, true) - t.end() -}) - -test('json cycles', function(t) { - var a = observable({ - b: 1, - c: [2], - d: mobx.map(), - e: a - }); - - a.e = a; - a.c.push(a, a.d); - a.d.set("f", a); - a.d.set("d", a.d); - a.d.set("c", a.c); - - var cloneA = mobx.toJSON(a, true); - var cloneC = cloneA.c; - var cloneD = cloneA.d; - - t.equal(cloneA.b, 1); - t.equal(cloneA.c[0], 2); - t.equal(cloneA.c[1], cloneA); - t.equal(cloneA.c[2], cloneD); - t.equal(cloneD.f, cloneA); - t.equal(cloneD.d, cloneD); - t.equal(cloneD.c, cloneC); - t.equal(cloneA.e, cloneA); - - t.end(); -}) - -test('#285 class instances with toJS', t => { - function Person() { - this.firstName = "michel"; - mobx.extendObservable(this, { - lastName: "weststrate", - tags: ["user", "mobx-member"], - fullName: function() { - return this.firstName + this.lastName - } - }) - } - - const p1 = new Person(); - // check before lazy initialization - t.deepEqual(mobx.toJS(p1), { - firstName: "michel", - lastName: "weststrate", - tags: ["user", "mobx-member"] - }); - - // check after lazy initialization - t.deepEqual(mobx.toJS(p1), { - firstName: "michel", - lastName: "weststrate", - tags: ["user", "mobx-member"] - }); - - t.end() -}) - -test('#285 non-mobx class instances with toJS', t => { - function Person() { - this.firstName = "michel"; - this.lastName = mobx.observable("weststrate"); - } - - const p1 = new Person(); - // check before lazy initialization - t.deepEqual(mobx.toJS(p1), { - firstName: "michel", - lastName: "weststrate" - }); - - // check after lazy initialization - t.deepEqual(mobx.toJS(p1), { - firstName: "michel", - lastName: "weststrate" - }); - - t.end() -}) - function stripSpyOutput(events) { events.forEach(ev => { delete ev.time; diff --git a/test/tojs-legacy.js b/test/tojs-legacy.js new file mode 100644 index 000000000..292fe5b4c --- /dev/null +++ b/test/tojs-legacy.js @@ -0,0 +1,311 @@ +"use strict" + +var test = require('tape'); +var mobx = require('..'); +var m = mobx; +var observable = mobx.observable; +var transaction = mobx.transaction; + +test('json1', function(t) { + var todos = observable([ + { + title: "write blog" + }, + { + title: "improve coverge" + } + ]); + + var output; + mobx.autorun(function() { + output = todos.map(function(todo) { return todo.title; }).join(", "); + }); + + todos[1].title = "improve coverage"; // prints: write blog, improve coverage + t.equal(output, "write blog, improve coverage"); + todos.push({ title: "take a nap" }); // prints: write blog, improve coverage, take a nap + t.equal(output, "write blog, improve coverage, take a nap"); + + t.end(); +}) + +test('json2', function(t) { + var source = { + todos: [ + { + title: "write blog", + tags: ["react","frp"], + details: { + url: "somewhere" + } + }, + { + title: "do the dishes", + tags: ["mweh"], + details: { + url: "here" + } + } + ] + }; + + var o = mobx.observable(JSON.parse(JSON.stringify(source))); + + t.deepEqual(mobx.toJSlegacy(o), source); + + var analyze = observable(function() { + return [ + o.todos.length, + o.todos[1].details.url + ] + }); + + var alltags = observable(function() { + return o.todos.map(function(todo) { + return todo.tags.join(","); + }).join(","); + }); + + var ab = []; + var tb = []; + + m.observe(analyze, function(d) { ab.push(d); }, true); + m.observe(alltags, function(d) { tb.push(d); }, true); + + o.todos[0].details.url = "boe"; + o.todos[1].details.url = "ba"; + o.todos[0].tags[0] = "reactjs"; + o.todos[1].tags.push("pff"); + + t.deepEqual(mobx.toJSlegacy(o), { + "todos": [ + { + "title": "write blog", + "tags": [ + "reactjs", + "frp" + ], + "details": { + "url": "boe" + } + }, + { + "title": "do the dishes", + "tags": [ + "mweh", "pff" + ], + "details": { + "url": "ba" + } + } + ] + }); + t.deepEqual(ab, [ [ 2, 'here' ], [ 2, 'ba' ] ]); + t.deepEqual(tb, [ 'react,frp,mweh', 'reactjs,frp,mweh', 'reactjs,frp,mweh,pff' ]); + ab = []; + tb = []; + + o.todos.push(mobx.observable({ + title: "test", + tags: ["x"] + })); + + t.deepEqual(mobx.toJSON(o), { + "todos": [ + { + "title": "write blog", + "tags": [ + "reactjs", + "frp" + ], + "details": { + "url": "boe" + } + }, + { + "title": "do the dishes", + "tags": [ + "mweh", "pff" + ], + "details": { + "url": "ba" + } + }, + { + title: "test", + tags: ["x"] + } + ] + }); + t.deepEqual(ab, [[3, "ba"]]); + t.deepEqual(tb, ["reactjs,frp,mweh,pff,x"]); + ab = []; + tb = []; + + o.todos[1] = mobx.observable({ + title: "clean the attic", + tags: ["needs sabbatical"], + details: { + url: "booking.com" + } + }); + t.deepEqual(JSON.parse(JSON.stringify(o)), { + "todos": [ + { + "title": "write blog", + "tags": [ + "reactjs", + "frp" + ], + "details": { + "url": "boe" + } + }, + { + "title": "clean the attic", + "tags": [ + "needs sabbatical" + ], + "details": { + "url": "booking.com" + } + }, + { + title: "test", + tags: ["x"] + } + ] + }); + t.deepEqual(ab, [[3, "booking.com"]]); + t.deepEqual(tb, ["reactjs,frp,needs sabbatical,x"]); + ab = []; + tb = []; + + o.todos[1].details = mobx.observable({ url: "google" }); + o.todos[1].tags = ["foo", "bar"]; + t.deepEqual(mobx.toJSON(o, false), { + "todos": [ + { + "title": "write blog", + "tags": [ + "reactjs", + "frp" + ], + "details": { + "url": "boe" + } + }, + { + "title": "clean the attic", + "tags": [ + "foo", "bar" + ], + "details": { + "url": "google" + } + }, + { + title: "test", + tags: ["x"] + } + ] + }); + t.deepEqual(mobx.toJSON(o, true), mobx.toJSON(o, false)); + t.deepEqual(ab, [[3, "google"]]); + t.deepEqual(tb, ["reactjs,frp,foo,bar,x"]); + + t.end(); +}) + +test('toJS handles dates', t => { + var a = observable({ + d: new Date() + }); + + var b = mobx.toJSlegacy(a); + t.equal(b.d instanceof Date, true) + t.equal(a.d === b.d, true) + t.end() +}) + +test('json cycles', function(t) { + var a = observable({ + b: 1, + c: [2], + d: mobx.map(), + e: a + }); + + a.e = a; + a.c.push(a, a.d); + a.d.set("f", a); + a.d.set("d", a.d); + a.d.set("c", a.c); + + var cloneA = mobx.toJSON(a, true); + var cloneC = cloneA.c; + var cloneD = cloneA.d; + + t.equal(cloneA.b, 1); + t.equal(cloneA.c[0], 2); + t.equal(cloneA.c[1], cloneA); + t.equal(cloneA.c[2], cloneD); + t.equal(cloneD.f, cloneA); + t.equal(cloneD.d, cloneD); + t.equal(cloneD.c, cloneC); + t.equal(cloneA.e, cloneA); + + t.end(); +}) + +test('#285 class instances with toJS', t => { + function Person() { + this.firstName = "michel"; + mobx.extendObservable(this, { + lastName: "weststrate", + tags: ["user", "mobx-member"], + fullName: function() { + return this.firstName + this.lastName + } + }) + } + + const p1 = new Person(); + // check before lazy initialization + t.deepEqual(mobx.toJSlegacy(p1), { + firstName: "michel", + lastName: "weststrate", + tags: ["user", "mobx-member"] + }); + + // check after lazy initialization + t.deepEqual(mobx.toJSlegacy(p1), { + firstName: "michel", + lastName: "weststrate", + tags: ["user", "mobx-member"] + }); + + t.end() +}) + +test('#285 non-mobx class instances with toJS', t => { + function Person() { + this.firstName = "michel"; + this.lastName = mobx.observable("weststrate"); + } + + const p1 = new Person(); + // check before lazy initialization + t.deepEqual(mobx.toJSlegacy(p1), { + firstName: "michel", + lastName: "weststrate" + }); + + // check after lazy initialization + t.deepEqual(mobx.toJSlegacy(p1), { + firstName: "michel", + lastName: "weststrate" + }); + + t.end() +}) diff --git a/test/tojs.js b/test/tojs.js new file mode 100644 index 000000000..9567602ca --- /dev/null +++ b/test/tojs.js @@ -0,0 +1,306 @@ +"use strict" + +var test = require('tape'); +var mobx = require('..'); +var m = mobx; +var observable = mobx.observable; +var transaction = mobx.transaction; + +test('json1', function(t) { + var todos = observable([ + { + title: "write blog" + }, + { + title: "improve coverge" + } + ]); + + var output; + mobx.autorun(function() { + output = todos.map(function(todo) { return todo.title; }).join(", "); + }); + + todos[1].title = "improve coverage"; // prints: write blog, improve coverage + t.equal(output, "write blog, improve coverage"); + todos.push({ title: "take a nap" }); // prints: write blog, improve coverage, take a nap + t.equal(output, "write blog, improve coverage, take a nap"); + + t.end(); +}) + +test('json2', function(t) { + var source = { + todos: [ + { + title: "write blog", + tags: ["react","frp"], + details: { + url: "somewhere" + } + }, + { + title: "do the dishes", + tags: ["mweh"], + details: { + url: "here" + } + } + ] + }; + + var o = mobx.observable(JSON.parse(JSON.stringify(source))); + + t.deepEqual(mobx.toJS(o), source); + + var analyze = observable(function() { + return [ + o.todos.length, + o.todos[1].details.url + ] + }); + + var alltags = observable(function() { + return o.todos.map(function(todo) { + return todo.tags.join(","); + }).join(","); + }); + + var ab = []; + var tb = []; + + m.observe(analyze, function(d) { ab.push(d); }, true); + m.observe(alltags, function(d) { tb.push(d); }, true); + + o.todos[0].details.url = "boe"; + o.todos[1].details.url = "ba"; + o.todos[0].tags[0] = "reactjs"; + o.todos[1].tags.push("pff"); + + t.deepEqual(mobx.toJS(o), { + "todos": [ + { + "title": "write blog", + "tags": [ + "reactjs", + "frp" + ], + "details": { + "url": "boe" + } + }, + { + "title": "do the dishes", + "tags": [ + "mweh", "pff" + ], + "details": { + "url": "ba" + } + } + ] + }); + t.deepEqual(ab, [ [ 2, 'here' ], [ 2, 'ba' ] ]); + t.deepEqual(tb, [ 'react,frp,mweh', 'reactjs,frp,mweh', 'reactjs,frp,mweh,pff' ]); + ab = []; + tb = []; + + o.todos.push(mobx.observable({ + title: "test", + tags: ["x"] + })); + + t.deepEqual(mobx.toJSON(o), { + "todos": [ + { + "title": "write blog", + "tags": [ + "reactjs", + "frp" + ], + "details": { + "url": "boe" + } + }, + { + "title": "do the dishes", + "tags": [ + "mweh", "pff" + ], + "details": { + "url": "ba" + } + }, + { + title: "test", + tags: ["x"] + } + ] + }); + t.deepEqual(ab, [[3, "ba"]]); + t.deepEqual(tb, ["reactjs,frp,mweh,pff,x"]); + ab = []; + tb = []; + + o.todos[1] = mobx.observable({ + title: "clean the attic", + tags: ["needs sabbatical"], + details: { + url: "booking.com" + } + }); + t.deepEqual(JSON.parse(JSON.stringify(o)), { + "todos": [ + { + "title": "write blog", + "tags": [ + "reactjs", + "frp" + ], + "details": { + "url": "boe" + } + }, + { + "title": "clean the attic", + "tags": [ + "needs sabbatical" + ], + "details": { + "url": "booking.com" + } + }, + { + title: "test", + tags: ["x"] + } + ] + }); + t.deepEqual(ab, [[3, "booking.com"]]); + t.deepEqual(tb, ["reactjs,frp,needs sabbatical,x"]); + ab = []; + tb = []; + + o.todos[1].details = mobx.observable({ url: "google" }); + o.todos[1].tags = ["foo", "bar"]; + t.deepEqual(mobx.toJSON(o, false), { + "todos": [ + { + "title": "write blog", + "tags": [ + "reactjs", + "frp" + ], + "details": { + "url": "boe" + } + }, + { + "title": "clean the attic", + "tags": [ + "foo", "bar" + ], + "details": { + "url": "google" + } + }, + { + title: "test", + tags: ["x"] + } + ] + }); + t.deepEqual(mobx.toJSON(o, true), mobx.toJSON(o, false)); + t.deepEqual(ab, [[3, "google"]]); + t.deepEqual(tb, ["reactjs,frp,foo,bar,x"]); + + t.end(); +}) + +test('toJS handles dates', t => { + var a = observable({ + d: new Date() + }); + + var b = mobx.toJS(a); + t.equal(b.d instanceof Date, true) + t.equal(a.d === b.d, true) + t.end() +}) + +test('json cycles', function(t) { + var a = observable({ + b: 1, + c: [2], + d: mobx.map(), + e: a + }); + + a.e = a; + a.c.push(a, a.d); + a.d.set("f", a); + a.d.set("d", a.d); + a.d.set("c", a.c); + + var cloneA = mobx.toJSON(a, true); + var cloneC = cloneA.c; + var cloneD = cloneA.d; + + t.equal(cloneA.b, 1); + t.equal(cloneA.c[0], 2); + t.equal(cloneA.c[1], cloneA); + t.equal(cloneA.c[2], cloneD); + t.equal(cloneD.f, cloneA); + t.equal(cloneD.d, cloneD); + t.equal(cloneD.c, cloneC); + t.equal(cloneA.e, cloneA); + + t.end(); +}) + +test('#285 class instances with toJS', t => { + function Person() { + this.firstName = "michel"; + mobx.extendObservable(this, { + lastName: "weststrate", + tags: ["user", "mobx-member"], + fullName: function() { + return this.firstName + this.lastName + } + }) + } + + const p1 = new Person(); + // check before lazy initialization + t.deepEqual(mobx.toJS(p1), { + firstName: "michel", + lastName: "weststrate", + tags: ["user", "mobx-member"] + }); + + // check after lazy initialization + t.deepEqual(mobx.toJS(p1), { + firstName: "michel", + lastName: "weststrate", + tags: ["user", "mobx-member"] + }); + + t.end() +}) + +test('#285 non-mobx class instances with toJS', t => { + const nameObservable = mobx.observable("weststrate"); + function Person() { + this.firstName = "michel"; + this.lastName = nameObservable; + } + + const p1 = new Person(); + // check before lazy initialization + t.deepEqual(mobx.toJS(p1), { + firstName: "michel", + lastName: nameObservable // toJS doesn't recurse into non observable objects! + }); + + t.end() +}) From 8a380914def9821eee01e17e8aa22facb24bc25c Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 29 Sep 2016 10:41:46 +0200 Subject: [PATCH 2/4] additional test --- test/tojs.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/tojs.js b/test/tojs.js index 9567602ca..2b6ba188e 100644 --- a/test/tojs.js +++ b/test/tojs.js @@ -304,3 +304,19 @@ test('#285 non-mobx class instances with toJS', t => { t.end() }) + +test("verify #566 solution", t => { + function MyClass() {} + const a = new MyClass() + const b = mobx.observable({ x: 3 }) + const c = mobx.observable({ a: a, b: b }) + + t.ok(mobx.toJS(c).a === a) // true + t.ok(mobx.toJS(c).b !== b) // false, cloned + t.ok(mobx.toJS(c).b.x === b.x) // true, both 3 + + t.ok(mobx.toJSlegacy(c).a !== a) // false, cloned as well + t.ok(mobx.toJSlegacy(c).b !== b) // false, cloned + t.ok(mobx.toJSlegacy(c).b.x === b.x) // true, both 3 + t.end() +}) From f0bd0df17682c3e4f6edf82f6fabc4f710a6a953 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 29 Sep 2016 10:56:33 +0200 Subject: [PATCH 3/4] test fixes --- test/api.js | 2 +- test/tojs-legacy.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/api.js b/test/api.js index e33ff694c..d8a2071ab 100644 --- a/test/api.js +++ b/test/api.js @@ -46,7 +46,7 @@ test('correct api should be exposed', function(t) { 'useStrict', 'when', 'whyRun' - ]); + ].sort()); t.equals(Object.keys(mobx).filter(function(key) { return mobx[key] == undefined; }).length, 0); diff --git a/test/tojs-legacy.js b/test/tojs-legacy.js index 292fe5b4c..543f68ee0 100644 --- a/test/tojs-legacy.js +++ b/test/tojs-legacy.js @@ -6,7 +6,7 @@ var m = mobx; var observable = mobx.observable; var transaction = mobx.transaction; -test('json1', function(t) { +test('(legacy) json1', function(t) { var todos = observable([ { title: "write blog" @@ -29,7 +29,7 @@ test('json1', function(t) { t.end(); }) -test('json2', function(t) { +test('(legacy) json2', function(t) { var source = { todos: [ { @@ -217,7 +217,7 @@ test('json2', function(t) { t.end(); }) -test('toJS handles dates', t => { +test('(legacy) toJS handles dates', t => { var a = observable({ d: new Date() }); @@ -228,7 +228,7 @@ test('toJS handles dates', t => { t.end() }) -test('json cycles', function(t) { +test('(legacy) json cycles', function(t) { var a = observable({ b: 1, c: [2], @@ -258,7 +258,7 @@ test('json cycles', function(t) { t.end(); }) -test('#285 class instances with toJS', t => { +test('(legacy) #285 class instances with toJS', t => { function Person() { this.firstName = "michel"; mobx.extendObservable(this, { @@ -288,7 +288,7 @@ test('#285 class instances with toJS', t => { t.end() }) -test('#285 non-mobx class instances with toJS', t => { +test('(legacy) #285 non-mobx class instances with toJS', t => { function Person() { this.firstName = "michel"; this.lastName = mobx.observable("weststrate"); From 667228856b85568aab4f438b9702b57f71f02821 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Thu, 29 Sep 2016 11:07:56 +0200 Subject: [PATCH 4/4] test fixes --- test/tojs-legacy.js | 2 ++ test/tojs.js | 3 +++ 2 files changed, 5 insertions(+) diff --git a/test/tojs-legacy.js b/test/tojs-legacy.js index 543f68ee0..324dc820e 100644 --- a/test/tojs-legacy.js +++ b/test/tojs-legacy.js @@ -7,6 +7,8 @@ var observable = mobx.observable; var transaction = mobx.transaction; test('(legacy) json1', function(t) { + mobx.extras.resetGlobalState() + var todos = observable([ { title: "write blog" diff --git a/test/tojs.js b/test/tojs.js index 2b6ba188e..cbddf3c00 100644 --- a/test/tojs.js +++ b/test/tojs.js @@ -6,7 +6,10 @@ var m = mobx; var observable = mobx.observable; var transaction = mobx.transaction; + test('json1', function(t) { + mobx.extras.resetGlobalState() + var todos = observable([ { title: "write blog"