apiWatch.spec.ts 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985
  1. import {
  2. type ComponentInternalInstance,
  3. type ComponentPublicInstance,
  4. computed,
  5. defineComponent,
  6. getCurrentInstance,
  7. nextTick,
  8. onErrorCaptured,
  9. onWatcherCleanup,
  10. reactive,
  11. ref,
  12. watch,
  13. watchEffect,
  14. } from '../src/index'
  15. import {
  16. type TestElement,
  17. createApp,
  18. h,
  19. nodeOps,
  20. onMounted,
  21. render,
  22. serializeInner,
  23. watchPostEffect,
  24. watchSyncEffect,
  25. } from '@vue/runtime-test'
  26. import {
  27. type DebuggerEvent,
  28. ITERATE_KEY,
  29. type Ref,
  30. type ShallowRef,
  31. TrackOpTypes,
  32. TriggerOpTypes,
  33. effectScope,
  34. shallowReactive,
  35. shallowRef,
  36. toRef,
  37. triggerRef,
  38. } from '@vue/reactivity'
  39. import { renderToString } from '@vue/server-renderer'
  40. describe('api: watch', () => {
  41. it('effect', async () => {
  42. const state = reactive({ count: 0 })
  43. let dummy
  44. watchEffect(() => {
  45. dummy = state.count
  46. })
  47. expect(dummy).toBe(0)
  48. state.count++
  49. await nextTick()
  50. expect(dummy).toBe(1)
  51. })
  52. it('watching single source: getter', async () => {
  53. const state = reactive({ count: 0 })
  54. let dummy
  55. watch(
  56. () => state.count,
  57. (count, prevCount) => {
  58. dummy = [count, prevCount]
  59. // assert types
  60. count + 1
  61. if (prevCount) {
  62. prevCount + 1
  63. }
  64. },
  65. )
  66. state.count++
  67. await nextTick()
  68. expect(dummy).toMatchObject([1, 0])
  69. })
  70. it('watching single source: ref', async () => {
  71. const count = ref(0)
  72. let dummy
  73. watch(count, (count, prevCount) => {
  74. dummy = [count, prevCount]
  75. // assert types
  76. count + 1
  77. if (prevCount) {
  78. prevCount + 1
  79. }
  80. })
  81. count.value++
  82. await nextTick()
  83. expect(dummy).toMatchObject([1, 0])
  84. })
  85. it('watching single source: array', async () => {
  86. const array = reactive([] as number[])
  87. const spy = vi.fn()
  88. watch(array, spy)
  89. array.push(1)
  90. await nextTick()
  91. expect(spy).toBeCalledTimes(1)
  92. expect(spy).toBeCalledWith([1], [1], expect.anything())
  93. })
  94. it('should not call functions inside a reactive source array', () => {
  95. const spy1 = vi.fn()
  96. const array = reactive([spy1])
  97. const spy2 = vi.fn()
  98. watch(array, spy2, { immediate: true })
  99. expect(spy1).toBeCalledTimes(0)
  100. expect(spy2).toBeCalledWith([spy1], undefined, expect.anything())
  101. })
  102. it('should not unwrap refs in a reactive source array', async () => {
  103. const val = ref({ foo: 1 })
  104. const array = reactive([val])
  105. const spy = vi.fn()
  106. watch(array, spy, { immediate: true })
  107. expect(spy).toBeCalledTimes(1)
  108. expect(spy).toBeCalledWith([val], undefined, expect.anything())
  109. // deep by default
  110. val.value.foo++
  111. await nextTick()
  112. expect(spy).toBeCalledTimes(2)
  113. expect(spy).toBeCalledWith([val], [val], expect.anything())
  114. })
  115. it('should not fire if watched getter result did not change', async () => {
  116. const spy = vi.fn()
  117. const n = ref(0)
  118. watch(() => n.value % 2, spy)
  119. n.value++
  120. await nextTick()
  121. expect(spy).toBeCalledTimes(1)
  122. n.value += 2
  123. await nextTick()
  124. // should not be called again because getter result did not change
  125. expect(spy).toBeCalledTimes(1)
  126. })
  127. it('watching single source: computed ref', async () => {
  128. const count = ref(0)
  129. const plus = computed(() => count.value + 1)
  130. let dummy
  131. watch(plus, (count, prevCount) => {
  132. dummy = [count, prevCount]
  133. // assert types
  134. count + 1
  135. if (prevCount) {
  136. prevCount + 1
  137. }
  138. })
  139. count.value++
  140. await nextTick()
  141. expect(dummy).toMatchObject([2, 1])
  142. })
  143. it('watching primitive with deep: true', async () => {
  144. const count = ref(0)
  145. let dummy
  146. watch(
  147. count,
  148. (c, prevCount) => {
  149. dummy = [c, prevCount]
  150. },
  151. {
  152. deep: true,
  153. },
  154. )
  155. count.value++
  156. await nextTick()
  157. expect(dummy).toMatchObject([1, 0])
  158. })
  159. it('directly watching reactive object (with automatic deep: true)', async () => {
  160. const src = reactive({
  161. count: 0,
  162. })
  163. let dummy
  164. watch(src, ({ count }) => {
  165. dummy = count
  166. })
  167. src.count++
  168. await nextTick()
  169. expect(dummy).toBe(1)
  170. })
  171. it('directly watching reactive object with explicit deep: false', async () => {
  172. const src = reactive({
  173. state: {
  174. count: 0,
  175. },
  176. })
  177. let dummy
  178. watch(
  179. src,
  180. ({ state }) => {
  181. dummy = state?.count
  182. },
  183. {
  184. deep: false,
  185. },
  186. )
  187. // nested should not trigger
  188. src.state.count++
  189. await nextTick()
  190. expect(dummy).toBe(undefined)
  191. // root level should trigger
  192. src.state = { count: 1 }
  193. await nextTick()
  194. expect(dummy).toBe(1)
  195. })
  196. it('directly watching reactive array with explicit deep: false', async () => {
  197. const val = ref(1)
  198. const array: any[] = reactive([val])
  199. const spy = vi.fn()
  200. watch(array, spy, { immediate: true, deep: false })
  201. expect(spy).toBeCalledTimes(1)
  202. expect(spy).toBeCalledWith([val], undefined, expect.anything())
  203. val.value++
  204. await nextTick()
  205. expect(spy).toBeCalledTimes(1)
  206. array[1] = 2
  207. await nextTick()
  208. expect(spy).toBeCalledTimes(2)
  209. expect(spy).toBeCalledWith([val, 2], [val, 2], expect.anything())
  210. })
  211. // #9916
  212. it('watching shallow reactive array with deep: false', async () => {
  213. class foo {
  214. prop1: ShallowRef<string> = shallowRef('')
  215. prop2: string = ''
  216. }
  217. const obj1 = new foo()
  218. const obj2 = new foo()
  219. const collection = shallowReactive([obj1, obj2])
  220. const cb = vi.fn()
  221. watch(collection, cb, { deep: false })
  222. collection[0].prop1.value = 'foo'
  223. await nextTick()
  224. // should not trigger
  225. expect(cb).toBeCalledTimes(0)
  226. collection.push(new foo())
  227. await nextTick()
  228. // should trigger on array self mutation
  229. expect(cb).toBeCalledTimes(1)
  230. })
  231. it('should still respect deep: true on shallowReactive source', async () => {
  232. const obj = reactive({ a: 1 })
  233. const arr = shallowReactive([obj])
  234. let dummy
  235. watch(
  236. arr,
  237. () => {
  238. dummy = arr[0].a
  239. },
  240. { deep: true },
  241. )
  242. obj.a++
  243. await nextTick()
  244. expect(dummy).toBe(2)
  245. })
  246. it('watching multiple sources', async () => {
  247. const state = reactive({ count: 1 })
  248. const count = ref(1)
  249. const plus = computed(() => count.value + 1)
  250. let dummy
  251. watch([() => state.count, count, plus], (vals, oldVals) => {
  252. dummy = [vals, oldVals]
  253. // assert types
  254. vals.concat(1)
  255. oldVals.concat(1)
  256. })
  257. state.count++
  258. count.value++
  259. await nextTick()
  260. expect(dummy).toMatchObject([
  261. [2, 2, 3],
  262. [1, 1, 2],
  263. ])
  264. })
  265. it('watching multiple sources: undefined initial values and immediate: true', async () => {
  266. const a = ref()
  267. const b = ref()
  268. let called = false
  269. watch(
  270. [a, b],
  271. ([newA, newB], [oldA, oldB]) => {
  272. called = true
  273. expect([newA, newB]).toMatchObject([undefined, undefined])
  274. expect([oldA, oldB]).toMatchObject([undefined, undefined])
  275. },
  276. { immediate: true },
  277. )
  278. await nextTick()
  279. expect(called).toBe(true)
  280. })
  281. it('watching multiple sources: readonly array', async () => {
  282. const state = reactive({ count: 1 })
  283. const status = ref(false)
  284. let dummy
  285. watch([() => state.count, status] as const, (vals, oldVals) => {
  286. dummy = [vals, oldVals]
  287. const [count] = vals
  288. const [, oldStatus] = oldVals
  289. // assert types
  290. count + 1
  291. oldStatus === true
  292. })
  293. state.count++
  294. status.value = true
  295. await nextTick()
  296. expect(dummy).toMatchObject([
  297. [2, true],
  298. [1, false],
  299. ])
  300. })
  301. it('watching multiple sources: reactive object (with automatic deep: true)', async () => {
  302. const src = reactive({ count: 0 })
  303. let dummy
  304. watch([src], ([state]) => {
  305. dummy = state
  306. // assert types
  307. state.count === 1
  308. })
  309. src.count++
  310. await nextTick()
  311. expect(dummy).toMatchObject({ count: 1 })
  312. })
  313. it('warn invalid watch source', () => {
  314. // @ts-expect-error
  315. watch(1, () => {})
  316. expect(`Invalid watch source`).toHaveBeenWarned()
  317. })
  318. it('warn invalid watch source: multiple sources', () => {
  319. watch([1], () => {})
  320. expect(`Invalid watch source`).toHaveBeenWarned()
  321. })
  322. it('stopping the watcher (effect)', async () => {
  323. const state = reactive({ count: 0 })
  324. let dummy
  325. const stop = watchEffect(() => {
  326. dummy = state.count
  327. })
  328. expect(dummy).toBe(0)
  329. stop()
  330. state.count++
  331. await nextTick()
  332. // should not update
  333. expect(dummy).toBe(0)
  334. })
  335. it('stopping the watcher (SSR)', async () => {
  336. let dummy = 0
  337. const count = ref<number>(1)
  338. const captureValue = (value: number) => {
  339. dummy = value
  340. }
  341. const watchCallback = vi.fn(newValue => {
  342. captureValue(newValue)
  343. })
  344. const Comp = defineComponent({
  345. created() {
  346. const getter = () => this.count
  347. captureValue(getter()) // sets dummy to 1
  348. const stop = this.$watch(getter, watchCallback)
  349. stop()
  350. this.count = 2 // shouldn't trigger side effect
  351. },
  352. render() {
  353. return h('div', this.count)
  354. },
  355. setup() {
  356. return { count }
  357. },
  358. })
  359. let html
  360. html = await renderToString(h(Comp))
  361. // should not throw here
  362. expect(html).toBe(`<div>2</div>`)
  363. expect(watchCallback).not.toHaveBeenCalled()
  364. expect(dummy).toBe(1)
  365. await nextTick()
  366. count.value = 3 // shouldn't trigger side effect
  367. await nextTick()
  368. expect(watchCallback).not.toHaveBeenCalled()
  369. expect(dummy).toBe(1)
  370. })
  371. it('stopping the watcher (with source)', async () => {
  372. const state = reactive({ count: 0 })
  373. let dummy
  374. const stop = watch(
  375. () => state.count,
  376. count => {
  377. dummy = count
  378. },
  379. )
  380. state.count++
  381. await nextTick()
  382. expect(dummy).toBe(1)
  383. stop()
  384. state.count++
  385. await nextTick()
  386. // should not update
  387. expect(dummy).toBe(1)
  388. })
  389. it('cleanup registration (effect)', async () => {
  390. const state = reactive({ count: 0 })
  391. const cleanup = vi.fn()
  392. let dummy
  393. const stop = watchEffect(onCleanup => {
  394. onCleanup(cleanup)
  395. dummy = state.count
  396. })
  397. expect(dummy).toBe(0)
  398. state.count++
  399. await nextTick()
  400. expect(cleanup).toHaveBeenCalledTimes(1)
  401. expect(dummy).toBe(1)
  402. stop()
  403. expect(cleanup).toHaveBeenCalledTimes(2)
  404. })
  405. it('cleanup registration (with source)', async () => {
  406. const count = ref(0)
  407. const cleanup = vi.fn()
  408. let dummy
  409. const stop = watch(count, (count, prevCount, onCleanup) => {
  410. onCleanup(cleanup)
  411. dummy = count
  412. })
  413. count.value++
  414. await nextTick()
  415. expect(cleanup).toHaveBeenCalledTimes(0)
  416. expect(dummy).toBe(1)
  417. count.value++
  418. await nextTick()
  419. expect(cleanup).toHaveBeenCalledTimes(1)
  420. expect(dummy).toBe(2)
  421. stop()
  422. expect(cleanup).toHaveBeenCalledTimes(2)
  423. })
  424. it('onWatcherCleanup', async () => {
  425. const count = ref(0)
  426. const cleanupEffect = vi.fn()
  427. const cleanupWatch = vi.fn()
  428. const stopEffect = watchEffect(() => {
  429. onWatcherCleanup(cleanupEffect)
  430. count.value
  431. })
  432. const stopWatch = watch(count, () => {
  433. onWatcherCleanup(cleanupWatch)
  434. })
  435. count.value++
  436. await nextTick()
  437. expect(cleanupEffect).toHaveBeenCalledTimes(1)
  438. expect(cleanupWatch).toHaveBeenCalledTimes(0)
  439. count.value++
  440. await nextTick()
  441. expect(cleanupEffect).toHaveBeenCalledTimes(2)
  442. expect(cleanupWatch).toHaveBeenCalledTimes(1)
  443. stopEffect()
  444. expect(cleanupEffect).toHaveBeenCalledTimes(3)
  445. stopWatch()
  446. expect(cleanupWatch).toHaveBeenCalledTimes(2)
  447. })
  448. it('flush timing: pre (default)', async () => {
  449. const count = ref(0)
  450. const count2 = ref(0)
  451. let callCount = 0
  452. let result1
  453. let result2
  454. const assertion = vi.fn((count, count2Value) => {
  455. callCount++
  456. // on mount, the watcher callback should be called before DOM render
  457. // on update, should be called before the count is updated
  458. const expectedDOM = callCount === 1 ? `` : `${count - 1}`
  459. result1 = serializeInner(root) === expectedDOM
  460. // in a pre-flush callback, all state should have been updated
  461. const expectedState = callCount - 1
  462. result2 = count === expectedState && count2Value === expectedState
  463. })
  464. const Comp = {
  465. setup() {
  466. watchEffect(() => {
  467. assertion(count.value, count2.value)
  468. })
  469. return () => count.value
  470. },
  471. }
  472. const root = nodeOps.createElement('div')
  473. render(h(Comp), root)
  474. expect(assertion).toHaveBeenCalledTimes(1)
  475. expect(result1).toBe(true)
  476. expect(result2).toBe(true)
  477. count.value++
  478. count2.value++
  479. await nextTick()
  480. // two mutations should result in 1 callback execution
  481. expect(assertion).toHaveBeenCalledTimes(2)
  482. expect(result1).toBe(true)
  483. expect(result2).toBe(true)
  484. })
  485. it('flush timing: post', async () => {
  486. const count = ref(0)
  487. let result
  488. const assertion = vi.fn(count => {
  489. result = serializeInner(root) === `${count}`
  490. })
  491. const Comp = {
  492. setup() {
  493. watchEffect(
  494. () => {
  495. assertion(count.value)
  496. },
  497. { flush: 'post' },
  498. )
  499. return () => count.value
  500. },
  501. }
  502. const root = nodeOps.createElement('div')
  503. render(h(Comp), root)
  504. expect(assertion).toHaveBeenCalledTimes(1)
  505. expect(result).toBe(true)
  506. count.value++
  507. await nextTick()
  508. expect(assertion).toHaveBeenCalledTimes(2)
  509. expect(result).toBe(true)
  510. })
  511. it('watchPostEffect', async () => {
  512. const count = ref(0)
  513. let result
  514. const assertion = vi.fn(count => {
  515. result = serializeInner(root) === `${count}`
  516. })
  517. const Comp = {
  518. setup() {
  519. watchPostEffect(() => {
  520. assertion(count.value)
  521. })
  522. return () => count.value
  523. },
  524. }
  525. const root = nodeOps.createElement('div')
  526. render(h(Comp), root)
  527. expect(assertion).toHaveBeenCalledTimes(1)
  528. expect(result).toBe(true)
  529. count.value++
  530. await nextTick()
  531. expect(assertion).toHaveBeenCalledTimes(2)
  532. expect(result).toBe(true)
  533. })
  534. it('flush timing: sync', async () => {
  535. const count = ref(0)
  536. const count2 = ref(0)
  537. let callCount = 0
  538. let result1
  539. let result2
  540. const assertion = vi.fn(count => {
  541. callCount++
  542. // on mount, the watcher callback should be called before DOM render
  543. // on update, should be called before the count is updated
  544. const expectedDOM = callCount === 1 ? `` : `${count - 1}`
  545. result1 = serializeInner(root) === expectedDOM
  546. // in a sync callback, state mutation on the next line should not have
  547. // executed yet on the 2nd call, but will be on the 3rd call.
  548. const expectedState = callCount < 3 ? 0 : 1
  549. result2 = count2.value === expectedState
  550. })
  551. const Comp = {
  552. setup() {
  553. watchEffect(
  554. () => {
  555. assertion(count.value)
  556. },
  557. {
  558. flush: 'sync',
  559. },
  560. )
  561. return () => count.value
  562. },
  563. }
  564. const root = nodeOps.createElement('div')
  565. render(h(Comp), root)
  566. expect(assertion).toHaveBeenCalledTimes(1)
  567. expect(result1).toBe(true)
  568. expect(result2).toBe(true)
  569. count.value++
  570. count2.value++
  571. await nextTick()
  572. expect(assertion).toHaveBeenCalledTimes(3)
  573. expect(result1).toBe(true)
  574. expect(result2).toBe(true)
  575. })
  576. it('watchSyncEffect', async () => {
  577. const count = ref(0)
  578. const count2 = ref(0)
  579. let callCount = 0
  580. let result1
  581. let result2
  582. const assertion = vi.fn(count => {
  583. callCount++
  584. // on mount, the watcher callback should be called before DOM render
  585. // on update, should be called before the count is updated
  586. const expectedDOM = callCount === 1 ? `` : `${count - 1}`
  587. result1 = serializeInner(root) === expectedDOM
  588. // in a sync callback, state mutation on the next line should not have
  589. // executed yet on the 2nd call, but will be on the 3rd call.
  590. const expectedState = callCount < 3 ? 0 : 1
  591. result2 = count2.value === expectedState
  592. })
  593. const Comp = {
  594. setup() {
  595. watchSyncEffect(() => {
  596. assertion(count.value)
  597. })
  598. return () => count.value
  599. },
  600. }
  601. const root = nodeOps.createElement('div')
  602. render(h(Comp), root)
  603. expect(assertion).toHaveBeenCalledTimes(1)
  604. expect(result1).toBe(true)
  605. expect(result2).toBe(true)
  606. count.value++
  607. count2.value++
  608. await nextTick()
  609. expect(assertion).toHaveBeenCalledTimes(3)
  610. expect(result1).toBe(true)
  611. expect(result2).toBe(true)
  612. })
  613. it('should not fire on component unmount w/ flush: post', async () => {
  614. const toggle = ref(true)
  615. const cb = vi.fn()
  616. const Comp = {
  617. setup() {
  618. watch(toggle, cb, { flush: 'post' })
  619. },
  620. render() {},
  621. }
  622. const App = {
  623. render() {
  624. return toggle.value ? h(Comp) : null
  625. },
  626. }
  627. render(h(App), nodeOps.createElement('div'))
  628. expect(cb).not.toHaveBeenCalled()
  629. toggle.value = false
  630. await nextTick()
  631. expect(cb).not.toHaveBeenCalled()
  632. })
  633. // #2291
  634. it('should not fire on component unmount w/ flush: pre', async () => {
  635. const toggle = ref(true)
  636. const cb = vi.fn()
  637. const Comp = {
  638. setup() {
  639. watch(toggle, cb, { flush: 'pre' })
  640. },
  641. render() {},
  642. }
  643. const App = {
  644. render() {
  645. return toggle.value ? h(Comp) : null
  646. },
  647. }
  648. render(h(App), nodeOps.createElement('div'))
  649. expect(cb).not.toHaveBeenCalled()
  650. toggle.value = false
  651. await nextTick()
  652. expect(cb).not.toHaveBeenCalled()
  653. })
  654. // #7030
  655. it('should not fire on child component unmount w/ flush: pre', async () => {
  656. const visible = ref(true)
  657. const cb = vi.fn()
  658. const Parent = defineComponent({
  659. props: ['visible'],
  660. render() {
  661. return visible.value ? h(Comp) : null
  662. },
  663. })
  664. const Comp = {
  665. setup() {
  666. watch(visible, cb, { flush: 'pre' })
  667. },
  668. render() {},
  669. }
  670. const App = {
  671. render() {
  672. return h(Parent, {
  673. visible: visible.value,
  674. })
  675. },
  676. }
  677. render(h(App), nodeOps.createElement('div'))
  678. expect(cb).not.toHaveBeenCalled()
  679. visible.value = false
  680. await nextTick()
  681. expect(cb).not.toHaveBeenCalled()
  682. })
  683. // #7030
  684. it('flush: pre watcher in child component should not fire before parent update', async () => {
  685. const b = ref(0)
  686. const calls: string[] = []
  687. const Comp = {
  688. setup() {
  689. watch(
  690. () => b.value,
  691. val => {
  692. calls.push('watcher child')
  693. },
  694. { flush: 'pre' },
  695. )
  696. return () => {
  697. b.value
  698. calls.push('render child')
  699. }
  700. },
  701. }
  702. const Parent = {
  703. props: ['a'],
  704. setup() {
  705. watch(
  706. () => b.value,
  707. val => {
  708. calls.push('watcher parent')
  709. },
  710. { flush: 'pre' },
  711. )
  712. return () => {
  713. b.value
  714. calls.push('render parent')
  715. return h(Comp)
  716. }
  717. },
  718. }
  719. const App = {
  720. render() {
  721. return h(Parent, {
  722. a: b.value,
  723. })
  724. },
  725. }
  726. render(h(App), nodeOps.createElement('div'))
  727. expect(calls).toEqual(['render parent', 'render child'])
  728. b.value++
  729. await nextTick()
  730. expect(calls).toEqual([
  731. 'render parent',
  732. 'render child',
  733. 'watcher parent',
  734. 'render parent',
  735. 'watcher child',
  736. 'render child',
  737. ])
  738. })
  739. // #1763
  740. it('flush: pre watcher watching props should fire before child update', async () => {
  741. const a = ref(0)
  742. const b = ref(0)
  743. const c = ref(0)
  744. const calls: string[] = []
  745. const Comp = {
  746. props: ['a', 'b'],
  747. setup(props: any) {
  748. watch(
  749. () => props.a + props.b,
  750. () => {
  751. calls.push('watcher 1')
  752. c.value++
  753. },
  754. { flush: 'pre' },
  755. )
  756. // #1777 chained pre-watcher
  757. watch(
  758. c,
  759. () => {
  760. calls.push('watcher 2')
  761. },
  762. { flush: 'pre' },
  763. )
  764. return () => {
  765. c.value
  766. calls.push('render')
  767. }
  768. },
  769. }
  770. const App = {
  771. render() {
  772. return h(Comp, { a: a.value, b: b.value })
  773. },
  774. }
  775. render(h(App), nodeOps.createElement('div'))
  776. expect(calls).toEqual(['render'])
  777. // both props are updated
  778. // should trigger pre-flush watcher first and only once
  779. // then trigger child render
  780. a.value++
  781. b.value++
  782. await nextTick()
  783. expect(calls).toEqual(['render', 'watcher 1', 'watcher 2', 'render'])
  784. })
  785. // #5721
  786. it('flush: pre triggered in component setup should be buffered and called before mounted', () => {
  787. const count = ref(0)
  788. const calls: string[] = []
  789. const App = {
  790. render() {},
  791. setup() {
  792. watch(
  793. count,
  794. () => {
  795. calls.push('watch ' + count.value)
  796. },
  797. { flush: 'pre' },
  798. )
  799. onMounted(() => {
  800. calls.push('mounted')
  801. })
  802. // mutate multiple times
  803. count.value++
  804. count.value++
  805. count.value++
  806. },
  807. }
  808. render(h(App), nodeOps.createElement('div'))
  809. expect(calls).toMatchObject(['watch 3', 'mounted'])
  810. })
  811. // #1852
  812. it('flush: post watcher should fire after template refs updated', async () => {
  813. const toggle = ref(false)
  814. let dom: TestElement | null = null
  815. const App = {
  816. setup() {
  817. const domRef = ref<TestElement | null>(null)
  818. watch(
  819. toggle,
  820. () => {
  821. dom = domRef.value
  822. },
  823. { flush: 'post' },
  824. )
  825. return () => {
  826. return toggle.value ? h('p', { ref: domRef }) : null
  827. }
  828. },
  829. }
  830. render(h(App), nodeOps.createElement('div'))
  831. expect(dom).toBe(null)
  832. toggle.value = true
  833. await nextTick()
  834. expect(dom!.tag).toBe('p')
  835. })
  836. it('deep', async () => {
  837. const state = reactive({
  838. nested: {
  839. count: ref(0),
  840. },
  841. array: [1, 2, 3],
  842. map: new Map([
  843. ['a', 1],
  844. ['b', 2],
  845. ]),
  846. set: new Set([1, 2, 3]),
  847. })
  848. let dummy
  849. watch(
  850. () => state,
  851. state => {
  852. dummy = [
  853. state.nested.count,
  854. state.array[0],
  855. state.map.get('a'),
  856. state.set.has(1),
  857. ]
  858. },
  859. { deep: true },
  860. )
  861. state.nested.count++
  862. await nextTick()
  863. expect(dummy).toEqual([1, 1, 1, true])
  864. // nested array mutation
  865. state.array[0] = 2
  866. await nextTick()
  867. expect(dummy).toEqual([1, 2, 1, true])
  868. // nested map mutation
  869. state.map.set('a', 2)
  870. await nextTick()
  871. expect(dummy).toEqual([1, 2, 2, true])
  872. // nested set mutation
  873. state.set.delete(1)
  874. await nextTick()
  875. expect(dummy).toEqual([1, 2, 2, false])
  876. })
  877. it('watching deep ref', async () => {
  878. const count = ref(0)
  879. const double = computed(() => count.value * 2)
  880. const state = reactive([count, double])
  881. let dummy
  882. watch(
  883. () => state,
  884. state => {
  885. dummy = [state[0].value, state[1].value]
  886. },
  887. { deep: true },
  888. )
  889. count.value++
  890. await nextTick()
  891. expect(dummy).toEqual([1, 2])
  892. })
  893. it('deep with symbols', async () => {
  894. const symbol1 = Symbol()
  895. const symbol2 = Symbol()
  896. const symbol3 = Symbol()
  897. const symbol4 = Symbol()
  898. const raw: any = {
  899. [symbol1]: {
  900. [symbol2]: 1,
  901. },
  902. }
  903. Object.defineProperty(raw, symbol3, {
  904. writable: true,
  905. enumerable: false,
  906. value: 1,
  907. })
  908. const state = reactive(raw)
  909. const spy = vi.fn()
  910. watch(() => state, spy, { deep: true })
  911. await nextTick()
  912. expect(spy).toHaveBeenCalledTimes(0)
  913. state[symbol1][symbol2] = 2
  914. await nextTick()
  915. expect(spy).toHaveBeenCalledTimes(1)
  916. // Non-enumerable properties don't trigger deep watchers
  917. state[symbol3] = 3
  918. await nextTick()
  919. expect(spy).toHaveBeenCalledTimes(1)
  920. // Adding a new symbol property
  921. state[symbol4] = 1
  922. await nextTick()
  923. expect(spy).toHaveBeenCalledTimes(2)
  924. // Removing a symbol property
  925. delete state[symbol4]
  926. await nextTick()
  927. expect(spy).toHaveBeenCalledTimes(3)
  928. })
  929. it('immediate', async () => {
  930. const count = ref(0)
  931. const cb = vi.fn()
  932. watch(count, cb, { immediate: true })
  933. expect(cb).toHaveBeenCalledTimes(1)
  934. count.value++
  935. await nextTick()
  936. expect(cb).toHaveBeenCalledTimes(2)
  937. })
  938. it('immediate: triggers when initial value is null', async () => {
  939. const state = ref(null)
  940. const spy = vi.fn()
  941. watch(() => state.value, spy, { immediate: true })
  942. expect(spy).toHaveBeenCalled()
  943. })
  944. it('immediate: triggers when initial value is undefined', async () => {
  945. const state = ref()
  946. const spy = vi.fn()
  947. watch(() => state.value, spy, { immediate: true })
  948. expect(spy).toHaveBeenCalledWith(undefined, undefined, expect.any(Function))
  949. state.value = 3
  950. await nextTick()
  951. expect(spy).toHaveBeenCalledTimes(2)
  952. // testing if undefined can trigger the watcher
  953. state.value = undefined
  954. await nextTick()
  955. expect(spy).toHaveBeenCalledTimes(3)
  956. // it shouldn't trigger if the same value is set
  957. state.value = undefined
  958. await nextTick()
  959. expect(spy).toHaveBeenCalledTimes(3)
  960. })
  961. it('warn immediate option when using effect', async () => {
  962. const count = ref(0)
  963. let dummy
  964. watchEffect(
  965. () => {
  966. dummy = count.value
  967. },
  968. // @ts-expect-error
  969. { immediate: false },
  970. )
  971. expect(dummy).toBe(0)
  972. expect(`"immediate" option is only respected`).toHaveBeenWarned()
  973. count.value++
  974. await nextTick()
  975. expect(dummy).toBe(1)
  976. })
  977. it('warn and not respect deep option when using effect', async () => {
  978. const arr = ref([1, [2]])
  979. const spy = vi.fn()
  980. watchEffect(
  981. () => {
  982. spy()
  983. return arr
  984. },
  985. // @ts-expect-error
  986. { deep: true },
  987. )
  988. expect(spy).toHaveBeenCalledTimes(1)
  989. ;(arr.value[1] as Array<number>)[0] = 3
  990. await nextTick()
  991. expect(spy).toHaveBeenCalledTimes(1)
  992. expect(`"deep" option is only respected`).toHaveBeenWarned()
  993. })
  994. it('onTrack', async () => {
  995. const events: DebuggerEvent[] = []
  996. let dummy
  997. const onTrack = vi.fn((e: DebuggerEvent) => {
  998. events.push(e)
  999. })
  1000. const obj = reactive({ foo: 1, bar: 2 })
  1001. watchEffect(
  1002. () => {
  1003. dummy = [obj.foo, 'bar' in obj, Object.keys(obj)]
  1004. },
  1005. { onTrack },
  1006. )
  1007. await nextTick()
  1008. expect(dummy).toEqual([1, true, ['foo', 'bar']])
  1009. expect(onTrack).toHaveBeenCalledTimes(3)
  1010. expect(events).toMatchObject([
  1011. {
  1012. target: obj,
  1013. type: TrackOpTypes.GET,
  1014. key: 'foo',
  1015. },
  1016. {
  1017. target: obj,
  1018. type: TrackOpTypes.HAS,
  1019. key: 'bar',
  1020. },
  1021. {
  1022. target: obj,
  1023. type: TrackOpTypes.ITERATE,
  1024. key: ITERATE_KEY,
  1025. },
  1026. ])
  1027. })
  1028. it('onTrigger', async () => {
  1029. const events: DebuggerEvent[] = []
  1030. let dummy
  1031. const onTrigger = vi.fn((e: DebuggerEvent) => {
  1032. events.push(e)
  1033. })
  1034. const obj = reactive<{ foo?: number }>({ foo: 1 })
  1035. watchEffect(
  1036. () => {
  1037. dummy = obj.foo
  1038. },
  1039. { onTrigger },
  1040. )
  1041. await nextTick()
  1042. expect(dummy).toBe(1)
  1043. obj.foo!++
  1044. await nextTick()
  1045. expect(dummy).toBe(2)
  1046. expect(onTrigger).toHaveBeenCalledTimes(1)
  1047. expect(events[0]).toMatchObject({
  1048. type: TriggerOpTypes.SET,
  1049. key: 'foo',
  1050. oldValue: 1,
  1051. newValue: 2,
  1052. })
  1053. delete obj.foo
  1054. await nextTick()
  1055. expect(dummy).toBeUndefined()
  1056. expect(onTrigger).toHaveBeenCalledTimes(2)
  1057. expect(events[1]).toMatchObject({
  1058. type: TriggerOpTypes.DELETE,
  1059. key: 'foo',
  1060. oldValue: 2,
  1061. })
  1062. })
  1063. it('should work sync', () => {
  1064. const v = ref(1)
  1065. let calls = 0
  1066. watch(
  1067. v,
  1068. () => {
  1069. ++calls
  1070. },
  1071. {
  1072. flush: 'sync',
  1073. },
  1074. )
  1075. expect(calls).toBe(0)
  1076. v.value++
  1077. expect(calls).toBe(1)
  1078. })
  1079. test('should force trigger on triggerRef when watching a shallow ref', async () => {
  1080. const v = shallowRef({ a: 1 })
  1081. let sideEffect = 0
  1082. watch(v, obj => {
  1083. sideEffect = obj.a
  1084. })
  1085. v.value = v.value
  1086. await nextTick()
  1087. // should not trigger
  1088. expect(sideEffect).toBe(0)
  1089. v.value.a++
  1090. await nextTick()
  1091. // should not trigger
  1092. expect(sideEffect).toBe(0)
  1093. triggerRef(v)
  1094. await nextTick()
  1095. // should trigger now
  1096. expect(sideEffect).toBe(2)
  1097. })
  1098. test('should force trigger on triggerRef when watching multiple sources: shallow ref array', async () => {
  1099. const v = shallowRef([] as any)
  1100. const spy = vi.fn()
  1101. watch([v], () => {
  1102. spy()
  1103. })
  1104. v.value.push(1)
  1105. triggerRef(v)
  1106. await nextTick()
  1107. // should trigger now
  1108. expect(spy).toHaveBeenCalledTimes(1)
  1109. })
  1110. test('should force trigger on triggerRef with toRef from reactive', async () => {
  1111. const foo = reactive({ bar: 1 })
  1112. const bar = toRef(foo, 'bar')
  1113. const spy = vi.fn()
  1114. watchEffect(() => {
  1115. bar.value
  1116. spy()
  1117. })
  1118. expect(spy).toHaveBeenCalledTimes(1)
  1119. triggerRef(bar)
  1120. await nextTick()
  1121. // should trigger now
  1122. expect(spy).toHaveBeenCalledTimes(2)
  1123. })
  1124. // #2125
  1125. test('watchEffect should not recursively trigger itself', async () => {
  1126. const spy = vi.fn()
  1127. const price = ref(10)
  1128. const history = ref<number[]>([])
  1129. watchEffect(() => {
  1130. history.value.push(price.value)
  1131. spy()
  1132. })
  1133. await nextTick()
  1134. expect(spy).toHaveBeenCalledTimes(1)
  1135. })
  1136. // #2231
  1137. test('computed refs should not trigger watch if value has no change', async () => {
  1138. const spy = vi.fn()
  1139. const source = ref(0)
  1140. const price = computed(() => source.value === 0)
  1141. watch(price, spy)
  1142. source.value++
  1143. await nextTick()
  1144. source.value++
  1145. await nextTick()
  1146. expect(spy).toHaveBeenCalledTimes(1)
  1147. })
  1148. // https://github.com/vuejs/core/issues/2381
  1149. test('$watch should always register its effects with its own instance', async () => {
  1150. let instance: ComponentInternalInstance | null
  1151. let _show: Ref<boolean>
  1152. const Child = defineComponent({
  1153. render: () => h('div'),
  1154. mounted() {
  1155. instance = getCurrentInstance()
  1156. },
  1157. unmounted() {},
  1158. })
  1159. const Comp = defineComponent({
  1160. setup() {
  1161. const comp = ref<ComponentPublicInstance | undefined>()
  1162. const show = ref(true)
  1163. _show = show
  1164. return { comp, show }
  1165. },
  1166. render() {
  1167. return this.show
  1168. ? h(Child, {
  1169. ref: vm => void (this.comp = vm as ComponentPublicInstance),
  1170. })
  1171. : null
  1172. },
  1173. mounted() {
  1174. // this call runs while Comp is currentInstance, but
  1175. // the effect for this `$watch` should nonetheless be registered with Child
  1176. this.comp!.$watch(
  1177. () => this.show,
  1178. () => void 0,
  1179. )
  1180. },
  1181. })
  1182. render(h(Comp), nodeOps.createElement('div'))
  1183. expect(instance!).toBeDefined()
  1184. expect(instance!.scope.effects).toBeInstanceOf(Array)
  1185. // includes the component's own render effect AND the watcher effect
  1186. expect(instance!.scope.effects.length).toBe(2)
  1187. _show!.value = false
  1188. await nextTick()
  1189. await nextTick()
  1190. expect(instance!.scope.effects.length).toBe(0)
  1191. })
  1192. test('this.$watch should pass `this.proxy` to watch source as the first argument ', () => {
  1193. let instance: any
  1194. const source = vi.fn()
  1195. const Comp = defineComponent({
  1196. render() {},
  1197. created(this: any) {
  1198. instance = this
  1199. this.$watch(source, function () {})
  1200. },
  1201. })
  1202. const root = nodeOps.createElement('div')
  1203. createApp(Comp).mount(root)
  1204. expect(instance).toBeDefined()
  1205. expect(source.mock.calls.some(args => args.includes(instance)))
  1206. })
  1207. test('should not leak `this.proxy` to setup()', () => {
  1208. const source = vi.fn()
  1209. const Comp = defineComponent({
  1210. render() {},
  1211. setup() {
  1212. watch(source, () => {})
  1213. },
  1214. })
  1215. const root = nodeOps.createElement('div')
  1216. createApp(Comp).mount(root)
  1217. // should not have any arguments
  1218. expect(source.mock.calls[0]).toMatchObject([])
  1219. })
  1220. // #2728
  1221. test('pre watcher callbacks should not track dependencies', async () => {
  1222. const a = ref(0)
  1223. const b = ref(0)
  1224. const updated = vi.fn()
  1225. const Child = defineComponent({
  1226. props: ['a'],
  1227. updated,
  1228. watch: {
  1229. a() {
  1230. b.value
  1231. },
  1232. },
  1233. render() {
  1234. return h('div', this.a)
  1235. },
  1236. })
  1237. const Parent = defineComponent({
  1238. render() {
  1239. return h(Child, { a: a.value })
  1240. },
  1241. })
  1242. const root = nodeOps.createElement('div')
  1243. createApp(Parent).mount(root)
  1244. a.value++
  1245. await nextTick()
  1246. expect(updated).toHaveBeenCalledTimes(1)
  1247. b.value++
  1248. await nextTick()
  1249. // should not track b as dependency of Child
  1250. expect(updated).toHaveBeenCalledTimes(1)
  1251. })
  1252. test('watching keypath', async () => {
  1253. const spy = vi.fn()
  1254. const Comp = defineComponent({
  1255. render() {},
  1256. data() {
  1257. return {
  1258. a: {
  1259. b: 1,
  1260. },
  1261. }
  1262. },
  1263. watch: {
  1264. 'a.b': spy,
  1265. },
  1266. created(this: any) {
  1267. this.$watch('a.b', spy)
  1268. },
  1269. mounted(this: any) {
  1270. this.a.b++
  1271. },
  1272. })
  1273. const root = nodeOps.createElement('div')
  1274. createApp(Comp).mount(root)
  1275. await nextTick()
  1276. expect(spy).toHaveBeenCalledTimes(2)
  1277. })
  1278. it('watching sources: ref<any[]>', async () => {
  1279. const foo = ref([1])
  1280. const spy = vi.fn()
  1281. watch(foo, () => {
  1282. spy()
  1283. })
  1284. foo.value = foo.value.slice()
  1285. await nextTick()
  1286. expect(spy).toBeCalledTimes(1)
  1287. })
  1288. it('watching multiple sources: computed', async () => {
  1289. let count = 0
  1290. const value = ref('1')
  1291. const plus = computed(() => !!value.value)
  1292. watch([plus], () => {
  1293. count++
  1294. })
  1295. value.value = '2'
  1296. await nextTick()
  1297. expect(plus.value).toBe(true)
  1298. expect(count).toBe(0)
  1299. })
  1300. // #4158
  1301. test('watch should not register in owner component if created inside detached scope', () => {
  1302. let instance: ComponentInternalInstance
  1303. const Comp = {
  1304. setup() {
  1305. instance = getCurrentInstance()!
  1306. effectScope(true).run(() => {
  1307. watch(
  1308. () => 1,
  1309. () => {},
  1310. )
  1311. })
  1312. return () => ''
  1313. },
  1314. }
  1315. const root = nodeOps.createElement('div')
  1316. createApp(Comp).mount(root)
  1317. // should not record watcher in detached scope and only the instance's
  1318. // own update effect
  1319. expect(instance!.scope.effects.length).toBe(1)
  1320. })
  1321. test('watchEffect should keep running if created in a detached scope', async () => {
  1322. const trigger = ref(0)
  1323. let countWE = 0
  1324. let countW = 0
  1325. const Comp = {
  1326. setup() {
  1327. effectScope(true).run(() => {
  1328. watchEffect(() => {
  1329. trigger.value
  1330. countWE++
  1331. })
  1332. watch(trigger, () => countW++)
  1333. })
  1334. return () => ''
  1335. },
  1336. }
  1337. const root = nodeOps.createElement('div')
  1338. render(h(Comp), root)
  1339. // only watchEffect as ran so far
  1340. expect(countWE).toBe(1)
  1341. expect(countW).toBe(0)
  1342. trigger.value++
  1343. await nextTick()
  1344. // both watchers run while component is mounted
  1345. expect(countWE).toBe(2)
  1346. expect(countW).toBe(1)
  1347. render(null, root) // unmount
  1348. await nextTick()
  1349. trigger.value++
  1350. await nextTick()
  1351. // both watchers run again event though component has been unmounted
  1352. expect(countWE).toBe(3)
  1353. expect(countW).toBe(2)
  1354. })
  1355. const options = [
  1356. { name: 'only trigger once watch' },
  1357. {
  1358. deep: true,
  1359. name: 'only trigger once watch with deep',
  1360. },
  1361. {
  1362. flush: 'sync',
  1363. name: 'only trigger once watch with flush: sync',
  1364. },
  1365. {
  1366. flush: 'pre',
  1367. name: 'only trigger once watch with flush: pre',
  1368. },
  1369. {
  1370. immediate: true,
  1371. name: 'only trigger once watch with immediate',
  1372. },
  1373. ] as const
  1374. test.each(options)('$name', async option => {
  1375. const count = ref(0)
  1376. const cb = vi.fn()
  1377. watch(count, cb, { once: true, ...option })
  1378. count.value++
  1379. await nextTick()
  1380. expect(count.value).toBe(1)
  1381. expect(cb).toHaveBeenCalledTimes(1)
  1382. count.value++
  1383. await nextTick()
  1384. expect(count.value).toBe(2)
  1385. expect(cb).toHaveBeenCalledTimes(1)
  1386. })
  1387. // #5151
  1388. test('OnCleanup also needs to be cleaned,', async () => {
  1389. const spy1 = vi.fn()
  1390. const spy2 = vi.fn()
  1391. const num = ref(0)
  1392. watch(num, (value, oldValue, onCleanup) => {
  1393. if (value > 1) {
  1394. return
  1395. }
  1396. spy1()
  1397. onCleanup(() => {
  1398. // OnCleanup also needs to be cleaned
  1399. spy2()
  1400. })
  1401. })
  1402. num.value++
  1403. await nextTick()
  1404. expect(spy1).toHaveBeenCalledTimes(1)
  1405. expect(spy2).toHaveBeenCalledTimes(0)
  1406. num.value++
  1407. await nextTick()
  1408. expect(spy1).toHaveBeenCalledTimes(1)
  1409. expect(spy2).toHaveBeenCalledTimes(1)
  1410. num.value++
  1411. await nextTick()
  1412. // would not be calld when value>1
  1413. expect(spy1).toHaveBeenCalledTimes(1)
  1414. expect(spy2).toHaveBeenCalledTimes(1)
  1415. })
  1416. it('watching reactive depth', async () => {
  1417. const state = reactive({
  1418. a: {
  1419. b: {
  1420. c: {
  1421. d: {
  1422. e: 1,
  1423. },
  1424. },
  1425. },
  1426. },
  1427. })
  1428. const cb = vi.fn()
  1429. watch(state, cb, { deep: 2 })
  1430. state.a.b = { c: { d: { e: 2 } } }
  1431. await nextTick()
  1432. expect(cb).toHaveBeenCalledTimes(1)
  1433. state.a.b.c = { d: { e: 3 } }
  1434. await nextTick()
  1435. expect(cb).toHaveBeenCalledTimes(1)
  1436. state.a.b = { c: { d: { e: 4 } } }
  1437. await nextTick()
  1438. expect(cb).toHaveBeenCalledTimes(2)
  1439. })
  1440. it('watching ref depth', async () => {
  1441. const state = ref({
  1442. a: {
  1443. b: 2,
  1444. },
  1445. })
  1446. const cb = vi.fn()
  1447. watch(state, cb, { deep: 1 })
  1448. state.value.a.b = 3
  1449. await nextTick()
  1450. expect(cb).toHaveBeenCalledTimes(0)
  1451. state.value.a = { b: 3 }
  1452. await nextTick()
  1453. expect(cb).toHaveBeenCalledTimes(1)
  1454. })
  1455. it('watching array depth', async () => {
  1456. const arr = ref([
  1457. {
  1458. a: {
  1459. b: 2,
  1460. },
  1461. },
  1462. {
  1463. a: {
  1464. b: 3,
  1465. },
  1466. },
  1467. ])
  1468. const cb = vi.fn()
  1469. watch(arr, cb, { deep: 2 })
  1470. arr.value[0].a.b = 3
  1471. await nextTick()
  1472. expect(cb).toHaveBeenCalledTimes(0)
  1473. arr.value[0].a = { b: 3 }
  1474. await nextTick()
  1475. expect(cb).toHaveBeenCalledTimes(1)
  1476. arr.value[1].a = { b: 4 }
  1477. await nextTick()
  1478. expect(cb).toHaveBeenCalledTimes(2)
  1479. arr.value.push({ a: { b: 5 } })
  1480. await nextTick()
  1481. expect(cb).toHaveBeenCalledTimes(3)
  1482. arr.value.pop()
  1483. await nextTick()
  1484. expect(cb).toHaveBeenCalledTimes(4)
  1485. })
  1486. test('pause / resume', async () => {
  1487. const count = ref(0)
  1488. const cb = vi.fn()
  1489. const { pause, resume } = watch(count, cb)
  1490. count.value++
  1491. await nextTick()
  1492. expect(cb).toHaveBeenCalledTimes(1)
  1493. expect(cb).toHaveBeenLastCalledWith(1, 0, expect.any(Function))
  1494. pause()
  1495. count.value++
  1496. await nextTick()
  1497. expect(cb).toHaveBeenCalledTimes(1)
  1498. expect(cb).toHaveBeenLastCalledWith(1, 0, expect.any(Function))
  1499. resume()
  1500. count.value++
  1501. await nextTick()
  1502. expect(cb).toHaveBeenCalledTimes(2)
  1503. expect(cb).toHaveBeenLastCalledWith(3, 1, expect.any(Function))
  1504. count.value++
  1505. await nextTick()
  1506. expect(cb).toHaveBeenCalledTimes(3)
  1507. expect(cb).toHaveBeenLastCalledWith(4, 3, expect.any(Function))
  1508. pause()
  1509. count.value++
  1510. await nextTick()
  1511. expect(cb).toHaveBeenCalledTimes(3)
  1512. expect(cb).toHaveBeenLastCalledWith(4, 3, expect.any(Function))
  1513. resume()
  1514. await nextTick()
  1515. expect(cb).toHaveBeenCalledTimes(4)
  1516. expect(cb).toHaveBeenLastCalledWith(5, 4, expect.any(Function))
  1517. })
  1518. it('shallowReactive', async () => {
  1519. const state = shallowReactive({
  1520. msg: ref('hello'),
  1521. foo: {
  1522. a: ref(1),
  1523. b: 2,
  1524. },
  1525. bar: 'bar',
  1526. })
  1527. const spy = vi.fn()
  1528. watch(state, spy)
  1529. state.msg.value = 'hi'
  1530. await nextTick()
  1531. expect(spy).not.toHaveBeenCalled()
  1532. state.bar = 'bar2'
  1533. await nextTick()
  1534. expect(spy).toHaveBeenCalledTimes(1)
  1535. state.foo.a.value++
  1536. state.foo.b++
  1537. await nextTick()
  1538. expect(spy).toHaveBeenCalledTimes(1)
  1539. state.bar = 'bar3'
  1540. await nextTick()
  1541. expect(spy).toHaveBeenCalledTimes(2)
  1542. })
  1543. it('watching reactive with deep: false', async () => {
  1544. const state = reactive({
  1545. foo: {
  1546. a: 2,
  1547. },
  1548. bar: 'bar',
  1549. })
  1550. const spy = vi.fn()
  1551. watch(state, spy, { deep: false })
  1552. state.foo.a++
  1553. await nextTick()
  1554. expect(spy).toHaveBeenCalledTimes(0)
  1555. state.bar = 'bar2'
  1556. await nextTick()
  1557. expect(spy).toHaveBeenCalledTimes(1)
  1558. })
  1559. test("effect should be removed from scope's effects after it is stopped", () => {
  1560. const num = ref(0)
  1561. let unwatch: () => void
  1562. let instance: ComponentInternalInstance
  1563. const Comp = {
  1564. setup() {
  1565. instance = getCurrentInstance()!
  1566. unwatch = watch(num, () => {
  1567. console.log(num.value)
  1568. })
  1569. return () => null
  1570. },
  1571. }
  1572. const root = nodeOps.createElement('div')
  1573. createApp(Comp).mount(root)
  1574. expect(instance!.scope.effects.length).toBe(2)
  1575. unwatch!()
  1576. expect(instance!.scope.effects.length).toBe(1)
  1577. const scope = effectScope()
  1578. scope.run(() => {
  1579. unwatch = watch(num, () => {
  1580. console.log(num.value)
  1581. })
  1582. })
  1583. expect(scope.effects.length).toBe(1)
  1584. unwatch!()
  1585. expect(scope.effects.length).toBe(0)
  1586. scope.run(() => {
  1587. watch(num, () => {}, { once: true, immediate: true })
  1588. })
  1589. expect(scope.effects.length).toBe(0)
  1590. })
  1591. // simplified case of VueUse syncRef
  1592. test('sync watcher should not be batched', () => {
  1593. const a = ref(0)
  1594. const b = ref(0)
  1595. let pauseB = false
  1596. watch(
  1597. a,
  1598. () => {
  1599. pauseB = true
  1600. b.value = a.value + 1
  1601. pauseB = false
  1602. },
  1603. { flush: 'sync' },
  1604. )
  1605. watch(
  1606. b,
  1607. () => {
  1608. if (!pauseB) {
  1609. throw new Error('should not be called')
  1610. }
  1611. },
  1612. { flush: 'sync' },
  1613. )
  1614. a.value = 1
  1615. expect(b.value).toBe(2)
  1616. })
  1617. test('watchEffect should not fire on computed deps that did not change', async () => {
  1618. const a = ref(0)
  1619. const c = computed(() => a.value % 2)
  1620. const spy = vi.fn()
  1621. watchEffect(() => {
  1622. spy()
  1623. c.value
  1624. })
  1625. expect(spy).toHaveBeenCalledTimes(1)
  1626. a.value += 2
  1627. await nextTick()
  1628. expect(spy).toHaveBeenCalledTimes(1)
  1629. })
  1630. test('circular reference', async () => {
  1631. const obj = { a: 1 }
  1632. // @ts-expect-error
  1633. obj.b = obj
  1634. const foo = ref(obj)
  1635. const spy = vi.fn()
  1636. watch(foo, spy, { deep: true })
  1637. // @ts-expect-error
  1638. foo.value.b.a = 2
  1639. await nextTick()
  1640. expect(spy).toHaveBeenCalledTimes(1)
  1641. expect(foo.value.a).toBe(2)
  1642. })
  1643. test('watch immediate error in effect scope should be catched by onErrorCaptured', async () => {
  1644. const warn = vi.spyOn(console, 'warn')
  1645. warn.mockImplementation(() => {})
  1646. const ERROR_IN_SCOPE = 'ERROR_IN_SCOPE'
  1647. const ERROR_OUT_SCOPE = 'ERROR_OUT_SCOPE'
  1648. const errors = ref<string[]>([])
  1649. const Comp = {
  1650. setup() {
  1651. const trigger = ref(0)
  1652. effectScope(true).run(() => {
  1653. watch(
  1654. trigger,
  1655. () => {
  1656. throw new Error(ERROR_IN_SCOPE)
  1657. },
  1658. { immediate: true },
  1659. )
  1660. })
  1661. watchEffect(() => {
  1662. throw new Error(ERROR_OUT_SCOPE)
  1663. })
  1664. return () => ''
  1665. },
  1666. }
  1667. const root = nodeOps.createElement('div')
  1668. render(
  1669. h(
  1670. {
  1671. setup(_, { slots }) {
  1672. onErrorCaptured(e => {
  1673. errors.value.push(e.message)
  1674. return false
  1675. })
  1676. return () => h('div', slots.default && slots.default())
  1677. },
  1678. },
  1679. null,
  1680. () => [h(Comp)],
  1681. ),
  1682. root,
  1683. )
  1684. await nextTick()
  1685. // only watchEffect as ran so far
  1686. expect(errors.value).toHaveLength(2)
  1687. expect(errors.value[0]).toBe(ERROR_IN_SCOPE)
  1688. expect(errors.value[1]).toBe(ERROR_OUT_SCOPE)
  1689. warn.mockRestore()
  1690. })
  1691. test('should be executed correctly', () => {
  1692. const v = ref(1)
  1693. let foo = ''
  1694. watch(
  1695. v,
  1696. () => {
  1697. foo += '1'
  1698. },
  1699. {
  1700. flush: 'sync',
  1701. },
  1702. )
  1703. watch(
  1704. v,
  1705. () => {
  1706. foo += '2'
  1707. },
  1708. {
  1709. flush: 'sync',
  1710. },
  1711. )
  1712. expect(foo).toBe('')
  1713. v.value++
  1714. expect(foo).toBe('12')
  1715. })
  1716. // 12045
  1717. test('sync watcher should not break pre watchers', async () => {
  1718. const count1 = ref(0)
  1719. const count2 = ref(0)
  1720. watch(
  1721. count1,
  1722. () => {
  1723. count2.value++
  1724. },
  1725. { flush: 'sync' },
  1726. )
  1727. const spy1 = vi.fn()
  1728. watch([count1, count2], spy1)
  1729. const spy2 = vi.fn()
  1730. watch(count1, spy2)
  1731. count1.value++
  1732. await nextTick()
  1733. expect(spy1).toHaveBeenCalled()
  1734. expect(spy2).toHaveBeenCalled()
  1735. })
  1736. })