]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test: tests for observer computed
authorEvan You <yyx990803@gmail.com>
Thu, 20 Sep 2018 22:03:59 +0000 (18:03 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 20 Sep 2018 22:03:59 +0000 (18:03 -0400)
packages/observer/__tests__/computed.spec.ts
packages/observer/src/autorun.ts
packages/observer/src/computed.ts

index 04114631db0138fa31ae6f3aab903afd3a325c84..f969b65d76d11c058a25ffc1375f94d7442b97d6 100644 (file)
@@ -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)
+  })
+})
index c0c9aa826e71ff356953702b2811ee8b34b22a69..f47eb59106559e73956e9058ac180bc192d8f8d0 100644 (file)
@@ -7,6 +7,7 @@ export interface Autorun {
   active: boolean
   raw: Function
   deps: Array<Dep>
+  computed?: boolean
   scheduler?: Scheduler
   onTrack?: Debugger
   onTrigger?: Debugger
@@ -109,33 +110,45 @@ export function trigger(
 ) {
   const depsMap = targetMap.get(target) as KeyToDepMap
   const runners = new Set()
+  const computedRunners = new Set()
   if (type === OperationTypes.CLEAR) {
     // collection being cleared, trigger all runners for target
     depsMap.forEach(dep => {
-      addRunners(runners, dep)
+      addRunners(runners, computedRunners, dep)
     })
   } else {
     // schedule runs for SET | ADD | DELETE
     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
     if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
       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)
-  })
+  }
+  // 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(
   runners: Set<Autorun>,
+  computedRunners: Set<Autorun>,
   runnersToAdd: Set<Autorun> | undefined
 ) {
   if (runnersToAdd !== void 0) {
-    runnersToAdd.forEach(runners.add, runners)
+    runnersToAdd.forEach(runner => {
+      if (runner.computed) {
+        computedRunners.add(runner)
+      } else {
+        runners.add(runner)
+      }
+    })
   }
 }
 
index 01a4f7e79129dd5c0fb40fb8a77861ed75604419..c4eb6e94532fc4685ebe38be5ce445cff1cdaac2 100644 (file)
@@ -15,6 +15,8 @@ export function computed(getter: Function, context?: any): ComputedGetter {
       dirty = true
     }
   })
+  // mark runner as computed so that it gets priority during trigger
+  runner.computed = true
   const computedGetter = (() => {
     if (dirty) {
       value = runner()