-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
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
+ })
+ })
})