diff --git a/CHANGELOG.md b/CHANGELOG.md index cd9f5d2b5..7339c6574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * `@observable` is now always defined on the class and not in the instances. This means that `@observable` properties are enumerable, but won't appear if `Object.keys` or `hasOwnProperty` is used on a class _instance_. * if an (argumentless) action is passed to `observable` / `extendObservable`, it will not be converted into a computed property. * Implemented #316: `whyRun()` +* Fixed #285: class instances are now also converted by `toJS`. Also members defined on prototypes which are enumerable are converted. # 2.2.2: diff --git a/README.md b/README.md index 106c1df76..91d3b70f9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ _Simple, scalable state management_ [![Build Status](https://travis-ci.org/mobxjs/mobx.svg?branch=master)](https://travis-ci.org/mobxjs/mobx) [![Coverage Status](https://coveralls.io/repos/mobxjs/mobx/badge.svg?branch=master&service=github)](https://coveralls.io/github/mobxjs/mobx?branch=master) [![Join the chat at https://gitter.im/mobxjs/mobx](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mobxjs/mobx?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![#mobx channel on reactiflux discord](https://img.shields.io/badge/discord-%23mobx%20%40reactiflux-blue.svg)](https://discord.gg/0ZcbPKXt5bYAa2J1) * Installation: `npm install mobx --save`. React bindings: `npm install mobx-react --save` * [Ten minute, interactive MobX + React tutorial](https://mobxjs.github.io/mobx/getting-started.html) @@ -27,7 +26,7 @@ _Anything that can be derived from the application state, should be derived. Aut which includes the UI, data serialization, server communication, etc. -MobX unidirectional flow +MobX unidirectional flow React and MobX together are a powerful combination. React renders the application state by providing mechanisms to translate it into a tree of renderable components. MobX provides the mechanism to store and update the application state that React then uses. diff --git a/src/api/tojson.ts b/src/api/tojs.ts similarity index 91% rename from src/api/tojson.ts rename to src/api/tojs.ts index 955555a01..d0ca477d5 100644 --- a/src/api/tojson.ts +++ b/src/api/tojs.ts @@ -40,14 +40,16 @@ export function toJS(source, detectCycles: boolean = true, __alreadySeen: [any, ); return res; } - if (typeof source === "object" && isPlainObject(source)) { + if (isObservable(source) && source.$mobx instanceof ObservableValue) + return toJS(source(), detectCycles, __alreadySeen); + if (source instanceof ObservableValue) + return toJS(source.get(), detectCycles, __alreadySeen); + if (typeof source === "object") { const res = cache({}); - for (let key in source) if (source.hasOwnProperty(key)) + for (let key in source) res[key] = toJS(source[key], detectCycles, __alreadySeen); return res; } - if (isObservable(source) && source.$mobx instanceof ObservableValue) - return toJS(source(), detectCycles, __alreadySeen); return source; } diff --git a/src/mobx.ts b/src/mobx.ts index 78be170e8..e5da2430f 100644 --- a/src/mobx.ts +++ b/src/mobx.ts @@ -44,7 +44,7 @@ export { observe } from "./api/obse export { intercept } from "./api/intercept"; export { autorun, autorunAsync, autorunUntil, when, reaction } from "./api/autorun"; export { expr } from "./api/expr"; -export { toJSON, toJS } from "./api/tojson"; +export { toJSON, toJS } from "./api/tojs"; export { ITransformer, createTransformer } from "./api/createtransformer"; export { whyRun } from "./api/whyrun"; diff --git a/test/babel/babel-tests.js b/test/babel/babel-tests.js index be96ba7a5..49ba6cec3 100644 --- a/test/babel/babel-tests.js +++ b/test/babel/babel-tests.js @@ -523,3 +523,28 @@ test("enumerability", t => { t.end(); }) + +test("issue 285 (babel)", t => { + const {observable, toJS} = mobx; + + class Todo { + id = 1; + @observable title; + @observable finished = false; + @observable childThings = [1,2,3]; + constructor(title) { + this.title = title; + } + } + + var todo = new Todo("Something to do"); + + t.deepEqual(toJS(todo), { + id: 1, + title: "Something to do", + finished: false, + childThings: [1,2,3] + }) + + t.end(); +}) \ No newline at end of file diff --git a/test/observables.js b/test/observables.js index c0981b8f0..ad797a0b1 100644 --- a/test/observables.js +++ b/test/observables.js @@ -1099,6 +1099,58 @@ test('json cycles', function(t) { 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/typescript-tests.ts b/test/typescript-tests.ts index e1b71ed57..57e3c6ae3 100644 --- a/test/typescript-tests.ts +++ b/test/typescript-tests.ts @@ -796,3 +796,27 @@ test("enumerability", t => { t.end(); }) +test("issue 285 (babel)", t => { + const {observable, toJS} = mobx; + + class Todo { + id = 1; + @observable title: string; + @observable finished = false; + @observable childThings = [1,2,3]; + constructor(title: string) { + this.title = title; + } + } + + var todo = new Todo("Something to do"); + + t.deepEqual(toJS(todo), { + id: 1, + title: "Something to do", + finished: false, + childThings: [1,2,3] + }) + + t.end(); +})