2
0

apiWatch.spec.ts 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335
  1. import {
  2. watch,
  3. watchEffect,
  4. reactive,
  5. computed,
  6. nextTick,
  7. ref,
  8. defineComponent,
  9. getCurrentInstance,
  10. ComponentInternalInstance,
  11. ComponentPublicInstance
  12. } from '../src/index'
  13. import {
  14. render,
  15. nodeOps,
  16. serializeInner,
  17. TestElement,
  18. h,
  19. createApp,
  20. watchPostEffect,
  21. watchSyncEffect,
  22. onMounted
  23. } from '@vue/runtime-test'
  24. import {
  25. ITERATE_KEY,
  26. DebuggerEvent,
  27. TrackOpTypes,
  28. TriggerOpTypes,
  29. triggerRef,
  30. shallowRef,
  31. Ref,
  32. effectScope,
  33. toRef
  34. } from '@vue/reactivity'
  35. // reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
  36. describe('api: watch', () => {
  37. it('effect', async () => {
  38. const state = reactive({ count: 0 })
  39. let dummy
  40. watchEffect(() => {
  41. dummy = state.count
  42. })
  43. expect(dummy).toBe(0)
  44. state.count++
  45. await nextTick()
  46. expect(dummy).toBe(1)
  47. })
  48. it('watching single source: getter', async () => {
  49. const state = reactive({ count: 0 })
  50. let dummy
  51. watch(
  52. () => state.count,
  53. (count, prevCount) => {
  54. dummy = [count, prevCount]
  55. // assert types
  56. count + 1
  57. if (prevCount) {
  58. prevCount + 1
  59. }
  60. }
  61. )
  62. state.count++
  63. await nextTick()
  64. expect(dummy).toMatchObject([1, 0])
  65. })
  66. it('watching single source: ref', async () => {
  67. const count = ref(0)
  68. let dummy
  69. watch(count, (count, prevCount) => {
  70. dummy = [count, prevCount]
  71. // assert types
  72. count + 1
  73. if (prevCount) {
  74. prevCount + 1
  75. }
  76. })
  77. count.value++
  78. await nextTick()
  79. expect(dummy).toMatchObject([1, 0])
  80. })
  81. it('watching single source: array', async () => {
  82. const array = reactive([] as number[])
  83. const spy = vi.fn()
  84. watch(array, spy)
  85. array.push(1)
  86. await nextTick()
  87. expect(spy).toBeCalledTimes(1)
  88. expect(spy).toBeCalledWith([1], [1], expect.anything())
  89. })
  90. it('should not fire if watched getter result did not change', async () => {
  91. const spy = vi.fn()
  92. const n = ref(0)
  93. watch(() => n.value % 2, spy)
  94. n.value++
  95. await nextTick()
  96. expect(spy).toBeCalledTimes(1)
  97. n.value += 2
  98. await nextTick()
  99. // should not be called again because getter result did not change
  100. expect(spy).toBeCalledTimes(1)
  101. })
  102. it('watching single source: computed ref', async () => {
  103. const count = ref(0)
  104. const plus = computed(() => count.value + 1)
  105. let dummy
  106. watch(plus, (count, prevCount) => {
  107. dummy = [count, prevCount]
  108. // assert types
  109. count + 1
  110. if (prevCount) {
  111. prevCount + 1
  112. }
  113. })
  114. count.value++
  115. await nextTick()
  116. expect(dummy).toMatchObject([2, 1])
  117. })
  118. it('watching primitive with deep: true', async () => {
  119. const count = ref(0)
  120. let dummy
  121. watch(
  122. count,
  123. (c, prevCount) => {
  124. dummy = [c, prevCount]
  125. },
  126. {
  127. deep: true
  128. }
  129. )
  130. count.value++
  131. await nextTick()
  132. expect(dummy).toMatchObject([1, 0])
  133. })
  134. it('directly watching reactive object (with automatic deep: true)', async () => {
  135. const src = reactive({
  136. count: 0
  137. })
  138. let dummy
  139. watch(src, ({ count }) => {
  140. dummy = count
  141. })
  142. src.count++
  143. await nextTick()
  144. expect(dummy).toBe(1)
  145. })
  146. it('watching multiple sources', async () => {
  147. const state = reactive({ count: 1 })
  148. const count = ref(1)
  149. const plus = computed(() => count.value + 1)
  150. let dummy
  151. watch([() => state.count, count, plus], (vals, oldVals) => {
  152. dummy = [vals, oldVals]
  153. // assert types
  154. vals.concat(1)
  155. oldVals.concat(1)
  156. })
  157. state.count++
  158. count.value++
  159. await nextTick()
  160. expect(dummy).toMatchObject([
  161. [2, 2, 3],
  162. [1, 1, 2]
  163. ])
  164. })
  165. it('watching multiple sources: undefined initial values and immediate: true', async () => {
  166. const a = ref()
  167. const b = ref()
  168. let called = false
  169. watch(
  170. [a, b],
  171. ([newA, newB], [oldA, oldB]) => {
  172. called = true
  173. expect([newA, newB]).toMatchObject([undefined, undefined])
  174. expect([oldA, oldB]).toMatchObject([undefined, undefined])
  175. },
  176. { immediate: true }
  177. )
  178. await nextTick()
  179. expect(called).toBe(true)
  180. })
  181. it('watching multiple sources: readonly array', async () => {
  182. const state = reactive({ count: 1 })
  183. const status = ref(false)
  184. let dummy
  185. watch([() => state.count, status] as const, (vals, oldVals) => {
  186. dummy = [vals, oldVals]
  187. const [count] = vals
  188. const [, oldStatus] = oldVals
  189. // assert types
  190. count + 1
  191. oldStatus === true
  192. })
  193. state.count++
  194. status.value = true
  195. await nextTick()
  196. expect(dummy).toMatchObject([
  197. [2, true],
  198. [1, false]
  199. ])
  200. })
  201. it('watching multiple sources: reactive object (with automatic deep: true)', async () => {
  202. const src = reactive({ count: 0 })
  203. let dummy
  204. watch([src], ([state]) => {
  205. dummy = state
  206. // assert types
  207. state.count === 1
  208. })
  209. src.count++
  210. await nextTick()
  211. expect(dummy).toMatchObject({ count: 1 })
  212. })
  213. it('warn invalid watch source', () => {
  214. // @ts-expect-error
  215. watch(1, () => {})
  216. expect(`Invalid watch source`).toHaveBeenWarned()
  217. })
  218. it('warn invalid watch source: multiple sources', () => {
  219. watch([1], () => {})
  220. expect(`Invalid watch source`).toHaveBeenWarned()
  221. })
  222. it('stopping the watcher (effect)', async () => {
  223. const state = reactive({ count: 0 })
  224. let dummy
  225. const stop = watchEffect(() => {
  226. dummy = state.count
  227. })
  228. expect(dummy).toBe(0)
  229. stop()
  230. state.count++
  231. await nextTick()
  232. // should not update
  233. expect(dummy).toBe(0)
  234. })
  235. it('stopping the watcher (with source)', async () => {
  236. const state = reactive({ count: 0 })
  237. let dummy
  238. const stop = watch(
  239. () => state.count,
  240. count => {
  241. dummy = count
  242. }
  243. )
  244. state.count++
  245. await nextTick()
  246. expect(dummy).toBe(1)
  247. stop()
  248. state.count++
  249. await nextTick()
  250. // should not update
  251. expect(dummy).toBe(1)
  252. })
  253. it('cleanup registration (effect)', async () => {
  254. const state = reactive({ count: 0 })
  255. const cleanup = vi.fn()
  256. let dummy
  257. const stop = watchEffect(onCleanup => {
  258. onCleanup(cleanup)
  259. dummy = state.count
  260. })
  261. expect(dummy).toBe(0)
  262. state.count++
  263. await nextTick()
  264. expect(cleanup).toHaveBeenCalledTimes(1)
  265. expect(dummy).toBe(1)
  266. stop()
  267. expect(cleanup).toHaveBeenCalledTimes(2)
  268. })
  269. it('cleanup registration (with source)', async () => {
  270. const count = ref(0)
  271. const cleanup = vi.fn()
  272. let dummy
  273. const stop = watch(count, (count, prevCount, onCleanup) => {
  274. onCleanup(cleanup)
  275. dummy = count
  276. })
  277. count.value++
  278. await nextTick()
  279. expect(cleanup).toHaveBeenCalledTimes(0)
  280. expect(dummy).toBe(1)
  281. count.value++
  282. await nextTick()
  283. expect(cleanup).toHaveBeenCalledTimes(1)
  284. expect(dummy).toBe(2)
  285. stop()
  286. expect(cleanup).toHaveBeenCalledTimes(2)
  287. })
  288. it('flush timing: pre (default)', async () => {
  289. const count = ref(0)
  290. const count2 = ref(0)
  291. let callCount = 0
  292. let result1
  293. let result2
  294. const assertion = vi.fn((count, count2Value) => {
  295. callCount++
  296. // on mount, the watcher callback should be called before DOM render
  297. // on update, should be called before the count is updated
  298. const expectedDOM = callCount === 1 ? `` : `${count - 1}`
  299. result1 = serializeInner(root) === expectedDOM
  300. // in a pre-flush callback, all state should have been updated
  301. const expectedState = callCount - 1
  302. result2 = count === expectedState && count2Value === expectedState
  303. })
  304. const Comp = {
  305. setup() {
  306. watchEffect(() => {
  307. assertion(count.value, count2.value)
  308. })
  309. return () => count.value
  310. }
  311. }
  312. const root = nodeOps.createElement('div')
  313. render(h(Comp), root)
  314. expect(assertion).toHaveBeenCalledTimes(1)
  315. expect(result1).toBe(true)
  316. expect(result2).toBe(true)
  317. count.value++
  318. count2.value++
  319. await nextTick()
  320. // two mutations should result in 1 callback execution
  321. expect(assertion).toHaveBeenCalledTimes(2)
  322. expect(result1).toBe(true)
  323. expect(result2).toBe(true)
  324. })
  325. it('flush timing: post', async () => {
  326. const count = ref(0)
  327. let result
  328. const assertion = vi.fn(count => {
  329. result = serializeInner(root) === `${count}`
  330. })
  331. const Comp = {
  332. setup() {
  333. watchEffect(
  334. () => {
  335. assertion(count.value)
  336. },
  337. { flush: 'post' }
  338. )
  339. return () => count.value
  340. }
  341. }
  342. const root = nodeOps.createElement('div')
  343. render(h(Comp), root)
  344. expect(assertion).toHaveBeenCalledTimes(1)
  345. expect(result).toBe(true)
  346. count.value++
  347. await nextTick()
  348. expect(assertion).toHaveBeenCalledTimes(2)
  349. expect(result).toBe(true)
  350. })
  351. it('watchPostEffect', async () => {
  352. const count = ref(0)
  353. let result
  354. const assertion = vi.fn(count => {
  355. result = serializeInner(root) === `${count}`
  356. })
  357. const Comp = {
  358. setup() {
  359. watchPostEffect(() => {
  360. assertion(count.value)
  361. })
  362. return () => count.value
  363. }
  364. }
  365. const root = nodeOps.createElement('div')
  366. render(h(Comp), root)
  367. expect(assertion).toHaveBeenCalledTimes(1)
  368. expect(result).toBe(true)
  369. count.value++
  370. await nextTick()
  371. expect(assertion).toHaveBeenCalledTimes(2)
  372. expect(result).toBe(true)
  373. })
  374. it('flush timing: sync', async () => {
  375. const count = ref(0)
  376. const count2 = ref(0)
  377. let callCount = 0
  378. let result1
  379. let result2
  380. const assertion = vi.fn(count => {
  381. callCount++
  382. // on mount, the watcher callback should be called before DOM render
  383. // on update, should be called before the count is updated
  384. const expectedDOM = callCount === 1 ? `` : `${count - 1}`
  385. result1 = serializeInner(root) === expectedDOM
  386. // in a sync callback, state mutation on the next line should not have
  387. // executed yet on the 2nd call, but will be on the 3rd call.
  388. const expectedState = callCount < 3 ? 0 : 1
  389. result2 = count2.value === expectedState
  390. })
  391. const Comp = {
  392. setup() {
  393. watchEffect(
  394. () => {
  395. assertion(count.value)
  396. },
  397. {
  398. flush: 'sync'
  399. }
  400. )
  401. return () => count.value
  402. }
  403. }
  404. const root = nodeOps.createElement('div')
  405. render(h(Comp), root)
  406. expect(assertion).toHaveBeenCalledTimes(1)
  407. expect(result1).toBe(true)
  408. expect(result2).toBe(true)
  409. count.value++
  410. count2.value++
  411. await nextTick()
  412. expect(assertion).toHaveBeenCalledTimes(3)
  413. expect(result1).toBe(true)
  414. expect(result2).toBe(true)
  415. })
  416. it('watchSyncEffect', async () => {
  417. const count = ref(0)
  418. const count2 = ref(0)
  419. let callCount = 0
  420. let result1
  421. let result2
  422. const assertion = vi.fn(count => {
  423. callCount++
  424. // on mount, the watcher callback should be called before DOM render
  425. // on update, should be called before the count is updated
  426. const expectedDOM = callCount === 1 ? `` : `${count - 1}`
  427. result1 = serializeInner(root) === expectedDOM
  428. // in a sync callback, state mutation on the next line should not have
  429. // executed yet on the 2nd call, but will be on the 3rd call.
  430. const expectedState = callCount < 3 ? 0 : 1
  431. result2 = count2.value === expectedState
  432. })
  433. const Comp = {
  434. setup() {
  435. watchSyncEffect(() => {
  436. assertion(count.value)
  437. })
  438. return () => count.value
  439. }
  440. }
  441. const root = nodeOps.createElement('div')
  442. render(h(Comp), root)
  443. expect(assertion).toHaveBeenCalledTimes(1)
  444. expect(result1).toBe(true)
  445. expect(result2).toBe(true)
  446. count.value++
  447. count2.value++
  448. await nextTick()
  449. expect(assertion).toHaveBeenCalledTimes(3)
  450. expect(result1).toBe(true)
  451. expect(result2).toBe(true)
  452. })
  453. it('should not fire on component unmount w/ flush: post', async () => {
  454. const toggle = ref(true)
  455. const cb = vi.fn()
  456. const Comp = {
  457. setup() {
  458. watch(toggle, cb, { flush: 'post' })
  459. },
  460. render() {}
  461. }
  462. const App = {
  463. render() {
  464. return toggle.value ? h(Comp) : null
  465. }
  466. }
  467. render(h(App), nodeOps.createElement('div'))
  468. expect(cb).not.toHaveBeenCalled()
  469. toggle.value = false
  470. await nextTick()
  471. expect(cb).not.toHaveBeenCalled()
  472. })
  473. // #2291
  474. it('should not fire on component unmount w/ flush: pre', async () => {
  475. const toggle = ref(true)
  476. const cb = vi.fn()
  477. const Comp = {
  478. setup() {
  479. watch(toggle, cb, { flush: 'pre' })
  480. },
  481. render() {}
  482. }
  483. const App = {
  484. render() {
  485. return toggle.value ? h(Comp) : null
  486. }
  487. }
  488. render(h(App), nodeOps.createElement('div'))
  489. expect(cb).not.toHaveBeenCalled()
  490. toggle.value = false
  491. await nextTick()
  492. expect(cb).not.toHaveBeenCalled()
  493. })
  494. // #7030
  495. it('should not fire on child component unmount w/ flush: pre', async () => {
  496. const visible = ref(true)
  497. const cb = vi.fn()
  498. const Parent = defineComponent({
  499. props: ['visible'],
  500. render() {
  501. return visible.value ? h(Comp) : null
  502. }
  503. })
  504. const Comp = {
  505. setup() {
  506. watch(visible, cb, { flush: 'pre' })
  507. },
  508. render() {}
  509. }
  510. const App = {
  511. render() {
  512. return h(Parent, {
  513. visible: visible.value
  514. })
  515. }
  516. }
  517. render(h(App), nodeOps.createElement('div'))
  518. expect(cb).not.toHaveBeenCalled()
  519. visible.value = false
  520. await nextTick()
  521. expect(cb).not.toHaveBeenCalled()
  522. })
  523. // #7030
  524. it('flush: pre watcher in child component should not fire before parent update', async () => {
  525. const b = ref(0)
  526. const calls: string[] = []
  527. const Comp = {
  528. setup() {
  529. watch(
  530. () => b.value,
  531. val => {
  532. calls.push('watcher child')
  533. },
  534. { flush: 'pre' }
  535. )
  536. return () => {
  537. b.value
  538. calls.push('render child')
  539. }
  540. }
  541. }
  542. const Parent = {
  543. props: ['a'],
  544. setup() {
  545. watch(
  546. () => b.value,
  547. val => {
  548. calls.push('watcher parent')
  549. },
  550. { flush: 'pre' }
  551. )
  552. return () => {
  553. b.value
  554. calls.push('render parent')
  555. return h(Comp)
  556. }
  557. }
  558. }
  559. const App = {
  560. render() {
  561. return h(Parent, {
  562. a: b.value
  563. })
  564. }
  565. }
  566. render(h(App), nodeOps.createElement('div'))
  567. expect(calls).toEqual(['render parent', 'render child'])
  568. b.value++
  569. await nextTick()
  570. expect(calls).toEqual([
  571. 'render parent',
  572. 'render child',
  573. 'watcher parent',
  574. 'render parent',
  575. 'watcher child',
  576. 'render child'
  577. ])
  578. })
  579. // #1763
  580. it('flush: pre watcher watching props should fire before child update', async () => {
  581. const a = ref(0)
  582. const b = ref(0)
  583. const c = ref(0)
  584. const calls: string[] = []
  585. const Comp = {
  586. props: ['a', 'b'],
  587. setup(props: any) {
  588. watch(
  589. () => props.a + props.b,
  590. () => {
  591. calls.push('watcher 1')
  592. c.value++
  593. },
  594. { flush: 'pre' }
  595. )
  596. // #1777 chained pre-watcher
  597. watch(
  598. c,
  599. () => {
  600. calls.push('watcher 2')
  601. },
  602. { flush: 'pre' }
  603. )
  604. return () => {
  605. c.value
  606. calls.push('render')
  607. }
  608. }
  609. }
  610. const App = {
  611. render() {
  612. return h(Comp, { a: a.value, b: b.value })
  613. }
  614. }
  615. render(h(App), nodeOps.createElement('div'))
  616. expect(calls).toEqual(['render'])
  617. // both props are updated
  618. // should trigger pre-flush watcher first and only once
  619. // then trigger child render
  620. a.value++
  621. b.value++
  622. await nextTick()
  623. expect(calls).toEqual(['render', 'watcher 1', 'watcher 2', 'render'])
  624. })
  625. // #5721
  626. it('flush: pre triggered in component setup should be buffered and called before mounted', () => {
  627. const count = ref(0)
  628. const calls: string[] = []
  629. const App = {
  630. render() {},
  631. setup() {
  632. watch(
  633. count,
  634. () => {
  635. calls.push('watch ' + count.value)
  636. },
  637. { flush: 'pre' }
  638. )
  639. onMounted(() => {
  640. calls.push('mounted')
  641. })
  642. // mutate multiple times
  643. count.value++
  644. count.value++
  645. count.value++
  646. }
  647. }
  648. render(h(App), nodeOps.createElement('div'))
  649. expect(calls).toMatchObject(['watch 3', 'mounted'])
  650. })
  651. // #1852
  652. it('flush: post watcher should fire after template refs updated', async () => {
  653. const toggle = ref(false)
  654. let dom: TestElement | null = null
  655. const App = {
  656. setup() {
  657. const domRef = ref<TestElement | null>(null)
  658. watch(
  659. toggle,
  660. () => {
  661. dom = domRef.value
  662. },
  663. { flush: 'post' }
  664. )
  665. return () => {
  666. return toggle.value ? h('p', { ref: domRef }) : null
  667. }
  668. }
  669. }
  670. render(h(App), nodeOps.createElement('div'))
  671. expect(dom).toBe(null)
  672. toggle.value = true
  673. await nextTick()
  674. expect(dom!.tag).toBe('p')
  675. })
  676. 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. })
  688. let dummy
  689. watch(
  690. () => state,
  691. state => {
  692. dummy = [
  693. state.nested.count,
  694. state.array[0],
  695. state.map.get('a'),
  696. state.set.has(1)
  697. ]
  698. },
  699. { deep: true }
  700. )
  701. state.nested.count++
  702. await nextTick()
  703. expect(dummy).toEqual([1, 1, 1, true])
  704. // nested array mutation
  705. state.array[0] = 2
  706. await nextTick()
  707. expect(dummy).toEqual([1, 2, 1, true])
  708. // nested map mutation
  709. state.map.set('a', 2)
  710. await nextTick()
  711. expect(dummy).toEqual([1, 2, 2, true])
  712. // nested set mutation
  713. state.set.delete(1)
  714. await nextTick()
  715. expect(dummy).toEqual([1, 2, 2, false])
  716. })
  717. it('watching deep ref', async () => {
  718. const count = ref(0)
  719. const double = computed(() => count.value * 2)
  720. const state = reactive([count, double])
  721. let dummy
  722. watch(
  723. () => state,
  724. state => {
  725. dummy = [state[0].value, state[1].value]
  726. },
  727. { deep: true }
  728. )
  729. count.value++
  730. await nextTick()
  731. expect(dummy).toEqual([1, 2])
  732. })
  733. it('immediate', async () => {
  734. const count = ref(0)
  735. const cb = vi.fn()
  736. watch(count, cb, { immediate: true })
  737. expect(cb).toHaveBeenCalledTimes(1)
  738. count.value++
  739. await nextTick()
  740. expect(cb).toHaveBeenCalledTimes(2)
  741. })
  742. it('immediate: triggers when initial value is null', async () => {
  743. const state = ref(null)
  744. const spy = vi.fn()
  745. watch(() => state.value, spy, { immediate: true })
  746. expect(spy).toHaveBeenCalled()
  747. })
  748. it('immediate: triggers when initial value is undefined', async () => {
  749. const state = ref()
  750. const spy = vi.fn()
  751. watch(() => state.value, spy, { immediate: true })
  752. expect(spy).toHaveBeenCalledWith(undefined, undefined, expect.any(Function))
  753. state.value = 3
  754. await nextTick()
  755. expect(spy).toHaveBeenCalledTimes(2)
  756. // testing if undefined can trigger the watcher
  757. state.value = undefined
  758. await nextTick()
  759. expect(spy).toHaveBeenCalledTimes(3)
  760. // it shouldn't trigger if the same value is set
  761. state.value = undefined
  762. await nextTick()
  763. expect(spy).toHaveBeenCalledTimes(3)
  764. })
  765. it('warn immediate option when using effect', async () => {
  766. const count = ref(0)
  767. let dummy
  768. watchEffect(
  769. () => {
  770. dummy = count.value
  771. },
  772. // @ts-expect-error
  773. { immediate: false }
  774. )
  775. expect(dummy).toBe(0)
  776. expect(`"immediate" option is only respected`).toHaveBeenWarned()
  777. count.value++
  778. await nextTick()
  779. expect(dummy).toBe(1)
  780. })
  781. it('warn and not respect deep option when using effect', async () => {
  782. const arr = ref([1, [2]])
  783. const spy = vi.fn()
  784. watchEffect(
  785. () => {
  786. spy()
  787. return arr
  788. },
  789. // @ts-expect-error
  790. { deep: true }
  791. )
  792. expect(spy).toHaveBeenCalledTimes(1)
  793. ;(arr.value[1] as Array<number>)[0] = 3
  794. await nextTick()
  795. expect(spy).toHaveBeenCalledTimes(1)
  796. expect(`"deep" option is only respected`).toHaveBeenWarned()
  797. })
  798. it('onTrack', async () => {
  799. const events: DebuggerEvent[] = []
  800. let dummy
  801. const onTrack = vi.fn((e: DebuggerEvent) => {
  802. events.push(e)
  803. })
  804. const obj = reactive({ foo: 1, bar: 2 })
  805. watchEffect(
  806. () => {
  807. dummy = [obj.foo, 'bar' in obj, Object.keys(obj)]
  808. },
  809. { onTrack }
  810. )
  811. await nextTick()
  812. expect(dummy).toEqual([1, true, ['foo', 'bar']])
  813. expect(onTrack).toHaveBeenCalledTimes(3)
  814. expect(events).toMatchObject([
  815. {
  816. target: obj,
  817. type: TrackOpTypes.GET,
  818. key: 'foo'
  819. },
  820. {
  821. target: obj,
  822. type: TrackOpTypes.HAS,
  823. key: 'bar'
  824. },
  825. {
  826. target: obj,
  827. type: TrackOpTypes.ITERATE,
  828. key: ITERATE_KEY
  829. }
  830. ])
  831. })
  832. it('onTrigger', async () => {
  833. const events: DebuggerEvent[] = []
  834. let dummy
  835. const onTrigger = vi.fn((e: DebuggerEvent) => {
  836. events.push(e)
  837. })
  838. const obj = reactive<{ foo?: number }>({ foo: 1 })
  839. watchEffect(
  840. () => {
  841. dummy = obj.foo
  842. },
  843. { onTrigger }
  844. )
  845. await nextTick()
  846. expect(dummy).toBe(1)
  847. obj.foo!++
  848. await nextTick()
  849. expect(dummy).toBe(2)
  850. expect(onTrigger).toHaveBeenCalledTimes(1)
  851. expect(events[0]).toMatchObject({
  852. type: TriggerOpTypes.SET,
  853. key: 'foo',
  854. oldValue: 1,
  855. newValue: 2
  856. })
  857. delete obj.foo
  858. await nextTick()
  859. expect(dummy).toBeUndefined()
  860. expect(onTrigger).toHaveBeenCalledTimes(2)
  861. expect(events[1]).toMatchObject({
  862. type: TriggerOpTypes.DELETE,
  863. key: 'foo',
  864. oldValue: 2
  865. })
  866. })
  867. it('should work sync', () => {
  868. const v = ref(1)
  869. let calls = 0
  870. watch(
  871. v,
  872. () => {
  873. ++calls
  874. },
  875. {
  876. flush: 'sync'
  877. }
  878. )
  879. expect(calls).toBe(0)
  880. v.value++
  881. expect(calls).toBe(1)
  882. })
  883. test('should force trigger on triggerRef when watching a shallow ref', async () => {
  884. const v = shallowRef({ a: 1 })
  885. let sideEffect = 0
  886. watch(v, obj => {
  887. sideEffect = obj.a
  888. })
  889. v.value = v.value
  890. await nextTick()
  891. // should not trigger
  892. expect(sideEffect).toBe(0)
  893. v.value.a++
  894. await nextTick()
  895. // should not trigger
  896. expect(sideEffect).toBe(0)
  897. triggerRef(v)
  898. await nextTick()
  899. // should trigger now
  900. expect(sideEffect).toBe(2)
  901. })
  902. test('should force trigger on triggerRef when watching multiple sources: shallow ref array', async () => {
  903. const v = shallowRef([] as any)
  904. const spy = vi.fn()
  905. watch([v], () => {
  906. spy()
  907. })
  908. v.value.push(1)
  909. triggerRef(v)
  910. await nextTick()
  911. // should trigger now
  912. expect(spy).toHaveBeenCalledTimes(1)
  913. })
  914. test('should force trigger on triggerRef with toRef from reactive', async () => {
  915. const foo = reactive({ bar: 1 })
  916. const bar = toRef(foo, 'bar')
  917. const spy = vi.fn()
  918. watchEffect(() => {
  919. bar.value
  920. spy()
  921. })
  922. expect(spy).toHaveBeenCalledTimes(1)
  923. triggerRef(bar)
  924. await nextTick()
  925. // should trigger now
  926. expect(spy).toHaveBeenCalledTimes(2)
  927. })
  928. // #2125
  929. test('watchEffect should not recursively trigger itself', async () => {
  930. const spy = vi.fn()
  931. const price = ref(10)
  932. const history = ref<number[]>([])
  933. watchEffect(() => {
  934. history.value.push(price.value)
  935. spy()
  936. })
  937. await nextTick()
  938. expect(spy).toHaveBeenCalledTimes(1)
  939. })
  940. // #2231
  941. test('computed refs should not trigger watch if value has no change', async () => {
  942. const spy = vi.fn()
  943. const source = ref(0)
  944. const price = computed(() => source.value === 0)
  945. watch(price, spy)
  946. source.value++
  947. await nextTick()
  948. source.value++
  949. await nextTick()
  950. expect(spy).toHaveBeenCalledTimes(1)
  951. })
  952. // https://github.com/vuejs/core/issues/2381
  953. test('$watch should always register its effects with its own instance', async () => {
  954. let instance: ComponentInternalInstance | null
  955. let _show: Ref<boolean>
  956. const Child = defineComponent({
  957. render: () => h('div'),
  958. mounted() {
  959. instance = getCurrentInstance()
  960. },
  961. unmounted() {}
  962. })
  963. const Comp = defineComponent({
  964. setup() {
  965. const comp = ref<ComponentPublicInstance | undefined>()
  966. const show = ref(true)
  967. _show = show
  968. return { comp, show }
  969. },
  970. render() {
  971. return this.show
  972. ? h(Child, {
  973. ref: vm => void (this.comp = vm as ComponentPublicInstance)
  974. })
  975. : null
  976. },
  977. mounted() {
  978. // this call runs while Comp is currentInstance, but
  979. // the effect for this `$watch` should nonetheless be registered with Child
  980. this.comp!.$watch(
  981. () => this.show,
  982. () => void 0
  983. )
  984. }
  985. })
  986. render(h(Comp), nodeOps.createElement('div'))
  987. expect(instance!).toBeDefined()
  988. expect(instance!.scope.effects).toBeInstanceOf(Array)
  989. // includes the component's own render effect AND the watcher effect
  990. expect(instance!.scope.effects.length).toBe(2)
  991. _show!.value = false
  992. await nextTick()
  993. await nextTick()
  994. expect(instance!.scope.effects[0].active).toBe(false)
  995. })
  996. test('this.$watch should pass `this.proxy` to watch source as the first argument ', () => {
  997. let instance: any
  998. const source = vi.fn()
  999. const Comp = defineComponent({
  1000. render() {},
  1001. created(this: any) {
  1002. instance = this
  1003. this.$watch(source, function () {})
  1004. }
  1005. })
  1006. const root = nodeOps.createElement('div')
  1007. createApp(Comp).mount(root)
  1008. expect(instance).toBeDefined()
  1009. expect(source.mock.calls.some(args => args.includes(instance)))
  1010. })
  1011. test('should not leak `this.proxy` to setup()', () => {
  1012. const source = vi.fn()
  1013. const Comp = defineComponent({
  1014. render() {},
  1015. setup() {
  1016. watch(source, () => {})
  1017. }
  1018. })
  1019. const root = nodeOps.createElement('div')
  1020. createApp(Comp).mount(root)
  1021. // should not have any arguments
  1022. expect(source.mock.calls[0]).toMatchObject([])
  1023. })
  1024. // #2728
  1025. test('pre watcher callbacks should not track dependencies', async () => {
  1026. const a = ref(0)
  1027. const b = ref(0)
  1028. const updated = vi.fn()
  1029. const Child = defineComponent({
  1030. props: ['a'],
  1031. updated,
  1032. watch: {
  1033. a() {
  1034. b.value
  1035. }
  1036. },
  1037. render() {
  1038. return h('div', this.a)
  1039. }
  1040. })
  1041. const Parent = defineComponent({
  1042. render() {
  1043. return h(Child, { a: a.value })
  1044. }
  1045. })
  1046. const root = nodeOps.createElement('div')
  1047. createApp(Parent).mount(root)
  1048. a.value++
  1049. await nextTick()
  1050. expect(updated).toHaveBeenCalledTimes(1)
  1051. b.value++
  1052. await nextTick()
  1053. // should not track b as dependency of Child
  1054. expect(updated).toHaveBeenCalledTimes(1)
  1055. })
  1056. test('watching keypath', async () => {
  1057. const spy = vi.fn()
  1058. const Comp = defineComponent({
  1059. render() {},
  1060. data() {
  1061. return {
  1062. a: {
  1063. b: 1
  1064. }
  1065. }
  1066. },
  1067. watch: {
  1068. 'a.b': spy
  1069. },
  1070. created(this: any) {
  1071. this.$watch('a.b', spy)
  1072. },
  1073. mounted(this: any) {
  1074. this.a.b++
  1075. }
  1076. })
  1077. const root = nodeOps.createElement('div')
  1078. createApp(Comp).mount(root)
  1079. await nextTick()
  1080. expect(spy).toHaveBeenCalledTimes(2)
  1081. })
  1082. it('watching sources: ref<any[]>', async () => {
  1083. const foo = ref([1])
  1084. const spy = vi.fn()
  1085. watch(foo, () => {
  1086. spy()
  1087. })
  1088. foo.value = foo.value.slice()
  1089. await nextTick()
  1090. expect(spy).toBeCalledTimes(1)
  1091. })
  1092. it('watching multiple sources: computed', async () => {
  1093. let count = 0
  1094. const value = ref('1')
  1095. const plus = computed(() => !!value.value)
  1096. watch([plus], () => {
  1097. count++
  1098. })
  1099. value.value = '2'
  1100. await nextTick()
  1101. expect(plus.value).toBe(true)
  1102. expect(count).toBe(0)
  1103. })
  1104. // #4158
  1105. test('watch should not register in owner component if created inside detached scope', () => {
  1106. let instance: ComponentInternalInstance
  1107. const Comp = {
  1108. setup() {
  1109. instance = getCurrentInstance()!
  1110. effectScope(true).run(() => {
  1111. watch(
  1112. () => 1,
  1113. () => {}
  1114. )
  1115. })
  1116. return () => ''
  1117. }
  1118. }
  1119. const root = nodeOps.createElement('div')
  1120. createApp(Comp).mount(root)
  1121. // should not record watcher in detached scope and only the instance's
  1122. // own update effect
  1123. expect(instance!.scope.effects.length).toBe(1)
  1124. })
  1125. test('watchEffect should keep running if created in a detached scope', async () => {
  1126. const trigger = ref(0)
  1127. let countWE = 0
  1128. let countW = 0
  1129. const Comp = {
  1130. setup() {
  1131. effectScope(true).run(() => {
  1132. watchEffect(() => {
  1133. trigger.value
  1134. countWE++
  1135. })
  1136. watch(trigger, () => countW++)
  1137. })
  1138. return () => ''
  1139. }
  1140. }
  1141. const root = nodeOps.createElement('div')
  1142. render(h(Comp), root)
  1143. // only watchEffect as ran so far
  1144. expect(countWE).toBe(1)
  1145. expect(countW).toBe(0)
  1146. trigger.value++
  1147. await nextTick()
  1148. // both watchers run while component is mounted
  1149. expect(countWE).toBe(2)
  1150. expect(countW).toBe(1)
  1151. render(null, root) // unmount
  1152. await nextTick()
  1153. trigger.value++
  1154. await nextTick()
  1155. // both watchers run again event though component has been unmounted
  1156. expect(countWE).toBe(3)
  1157. expect(countW).toBe(2)
  1158. })
  1159. // #5151
  1160. test('OnCleanup also needs to be cleaned,', async () => {
  1161. const spy1 = vi.fn()
  1162. const spy2 = vi.fn()
  1163. const num = ref(0)
  1164. watch(num, (value, oldValue, onCleanup) => {
  1165. if (value > 1) {
  1166. return
  1167. }
  1168. spy1()
  1169. onCleanup(() => {
  1170. // OnCleanup also needs to be cleaned
  1171. spy2()
  1172. })
  1173. })
  1174. num.value++
  1175. await nextTick()
  1176. expect(spy1).toHaveBeenCalledTimes(1)
  1177. expect(spy2).toHaveBeenCalledTimes(0)
  1178. num.value++
  1179. await nextTick()
  1180. expect(spy1).toHaveBeenCalledTimes(1)
  1181. expect(spy2).toHaveBeenCalledTimes(1)
  1182. num.value++
  1183. await nextTick()
  1184. // would not be calld when value>1
  1185. expect(spy1).toHaveBeenCalledTimes(1)
  1186. expect(spy2).toHaveBeenCalledTimes(1)
  1187. })
  1188. })