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.