]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test: finish tests for watch api
authorEvan You <yyx990803@gmail.com>
Tue, 27 Aug 2019 15:35:22 +0000 (11:35 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 27 Aug 2019 15:35:22 +0000 (11:35 -0400)
packages/reactivity/__tests__/effect.spec.ts
packages/reactivity/src/index.ts
packages/runtime-core/__tests__/apiWatch.spec.ts
packages/runtime-core/src/apiWatch.ts

index ce8908e26b3b3375a3e21f646d22b72b5100e5b7..eb0e7f4b388c34a4bd3231cc7791b8f1d1a49920 100644 (file)
@@ -531,7 +531,7 @@ describe('reactivity/effect', () => {
   })
 
   it('events: onTrack', () => {
-    let events: any[] = []
+    let events: DebuggerEvent[] = []
     let dummy
     const onTrack = jest.fn((e: DebuggerEvent) => {
       events.push(e)
index 0ba19b51548f930b72a7f38bc455feee4892d690..5b9824b801cee25fe0a56c04d12fdc336ba18ff9 100644 (file)
@@ -12,6 +12,7 @@ export { computed, ComputedRef, ComputedOptions } from './computed'
 export {
   effect,
   stop,
+  ITERATE_KEY,
   ReactiveEffect,
   ReactiveEffectOptions,
   DebuggerEvent
index c3f9c7d22d37c4e8a3bc7edf730856cb1b90fd91..579dbf8e40b2e173ba004eef134a8645952a5b43 100644 (file)
@@ -1,4 +1,14 @@
-import { watch, reactive, computed, nextTick, ref } from '../src/index'
+import {
+  watch,
+  reactive,
+  computed,
+  nextTick,
+  ref,
+  h,
+  OperationTypes
+} from '../src/index'
+import { render, nodeOps, serializeInner } from '@vue/runtime-test'
+import { ITERATE_KEY, DebuggerEvent } from '@vue/reactivity'
 
 // reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
 
@@ -137,17 +147,227 @@ describe('api: watch', () => {
     expect(cleanup).toHaveBeenCalledTimes(2)
   })
 
-  it('flush timing: post', () => {})
+  it('flush timing: post', async () => {
+    const count = ref(0)
+    const assertion = jest.fn(count => {
+      expect(serializeInner(root)).toBe(`${count}`)
+    })
+
+    const Comp = {
+      setup() {
+        watch(() => {
+          assertion(count.value)
+        })
+        return () => count.value
+      }
+    }
+    const root = nodeOps.createElement('div')
+    render(h(Comp), root)
+    await nextTick()
+    expect(assertion).toHaveBeenCalledTimes(1)
+
+    count.value++
+    await nextTick()
+    expect(assertion).toHaveBeenCalledTimes(2)
+  })
+
+  it('flush timing: pre', async () => {
+    const count = ref(0)
+    const count2 = ref(0)
+
+    let callCount = 0
+    const assertion = jest.fn((count, count2Value) => {
+      callCount++
+      // on mount, the watcher callback should be called before DOM render
+      // on update, should be called before the count is updated
+      const expectedDOM = callCount === 1 ? `` : `${count - 1}`
+      expect(serializeInner(root)).toBe(expectedDOM)
+
+      // in a pre-flush callback, all state should have been updated
+      const expectedState = callCount === 1 ? 0 : 1
+      expect(count2Value).toBe(expectedState)
+    })
+
+    const Comp = {
+      setup() {
+        watch(
+          () => {
+            assertion(count.value, count2.value)
+          },
+          {
+            flush: 'pre'
+          }
+        )
+        return () => count.value
+      }
+    }
+    const root = nodeOps.createElement('div')
+    render(h(Comp), root)
+    await nextTick()
+    expect(assertion).toHaveBeenCalledTimes(1)
+
+    count.value++
+    count2.value++
+    await nextTick()
+    // two mutations should result in 1 callback execution
+    expect(assertion).toHaveBeenCalledTimes(2)
+  })
+
+  it('flush timing: sync', async () => {
+    const count = ref(0)
+    const count2 = ref(0)
 
-  it('flush timing: pre', () => {})
+    let callCount = 0
+    const assertion = jest.fn(count => {
+      callCount++
+      // on mount, the watcher callback should be called before DOM render
+      // on update, should be called before the count is updated
+      const expectedDOM = callCount === 1 ? `` : `${count - 1}`
+      expect(serializeInner(root)).toBe(expectedDOM)
 
-  it('flush timing: sync', () => {})
+      // in a sync callback, state mutation on the next line should not have
+      // executed yet on the 2nd call, but will be on the 3rd call.
+      const expectedState = callCount < 3 ? 0 : 1
+      expect(count2.value).toBe(expectedState)
+    })
+
+    const Comp = {
+      setup() {
+        watch(
+          () => {
+            assertion(count.value)
+          },
+          {
+            flush: 'sync'
+          }
+        )
+        return () => count.value
+      }
+    }
+    const root = nodeOps.createElement('div')
+    render(h(Comp), root)
+    await nextTick()
+    expect(assertion).toHaveBeenCalledTimes(1)
 
-  it('deep', () => {})
+    count.value++
+    count2.value++
+    await nextTick()
+    expect(assertion).toHaveBeenCalledTimes(3)
+  })
 
-  it('lazy', () => {})
+  it('deep', async () => {
+    const state = reactive({
+      nested: {
+        count: ref(0)
+      },
+      array: [1, 2, 3]
+    })
 
-  it('onTrack', () => {})
+    let dummy
+    let arr
+    watch(
+      () => state,
+      state => {
+        dummy = state.nested.count
+        arr = state.array[2]
+      },
+      { deep: true }
+    )
 
-  it('onTrigger', () => {})
+    await nextTick()
+    expect(dummy).toBe(0)
+    expect(arr).toBe(3)
+
+    state.nested.count++
+    await nextTick()
+    expect(dummy).toBe(1)
+    expect(arr).toBe(3)
+
+    // nested array mutation
+    state.array[2] = 4
+    await nextTick()
+    expect(dummy).toBe(1)
+    expect(arr).toBe(4)
+  })
+
+  it('lazy', async () => {
+    const count = ref(0)
+    const cb = jest.fn()
+    watch(count, cb, { lazy: true })
+    await nextTick()
+    expect(cb).not.toHaveBeenCalled()
+    count.value++
+    await nextTick()
+    expect(cb).toHaveBeenCalled()
+  })
+
+  it('onTrack', async () => {
+    let events: DebuggerEvent[] = []
+    let dummy
+    const onTrack = jest.fn((e: DebuggerEvent) => {
+      events.push(e)
+    })
+    const obj = reactive({ foo: 1, bar: 2 })
+    watch(
+      () => {
+        dummy = [obj.foo, 'bar' in obj, Object.keys(obj)]
+      },
+      { onTrack }
+    )
+    await nextTick()
+    expect(dummy).toEqual([1, true, ['foo', 'bar']])
+    expect(onTrack).toHaveBeenCalledTimes(3)
+    expect(events).toMatchObject([
+      {
+        type: OperationTypes.GET,
+        key: 'foo'
+      },
+      {
+        type: OperationTypes.HAS,
+        key: 'bar'
+      },
+      {
+        type: OperationTypes.ITERATE,
+        key: ITERATE_KEY
+      }
+    ])
+  })
+
+  it('onTrigger', async () => {
+    let events: DebuggerEvent[] = []
+    let dummy
+    const onTrigger = jest.fn((e: DebuggerEvent) => {
+      events.push(e)
+    })
+    const obj = reactive({ foo: 1 })
+    watch(
+      () => {
+        dummy = obj.foo
+      },
+      { onTrigger }
+    )
+    await nextTick()
+    expect(dummy).toBe(1)
+
+    obj.foo++
+    await nextTick()
+    expect(dummy).toBe(2)
+    expect(onTrigger).toHaveBeenCalledTimes(1)
+    expect(events[0]).toMatchObject({
+      type: OperationTypes.SET,
+      key: 'foo',
+      oldValue: 1,
+      newValue: 2
+    })
+
+    delete obj.foo
+    await nextTick()
+    expect(dummy).toBeUndefined()
+    expect(onTrigger).toHaveBeenCalledTimes(2)
+    expect(events[1]).toMatchObject({
+      type: OperationTypes.DELETE,
+      key: 'foo',
+      oldValue: 2
+    })
+  })
 })
index bc042173f69044f5e7eef817c5beb123e4c85539..943011f0fea72a7b5b3ffb3581efbea456499208 100644 (file)
@@ -8,6 +8,7 @@ import {
 import { queueJob, queuePostFlushCb } from './scheduler'
 import { EMPTY_OBJ, isObject, isArray, isFunction } from '@vue/shared'
 import { recordEffect } from './apiReactivity'
+import { getCurrentInstance } from './component'
 
 export interface WatchOptions {
   lazy?: boolean
@@ -113,8 +114,21 @@ function doWatch(
       }
     : void 0
 
+  const instance = getCurrentInstance()
   const scheduler =
-    flush === 'sync' ? invoke : flush === 'pre' ? queueJob : queuePostFlushCb
+    flush === 'sync'
+      ? invoke
+      : flush === 'pre'
+        ? (job: () => void) => {
+            if (!instance || instance.vnode.el != null) {
+              queueJob(job)
+            } else {
+              // with 'pre' option, the first call must happen before
+              // the component is mounted so it is called synchronously.
+              job()
+            }
+          }
+        : queuePostFlushCb
 
   const runner = effect(getter, {
     lazy: true,