test: tests for observer computed
This commit is contained in:
parent
8a714a6c39
commit
fa9b3df5ab
@ -1 +1,139 @@
|
|||||||
describe('observer/computed', () => {})
|
import { computed, observable, autorun } from '../src'
|
||||||
|
|
||||||
|
describe('observer/computed', () => {
|
||||||
|
it('should return updated value', () => {
|
||||||
|
const value: any = observable({})
|
||||||
|
const cValue = computed(() => value.foo)
|
||||||
|
expect(cValue()).toBe(undefined)
|
||||||
|
value.foo = 1
|
||||||
|
expect(cValue()).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should compute lazily', () => {
|
||||||
|
const value: any = observable({})
|
||||||
|
const getter = jest.fn(() => value.foo)
|
||||||
|
const cValue = computed(getter)
|
||||||
|
|
||||||
|
// lazy
|
||||||
|
expect(getter).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
expect(cValue()).toBe(undefined)
|
||||||
|
expect(getter).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
// should not compute again
|
||||||
|
cValue()
|
||||||
|
expect(getter).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
// should not compute until needed
|
||||||
|
value.foo = 1
|
||||||
|
expect(getter).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
// now it should compute
|
||||||
|
expect(cValue()).toBe(1)
|
||||||
|
expect(getter).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
|
// should not compute again
|
||||||
|
cValue()
|
||||||
|
expect(getter).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should accept context', () => {
|
||||||
|
const value: any = observable({})
|
||||||
|
let callCtx, callArg
|
||||||
|
const getter = function(arg: any) {
|
||||||
|
callCtx = this
|
||||||
|
callArg = arg
|
||||||
|
return value.foo
|
||||||
|
}
|
||||||
|
const ctx = {}
|
||||||
|
const cValue = computed(getter, ctx)
|
||||||
|
cValue()
|
||||||
|
expect(callCtx).toBe(ctx)
|
||||||
|
expect(callArg).toBe(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should trigger autorun', () => {
|
||||||
|
const value: any = observable({})
|
||||||
|
const cValue = computed(() => value.foo)
|
||||||
|
let dummy
|
||||||
|
autorun(() => {
|
||||||
|
dummy = cValue()
|
||||||
|
})
|
||||||
|
expect(dummy).toBe(undefined)
|
||||||
|
value.foo = 1
|
||||||
|
expect(dummy).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should work when chained', () => {
|
||||||
|
const value: any = observable({ foo: 0 })
|
||||||
|
const c1 = computed(() => value.foo)
|
||||||
|
const c2 = computed(() => c1() + 1)
|
||||||
|
expect(c2()).toBe(1)
|
||||||
|
expect(c1()).toBe(0)
|
||||||
|
value.foo++
|
||||||
|
expect(c2()).toBe(2)
|
||||||
|
expect(c1()).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should trigger autorun when chained', () => {
|
||||||
|
const value: any = observable({ foo: 0 })
|
||||||
|
const getter1 = jest.fn(() => value.foo)
|
||||||
|
const getter2 = jest.fn(() => {
|
||||||
|
return c1() + 1
|
||||||
|
})
|
||||||
|
const c1 = computed(getter1)
|
||||||
|
const c2 = computed(getter2)
|
||||||
|
|
||||||
|
let dummy
|
||||||
|
autorun(() => {
|
||||||
|
dummy = c2()
|
||||||
|
})
|
||||||
|
expect(dummy).toBe(1)
|
||||||
|
expect(getter1).toHaveBeenCalledTimes(1)
|
||||||
|
expect(getter2).toHaveBeenCalledTimes(1)
|
||||||
|
value.foo++
|
||||||
|
expect(dummy).toBe(2)
|
||||||
|
// should not result in duplicate calls
|
||||||
|
expect(getter1).toHaveBeenCalledTimes(2)
|
||||||
|
expect(getter2).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should trigger autorun when chained (mixed invocations)', () => {
|
||||||
|
const value: any = observable({ foo: 0 })
|
||||||
|
const getter1 = jest.fn(() => value.foo)
|
||||||
|
const getter2 = jest.fn(() => {
|
||||||
|
return c1() + 1
|
||||||
|
})
|
||||||
|
const c1 = computed(getter1)
|
||||||
|
const c2 = computed(getter2)
|
||||||
|
|
||||||
|
let dummy
|
||||||
|
autorun(() => {
|
||||||
|
dummy = c1() + c2()
|
||||||
|
})
|
||||||
|
expect(dummy).toBe(1)
|
||||||
|
|
||||||
|
expect(getter1).toHaveBeenCalledTimes(1)
|
||||||
|
expect(getter2).toHaveBeenCalledTimes(1)
|
||||||
|
value.foo++
|
||||||
|
expect(dummy).toBe(3)
|
||||||
|
// should not result in duplicate calls
|
||||||
|
expect(getter1).toHaveBeenCalledTimes(2)
|
||||||
|
expect(getter2).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should no longer update when stopped', () => {
|
||||||
|
const value: any = observable({})
|
||||||
|
const cValue = computed(() => value.foo)
|
||||||
|
let dummy
|
||||||
|
autorun(() => {
|
||||||
|
dummy = cValue()
|
||||||
|
})
|
||||||
|
expect(dummy).toBe(undefined)
|
||||||
|
value.foo = 1
|
||||||
|
expect(dummy).toBe(1)
|
||||||
|
cValue.stop()
|
||||||
|
value.foo = 2
|
||||||
|
expect(dummy).toBe(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -7,6 +7,7 @@ export interface Autorun {
|
|||||||
active: boolean
|
active: boolean
|
||||||
raw: Function
|
raw: Function
|
||||||
deps: Array<Dep>
|
deps: Array<Dep>
|
||||||
|
computed?: boolean
|
||||||
scheduler?: Scheduler
|
scheduler?: Scheduler
|
||||||
onTrack?: Debugger
|
onTrack?: Debugger
|
||||||
onTrigger?: Debugger
|
onTrigger?: Debugger
|
||||||
@ -109,33 +110,45 @@ export function trigger(
|
|||||||
) {
|
) {
|
||||||
const depsMap = targetMap.get(target) as KeyToDepMap
|
const depsMap = targetMap.get(target) as KeyToDepMap
|
||||||
const runners = new Set()
|
const runners = new Set()
|
||||||
|
const computedRunners = new Set()
|
||||||
if (type === OperationTypes.CLEAR) {
|
if (type === OperationTypes.CLEAR) {
|
||||||
// collection being cleared, trigger all runners for target
|
// collection being cleared, trigger all runners for target
|
||||||
depsMap.forEach(dep => {
|
depsMap.forEach(dep => {
|
||||||
addRunners(runners, dep)
|
addRunners(runners, computedRunners, dep)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// schedule runs for SET | ADD | DELETE
|
// schedule runs for SET | ADD | DELETE
|
||||||
if (key !== void 0) {
|
if (key !== void 0) {
|
||||||
addRunners(runners, depsMap.get(key as string | symbol))
|
addRunners(runners, computedRunners, depsMap.get(key as string | symbol))
|
||||||
}
|
}
|
||||||
// also run for iteration key on ADD | DELETE
|
// also run for iteration key on ADD | DELETE
|
||||||
if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
|
if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
|
||||||
const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
|
const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
|
||||||
addRunners(runners, depsMap.get(iterationKey))
|
addRunners(runners, computedRunners, depsMap.get(iterationKey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
runners.forEach(runner => {
|
const run = (runner: Autorun) => {
|
||||||
scheduleRun(runner, target, type, key, extraInfo)
|
scheduleRun(runner, target, type, key, extraInfo)
|
||||||
})
|
}
|
||||||
|
// Important: computed runners must be run first so that computed getters
|
||||||
|
// can be invalidated before any normal runners that depend on them are run.
|
||||||
|
computedRunners.forEach(run)
|
||||||
|
runners.forEach(run)
|
||||||
}
|
}
|
||||||
|
|
||||||
function addRunners(
|
function addRunners(
|
||||||
runners: Set<Autorun>,
|
runners: Set<Autorun>,
|
||||||
|
computedRunners: Set<Autorun>,
|
||||||
runnersToAdd: Set<Autorun> | undefined
|
runnersToAdd: Set<Autorun> | undefined
|
||||||
) {
|
) {
|
||||||
if (runnersToAdd !== void 0) {
|
if (runnersToAdd !== void 0) {
|
||||||
runnersToAdd.forEach(runners.add, runners)
|
runnersToAdd.forEach(runner => {
|
||||||
|
if (runner.computed) {
|
||||||
|
computedRunners.add(runner)
|
||||||
|
} else {
|
||||||
|
runners.add(runner)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ export function computed(getter: Function, context?: any): ComputedGetter {
|
|||||||
dirty = true
|
dirty = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// mark runner as computed so that it gets priority during trigger
|
||||||
|
runner.computed = true
|
||||||
const computedGetter = (() => {
|
const computedGetter = (() => {
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
value = runner()
|
value = runner()
|
||||||
|
Loading…
Reference in New Issue
Block a user