test/unit/features/v3/apiWatch.spec.ts TYPESCRIPT 1,235 lines View on github.com → Search inside
1import Vue from 'vue'2import {3  watch,4  watchEffect,5  watchPostEffect,6  watchSyncEffect,7  reactive,8  computed,9  ref,10  triggerRef,11  shallowRef,12  h,13  onMounted,14  getCurrentInstance,15  effectScope,16  TrackOpTypes,17  TriggerOpTypes,18  DebuggerEvent19} from 'v3'20import { nextTick } from 'core/util'21import { set } from 'core/observer'22import { Component } from 'types/component'2324// reference: https://vue-composition-api-rfc.netlify.com/api.html#watch2526describe('api: watch', () => {27  it('effect', async () => {28    const state = reactive({ count: 0 })29    let dummy30    watchEffect(() => {31      dummy = state.count32    })33    expect(dummy).toBe(0)3435    state.count++36    await nextTick()37    expect(dummy).toBe(1)38  })3940  it('watching single source: getter', async () => {41    const state = reactive({ count: 0 })42    let dummy43    watch(44      () => state.count,45      (count, prevCount) => {46        dummy = [count, prevCount]47        // assert types48        count + 149        if (prevCount) {50          prevCount + 151        }52      }53    )54    state.count++55    await nextTick()56    expect(dummy).toMatchObject([1, 0])57  })5859  it('watching single source: ref', async () => {60    const count = ref(0)61    let dummy62    watch(count, (count, prevCount) => {63      dummy = [count, prevCount]64      // assert types65      count + 166      if (prevCount) {67        prevCount + 168      }69    })70    count.value++71    await nextTick()72    expect(dummy).toMatchObject([1, 0])73  })7475  it('watching single source: array', async () => {76    const array = reactive({ a: [] as number[] }).a77    const spy = vi.fn()78    watch(array, spy)79    array.push(1)80    await nextTick()81    expect(spy).toBeCalledTimes(1)82    expect(spy).toBeCalledWith([1], expect.anything(), expect.anything())83  })8485  it('should not fire if watched getter result did not change', async () => {86    const spy = vi.fn()87    const n = ref(0)88    watch(() => n.value % 2, spy)8990    n.value++91    await nextTick()92    expect(spy).toBeCalledTimes(1)9394    n.value += 295    await nextTick()96    // should not be called again because getter result did not change97    expect(spy).toBeCalledTimes(1)98  })99100  it('watching single source: computed ref', async () => {101    const count = ref(0)102    const plus = computed(() => count.value + 1)103    let dummy104    watch(plus, (count, prevCount) => {105      dummy = [count, prevCount]106      // assert types107      count + 1108      if (prevCount) {109        prevCount + 1110      }111    })112    count.value++113    await nextTick()114    expect(dummy).toMatchObject([2, 1])115  })116117  it('watching primitive with deep: true', async () => {118    const count = ref(0)119    let dummy120    watch(121      count,122      (c, prevCount) => {123        dummy = [c, prevCount]124      },125      {126        deep: true127      }128    )129    count.value++130    await nextTick()131    expect(dummy).toMatchObject([1, 0])132  })133134  it('directly watching reactive object (with automatic deep: true)', async () => {135    const src = reactive({136      count: 0137    })138    let dummy139    watch(src, ({ count }) => {140      dummy = count141    })142    src.count++143    await nextTick()144    expect(dummy).toBe(1)145  })146147  it('deep watch w/ raw refs', async () => {148    const count = ref(0)149    const src = reactive({150      arr: [count]151    })152    let dummy153    watch(src, ({ arr: [{ value }] }) => {154      dummy = value155    })156    count.value++157    await nextTick()158    expect(dummy).toBe(1)159  })160161  it('watching multiple sources', async () => {162    const state = reactive({ count: 1 })163    const count = ref(1)164    const plus = computed(() => count.value + 1)165166    let dummy167    watch([() => state.count, count, plus], (vals, oldVals) => {168      dummy = [vals, oldVals]169      // assert types170      vals.concat(1)171      oldVals.concat(1)172    })173174    state.count++175    count.value++176    await nextTick()177    expect(dummy).toMatchObject([178      [2, 2, 3],179      [1, 1, 2]180    ])181  })182183  it('watching multiple sources: readonly array', async () => {184    const state = reactive({ count: 1 })185    const status = ref(false)186187    let dummy188    watch([() => state.count, status] as const, (vals, oldVals) => {189      dummy = [vals, oldVals]190      const [count] = vals191      const [, oldStatus] = oldVals192      // assert types193      count + 1194      oldStatus === true195    })196197    state.count++198    status.value = true199    await nextTick()200    expect(dummy).toMatchObject([201      [2, true],202      [1, false]203    ])204  })205206  it('watching multiple sources: reactive object (with automatic deep: true)', async () => {207    const src = reactive({ count: 0 })208    let dummy209    watch([src], ([state]) => {210      dummy = state211      // assert types212      state.count === 1213    })214    src.count++215    await nextTick()216    expect(dummy).toMatchObject({ count: 1 })217  })218219  it('warn invalid watch source', () => {220    // @ts-expect-error221    watch(1, () => {})222    expect(`Invalid watch source`).toHaveBeenWarned()223  })224225  it('warn invalid watch source: multiple sources', () => {226    watch([1], () => {})227    expect(`Invalid watch source`).toHaveBeenWarned()228  })229230  it('stopping the watcher (effect)', async () => {231    const state = reactive({ count: 0 })232    let dummy233    const stop = watchEffect(() => {234      dummy = state.count235    })236    expect(dummy).toBe(0)237238    stop()239    state.count++240    await nextTick()241    // should not update242    expect(dummy).toBe(0)243  })244245  it('stopping the watcher (with source)', async () => {246    const state = reactive({ count: 0 })247    let dummy248    const stop = watch(249      () => state.count,250      count => {251        dummy = count252      }253    )254255    state.count++256    await nextTick()257    expect(dummy).toBe(1)258259    stop()260    state.count++261    await nextTick()262    // should not update263    expect(dummy).toBe(1)264  })265266  it('cleanup registration (effect)', async () => {267    const state = reactive({ count: 0 })268    const cleanup = vi.fn()269    let dummy270    const stop = watchEffect(onCleanup => {271      onCleanup(cleanup)272      dummy = state.count273    })274    expect(dummy).toBe(0)275276    state.count++277    await nextTick()278    expect(cleanup).toHaveBeenCalledTimes(1)279    expect(dummy).toBe(1)280281    stop()282    expect(cleanup).toHaveBeenCalledTimes(2)283  })284285  it('cleanup registration (with source)', async () => {286    const count = ref(0)287    const cleanup = vi.fn()288    let dummy289    const stop = watch(count, (count, prevCount, onCleanup) => {290      onCleanup(cleanup)291      dummy = count292    })293294    count.value++295    await nextTick()296    expect(cleanup).toHaveBeenCalledTimes(0)297    expect(dummy).toBe(1)298299    count.value++300    await nextTick()301    expect(cleanup).toHaveBeenCalledTimes(1)302    expect(dummy).toBe(2)303304    stop()305    expect(cleanup).toHaveBeenCalledTimes(2)306  })307308  it('flush timing: pre (default)', async () => {309    const count = ref(0)310    const count2 = ref(0)311312    let callCount = 0313    let result1314    let result2315    const assertion = vi.fn((count, count2Value) => {316      callCount++317      // on mount, the watcher callback should be called before DOM render318      // on update, should be called before the count is updated319      const expectedDOM =320        callCount === 1 ? `<div></div>` : `<div>${count - 1}</div>`321      result1 = container.innerHTML === expectedDOM322323      // in a pre-flush callback, all state should have been updated324      const expectedState = callCount - 1325      result2 = count === expectedState && count2Value === expectedState326    })327328    const Comp = {329      setup() {330        watchEffect(() => {331          assertion(count.value, count2.value)332        })333        return () => h('div', count.value)334      }335    }336    const container = document.createElement('div')337    const root = document.createElement('div')338    container.appendChild(root)339    new Vue(Comp).$mount(root)340    expect(assertion).toHaveBeenCalledTimes(1)341    expect(result1).toBe(true)342    expect(result2).toBe(true)343344    count.value++345    count2.value++346    await nextTick()347    // two mutations should result in 1 callback execution348    expect(assertion).toHaveBeenCalledTimes(2)349    expect(result1).toBe(true)350    expect(result2).toBe(true)351  })352353  // #12569354  it('flush:pre watcher triggered before component mount (in child components)', () => {355    const count = ref(0)356    const spy = vi.fn()357    const Comp = {358      setup() {359        watch(count, spy)360        count.value++361        return h => h('div')362      }363    }364    new Vue({365      render: h => h(Comp)366    }).$mount()367    expect(spy).toHaveBeenCalledTimes(1)368  })369370  it('flush timing: post', async () => {371    const count = ref(0)372    let result373    const assertion = vi.fn(count => {374      result = container.innerHTML === `<div>${count}</div>`375    })376377    const Comp = {378      setup() {379        watchEffect(380          () => {381            assertion(count.value)382          },383          { flush: 'post' }384        )385        return () => h('div', count.value)386      }387    }388    const container = document.createElement('div')389    const root = document.createElement('div')390    container.appendChild(root)391    new Vue(Comp).$mount(root)392    expect(assertion).toHaveBeenCalledTimes(1)393    expect(result).toBe(true)394395    count.value++396    await nextTick()397    expect(assertion).toHaveBeenCalledTimes(2)398    expect(result).toBe(true)399  })400401  it('watchPostEffect', async () => {402    const count = ref(0)403    let result404    const assertion = vi.fn(count => {405      result = container.innerHTML === `<div>${count}</div>`406    })407408    const Comp = {409      setup() {410        watchPostEffect(() => {411          assertion(count.value)412        })413        return () => h('div', count.value)414      }415    }416    const container = document.createElement('div')417    const root = document.createElement('div')418    container.appendChild(root)419    new Vue(Comp).$mount(root)420    expect(assertion).toHaveBeenCalledTimes(1)421    expect(result).toBe(true)422423    count.value++424    await nextTick()425    expect(assertion).toHaveBeenCalledTimes(2)426    expect(result).toBe(true)427  })428429  it('flush timing: sync', async () => {430    const count = ref(0)431    const count2 = ref(0)432433    let callCount = 0434    let result1435    let result2436    const assertion = vi.fn(count => {437      callCount++438      // on mount, the watcher callback should be called before DOM render439      // on update, should be called before the count is updated440      const expectedDOM =441        callCount === 1 ? `<div></div>` : `<div>${count - 1}</div>`442      result1 = container.innerHTML === expectedDOM443444      // in a sync callback, state mutation on the next line should not have445      // executed yet on the 2nd call, but will be on the 3rd call.446      const expectedState = callCount < 3 ? 0 : 1447      result2 = count2.value === expectedState448    })449450    const Comp = {451      setup() {452        watchEffect(453          () => {454            assertion(count.value)455          },456          {457            flush: 'sync'458          }459        )460        return () => h('div', count.value)461      }462    }463    const container = document.createElement('div')464    const root = document.createElement('div')465    container.appendChild(root)466    new Vue(Comp).$mount(root)467    expect(assertion).toHaveBeenCalledTimes(1)468    expect(result1).toBe(true)469    expect(result2).toBe(true)470471    count.value++472    count2.value++473    await nextTick()474    expect(assertion).toHaveBeenCalledTimes(3)475    expect(result1).toBe(true)476    expect(result2).toBe(true)477  })478479  it('watchSyncEffect', async () => {480    const count = ref(0)481    const count2 = ref(0)482483    let callCount = 0484    let result1485    let result2486    const assertion = vi.fn(count => {487      callCount++488      // on mount, the watcher callback should be called before DOM render489      // on update, should be called before the count is updated490      const expectedDOM =491        callCount === 1 ? `<div></div>` : `<div>${count - 1}</div>`492      result1 = container.innerHTML === expectedDOM493494      // in a sync callback, state mutation on the next line should not have495      // executed yet on the 2nd call, but will be on the 3rd call.496      const expectedState = callCount < 3 ? 0 : 1497      result2 = count2.value === expectedState498    })499500    const Comp = {501      setup() {502        watchSyncEffect(() => {503          assertion(count.value)504        })505        return () => h('div', count.value)506      }507    }508    const container = document.createElement('div')509    const root = document.createElement('div')510    container.appendChild(root)511    new Vue(Comp).$mount(root)512    expect(assertion).toHaveBeenCalledTimes(1)513    expect(result1).toBe(true)514    expect(result2).toBe(true)515516    count.value++517    count2.value++518    await nextTick()519    expect(assertion).toHaveBeenCalledTimes(3)520    expect(result1).toBe(true)521    expect(result2).toBe(true)522  })523524  it('should not fire on component unmount w/ flush: post', async () => {525    const toggle = ref(true)526    const cb = vi.fn()527    const Comp = {528      setup() {529        watch(toggle, cb, { flush: 'post' })530      },531      render() {}532    }533    const App = {534      render() {535        return toggle.value ? h(Comp) : null536      }537    }538    new Vue(App).$mount()539    expect(cb).not.toHaveBeenCalled()540    toggle.value = false541    await nextTick()542    expect(cb).not.toHaveBeenCalled()543  })544545  it('should not fire on component unmount w/ flush: pre', async () => {546    const toggle = ref(true)547    const cb = vi.fn()548    const Comp = {549      setup() {550        watch(toggle, cb, { flush: 'pre' })551      },552      render() {}553    }554    const App = {555      render() {556        return toggle.value ? h(Comp) : null557      }558    }559    new Vue(App).$mount()560    expect(cb).not.toHaveBeenCalled()561    toggle.value = false562    await nextTick()563    expect(cb).not.toHaveBeenCalled()564  })565566  // vuejs/core#1763567  it('flush: pre watcher watching props should fire before child update', async () => {568    const a = ref(0)569    const b = ref(0)570    const c = ref(0)571    const calls: string[] = []572573    const Comp = {574      props: ['a', 'b'],575      setup(props: any) {576        watch(577          () => props.a + props.b,578          () => {579            calls.push('watcher 1')580            c.value++581          },582          { flush: 'pre' }583        )584585        // vuejs/core#1777 chained pre-watcher586        watch(587          c,588          () => {589            calls.push('watcher 2')590          },591          { flush: 'pre' }592        )593        return () => {594          c.value595          calls.push('render')596        }597      }598    }599600    const App = {601      render() {602        return h(Comp, { props: { a: a.value, b: b.value } })603      }604    }605606    new Vue(App).$mount()607    expect(calls).toEqual(['render'])608609    // both props are updated610    // should trigger pre-flush watcher first and only once611    // then trigger child render612    a.value++613    b.value++614    await nextTick()615    expect(calls).toEqual(['render', 'watcher 1', 'watcher 2', 'render'])616  })617618  // vuejs/core#5721619  it('flush: pre triggered in component setup should be buffered and called before mounted', () => {620    const count = ref(0)621    const calls: string[] = []622    const App = {623      render() {},624      setup() {625        watch(626          count,627          () => {628            calls.push('watch ' + count.value)629          },630          { flush: 'pre' }631        )632        onMounted(() => {633          calls.push('mounted')634        })635        // mutate multiple times636        count.value++637        count.value++638        count.value++639      }640    }641    new Vue(App).$mount()642    expect(calls).toMatchObject(['watch 3', 'mounted'])643  })644645  // vuejs/core#1852646  it('flush: post watcher should fire after template refs updated', async () => {647    const toggle = ref(false)648    let dom: HTMLElement | null = null649650    const App = {651      setup() {652        const domRef = ref<any>(null)653654        watch(655          toggle,656          () => {657            dom = domRef.value658          },659          { flush: 'post' }660        )661662        return () => {663          return toggle.value ? h('p', { ref: domRef }) : null664        }665      }666    }667668    new Vue(App).$mount()669    expect(dom).toBe(null)670671    toggle.value = true672    await nextTick()673    expect(dom!.tagName).toBe('P')674  })675676  it('deep', async () => {677    const state = reactive({678      nested: {679        count: ref(0)680      },681      array: [1, 2, 3]682      // map: new Map([683      //   ['a', 1],684      //   ['b', 2]685      // ]),686      // set: new Set([1, 2, 3])687    })688689    let dummy690    watch(691      () => state,692      state => {693        dummy = [694          state.nested.count,695          state.array[0]696          // state.map.get('a'),697          // state.set.has(1)698        ]699      },700      { deep: true }701    )702703    state.nested.count++704    await nextTick()705    expect(dummy).toEqual([1, 1])706707    // nested array mutation708    set(state.array, 0, 2)709    await nextTick()710    expect(dummy).toEqual([1, 2])711712    // nested map mutation713    // state.map.set('a', 2)714    // await nextTick()715    // expect(dummy).toEqual([1, 2, 2, true])716717    // nested set mutation718    // state.set.delete(1)719    // await nextTick()720    // expect(dummy).toEqual([1, 2, 2, false])721  })722723  it('watching deep ref', async () => {724    const count = ref(0)725    const double = computed(() => count.value * 2)726    const state = reactive({ count, double })727728    let dummy729    watch(730      () => state,731      state => {732        dummy = [state.count, state.double]733      },734      { deep: true }735    )736737    count.value++738    await nextTick()739    expect(dummy).toEqual([1, 2])740  })741742  it('immediate', async () => {743    const count = ref(0)744    const cb = vi.fn()745    watch(count, cb, { immediate: true })746    expect(cb).toHaveBeenCalledTimes(1)747    count.value++748    await nextTick()749    expect(cb).toHaveBeenCalledTimes(2)750  })751752  it('immediate: triggers when initial value is null', async () => {753    const state = ref(null)754    const spy = vi.fn()755    watch(() => state.value, spy, { immediate: true })756    expect(spy).toHaveBeenCalled()757  })758759  it('immediate: triggers when initial value is undefined', async () => {760    const state = ref()761    const spy = vi.fn()762    watch(() => state.value, spy, { immediate: true })763    expect(spy).toHaveBeenCalled()764    state.value = 3765    await nextTick()766    expect(spy).toHaveBeenCalledTimes(2)767    // testing if undefined can trigger the watcher768    state.value = undefined769    await nextTick()770    expect(spy).toHaveBeenCalledTimes(3)771    // it shouldn't trigger if the same value is set772    state.value = undefined773    await nextTick()774    expect(spy).toHaveBeenCalledTimes(3)775  })776777  it('warn immediate option when using effect', async () => {778    const count = ref(0)779    let dummy780    watchEffect(781      () => {782        dummy = count.value783      },784      // @ts-expect-error785      { immediate: false }786    )787    expect(dummy).toBe(0)788    expect(`"immediate" option is only respected`).toHaveBeenWarned()789790    count.value++791    await nextTick()792    expect(dummy).toBe(1)793  })794795  it('warn and not respect deep option when using effect', async () => {796    const arr = ref([1, [2]])797    const spy = vi.fn()798    watchEffect(799      () => {800        spy()801        return arr802      },803      // @ts-expect-error804      { deep: true }805    )806    expect(spy).toHaveBeenCalledTimes(1)807    ;(arr.value[1] as Array<number>)[0] = 3808    await nextTick()809    expect(spy).toHaveBeenCalledTimes(1)810    expect(`"deep" option is only respected`).toHaveBeenWarned()811  })812813  it('onTrack', async () => {814    const events: DebuggerEvent[] = []815    let dummy816    const onTrack = vi.fn((e: DebuggerEvent) => {817      events.push(e)818    })819    const obj = reactive({ foo: 1 })820    const r = ref(2)821    const c = computed(() => r.value + 1)822    watchEffect(823      () => {824        dummy = obj.foo + r.value + c.value825      },826      { onTrack }827    )828    await nextTick()829    expect(dummy).toEqual(6)830    expect(onTrack).toHaveBeenCalledTimes(3)831    expect(events).toMatchObject([832      {833        target: obj,834        type: TrackOpTypes.GET,835        key: 'foo'836      },837      {838        target: r,839        type: TrackOpTypes.GET,840        key: 'value'841      },842      {843        target: c,844        type: TrackOpTypes.GET,845        key: 'value'846      }847    ])848  })849850  it('onTrigger', async () => {851    const events: DebuggerEvent[] = []852    let dummy853    const onTrigger = vi.fn((e: DebuggerEvent) => {854      events.push(e)855    })856    const obj = reactive<{857      foo: number858      bar: any[]859      baz: { qux?: number }860    }>({ foo: 1, bar: [], baz: {} })861862    watchEffect(863      () => {864        dummy = obj.foo + (obj.bar[0] || 0) + (obj.baz.qux || 0)865      },866      { onTrigger }867    )868    await nextTick()869    expect(dummy).toBe(1)870871    obj.foo++872    await nextTick()873    expect(dummy).toBe(2)874    expect(onTrigger).toHaveBeenCalledTimes(1)875    expect(events[0]).toMatchObject({876      type: TriggerOpTypes.SET,877      key: 'foo',878      target: obj,879      oldValue: 1,880      newValue: 2881    })882883    obj.bar.push(1)884    await nextTick()885    expect(dummy).toBe(3)886    expect(onTrigger).toHaveBeenCalledTimes(2)887    expect(events[1]).toMatchObject({888      type: TriggerOpTypes.ARRAY_MUTATION,889      target: obj.bar,890      key: 'push'891    })892893    set(obj.baz, 'qux', 1)894    await nextTick()895    expect(dummy).toBe(4)896    expect(onTrigger).toHaveBeenCalledTimes(3)897    expect(events[2]).toMatchObject({898      type: TriggerOpTypes.ADD,899      target: obj.baz,900      key: 'qux'901    })902  })903904  it('should work sync', () => {905    const v = ref(1)906    let calls = 0907908    watch(909      v,910      () => {911        ++calls912      },913      {914        flush: 'sync'915      }916    )917918    expect(calls).toBe(0)919    v.value++920    expect(calls).toBe(1)921  })922923  test('should force trigger on triggerRef when watching a shallow ref', async () => {924    const v = shallowRef({ a: 1 })925    let sideEffect = 0926    watch(v, obj => {927      sideEffect = obj.a928    })929930    v.value = v.value931    await nextTick()932    // should not trigger933    expect(sideEffect).toBe(0)934935    v.value.a++936    await nextTick()937    // should not trigger938    expect(sideEffect).toBe(0)939940    triggerRef(v)941    await nextTick()942    // should trigger now943    expect(sideEffect).toBe(2)944  })945946  test('should force trigger on triggerRef when watching multiple sources: shallow ref array', async () => {947    const v = shallowRef([] as any)948    const spy = vi.fn()949    watch([v], () => {950      spy()951    })952953    v.value.push(1)954    triggerRef(v)955956    await nextTick()957    // should trigger now958    expect(spy).toHaveBeenCalledTimes(1)959  })960961  // vuejs/core#2125962  test('watchEffect should not recursively trigger itself', async () => {963    const spy = vi.fn()964    const price = ref(10)965    const history = ref<number[]>([])966    watchEffect(() => {967      history.value.push(price.value)968      spy()969    })970    await nextTick()971    expect(spy).toHaveBeenCalledTimes(1)972  })973974  // vuejs/core#2231975  test('computed refs should not trigger watch if value has no change', async () => {976    const spy = vi.fn()977    const source = ref(0)978    const price = computed(() => source.value === 0)979    watch(price, spy)980    source.value++981    await nextTick()982    source.value++983    await nextTick()984    expect(spy).toHaveBeenCalledTimes(1)985  })986987  test('this.$watch should pass `this.proxy` to watch source as the first argument ', () => {988    let instance: any989    const source = vi.fn()990991    const Comp = {992      render() {},993      created(this: any) {994        instance = this995        this.$watch(source, function () {})996      }997    }998999    const root = document.createElement('div')1000    new Vue(Comp).$mount(root)10011002    expect(instance).toBeDefined()1003    expect(source).toHaveBeenCalledWith(instance)1004  })10051006  test('should not leak `this.proxy` to setup()', () => {1007    const source = vi.fn()10081009    const Comp = {1010      render() {},1011      setup() {1012        watch(source, () => {})1013      }1014    }10151016    const root = document.createElement('div')1017    new Vue(Comp).$mount(root)1018    // should not have any arguments1019    expect(source.mock.calls[0]).toMatchObject([])1020  })10211022  // vuejs/core#27281023  test('pre watcher callbacks should not track dependencies', async () => {1024    const a = ref(0)1025    const b = ref(0)1026    const updated = vi.fn()1027    const cb = vi.fn()10281029    const Child = {1030      props: ['a'],1031      updated,1032      watch: {1033        a() {1034          cb()1035          b.value1036        }1037      },1038      render() {1039        return h('div', this.a)1040      }1041    }10421043    const Parent = {1044      render() {1045        return h(Child, { props: { a: a.value } })1046      }1047    }10481049    const root = document.createElement('div')1050    new Vue(Parent).$mount(root)10511052    a.value++1053    await nextTick()1054    expect(updated).toHaveBeenCalledTimes(1)1055    expect(cb).toHaveBeenCalledTimes(1)10561057    b.value++1058    await nextTick()1059    // should not track b as dependency of Child1060    expect(updated).toHaveBeenCalledTimes(1)1061    expect(cb).toHaveBeenCalledTimes(1)1062  })10631064  it('watching sources: ref<any[]>', async () => {1065    const foo = ref([1])1066    const spy = vi.fn()1067    watch(foo, () => {1068      spy()1069    })1070    foo.value = foo.value.slice()1071    await nextTick()1072    expect(spy).toBeCalledTimes(1)1073  })10741075  it('watching multiple sources: computed', async () => {1076    let count = 01077    const value = ref('1')1078    const plus = computed(() => !!value.value)1079    watch([plus], () => {1080      count++1081    })1082    value.value = '2'1083    await nextTick()1084    expect(plus.value).toBe(true)1085    expect(count).toBe(0)1086  })10871088  // vuejs/core#41581089  test('watch should not register in owner component if created inside detached scope', () => {1090    let instance: Component1091    const Comp = {1092      setup() {1093        instance = getCurrentInstance()!.proxy1094        effectScope(true).run(() => {1095          watch(1096            () => 1,1097            () => {}1098          )1099        })1100        return () => ''1101      }1102    }1103    const root = document.createElement('div')1104    new Vue(Comp).$mount(root)1105    // should not record watcher in detached scope and only the instance's1106    // own update effect1107    expect(instance!._scope.effects.length).toBe(1)1108  })11091110  // #125781111  test('template ref triggered watcher should fire after component mount', async () => {1112    const order: string[] = []1113    const Child = { template: '<div/>' }1114    const App = {1115      setup() {1116        const child = ref<any>(null)1117        onMounted(() => {1118          order.push('mounted')1119        })1120        watch(child, () => {1121          order.push('watcher')1122        })1123        return { child }1124      },1125      components: { Child },1126      template: `<Child ref="child"/>`1127    }1128    new Vue(App).$mount()11291130    await nextTick()1131    expect(order).toMatchObject([`mounted`, `watcher`])1132  })11331134  // #126241135  test('pre watch triggered in mounted hook', async () => {1136    const spy = vi.fn()1137    new Vue({1138      setup() {1139        const c = ref(0)11401141        onMounted(() => {1142          c.value++1143        })11441145        watchEffect(() => spy(c.value))1146        return () => {}1147      }1148    }).$mount()1149    expect(spy).toHaveBeenCalledTimes(1)1150    await nextTick()1151    expect(spy).toHaveBeenCalledTimes(2)1152  })11531154  // #126431155  test('should trigger watch on reactive object when new property is added via set()', () => {1156    const spy = vi.fn()1157    const obj = reactive({})1158    watch(obj, spy, { flush: 'sync' })1159    set(obj, 'foo', 1)1160    expect(spy).toHaveBeenCalled()1161  })11621163  test('should not trigger watch when calling set() on ref value', () => {1164    const spy = vi.fn()1165    const r = ref({})1166    watch(r, spy, { flush: 'sync' })1167    set(r.value, 'foo', 1)1168    expect(spy).not.toHaveBeenCalled()1169  })11701171  // #126641172  it('queueing multiple flush: post watchers', async () => {1173    const parentSpy = vi.fn()1174    const childSpy = vi.fn()11751176    const Child = {1177      setup() {1178        const el = ref()1179        watch(el, childSpy, { flush: 'post' })1180        return { el }1181      },1182      template: `<div><span ref="el">hello child</span></div>`1183    }1184    const App = {1185      components: { Child },1186      setup() {1187        const el = ref()1188        watch(el, parentSpy, { flush: 'post' })1189        return { el }1190      },1191      template: `<div><Child /><span ref="el">hello app1</span></div>`1192    }11931194    const container = document.createElement('div')1195    const root = document.createElement('div')1196    container.appendChild(root)1197    new Vue(App).$mount(root)11981199    await nextTick()1200    expect(parentSpy).toHaveBeenCalledTimes(1)1201    expect(childSpy).toHaveBeenCalledTimes(1)1202  })12031204  // #129671205  test('trigger when adding new property with Vue.set (getter)', async () => {1206    const spy = vi.fn()1207    const r = reactive({ exist: 5 })1208    watch(() => r, spy, { deep: true })1209    set(r, 'add', 1)12101211    await nextTick()1212    expect(spy).toHaveBeenCalledTimes(1)1213  })12141215  test('trigger when adding new property with Vue.set (getter in array source)', async () => {1216    const spy = vi.fn()1217    const r = reactive({ exist: 5 })1218    watch([() => r], spy, { deep: true })1219    set(r, 'add', 1)12201221    await nextTick()1222    expect(spy).toHaveBeenCalledTimes(1)1223  })12241225  test('trigger when adding new property with Vue.set (reactive in array source)', async () => {1226    const spy = vi.fn()1227    const r = reactive({ exist: 5 })1228    watch([r], spy, { deep: true })1229    set(r, 'add', 1)12301231    await nextTick()1232    expect(spy).toHaveBeenCalledTimes(1)1233  })1234})

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.