Created
December 13, 2016 04:35
-
-
Save jhoguet/59a49cfb36084f3cab46cf739d83bd77 to your computer and use it in GitHub Desktop.
in trying to wrap my head around mobx, I found it easiest to see how it handles a few scenarios
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { observable, observe, transaction, computed, reaction } from 'mobx'; | |
import expect, { createSpy } from 'expect'; | |
describe('learning mobx', () => { | |
it('some mobx basics', () => { | |
const computedSpy = createSpy(); | |
const o1 = observable(1); | |
const o2 = observable(10) | |
const o3 = observable(() => { | |
computedSpy(); | |
return o1.get() + o2.get(); | |
}); | |
// computed is lazy, no computation yet | |
expect(computedSpy.calls.length).toBe(0); | |
expect(o1.get()).toBe(1); | |
expect(o2.get()).toBe(10); | |
expect(o3.get()).toBe(11); | |
expect(computedSpy.calls.length).toBe(1); | |
computedSpy.reset(); | |
o1.set(2); | |
expect(computedSpy.calls.length).toBe(0); | |
expect(o1.get()).toBe(2); | |
expect(o2.get()).toBe(10); | |
expect(o3.get()).toBe(12); | |
expect(computedSpy.calls.length).toBe(1); | |
computedSpy.reset(); | |
o2.set(20); | |
expect(computedSpy.calls.length).toBe(0); | |
expect(o1.get()).toBe(2); | |
expect(o2.get()).toBe(20); | |
expect(o3.get()).toBe(22); | |
expect(computedSpy.calls.length).toBe(1); | |
computedSpy.reset(); | |
o1.set(3); | |
o2.set(30); | |
expect(computedSpy.calls.length).toBe(0); | |
expect(o1.get()).toBe(3); | |
expect(o2.get()).toBe(30); | |
expect(o3.get()).toBe(33); | |
// only exectued computed once, because it is lazy | |
expect(computedSpy.calls.length).toBe(1); | |
computedSpy.reset(); | |
// now add observer which changes everything | |
const o1Spy = createSpy(); | |
observe(o1, o1Spy); | |
expect(o1Spy.calls.length).toBe(0);// hasn't been invoked yet... odd | |
const o2Spy = createSpy(); | |
// we can opt-in to firing immediately | |
observe(o2, o2Spy, true); | |
expect(o2Spy.calls.length).toBe(1); | |
o2Spy.reset(); | |
const o3Spy = createSpy(); | |
observe(o3, o3Spy); | |
expect(o3Spy.calls.length).toBe(0); | |
o1.set(4); | |
expect(o1Spy.calls.length).toBe(1); | |
expect(o2Spy.calls.length).toBe(0); | |
// notice computed isn't lazy anymore (makes sense since someone is observing it) | |
expect(o3Spy.calls.length).toBe(1); | |
o1Spy.reset(); | |
o3Spy.reset(); | |
// what if I change more than one thing | |
o1.set(5); | |
o2.set(50); | |
expect(o1Spy.calls.length).toBe(1); | |
expect(o2Spy.calls.length).toBe(1); | |
expect(o3Spy.calls.length).toBe(2); | |
o1Spy.reset(); | |
o2Spy.reset(); | |
o3Spy.reset(); | |
// what if I change more than one thing in a transaction | |
transaction(() => { | |
o1.set(6); | |
o2.set(60); | |
}); | |
expect(o1Spy.calls.length).toBe(1); | |
expect(o2Spy.calls.length).toBe(1); | |
expect(o3Spy.calls.length).toBe(1); // only one observation this time | |
o1Spy.reset(); | |
o2Spy.reset(); | |
o3Spy.reset(); | |
// what if I change the same thing more than once... | |
o1.set(7); | |
o1.set(8); | |
expect(o1Spy.calls.length).toBe(2); | |
expect(o2Spy.calls.length).toBe(0); | |
expect(o3Spy.calls.length).toBe(2); | |
o1Spy.reset(); | |
o3Spy.reset(); | |
// what if I set a value to the same value | |
o1.set(o1.get()); | |
o2.set(o2.get()); | |
expect(o1Spy.calls.length).toBe(0); | |
expect(o2Spy.calls.length).toBe(0); | |
expect(o3Spy.calls.length).toBe(0); | |
o1Spy.reset(); | |
o3Spy.reset(); | |
}); | |
describe('depper into computed', () => { | |
it('is lazy', () => { | |
const o = observable(1); | |
const cSpy = createSpy().andCall(() => o.get() + 1); | |
const c = observable(() => cSpy()); | |
expect(cSpy.calls.length).toBe(0); | |
}); | |
it('will not cache even if dependencies have not changed', () => { | |
const o = observable(1); | |
const cSpy = createSpy(); | |
const c = computed(() => { | |
cSpy(); | |
return o.get() + 1; | |
}); | |
c.get(); // | |
c.get(); | |
c.get(); | |
// ideally this would be 1 since the dependency o, has not changed | |
expect(cSpy.calls.length).toBe(3); | |
}); | |
it('not lazy and caches when observed (inconsistency)', () => { | |
// which is kind of weird, because | |
const o = observable(1); | |
const cSpy = createSpy().andCall(() => o.get() + 1); | |
const c = observable(() => cSpy()); | |
const observeCSpy = createSpy(); | |
observe(c, observeCSpy); | |
expect(observeCSpy.calls.length).toBe(0); | |
// even though observe didn't emit, it still computed | |
expect(cSpy.calls.length).toBe(1); | |
c.get(); | |
c.get(); | |
c.get(); | |
// and now it doesn't re-compute every time | |
expect(cSpy.calls.length).toBe(1); | |
// this is a very odd side effect - it seems like you should get this same behavior either way | |
}); | |
it('should observe changes to its dependencies', () => { | |
const o = observable(1); | |
const cSpy = createSpy().andCall(() => o.get() + 1); | |
const c = observable(() => cSpy()); | |
expect(cSpy.calls.length).toBe(0); // in lazy mode | |
c.get(); | |
expect(cSpy.calls.length).toBe(1); | |
cSpy.reset(); | |
o.set(2); | |
expect(cSpy.calls.length).toBe(0); // in lazy mode | |
c.get(); | |
c.get(); | |
c.get(); | |
expect(cSpy.calls.length).toBe(3); // no caching | |
cSpy.reset(); | |
// then we start observing and the behavior flips | |
const cObserver = createSpy(); | |
observe(c, cObserver); | |
expect(cSpy.calls.length).toBe(1); // computed as soon as we observed | |
cSpy.reset(); | |
cObserver.reset(); | |
o.set(o.get() + 1); | |
expect(cSpy.calls.length).toBe(1); // now it proactively recomputes | |
expect(cObserver.calls.length).toBe(1); | |
cSpy.reset(); | |
cObserver.reset(); | |
c.get(); | |
c.get(); | |
c.get(); | |
expect(cSpy.calls.length).toBe(0); // now it is caching | |
}); | |
it('should observe changes to dependencies as they evolve', () => { | |
const o1 = observable(1); | |
const o2 = observable(10); | |
const c = computed(() => { | |
if (o1.get() === 1){ | |
return 1; | |
} | |
else { | |
return o1.get() + o2.get(); | |
} | |
}); | |
const cSpy = createSpy(); | |
observe(c, cSpy); | |
cSpy.reset(); | |
// first time through, it won't be tracking o2, which is | |
// acceptible since that path isn't reachable til o1 changes | |
// but .... if we change o1, will it START tracking o2 | |
o1.set(2); | |
expect(cSpy.calls.length).toBe(1); | |
cSpy.reset(); | |
// now is it tracking o2? | |
o2.set(20); | |
expect(cSpy.calls.length).toBe(1); | |
// it is... great | |
}); | |
it('can async', () => { | |
function fromPromise(p){ | |
const o = observable(undefined); | |
p.then(val => o.set(val)); | |
return o | |
} | |
const spy = createSpy(); | |
const p = Promise.resolve(1); | |
const o = fromPromise(p); | |
observe(o, spy, true); | |
expect(spy.calls.length).toBe(1) | |
expect(typeof o.get()).toBe('undefined'); | |
spy.reset(); | |
return p.then(() => { | |
expect(spy.calls.length).toBe(1); | |
expect(o.get()).toBe(1); | |
}) | |
}); | |
it('can computedAsync', () => { | |
function asyncComputed(mapToDependencies, dependenciesToPromise){ | |
const innerObservable = observable(); | |
reaction(mapToDependencies, dependencies => { | |
innerObservable.set(undefined); | |
dependenciesToPromise(dependencies) | |
.then(val => innerObservable.set(val)); | |
}, true); | |
return computed(() => { | |
return innerObservable.get(); | |
}); | |
} | |
const o = observable('Jon'); | |
const oToPromise = name => Promise.resolve(`Hello ${name}`); | |
const c = asyncComputed(() => o.get(), oToPromise); | |
const spy = createSpy(); | |
observe(c, spy, true); | |
expect(spy.calls.length).toBe(1) | |
expect(typeof c.get()).toBe('undefined'); | |
spy.reset(); | |
return Promise.resolve() | |
.then(() => { | |
expect(spy.calls.length).toBe(1) | |
expect(c.get()).toBe('Hello Jon'); | |
spy.reset(); | |
}) | |
.then(() => { | |
o.set('Angela'); | |
expect(c.get()).toBe(undefined); | |
expect(spy.calls.length).toBe(1) | |
spy.reset(); | |
}) | |
.then(() => { | |
expect(spy.calls.length).toBe(1) | |
expect(c.get()).toBe('Hello Angela'); | |
spy.reset(); | |
}); | |
}) | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment