Skip to content

Commit

Permalink
More tests, better changelog, added in trap
Browse files Browse the repository at this point in the history
  • Loading branch information
mweststrate committed May 28, 2018
1 parent e9d8760 commit e794a2c
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 10 deletions.
20 changes: 12 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
# 5.0.0

# Improvements

* uses & requires proxies. otherwise use mobx 4
* observable objects created with `observable({...})` support dynamically adding keys
- arrays are now spreadable and such

# Breaking

* The minimum runtime target is now ES2015, not ES5
* `spy` is no longer available in production builds
* `spy` is a no-op in production builds
* all deprecated api's are dropped. Make sure to not have any deprecation warnings before upgrading
* uses proxies. otherwise use mobx 4
* dropped `array.move`
* dropped `array.move` and `array.peek`
* dropped third arg to `array.find` and `array.findIndex` (not in standard)
* `proxy` argument to `observable.object`
* `proxy: false` argument to `observable.object` to disable proxying (faster, but no dynamic key support)
* `.$mobx` property has been dropped from all observables and replaced by a Symbol. Instead of e.g. `x.$mobx.name`, use `import { $mobx } from "mobx"; x[$mobx].name`
* in some cases, the order in which autoruns are fired could have changed due to some internal optimizations (note that MobX never had a guarantee about the order in which autoruns fired!)
* dropped `observableArray.peek()`, there is no need for it anymore

Proxies
- caveats: prebound methods this ain't the proxy!
- arrays are now spreadable and such
- observable.object option record: true
- caveats: prebound methods `this` ain't the proxy!

# 4.3.0

Expand Down
8 changes: 8 additions & 0 deletions src/types/dynamicobject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ function getAdm(target): ObservableObjectAdministration {
// Optimization: we don't need the intermediate objects and could have a completely custom administration for DynamicObjects,
// and skip either the internal values map, or the base object with its property descriptors!
const objectProxyTraps: ProxyHandler<any> = {
has(target: IIsObservableObject, name: PropertyKey) {
if (name === $mobx || name === "constructor" || name === mobxDidRunLazyInitializersSymbol)
return true
const adm = getAdm(target)
if (adm.values.get(name as string)) return true
if (typeof name === "string") return adm.has(name)
return (name as any) in target
},
get(target: IIsObservableObject, name: PropertyKey) {
if (name === $mobx || name === "constructor" || name === mobxDidRunLazyInitializersSymbol)
return target[name]
Expand Down
163 changes: 163 additions & 0 deletions test/base/object-api-proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
const mobx = require("../../src/mobx")
const {
when,
set,
remove,
values,
runInAction,
entries,
reaction,
observable,
extendObservable,
$mobx
} = mobx

test("keys should be observable when extending", () => {
const todos = observable({})

const todoTitles = []
reaction(
() => Object.keys(todos).map(key => `${key}: ${todos[key]}`),
titles => todoTitles.push(titles.join(","))
)

runInAction(() => {
Object.assign(todos, {
lewis: "Read Lewis",
chesterton: "Be mind blown by Chesterton"
})
})
expect(todoTitles).toEqual(["lewis: Read Lewis,chesterton: Be mind blown by Chesterton"])

Object.assign(todos, { lewis: "Read Lewis twice" })
Object.assign(todos, { coffee: "Grab coffee" })
expect(todoTitles).toEqual([
"lewis: Read Lewis,chesterton: Be mind blown by Chesterton",
"lewis: Read Lewis twice,chesterton: Be mind blown by Chesterton",
"lewis: Read Lewis twice,chesterton: Be mind blown by Chesterton,coffee: Grab coffee"
])
})

test("toJS respects key changes", () => {
const todos = observable({})

const serialized = []
mobx.autorun(() => {
serialized.push(JSON.stringify(mobx.toJS(todos)))
})

runInAction(() => {
Object.assign(todos, {
lewis: "Read Lewis",
chesterton: "Be mind blown by Chesterton"
})
})
Object.assign(todos, { lewis: "Read Lewis twice" })
Object.assign(todos, { coffee: "Grab coffee" })
expect(serialized).toEqual([
"{}",
'{"lewis":"Read Lewis","chesterton":"Be mind blown by Chesterton"}',
'{"lewis":"Read Lewis twice","chesterton":"Be mind blown by Chesterton"}',
'{"lewis":"Read Lewis twice","chesterton":"Be mind blown by Chesterton","coffee":"Grab coffee"}'
])
})

test("object - set, remove, values are reactive", () => {
const todos = observable({})
const snapshots = []

reaction(() => values(todos), values => snapshots.push(values))

expect("x" in todos).toBe(false)
expect(todos.x).toBe(undefined)
todos.x = 3
expect("x" in todos).toBe(true)
expect(todos.x).toBe(3)
delete todos.y
todos.z = 4
todos.x = 5
delete todos.z

expect(snapshots).toEqual([[3], [3, 4], [5, 4], [5]])
})

test("object - set, remove, entries are reactive", () => {
const todos = observable({})
const snapshots = []

reaction(() => entries(todos), entries => snapshots.push(entries))

expect("x" in todos).toBe(false)
expect(todos.x).toBe(undefined)
todos.x = 3
expect("x" in todos).toBe(true)
expect(todos.x).toBe(3)
delete todos.y
todos.z = 4
todos.x = 5
delete todos.z

expect(snapshots).toEqual([[["x", 3]], [["x", 3], ["z", 4]], [["x", 5], ["z", 4]], [["x", 5]]])
})

test("object - set, remove, keys are reactive", () => {
const todos = observable({ a: 3 })
const snapshots = []

reaction(() => Object.keys(todos), keys => snapshots.push(keys))

todos.x = 3
delete todos.y
todos.z = 4
todos.x = 5
delete todos.z
delete todos.a

expect(snapshots).toEqual([["a", "x"], ["a", "x", "z"], ["a", "x"], ["x"]])
})

test("dynamically adding properties should preserve the original modifiers of an object", () => {
const todos = observable.object(
{
a: { title: "get coffee" }
},
{},
{ deep: false }
)
expect(mobx.isObservable(todos.a)).toBe(false)
Object.assign(todos, { b: { title: "get tea" } })
expect(mobx.isObservable(todos.b)).toBe(false)
})

test("has and get are reactive", async () => {
const todos = observable({})

const p1 = when(() => {
debugger
return "x" in todos
})
const p2 = when(() => {
return todos.y === 3
})

setTimeout(() => {
Object.assign(todos, { x: false, y: 3 })
}, 100)

await p1
await p2
})

test("computed props are not considered part of collections", () => {
const x = observable({
get y() {
return 3
}
})
expect(mobx.isComputedProp(x, "y")).toBe(true)
expect(x.y).toBe(3)
expect("y" in x).toBe(true) // `in` also checks proto type, so should return true!
expect(Object.keys(x)).toEqual([])
expect(values(x)).toEqual([])
expect(entries(x)).toEqual([])
})
2 changes: 0 additions & 2 deletions test/base/object-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ const {
$mobx
} = mobx

// TODO: duplicate test suite for not using utilities

test("keys should be observable when extending", () => {
const todos = observable({})

Expand Down

0 comments on commit e794a2c

Please sign in to comment.