computed.spec.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. import { h, nextTick, nodeOps, render, serializeInner } from '@vue/runtime-test'
  2. import {
  3. type DebuggerEvent,
  4. ITERATE_KEY,
  5. TrackOpTypes,
  6. TriggerOpTypes,
  7. type WritableComputedRef,
  8. computed,
  9. effect,
  10. isReadonly,
  11. reactive,
  12. ref,
  13. shallowRef,
  14. toRaw,
  15. } from '../src'
  16. import { DirtyLevels } from '../src/constants'
  17. import { COMPUTED_SIDE_EFFECT_WARN } from '../src/computed'
  18. describe('reactivity/computed', () => {
  19. it('should return updated value', () => {
  20. const value = reactive<{ foo?: number }>({})
  21. const cValue = computed(() => value.foo)
  22. expect(cValue.value).toBe(undefined)
  23. value.foo = 1
  24. expect(cValue.value).toBe(1)
  25. })
  26. it('should compute lazily', () => {
  27. const value = reactive<{ foo?: number }>({})
  28. const getter = vi.fn(() => value.foo)
  29. const cValue = computed(getter)
  30. // lazy
  31. expect(getter).not.toHaveBeenCalled()
  32. expect(cValue.value).toBe(undefined)
  33. expect(getter).toHaveBeenCalledTimes(1)
  34. // should not compute again
  35. cValue.value
  36. expect(getter).toHaveBeenCalledTimes(1)
  37. // should not compute until needed
  38. value.foo = 1
  39. expect(getter).toHaveBeenCalledTimes(1)
  40. // now it should compute
  41. expect(cValue.value).toBe(1)
  42. expect(getter).toHaveBeenCalledTimes(2)
  43. // should not compute again
  44. cValue.value
  45. expect(getter).toHaveBeenCalledTimes(2)
  46. })
  47. it('should trigger effect', () => {
  48. const value = reactive<{ foo?: number }>({})
  49. const cValue = computed(() => value.foo)
  50. let dummy
  51. effect(() => {
  52. dummy = cValue.value
  53. })
  54. expect(dummy).toBe(undefined)
  55. value.foo = 1
  56. expect(dummy).toBe(1)
  57. })
  58. it('should work when chained', () => {
  59. const value = reactive({ foo: 0 })
  60. const c1 = computed(() => value.foo)
  61. const c2 = computed(() => c1.value + 1)
  62. expect(c2.value).toBe(1)
  63. expect(c1.value).toBe(0)
  64. value.foo++
  65. expect(c2.value).toBe(2)
  66. expect(c1.value).toBe(1)
  67. })
  68. it('should trigger effect when chained', () => {
  69. const value = reactive({ foo: 0 })
  70. const getter1 = vi.fn(() => value.foo)
  71. const getter2 = vi.fn(() => {
  72. return c1.value + 1
  73. })
  74. const c1 = computed(getter1)
  75. const c2 = computed(getter2)
  76. let dummy
  77. effect(() => {
  78. dummy = c2.value
  79. })
  80. expect(dummy).toBe(1)
  81. expect(getter1).toHaveBeenCalledTimes(1)
  82. expect(getter2).toHaveBeenCalledTimes(1)
  83. value.foo++
  84. expect(dummy).toBe(2)
  85. // should not result in duplicate calls
  86. expect(getter1).toHaveBeenCalledTimes(2)
  87. expect(getter2).toHaveBeenCalledTimes(2)
  88. })
  89. it('should trigger effect when chained (mixed invocations)', () => {
  90. const value = reactive({ foo: 0 })
  91. const getter1 = vi.fn(() => value.foo)
  92. const getter2 = vi.fn(() => {
  93. return c1.value + 1
  94. })
  95. const c1 = computed(getter1)
  96. const c2 = computed(getter2)
  97. let dummy
  98. effect(() => {
  99. dummy = c1.value + c2.value
  100. })
  101. expect(dummy).toBe(1)
  102. expect(getter1).toHaveBeenCalledTimes(1)
  103. expect(getter2).toHaveBeenCalledTimes(1)
  104. value.foo++
  105. expect(dummy).toBe(3)
  106. // should not result in duplicate calls
  107. expect(getter1).toHaveBeenCalledTimes(2)
  108. expect(getter2).toHaveBeenCalledTimes(2)
  109. })
  110. it('should no longer update when stopped', () => {
  111. const value = reactive<{ foo?: number }>({})
  112. const cValue = computed(() => value.foo)
  113. let dummy
  114. effect(() => {
  115. dummy = cValue.value
  116. })
  117. expect(dummy).toBe(undefined)
  118. value.foo = 1
  119. expect(dummy).toBe(1)
  120. cValue.effect.stop()
  121. value.foo = 2
  122. expect(dummy).toBe(1)
  123. })
  124. it('should support setter', () => {
  125. const n = ref(1)
  126. const plusOne = computed({
  127. get: () => n.value + 1,
  128. set: val => {
  129. n.value = val - 1
  130. },
  131. })
  132. expect(plusOne.value).toBe(2)
  133. n.value++
  134. expect(plusOne.value).toBe(3)
  135. plusOne.value = 0
  136. expect(n.value).toBe(-1)
  137. })
  138. it('should trigger effect w/ setter', () => {
  139. const n = ref(1)
  140. const plusOne = computed({
  141. get: () => n.value + 1,
  142. set: val => {
  143. n.value = val - 1
  144. },
  145. })
  146. let dummy
  147. effect(() => {
  148. dummy = n.value
  149. })
  150. expect(dummy).toBe(1)
  151. plusOne.value = 0
  152. expect(dummy).toBe(-1)
  153. })
  154. // #5720
  155. it('should invalidate before non-computed effects', () => {
  156. let plusOneValues: number[] = []
  157. const n = ref(0)
  158. const plusOne = computed(() => n.value + 1)
  159. effect(() => {
  160. n.value
  161. plusOneValues.push(plusOne.value)
  162. })
  163. // access plusOne, causing it to be non-dirty
  164. plusOne.value
  165. // mutate n
  166. n.value++
  167. // on the 2nd run, plusOne.value should have already updated.
  168. expect(plusOneValues).toMatchObject([1, 2])
  169. })
  170. it('should warn if trying to set a readonly computed', () => {
  171. const n = ref(1)
  172. const plusOne = computed(() => n.value + 1)
  173. ;(plusOne as WritableComputedRef<number>).value++ // Type cast to prevent TS from preventing the error
  174. expect(
  175. 'Write operation failed: computed value is readonly',
  176. ).toHaveBeenWarnedLast()
  177. })
  178. it('should be readonly', () => {
  179. let a = { a: 1 }
  180. const x = computed(() => a)
  181. expect(isReadonly(x)).toBe(true)
  182. expect(isReadonly(x.value)).toBe(false)
  183. expect(isReadonly(x.value.a)).toBe(false)
  184. const z = computed<typeof a>({
  185. get() {
  186. return a
  187. },
  188. set(v) {
  189. a = v
  190. },
  191. })
  192. expect(isReadonly(z)).toBe(false)
  193. expect(isReadonly(z.value.a)).toBe(false)
  194. })
  195. it('should expose value when stopped', () => {
  196. const x = computed(() => 1)
  197. x.effect.stop()
  198. expect(x.value).toBe(1)
  199. })
  200. it('debug: onTrack', () => {
  201. let events: DebuggerEvent[] = []
  202. const onTrack = vi.fn((e: DebuggerEvent) => {
  203. events.push(e)
  204. })
  205. const obj = reactive({ foo: 1, bar: 2 })
  206. const c = computed(() => (obj.foo, 'bar' in obj, Object.keys(obj)), {
  207. onTrack,
  208. })
  209. expect(c.value).toEqual(['foo', 'bar'])
  210. expect(onTrack).toHaveBeenCalledTimes(3)
  211. expect(events).toEqual([
  212. {
  213. effect: c.effect,
  214. target: toRaw(obj),
  215. type: TrackOpTypes.GET,
  216. key: 'foo',
  217. },
  218. {
  219. effect: c.effect,
  220. target: toRaw(obj),
  221. type: TrackOpTypes.HAS,
  222. key: 'bar',
  223. },
  224. {
  225. effect: c.effect,
  226. target: toRaw(obj),
  227. type: TrackOpTypes.ITERATE,
  228. key: ITERATE_KEY,
  229. },
  230. ])
  231. })
  232. it('debug: onTrigger (reactive)', () => {
  233. let events: DebuggerEvent[] = []
  234. const onTrigger = vi.fn((e: DebuggerEvent) => {
  235. events.push(e)
  236. })
  237. const obj = reactive<{ foo?: number }>({ foo: 1 })
  238. const c = computed(() => obj.foo, { onTrigger })
  239. // computed won't trigger compute until accessed
  240. c.value
  241. obj.foo!++
  242. expect(c.value).toBe(2)
  243. expect(onTrigger).toHaveBeenCalledTimes(1)
  244. expect(events[0]).toEqual({
  245. effect: c.effect,
  246. target: toRaw(obj),
  247. type: TriggerOpTypes.SET,
  248. key: 'foo',
  249. oldValue: 1,
  250. newValue: 2,
  251. })
  252. delete obj.foo
  253. expect(c.value).toBeUndefined()
  254. expect(onTrigger).toHaveBeenCalledTimes(2)
  255. expect(events[1]).toEqual({
  256. effect: c.effect,
  257. target: toRaw(obj),
  258. type: TriggerOpTypes.DELETE,
  259. key: 'foo',
  260. oldValue: 2,
  261. })
  262. })
  263. // https://github.com/vuejs/core/pull/5912#issuecomment-1497596875
  264. it('should query deps dirty sequentially', () => {
  265. const cSpy = vi.fn()
  266. const a = ref<null | { v: number }>({
  267. v: 1,
  268. })
  269. const b = computed(() => {
  270. return a.value
  271. })
  272. const c = computed(() => {
  273. cSpy()
  274. return b.value?.v
  275. })
  276. const d = computed(() => {
  277. if (b.value) {
  278. return c.value
  279. }
  280. return 0
  281. })
  282. d.value
  283. a.value!.v = 2
  284. a.value = null
  285. d.value
  286. expect(cSpy).toHaveBeenCalledTimes(1)
  287. })
  288. // https://github.com/vuejs/core/pull/5912#issuecomment-1738257692
  289. it('chained computed dirty reallocation after querying dirty', () => {
  290. let _msg: string | undefined
  291. const items = ref<number[]>()
  292. const isLoaded = computed(() => {
  293. return !!items.value
  294. })
  295. const msg = computed(() => {
  296. if (isLoaded.value) {
  297. return 'The items are loaded'
  298. } else {
  299. return 'The items are not loaded'
  300. }
  301. })
  302. effect(() => {
  303. _msg = msg.value
  304. })
  305. items.value = [1, 2, 3]
  306. items.value = [1, 2, 3]
  307. items.value = undefined
  308. expect(_msg).toBe('The items are not loaded')
  309. })
  310. it('chained computed dirty reallocation after trigger computed getter', () => {
  311. let _msg: string | undefined
  312. const items = ref<number[]>()
  313. const isLoaded = computed(() => {
  314. return !!items.value
  315. })
  316. const msg = computed(() => {
  317. if (isLoaded.value) {
  318. return 'The items are loaded'
  319. } else {
  320. return 'The items are not loaded'
  321. }
  322. })
  323. _msg = msg.value
  324. items.value = [1, 2, 3]
  325. isLoaded.value // <- trigger computed getter
  326. _msg = msg.value
  327. items.value = undefined
  328. _msg = msg.value
  329. expect(_msg).toBe('The items are not loaded')
  330. })
  331. // https://github.com/vuejs/core/pull/5912#issuecomment-1739159832
  332. it('deps order should be consistent with the last time get value', () => {
  333. const cSpy = vi.fn()
  334. const a = ref(0)
  335. const b = computed(() => {
  336. return a.value % 3 !== 0
  337. })
  338. const c = computed(() => {
  339. cSpy()
  340. if (a.value % 3 === 2) {
  341. return 'expensive'
  342. }
  343. return 'cheap'
  344. })
  345. const d = computed(() => {
  346. return a.value % 3 === 2
  347. })
  348. const e = computed(() => {
  349. if (b.value) {
  350. if (d.value) {
  351. return 'Avoiding expensive calculation'
  352. }
  353. }
  354. return c.value
  355. })
  356. e.value
  357. a.value++
  358. e.value
  359. expect(e.effect.deps.length).toBe(3)
  360. expect(e.effect.deps.indexOf((b as any).dep)).toBe(0)
  361. expect(e.effect.deps.indexOf((d as any).dep)).toBe(1)
  362. expect(e.effect.deps.indexOf((c as any).dep)).toBe(2)
  363. expect(cSpy).toHaveBeenCalledTimes(2)
  364. a.value++
  365. e.value
  366. expect(cSpy).toHaveBeenCalledTimes(2)
  367. })
  368. it('should trigger by the second computed that maybe dirty', () => {
  369. const cSpy = vi.fn()
  370. const src1 = ref(0)
  371. const src2 = ref(0)
  372. const c1 = computed(() => src1.value)
  373. const c2 = computed(() => (src1.value % 2) + src2.value)
  374. const c3 = computed(() => {
  375. cSpy()
  376. c1.value
  377. c2.value
  378. })
  379. c3.value
  380. src1.value = 2
  381. c3.value
  382. expect(cSpy).toHaveBeenCalledTimes(2)
  383. src2.value = 1
  384. c3.value
  385. expect(cSpy).toHaveBeenCalledTimes(3)
  386. })
  387. it('should trigger the second effect', () => {
  388. const fnSpy = vi.fn()
  389. const v = ref(1)
  390. const c = computed(() => v.value)
  391. effect(() => {
  392. c.value
  393. })
  394. effect(() => {
  395. c.value
  396. fnSpy()
  397. })
  398. expect(fnSpy).toBeCalledTimes(1)
  399. v.value = 2
  400. expect(fnSpy).toBeCalledTimes(2)
  401. })
  402. it('should mark dirty as MaybeDirty_ComputedSideEffect_Origin', () => {
  403. const v = ref(1)
  404. const c = computed(() => {
  405. v.value += 1
  406. return v.value
  407. })
  408. c.value
  409. expect(c.effect._dirtyLevel).toBe(
  410. DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
  411. )
  412. expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
  413. })
  414. it('should not infinite re-run effect when effect access original side effect computed', async () => {
  415. const spy = vi.fn()
  416. const v = ref(0)
  417. const c = computed(() => {
  418. v.value += 1
  419. return v.value
  420. })
  421. const Comp = {
  422. setup: () => {
  423. return () => {
  424. spy()
  425. return v.value + c.value
  426. }
  427. },
  428. }
  429. const root = nodeOps.createElement('div')
  430. render(h(Comp), root)
  431. expect(spy).toBeCalledTimes(1)
  432. await nextTick()
  433. expect(c.effect._dirtyLevel).toBe(
  434. DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
  435. )
  436. expect(serializeInner(root)).toBe('2')
  437. expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
  438. })
  439. it('should not infinite re-run effect when effect access chained side effect computed', async () => {
  440. const spy = vi.fn()
  441. const v = ref(0)
  442. const c1 = computed(() => {
  443. v.value += 1
  444. return v.value
  445. })
  446. const c2 = computed(() => v.value + c1.value)
  447. const Comp = {
  448. setup: () => {
  449. return () => {
  450. spy()
  451. return v.value + c1.value + c2.value
  452. }
  453. },
  454. }
  455. const root = nodeOps.createElement('div')
  456. render(h(Comp), root)
  457. expect(spy).toBeCalledTimes(1)
  458. await nextTick()
  459. expect(c1.effect._dirtyLevel).toBe(
  460. DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
  461. )
  462. expect(c2.effect._dirtyLevel).toBe(
  463. DirtyLevels.MaybeDirty_ComputedSideEffect,
  464. )
  465. expect(serializeInner(root)).toBe('4')
  466. expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
  467. })
  468. it('should chained recurse effects clear dirty after trigger', () => {
  469. const v = ref(1)
  470. const c1 = computed(() => v.value)
  471. const c2 = computed(() => c1.value)
  472. c1.effect.allowRecurse = true
  473. c2.effect.allowRecurse = true
  474. c2.value
  475. expect(c1.effect._dirtyLevel).toBe(DirtyLevels.NotDirty)
  476. expect(c2.effect._dirtyLevel).toBe(DirtyLevels.NotDirty)
  477. })
  478. it('should chained computeds dirtyLevel update with first computed effect', () => {
  479. const v = ref(0)
  480. const c1 = computed(() => {
  481. if (v.value === 0) {
  482. v.value = 1
  483. }
  484. return v.value
  485. })
  486. const c2 = computed(() => c1.value)
  487. const c3 = computed(() => c2.value)
  488. c3.value
  489. expect(c1.effect._dirtyLevel).toBe(
  490. DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
  491. )
  492. expect(c2.effect._dirtyLevel).toBe(
  493. DirtyLevels.MaybeDirty_ComputedSideEffect,
  494. )
  495. expect(c3.effect._dirtyLevel).toBe(
  496. DirtyLevels.MaybeDirty_ComputedSideEffect,
  497. )
  498. expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
  499. })
  500. it('should work when chained(ref+computed)', () => {
  501. const v = ref(0)
  502. const c1 = computed(() => {
  503. if (v.value === 0) {
  504. v.value = 1
  505. }
  506. return 'foo'
  507. })
  508. const c2 = computed(() => v.value + c1.value)
  509. expect(c2.value).toBe('0foo')
  510. expect(c2.effect._dirtyLevel).toBe(
  511. DirtyLevels.MaybeDirty_ComputedSideEffect,
  512. )
  513. expect(c2.value).toBe('1foo')
  514. expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
  515. })
  516. it('should trigger effect even computed already dirty', () => {
  517. const fnSpy = vi.fn()
  518. const v = ref(0)
  519. const c1 = computed(() => {
  520. if (v.value === 0) {
  521. v.value = 1
  522. }
  523. return 'foo'
  524. })
  525. const c2 = computed(() => v.value + c1.value)
  526. effect(() => {
  527. fnSpy()
  528. c2.value
  529. })
  530. expect(fnSpy).toBeCalledTimes(1)
  531. expect(c1.effect._dirtyLevel).toBe(
  532. DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
  533. )
  534. expect(c2.effect._dirtyLevel).toBe(
  535. DirtyLevels.MaybeDirty_ComputedSideEffect,
  536. )
  537. v.value = 2
  538. expect(fnSpy).toBeCalledTimes(2)
  539. expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
  540. })
  541. // #10185
  542. it('should not override queried MaybeDirty result', () => {
  543. class Item {
  544. v = ref(0)
  545. }
  546. const v1 = shallowRef()
  547. const v2 = ref(false)
  548. const c1 = computed(() => {
  549. let c = v1.value
  550. if (!v1.value) {
  551. c = new Item()
  552. v1.value = c
  553. }
  554. return c.v.value
  555. })
  556. const c2 = computed(() => {
  557. if (!v2.value) return 'no'
  558. return c1.value ? 'yes' : 'no'
  559. })
  560. const c3 = computed(() => c2.value)
  561. c3.value
  562. v2.value = true
  563. expect(c2.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
  564. expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
  565. c3.value
  566. expect(c1.effect._dirtyLevel).toBe(
  567. DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
  568. )
  569. expect(c2.effect._dirtyLevel).toBe(
  570. DirtyLevels.MaybeDirty_ComputedSideEffect,
  571. )
  572. expect(c3.effect._dirtyLevel).toBe(
  573. DirtyLevels.MaybeDirty_ComputedSideEffect,
  574. )
  575. v1.value.v.value = 999
  576. expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
  577. expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
  578. expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
  579. expect(c3.value).toBe('yes')
  580. expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
  581. })
  582. it('should be not dirty after deps mutate (mutate deps in computed)', async () => {
  583. const state = reactive<any>({})
  584. const consumer = computed(() => {
  585. if (!('a' in state)) state.a = 1
  586. return state.a
  587. })
  588. const Comp = {
  589. setup: () => {
  590. nextTick().then(() => {
  591. state.a = 2
  592. })
  593. return () => consumer.value
  594. },
  595. }
  596. const root = nodeOps.createElement('div')
  597. render(h(Comp), root)
  598. await nextTick()
  599. await nextTick()
  600. expect(serializeInner(root)).toBe(`2`)
  601. expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
  602. })
  603. it('should not trigger effect scheduler by recurse computed effect', async () => {
  604. const v = ref('Hello')
  605. const c = computed(() => {
  606. v.value += ' World'
  607. return v.value
  608. })
  609. const Comp = {
  610. setup: () => {
  611. return () => c.value
  612. },
  613. }
  614. const root = nodeOps.createElement('div')
  615. render(h(Comp), root)
  616. await nextTick()
  617. expect(c.effect._dirtyLevel).toBe(
  618. DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
  619. )
  620. expect(serializeInner(root)).toBe('Hello World')
  621. v.value += ' World'
  622. expect(c.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
  623. await nextTick()
  624. expect(c.effect._dirtyLevel).toBe(
  625. DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
  626. )
  627. expect(serializeInner(root)).toBe('Hello World World World')
  628. expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
  629. })
  630. it('should chained computeds keep reactivity when computed effect happens', async () => {
  631. const v = ref('Hello')
  632. const c = computed(() => {
  633. v.value += ' World'
  634. return v.value
  635. })
  636. const d = computed(() => c.value)
  637. const e = computed(() => d.value)
  638. const Comp = {
  639. setup: () => {
  640. return () => d.value + ' | ' + e.value
  641. },
  642. }
  643. const root = nodeOps.createElement('div')
  644. render(h(Comp), root)
  645. await nextTick()
  646. expect(serializeInner(root)).toBe('Hello World | Hello World')
  647. v.value += ' World'
  648. await nextTick()
  649. expect(serializeInner(root)).toBe(
  650. 'Hello World World World | Hello World World World',
  651. )
  652. expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
  653. })
  654. it('should keep dirty level when side effect computed value changed', () => {
  655. const v = ref(0)
  656. const c = computed(() => {
  657. v.value += 1
  658. return v.value
  659. })
  660. const d = computed(() => {
  661. return { d: c.value }
  662. })
  663. const Comp = {
  664. setup: () => {
  665. return () => {
  666. return [d.value.d, d.value.d]
  667. }
  668. },
  669. }
  670. const root = nodeOps.createElement('div')
  671. render(h(Comp), root)
  672. expect(d.value.d).toBe(1)
  673. expect(serializeInner(root)).toBe('11')
  674. expect(c.effect._dirtyLevel).toBe(
  675. DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
  676. )
  677. expect(d.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty_ComputedSideEffect)
  678. expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
  679. })
  680. it('debug: onTrigger (ref)', () => {
  681. let events: DebuggerEvent[] = []
  682. const onTrigger = vi.fn((e: DebuggerEvent) => {
  683. events.push(e)
  684. })
  685. const obj = ref(1)
  686. const c = computed(() => obj.value, { onTrigger })
  687. // computed won't trigger compute until accessed
  688. c.value
  689. obj.value++
  690. expect(c.value).toBe(2)
  691. expect(onTrigger).toHaveBeenCalledTimes(1)
  692. expect(events[0]).toEqual({
  693. effect: c.effect,
  694. target: toRaw(obj),
  695. type: TriggerOpTypes.SET,
  696. key: 'value',
  697. oldValue: 1,
  698. newValue: 2,
  699. })
  700. })
  701. })