apiWatch.spec.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  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. } from '@vue/runtime-test'
  21. import {
  22. ITERATE_KEY,
  23. DebuggerEvent,
  24. TrackOpTypes,
  25. TriggerOpTypes,
  26. triggerRef,
  27. shallowRef,
  28. Ref
  29. } from '@vue/reactivity'
  30. // reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
  31. describe('api: watch', () => {
  32. it('effect', async () => {
  33. const state = reactive({ count: 0 })
  34. let dummy
  35. watchEffect(() => {
  36. dummy = state.count
  37. })
  38. expect(dummy).toBe(0)
  39. state.count++
  40. await nextTick()
  41. expect(dummy).toBe(1)
  42. })
  43. it('watching single source: getter', async () => {
  44. const state = reactive({ count: 0 })
  45. let dummy
  46. watch(
  47. () => state.count,
  48. (count, prevCount) => {
  49. dummy = [count, prevCount]
  50. // assert types
  51. count + 1
  52. if (prevCount) {
  53. prevCount + 1
  54. }
  55. }
  56. )
  57. state.count++
  58. await nextTick()
  59. expect(dummy).toMatchObject([1, 0])
  60. })
  61. it('watching single source: ref', async () => {
  62. const count = ref(0)
  63. let dummy
  64. watch(count, (count, prevCount) => {
  65. dummy = [count, prevCount]
  66. // assert types
  67. count + 1
  68. if (prevCount) {
  69. prevCount + 1
  70. }
  71. })
  72. count.value++
  73. await nextTick()
  74. expect(dummy).toMatchObject([1, 0])
  75. })
  76. it('watching single source: array', async () => {
  77. const array = reactive([] as number[])
  78. const spy = jest.fn()
  79. watch(array, spy)
  80. array.push(1)
  81. await nextTick()
  82. expect(spy).toBeCalledTimes(1)
  83. expect(spy).toBeCalledWith([1], expect.anything(), expect.anything())
  84. })
  85. it('should not fire if watched getter result did not change', async () => {
  86. const spy = jest.fn()
  87. const n = ref(0)
  88. watch(() => n.value % 2, spy)
  89. n.value++
  90. await nextTick()
  91. expect(spy).toBeCalledTimes(1)
  92. n.value += 2
  93. await nextTick()
  94. // should not be called again because getter result did not change
  95. expect(spy).toBeCalledTimes(1)
  96. })
  97. it('watching single source: computed ref', async () => {
  98. const count = ref(0)
  99. const plus = computed(() => count.value + 1)
  100. let dummy
  101. watch(plus, (count, prevCount) => {
  102. dummy = [count, prevCount]
  103. // assert types
  104. count + 1
  105. if (prevCount) {
  106. prevCount + 1
  107. }
  108. })
  109. count.value++
  110. await nextTick()
  111. expect(dummy).toMatchObject([2, 1])
  112. })
  113. it('watching primitive with deep: true', async () => {
  114. const count = ref(0)
  115. let dummy
  116. watch(
  117. count,
  118. (c, prevCount) => {
  119. dummy = [c, prevCount]
  120. },
  121. {
  122. deep: true
  123. }
  124. )
  125. count.value++
  126. await nextTick()
  127. expect(dummy).toMatchObject([1, 0])
  128. })
  129. it('directly watching reactive object (with automatic deep: true)', async () => {
  130. const src = reactive({
  131. count: 0
  132. })
  133. let dummy
  134. watch(src, ({ count }) => {
  135. dummy = count
  136. })
  137. src.count++
  138. await nextTick()
  139. expect(dummy).toBe(1)
  140. })
  141. it('watching multiple sources', async () => {
  142. const state = reactive({ count: 1 })
  143. const count = ref(1)
  144. const plus = computed(() => count.value + 1)
  145. let dummy
  146. watch([() => state.count, count, plus], (vals, oldVals) => {
  147. dummy = [vals, oldVals]
  148. // assert types
  149. vals.concat(1)
  150. oldVals.concat(1)
  151. })
  152. state.count++
  153. count.value++
  154. await nextTick()
  155. expect(dummy).toMatchObject([[2, 2, 3], [1, 1, 2]])
  156. })
  157. it('watching multiple sources: readonly array', async () => {
  158. const state = reactive({ count: 1 })
  159. const status = ref(false)
  160. let dummy
  161. watch([() => state.count, status] as const, (vals, oldVals) => {
  162. dummy = [vals, oldVals]
  163. const [count] = vals
  164. const [, oldStatus] = oldVals
  165. // assert types
  166. count + 1
  167. oldStatus === true
  168. })
  169. state.count++
  170. status.value = true
  171. await nextTick()
  172. expect(dummy).toMatchObject([[2, true], [1, false]])
  173. })
  174. it('watching multiple sources: reactive object (with automatic deep: true)', async () => {
  175. const src = reactive({ count: 0 })
  176. let dummy
  177. watch([src], ([state]) => {
  178. dummy = state
  179. // assert types
  180. state.count === 1
  181. })
  182. src.count++
  183. await nextTick()
  184. expect(dummy).toMatchObject({ count: 1 })
  185. })
  186. it('warn invalid watch source', () => {
  187. // @ts-ignore
  188. watch(1, () => {})
  189. expect(`Invalid watch source`).toHaveBeenWarned()
  190. })
  191. it('warn invalid watch source: multiple sources', () => {
  192. watch([1], () => {})
  193. expect(`Invalid watch source`).toHaveBeenWarned()
  194. })
  195. it('stopping the watcher (effect)', async () => {
  196. const state = reactive({ count: 0 })
  197. let dummy
  198. const stop = watchEffect(() => {
  199. dummy = state.count
  200. })
  201. expect(dummy).toBe(0)
  202. stop()
  203. state.count++
  204. await nextTick()
  205. // should not update
  206. expect(dummy).toBe(0)
  207. })
  208. it('stopping the watcher (with source)', async () => {
  209. const state = reactive({ count: 0 })
  210. let dummy
  211. const stop = watch(
  212. () => state.count,
  213. count => {
  214. dummy = count
  215. }
  216. )
  217. state.count++
  218. await nextTick()
  219. expect(dummy).toBe(1)
  220. stop()
  221. state.count++
  222. await nextTick()
  223. // should not update
  224. expect(dummy).toBe(1)
  225. })
  226. it('cleanup registration (effect)', async () => {
  227. const state = reactive({ count: 0 })
  228. const cleanup = jest.fn()
  229. let dummy
  230. const stop = watchEffect(onCleanup => {
  231. onCleanup(cleanup)
  232. dummy = state.count
  233. })
  234. expect(dummy).toBe(0)
  235. state.count++
  236. await nextTick()
  237. expect(cleanup).toHaveBeenCalledTimes(1)
  238. expect(dummy).toBe(1)
  239. stop()
  240. expect(cleanup).toHaveBeenCalledTimes(2)
  241. })
  242. it('cleanup registration (with source)', async () => {
  243. const count = ref(0)
  244. const cleanup = jest.fn()
  245. let dummy
  246. const stop = watch(count, (count, prevCount, onCleanup) => {
  247. onCleanup(cleanup)
  248. dummy = count
  249. })
  250. count.value++
  251. await nextTick()
  252. expect(cleanup).toHaveBeenCalledTimes(0)
  253. expect(dummy).toBe(1)
  254. count.value++
  255. await nextTick()
  256. expect(cleanup).toHaveBeenCalledTimes(1)
  257. expect(dummy).toBe(2)
  258. stop()
  259. expect(cleanup).toHaveBeenCalledTimes(2)
  260. })
  261. it('flush timing: pre (default)', async () => {
  262. const count = ref(0)
  263. const count2 = ref(0)
  264. let callCount = 0
  265. let result1
  266. let result2
  267. const assertion = jest.fn((count, count2Value) => {
  268. callCount++
  269. // on mount, the watcher callback should be called before DOM render
  270. // on update, should be called before the count is updated
  271. const expectedDOM = callCount === 1 ? `` : `${count - 1}`
  272. result1 = serializeInner(root) === expectedDOM
  273. // in a pre-flush callback, all state should have been updated
  274. const expectedState = callCount - 1
  275. result2 = count === expectedState && count2Value === expectedState
  276. })
  277. const Comp = {
  278. setup() {
  279. watchEffect(() => {
  280. assertion(count.value, count2.value)
  281. })
  282. return () => count.value
  283. }
  284. }
  285. const root = nodeOps.createElement('div')
  286. render(h(Comp), root)
  287. expect(assertion).toHaveBeenCalledTimes(1)
  288. expect(result1).toBe(true)
  289. expect(result2).toBe(true)
  290. count.value++
  291. count2.value++
  292. await nextTick()
  293. // two mutations should result in 1 callback execution
  294. expect(assertion).toHaveBeenCalledTimes(2)
  295. expect(result1).toBe(true)
  296. expect(result2).toBe(true)
  297. })
  298. it('flush timing: post', async () => {
  299. const count = ref(0)
  300. let result
  301. const assertion = jest.fn(count => {
  302. result = serializeInner(root) === `${count}`
  303. })
  304. const Comp = {
  305. setup() {
  306. watchEffect(
  307. () => {
  308. assertion(count.value)
  309. },
  310. { flush: 'post' }
  311. )
  312. return () => count.value
  313. }
  314. }
  315. const root = nodeOps.createElement('div')
  316. render(h(Comp), root)
  317. expect(assertion).toHaveBeenCalledTimes(1)
  318. expect(result).toBe(true)
  319. count.value++
  320. await nextTick()
  321. expect(assertion).toHaveBeenCalledTimes(2)
  322. expect(result).toBe(true)
  323. })
  324. it('flush timing: sync', async () => {
  325. const count = ref(0)
  326. const count2 = ref(0)
  327. let callCount = 0
  328. let result1
  329. let result2
  330. const assertion = jest.fn(count => {
  331. callCount++
  332. // on mount, the watcher callback should be called before DOM render
  333. // on update, should be called before the count is updated
  334. const expectedDOM = callCount === 1 ? `` : `${count - 1}`
  335. result1 = serializeInner(root) === expectedDOM
  336. // in a sync callback, state mutation on the next line should not have
  337. // executed yet on the 2nd call, but will be on the 3rd call.
  338. const expectedState = callCount < 3 ? 0 : 1
  339. result2 = count2.value === expectedState
  340. })
  341. const Comp = {
  342. setup() {
  343. watchEffect(
  344. () => {
  345. assertion(count.value)
  346. },
  347. {
  348. flush: 'sync'
  349. }
  350. )
  351. return () => count.value
  352. }
  353. }
  354. const root = nodeOps.createElement('div')
  355. render(h(Comp), root)
  356. expect(assertion).toHaveBeenCalledTimes(1)
  357. expect(result1).toBe(true)
  358. expect(result2).toBe(true)
  359. count.value++
  360. count2.value++
  361. await nextTick()
  362. expect(assertion).toHaveBeenCalledTimes(3)
  363. expect(result1).toBe(true)
  364. expect(result2).toBe(true)
  365. })
  366. it('should not fire on component unmount w/ flush: post', async () => {
  367. const toggle = ref(true)
  368. const cb = jest.fn()
  369. const Comp = {
  370. setup() {
  371. watch(toggle, cb, { flush: 'post' })
  372. },
  373. render() {}
  374. }
  375. const App = {
  376. render() {
  377. return toggle.value ? h(Comp) : null
  378. }
  379. }
  380. render(h(App), nodeOps.createElement('div'))
  381. expect(cb).not.toHaveBeenCalled()
  382. toggle.value = false
  383. await nextTick()
  384. expect(cb).not.toHaveBeenCalled()
  385. })
  386. it('should fire on component unmount w/ flush: pre', async () => {
  387. const toggle = ref(true)
  388. const cb = jest.fn()
  389. const Comp = {
  390. setup() {
  391. watch(toggle, cb, { flush: 'pre' })
  392. },
  393. render() {}
  394. }
  395. const App = {
  396. render() {
  397. return toggle.value ? h(Comp) : null
  398. }
  399. }
  400. render(h(App), nodeOps.createElement('div'))
  401. expect(cb).not.toHaveBeenCalled()
  402. toggle.value = false
  403. await nextTick()
  404. expect(cb).toHaveBeenCalledTimes(1)
  405. })
  406. // #1763
  407. it('flush: pre watcher watching props should fire before child update', async () => {
  408. const a = ref(0)
  409. const b = ref(0)
  410. const c = ref(0)
  411. const calls: string[] = []
  412. const Comp = {
  413. props: ['a', 'b'],
  414. setup(props: any) {
  415. watch(
  416. () => props.a + props.b,
  417. () => {
  418. calls.push('watcher 1')
  419. c.value++
  420. },
  421. { flush: 'pre' }
  422. )
  423. // #1777 chained pre-watcher
  424. watch(
  425. c,
  426. () => {
  427. calls.push('watcher 2')
  428. },
  429. { flush: 'pre' }
  430. )
  431. return () => {
  432. c.value
  433. calls.push('render')
  434. }
  435. }
  436. }
  437. const App = {
  438. render() {
  439. return h(Comp, { a: a.value, b: b.value })
  440. }
  441. }
  442. render(h(App), nodeOps.createElement('div'))
  443. expect(calls).toEqual(['render'])
  444. // both props are updated
  445. // should trigger pre-flush watcher first and only once
  446. // then trigger child render
  447. a.value++
  448. b.value++
  449. await nextTick()
  450. expect(calls).toEqual(['render', 'watcher 1', 'watcher 2', 'render'])
  451. })
  452. // #1852
  453. it('flush: post watcher should fire after template refs updated', async () => {
  454. const toggle = ref(false)
  455. let dom: TestElement | null = null
  456. const App = {
  457. setup() {
  458. const domRef = ref<TestElement | null>(null)
  459. watch(
  460. toggle,
  461. () => {
  462. dom = domRef.value
  463. },
  464. { flush: 'post' }
  465. )
  466. return () => {
  467. return toggle.value ? h('p', { ref: domRef }) : null
  468. }
  469. }
  470. }
  471. render(h(App), nodeOps.createElement('div'))
  472. expect(dom).toBe(null)
  473. toggle.value = true
  474. await nextTick()
  475. expect(dom!.tag).toBe('p')
  476. })
  477. it('deep', async () => {
  478. const state = reactive({
  479. nested: {
  480. count: ref(0)
  481. },
  482. array: [1, 2, 3],
  483. map: new Map([['a', 1], ['b', 2]]),
  484. set: new Set([1, 2, 3])
  485. })
  486. let dummy
  487. watch(
  488. () => state,
  489. state => {
  490. dummy = [
  491. state.nested.count,
  492. state.array[0],
  493. state.map.get('a'),
  494. state.set.has(1)
  495. ]
  496. },
  497. { deep: true }
  498. )
  499. state.nested.count++
  500. await nextTick()
  501. expect(dummy).toEqual([1, 1, 1, true])
  502. // nested array mutation
  503. state.array[0] = 2
  504. await nextTick()
  505. expect(dummy).toEqual([1, 2, 1, true])
  506. // nested map mutation
  507. state.map.set('a', 2)
  508. await nextTick()
  509. expect(dummy).toEqual([1, 2, 2, true])
  510. // nested set mutation
  511. state.set.delete(1)
  512. await nextTick()
  513. expect(dummy).toEqual([1, 2, 2, false])
  514. })
  515. it('watching deep ref', async () => {
  516. const count = ref(0)
  517. const double = computed(() => count.value * 2)
  518. const state = reactive([count, double])
  519. let dummy
  520. watch(
  521. () => state,
  522. state => {
  523. dummy = [state[0].value, state[1].value]
  524. },
  525. { deep: true }
  526. )
  527. count.value++
  528. await nextTick()
  529. expect(dummy).toEqual([1, 2])
  530. })
  531. it('immediate', async () => {
  532. const count = ref(0)
  533. const cb = jest.fn()
  534. watch(count, cb, { immediate: true })
  535. expect(cb).toHaveBeenCalledTimes(1)
  536. count.value++
  537. await nextTick()
  538. expect(cb).toHaveBeenCalledTimes(2)
  539. })
  540. it('immediate: triggers when initial value is null', async () => {
  541. const state = ref(null)
  542. const spy = jest.fn()
  543. watch(() => state.value, spy, { immediate: true })
  544. expect(spy).toHaveBeenCalled()
  545. })
  546. it('immediate: triggers when initial value is undefined', async () => {
  547. const state = ref()
  548. const spy = jest.fn()
  549. watch(() => state.value, spy, { immediate: true })
  550. expect(spy).toHaveBeenCalled()
  551. state.value = 3
  552. await nextTick()
  553. expect(spy).toHaveBeenCalledTimes(2)
  554. // testing if undefined can trigger the watcher
  555. state.value = undefined
  556. await nextTick()
  557. expect(spy).toHaveBeenCalledTimes(3)
  558. // it shouldn't trigger if the same value is set
  559. state.value = undefined
  560. await nextTick()
  561. expect(spy).toHaveBeenCalledTimes(3)
  562. })
  563. it('warn immediate option when using effect', async () => {
  564. const count = ref(0)
  565. let dummy
  566. watchEffect(
  567. () => {
  568. dummy = count.value
  569. },
  570. // @ts-ignore
  571. { immediate: false }
  572. )
  573. expect(dummy).toBe(0)
  574. expect(`"immediate" option is only respected`).toHaveBeenWarned()
  575. count.value++
  576. await nextTick()
  577. expect(dummy).toBe(1)
  578. })
  579. it('warn and not respect deep option when using effect', async () => {
  580. const arr = ref([1, [2]])
  581. const spy = jest.fn()
  582. watchEffect(
  583. () => {
  584. spy()
  585. return arr
  586. },
  587. // @ts-ignore
  588. { deep: true }
  589. )
  590. expect(spy).toHaveBeenCalledTimes(1)
  591. ;(arr.value[1] as Array<number>)[0] = 3
  592. await nextTick()
  593. expect(spy).toHaveBeenCalledTimes(1)
  594. expect(`"deep" option is only respected`).toHaveBeenWarned()
  595. })
  596. it('onTrack', async () => {
  597. const events: DebuggerEvent[] = []
  598. let dummy
  599. const onTrack = jest.fn((e: DebuggerEvent) => {
  600. events.push(e)
  601. })
  602. const obj = reactive({ foo: 1, bar: 2 })
  603. watchEffect(
  604. () => {
  605. dummy = [obj.foo, 'bar' in obj, Object.keys(obj)]
  606. },
  607. { onTrack }
  608. )
  609. await nextTick()
  610. expect(dummy).toEqual([1, true, ['foo', 'bar']])
  611. expect(onTrack).toHaveBeenCalledTimes(3)
  612. expect(events).toMatchObject([
  613. {
  614. target: obj,
  615. type: TrackOpTypes.GET,
  616. key: 'foo'
  617. },
  618. {
  619. target: obj,
  620. type: TrackOpTypes.HAS,
  621. key: 'bar'
  622. },
  623. {
  624. target: obj,
  625. type: TrackOpTypes.ITERATE,
  626. key: ITERATE_KEY
  627. }
  628. ])
  629. })
  630. it('onTrigger', async () => {
  631. const events: DebuggerEvent[] = []
  632. let dummy
  633. const onTrigger = jest.fn((e: DebuggerEvent) => {
  634. events.push(e)
  635. })
  636. const obj = reactive({ foo: 1 })
  637. watchEffect(
  638. () => {
  639. dummy = obj.foo
  640. },
  641. { onTrigger }
  642. )
  643. await nextTick()
  644. expect(dummy).toBe(1)
  645. obj.foo++
  646. await nextTick()
  647. expect(dummy).toBe(2)
  648. expect(onTrigger).toHaveBeenCalledTimes(1)
  649. expect(events[0]).toMatchObject({
  650. type: TriggerOpTypes.SET,
  651. key: 'foo',
  652. oldValue: 1,
  653. newValue: 2
  654. })
  655. // @ts-ignore
  656. delete obj.foo
  657. await nextTick()
  658. expect(dummy).toBeUndefined()
  659. expect(onTrigger).toHaveBeenCalledTimes(2)
  660. expect(events[1]).toMatchObject({
  661. type: TriggerOpTypes.DELETE,
  662. key: 'foo',
  663. oldValue: 2
  664. })
  665. })
  666. it('should work sync', () => {
  667. const v = ref(1)
  668. let calls = 0
  669. watch(
  670. v,
  671. () => {
  672. ++calls
  673. },
  674. {
  675. flush: 'sync'
  676. }
  677. )
  678. expect(calls).toBe(0)
  679. v.value++
  680. expect(calls).toBe(1)
  681. })
  682. test('should force trigger on triggerRef when watching a shallow ref', async () => {
  683. const v = shallowRef({ a: 1 })
  684. let sideEffect = 0
  685. watch(v, obj => {
  686. sideEffect = obj.a
  687. })
  688. v.value = v.value
  689. await nextTick()
  690. // should not trigger
  691. expect(sideEffect).toBe(0)
  692. v.value.a++
  693. await nextTick()
  694. // should not trigger
  695. expect(sideEffect).toBe(0)
  696. triggerRef(v)
  697. await nextTick()
  698. // should trigger now
  699. expect(sideEffect).toBe(2)
  700. })
  701. // #2125
  702. test('watchEffect should not recursively trigger itself', async () => {
  703. const spy = jest.fn()
  704. const price = ref(10)
  705. const history = ref<number[]>([])
  706. watchEffect(() => {
  707. history.value.push(price.value)
  708. spy()
  709. })
  710. await nextTick()
  711. expect(spy).toHaveBeenCalledTimes(1)
  712. })
  713. // #2231
  714. test('computed refs should not trigger watch if value has no change', async () => {
  715. const spy = jest.fn()
  716. const source = ref(0)
  717. const price = computed(() => source.value === 0)
  718. watch(price, spy)
  719. source.value++
  720. await nextTick()
  721. source.value++
  722. await nextTick()
  723. expect(spy).toHaveBeenCalledTimes(1)
  724. })
  725. // https://github.com/vuejs/vue-next/issues/2381
  726. test('$watch should always register its effects with itw own instance', async () => {
  727. let instance: ComponentInternalInstance | null
  728. let _show: Ref<boolean>
  729. const Child = defineComponent({
  730. render: () => h('div'),
  731. mounted() {
  732. instance = getCurrentInstance()
  733. },
  734. unmounted() {}
  735. })
  736. const Comp = defineComponent({
  737. setup() {
  738. const comp = ref<ComponentPublicInstance | undefined>()
  739. const show = ref(true)
  740. _show = show
  741. return { comp, show }
  742. },
  743. render() {
  744. return this.show
  745. ? h(Child, {
  746. ref: vm => void (this.comp = vm as ComponentPublicInstance)
  747. })
  748. : null
  749. },
  750. mounted() {
  751. // this call runs while Comp is currentInstance, but
  752. // the effect for this `$watch` should nontheless be registered with Child
  753. this.comp!.$watch(() => this.show, () => void 0)
  754. }
  755. })
  756. render(h(Comp), nodeOps.createElement('div'))
  757. expect(instance!).toBeDefined()
  758. expect(instance!.effects).toBeInstanceOf(Array)
  759. expect(instance!.effects!.length).toBe(1)
  760. _show!.value = false
  761. await nextTick()
  762. await nextTick()
  763. expect(instance!.effects![0].active).toBe(false)
  764. })
  765. test('this.$watch should pass `this.proxy` to watch source as the first argument ', () => {
  766. let instance: any
  767. const source = jest.fn()
  768. const Comp = defineComponent({
  769. render() {},
  770. created(this: any) {
  771. instance = this
  772. this.$watch(source, function() {})
  773. }
  774. })
  775. const root = nodeOps.createElement('div')
  776. createApp(Comp).mount(root)
  777. expect(instance).toBeDefined()
  778. expect(source).toHaveBeenCalledWith(instance)
  779. })
  780. })