effect.spec.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. import {
  2. reactive,
  3. effect,
  4. stop,
  5. toRaw,
  6. OperationTypes,
  7. DebuggerEvent,
  8. markNonReactive
  9. } from '../src/index'
  10. import { ITERATE_KEY } from '../src/effect'
  11. describe('reactivity/effect', () => {
  12. it('should run the passed function once (wrapped by a effect)', () => {
  13. const fnSpy = jest.fn(() => {})
  14. effect(fnSpy)
  15. expect(fnSpy).toHaveBeenCalledTimes(1)
  16. })
  17. it('should observe basic properties', () => {
  18. let dummy
  19. const counter = reactive({ num: 0 })
  20. effect(() => (dummy = counter.num))
  21. expect(dummy).toBe(0)
  22. counter.num = 7
  23. expect(dummy).toBe(7)
  24. })
  25. it('should observe multiple properties', () => {
  26. let dummy
  27. const counter = reactive({ num1: 0, num2: 0 })
  28. effect(() => (dummy = counter.num1 + counter.num1 + counter.num2))
  29. expect(dummy).toBe(0)
  30. counter.num1 = counter.num2 = 7
  31. expect(dummy).toBe(21)
  32. })
  33. it('should handle multiple effects', () => {
  34. let dummy1, dummy2
  35. const counter = reactive({ num: 0 })
  36. effect(() => (dummy1 = counter.num))
  37. effect(() => (dummy2 = counter.num))
  38. expect(dummy1).toBe(0)
  39. expect(dummy2).toBe(0)
  40. counter.num++
  41. expect(dummy1).toBe(1)
  42. expect(dummy2).toBe(1)
  43. })
  44. it('should observe nested properties', () => {
  45. let dummy
  46. const counter = reactive({ nested: { num: 0 } })
  47. effect(() => (dummy = counter.nested.num))
  48. expect(dummy).toBe(0)
  49. counter.nested.num = 8
  50. expect(dummy).toBe(8)
  51. })
  52. it('should observe delete operations', () => {
  53. let dummy
  54. const obj = reactive({ prop: 'value' })
  55. effect(() => (dummy = obj.prop))
  56. expect(dummy).toBe('value')
  57. delete obj.prop
  58. expect(dummy).toBe(undefined)
  59. })
  60. it('should observe has operations', () => {
  61. let dummy
  62. const obj = reactive<{ prop: string | number }>({ prop: 'value' })
  63. effect(() => (dummy = 'prop' in obj))
  64. expect(dummy).toBe(true)
  65. delete obj.prop
  66. expect(dummy).toBe(false)
  67. obj.prop = 12
  68. expect(dummy).toBe(true)
  69. })
  70. it('should observe properties on the prototype chain', () => {
  71. let dummy
  72. const counter = reactive({ num: 0 })
  73. const parentCounter = reactive({ num: 2 })
  74. Object.setPrototypeOf(counter, parentCounter)
  75. effect(() => (dummy = counter.num))
  76. expect(dummy).toBe(0)
  77. delete counter.num
  78. expect(dummy).toBe(2)
  79. parentCounter.num = 4
  80. expect(dummy).toBe(4)
  81. counter.num = 3
  82. expect(dummy).toBe(3)
  83. })
  84. it('should observe has operations on the prototype chain', () => {
  85. let dummy
  86. const counter = reactive({ num: 0 })
  87. const parentCounter = reactive({ num: 2 })
  88. Object.setPrototypeOf(counter, parentCounter)
  89. effect(() => (dummy = 'num' in counter))
  90. expect(dummy).toBe(true)
  91. delete counter.num
  92. expect(dummy).toBe(true)
  93. delete parentCounter.num
  94. expect(dummy).toBe(false)
  95. counter.num = 3
  96. expect(dummy).toBe(true)
  97. })
  98. it('should observe inherited property accessors', () => {
  99. let dummy, parentDummy, hiddenValue: any
  100. const obj = reactive<{ prop?: number }>({})
  101. const parent = reactive({
  102. set prop(value) {
  103. hiddenValue = value
  104. },
  105. get prop() {
  106. return hiddenValue
  107. }
  108. })
  109. Object.setPrototypeOf(obj, parent)
  110. effect(() => (dummy = obj.prop))
  111. effect(() => (parentDummy = parent.prop))
  112. expect(dummy).toBe(undefined)
  113. expect(parentDummy).toBe(undefined)
  114. obj.prop = 4
  115. expect(dummy).toBe(4)
  116. // this doesn't work, should it?
  117. // expect(parentDummy).toBe(4)
  118. parent.prop = 2
  119. expect(dummy).toBe(2)
  120. expect(parentDummy).toBe(2)
  121. })
  122. it('should observe function call chains', () => {
  123. let dummy
  124. const counter = reactive({ num: 0 })
  125. effect(() => (dummy = getNum()))
  126. function getNum() {
  127. return counter.num
  128. }
  129. expect(dummy).toBe(0)
  130. counter.num = 2
  131. expect(dummy).toBe(2)
  132. })
  133. it('should observe iteration', () => {
  134. let dummy
  135. const list = reactive(['Hello'])
  136. effect(() => (dummy = list.join(' ')))
  137. expect(dummy).toBe('Hello')
  138. list.push('World!')
  139. expect(dummy).toBe('Hello World!')
  140. list.shift()
  141. expect(dummy).toBe('World!')
  142. })
  143. it('should observe implicit array length changes', () => {
  144. let dummy
  145. const list = reactive(['Hello'])
  146. effect(() => (dummy = list.join(' ')))
  147. expect(dummy).toBe('Hello')
  148. list[1] = 'World!'
  149. expect(dummy).toBe('Hello World!')
  150. list[3] = 'Hello!'
  151. expect(dummy).toBe('Hello World! Hello!')
  152. })
  153. it('should observe sparse array mutations', () => {
  154. let dummy
  155. const list = reactive<string[]>([])
  156. list[1] = 'World!'
  157. effect(() => (dummy = list.join(' ')))
  158. expect(dummy).toBe(' World!')
  159. list[0] = 'Hello'
  160. expect(dummy).toBe('Hello World!')
  161. list.pop()
  162. expect(dummy).toBe('Hello')
  163. })
  164. it('should observe enumeration', () => {
  165. let dummy = 0
  166. const numbers = reactive<Record<string, number>>({ num1: 3 })
  167. effect(() => {
  168. dummy = 0
  169. for (let key in numbers) {
  170. dummy += numbers[key]
  171. }
  172. })
  173. expect(dummy).toBe(3)
  174. numbers.num2 = 4
  175. expect(dummy).toBe(7)
  176. delete numbers.num1
  177. expect(dummy).toBe(4)
  178. })
  179. it('should observe symbol keyed properties', () => {
  180. const key = Symbol('symbol keyed prop')
  181. let dummy, hasDummy
  182. const obj = reactive({ [key]: 'value' })
  183. effect(() => (dummy = obj[key]))
  184. effect(() => (hasDummy = key in obj))
  185. expect(dummy).toBe('value')
  186. expect(hasDummy).toBe(true)
  187. obj[key] = 'newValue'
  188. expect(dummy).toBe('newValue')
  189. delete obj[key]
  190. expect(dummy).toBe(undefined)
  191. expect(hasDummy).toBe(false)
  192. })
  193. it('should not observe well-known symbol keyed properties', () => {
  194. const key = Symbol.isConcatSpreadable
  195. let dummy
  196. const array: any = reactive([])
  197. effect(() => (dummy = array[key]))
  198. expect(array[key]).toBe(undefined)
  199. expect(dummy).toBe(undefined)
  200. array[key] = true
  201. expect(array[key]).toBe(true)
  202. expect(dummy).toBe(undefined)
  203. })
  204. it('should observe function valued properties', () => {
  205. const oldFunc = () => {}
  206. const newFunc = () => {}
  207. let dummy
  208. const obj = reactive({ func: oldFunc })
  209. effect(() => (dummy = obj.func))
  210. expect(dummy).toBe(oldFunc)
  211. obj.func = newFunc
  212. expect(dummy).toBe(newFunc)
  213. })
  214. it('should observe chained getters relying on this', () => {
  215. const obj = reactive({
  216. a: 1,
  217. get b() {
  218. return this.a
  219. }
  220. })
  221. let dummy
  222. effect(() => (dummy = obj.b))
  223. expect(dummy).toBe(1)
  224. obj.a++
  225. expect(dummy).toBe(2)
  226. })
  227. it('should observe methods relying on this', () => {
  228. const obj = reactive({
  229. a: 1,
  230. b() {
  231. return this.a
  232. }
  233. })
  234. let dummy
  235. effect(() => (dummy = obj.b()))
  236. expect(dummy).toBe(1)
  237. obj.a++
  238. expect(dummy).toBe(2)
  239. })
  240. it('should not observe set operations without a value change', () => {
  241. let hasDummy, getDummy
  242. const obj = reactive({ prop: 'value' })
  243. const getSpy = jest.fn(() => (getDummy = obj.prop))
  244. const hasSpy = jest.fn(() => (hasDummy = 'prop' in obj))
  245. effect(getSpy)
  246. effect(hasSpy)
  247. expect(getDummy).toBe('value')
  248. expect(hasDummy).toBe(true)
  249. obj.prop = 'value'
  250. expect(getSpy).toHaveBeenCalledTimes(1)
  251. expect(hasSpy).toHaveBeenCalledTimes(1)
  252. expect(getDummy).toBe('value')
  253. expect(hasDummy).toBe(true)
  254. })
  255. it('should not observe raw mutations', () => {
  256. let dummy
  257. const obj = reactive<{ prop?: string }>({})
  258. effect(() => (dummy = toRaw(obj).prop))
  259. expect(dummy).toBe(undefined)
  260. obj.prop = 'value'
  261. expect(dummy).toBe(undefined)
  262. })
  263. it('should not be triggered by raw mutations', () => {
  264. let dummy
  265. const obj = reactive<{ prop?: string }>({})
  266. effect(() => (dummy = obj.prop))
  267. expect(dummy).toBe(undefined)
  268. toRaw(obj).prop = 'value'
  269. expect(dummy).toBe(undefined)
  270. })
  271. it('should not be triggered by inherited raw setters', () => {
  272. let dummy, parentDummy, hiddenValue: any
  273. const obj = reactive<{ prop?: number }>({})
  274. const parent = reactive({
  275. set prop(value) {
  276. hiddenValue = value
  277. },
  278. get prop() {
  279. return hiddenValue
  280. }
  281. })
  282. Object.setPrototypeOf(obj, parent)
  283. effect(() => (dummy = obj.prop))
  284. effect(() => (parentDummy = parent.prop))
  285. expect(dummy).toBe(undefined)
  286. expect(parentDummy).toBe(undefined)
  287. toRaw(obj).prop = 4
  288. expect(dummy).toBe(undefined)
  289. expect(parentDummy).toBe(undefined)
  290. })
  291. it('should avoid implicit infinite recursive loops with itself', () => {
  292. const counter = reactive({ num: 0 })
  293. const counterSpy = jest.fn(() => counter.num++)
  294. effect(counterSpy)
  295. expect(counter.num).toBe(1)
  296. expect(counterSpy).toHaveBeenCalledTimes(1)
  297. counter.num = 4
  298. expect(counter.num).toBe(5)
  299. expect(counterSpy).toHaveBeenCalledTimes(2)
  300. })
  301. it('should allow explicitly recursive raw function loops', () => {
  302. const counter = reactive({ num: 0 })
  303. const numSpy = jest.fn(() => {
  304. counter.num++
  305. if (counter.num < 10) {
  306. numSpy()
  307. }
  308. })
  309. effect(numSpy)
  310. expect(counter.num).toEqual(10)
  311. expect(numSpy).toHaveBeenCalledTimes(10)
  312. })
  313. it('should avoid infinite loops with other effects', () => {
  314. const nums = reactive({ num1: 0, num2: 1 })
  315. const spy1 = jest.fn(() => (nums.num1 = nums.num2))
  316. const spy2 = jest.fn(() => (nums.num2 = nums.num1))
  317. effect(spy1)
  318. effect(spy2)
  319. expect(nums.num1).toBe(1)
  320. expect(nums.num2).toBe(1)
  321. expect(spy1).toHaveBeenCalledTimes(1)
  322. expect(spy2).toHaveBeenCalledTimes(1)
  323. nums.num2 = 4
  324. expect(nums.num1).toBe(4)
  325. expect(nums.num2).toBe(4)
  326. expect(spy1).toHaveBeenCalledTimes(2)
  327. expect(spy2).toHaveBeenCalledTimes(2)
  328. nums.num1 = 10
  329. expect(nums.num1).toBe(10)
  330. expect(nums.num2).toBe(10)
  331. expect(spy1).toHaveBeenCalledTimes(3)
  332. expect(spy2).toHaveBeenCalledTimes(3)
  333. })
  334. it('should return a new reactive version of the function', () => {
  335. function greet() {
  336. return 'Hello World'
  337. }
  338. const effect1 = effect(greet)
  339. const effect2 = effect(greet)
  340. expect(typeof effect1).toBe('function')
  341. expect(typeof effect2).toBe('function')
  342. expect(effect1).not.toBe(greet)
  343. expect(effect1).not.toBe(effect2)
  344. })
  345. it('should discover new branches while running automatically', () => {
  346. let dummy
  347. const obj = reactive({ prop: 'value', run: false })
  348. const conditionalSpy = jest.fn(() => {
  349. dummy = obj.run ? obj.prop : 'other'
  350. })
  351. effect(conditionalSpy)
  352. expect(dummy).toBe('other')
  353. expect(conditionalSpy).toHaveBeenCalledTimes(1)
  354. obj.prop = 'Hi'
  355. expect(dummy).toBe('other')
  356. expect(conditionalSpy).toHaveBeenCalledTimes(1)
  357. obj.run = true
  358. expect(dummy).toBe('Hi')
  359. expect(conditionalSpy).toHaveBeenCalledTimes(2)
  360. obj.prop = 'World'
  361. expect(dummy).toBe('World')
  362. expect(conditionalSpy).toHaveBeenCalledTimes(3)
  363. })
  364. it('should discover new branches when running manually', () => {
  365. let dummy
  366. let run = false
  367. const obj = reactive({ prop: 'value' })
  368. const runner = effect(() => {
  369. dummy = run ? obj.prop : 'other'
  370. })
  371. expect(dummy).toBe('other')
  372. runner()
  373. expect(dummy).toBe('other')
  374. run = true
  375. runner()
  376. expect(dummy).toBe('value')
  377. obj.prop = 'World'
  378. expect(dummy).toBe('World')
  379. })
  380. it('should not be triggered by mutating a property, which is used in an inactive branch', () => {
  381. let dummy
  382. const obj = reactive({ prop: 'value', run: true })
  383. const conditionalSpy = jest.fn(() => {
  384. dummy = obj.run ? obj.prop : 'other'
  385. })
  386. effect(conditionalSpy)
  387. expect(dummy).toBe('value')
  388. expect(conditionalSpy).toHaveBeenCalledTimes(1)
  389. obj.run = false
  390. expect(dummy).toBe('other')
  391. expect(conditionalSpy).toHaveBeenCalledTimes(2)
  392. obj.prop = 'value2'
  393. expect(dummy).toBe('other')
  394. expect(conditionalSpy).toHaveBeenCalledTimes(2)
  395. })
  396. it('should not double wrap if the passed function is a effect', () => {
  397. const runner = effect(() => {})
  398. const otherRunner = effect(runner)
  399. expect(runner).not.toBe(otherRunner)
  400. expect(runner.raw).toBe(otherRunner.raw)
  401. })
  402. it('should not run multiple times for a single mutation', () => {
  403. let dummy
  404. const obj = reactive<Record<string, number>>({})
  405. const fnSpy = jest.fn(() => {
  406. for (const key in obj) {
  407. dummy = obj[key]
  408. }
  409. dummy = obj.prop
  410. })
  411. effect(fnSpy)
  412. expect(fnSpy).toHaveBeenCalledTimes(1)
  413. obj.prop = 16
  414. expect(dummy).toBe(16)
  415. expect(fnSpy).toHaveBeenCalledTimes(2)
  416. })
  417. it('should allow nested effects', () => {
  418. const nums = reactive({ num1: 0, num2: 1, num3: 2 })
  419. const dummy: any = {}
  420. const childSpy = jest.fn(() => (dummy.num1 = nums.num1))
  421. const childeffect = effect(childSpy)
  422. const parentSpy = jest.fn(() => {
  423. dummy.num2 = nums.num2
  424. childeffect()
  425. dummy.num3 = nums.num3
  426. })
  427. effect(parentSpy)
  428. expect(dummy).toEqual({ num1: 0, num2: 1, num3: 2 })
  429. expect(parentSpy).toHaveBeenCalledTimes(1)
  430. expect(childSpy).toHaveBeenCalledTimes(2)
  431. // this should only call the childeffect
  432. nums.num1 = 4
  433. expect(dummy).toEqual({ num1: 4, num2: 1, num3: 2 })
  434. expect(parentSpy).toHaveBeenCalledTimes(1)
  435. expect(childSpy).toHaveBeenCalledTimes(3)
  436. // this calls the parenteffect, which calls the childeffect once
  437. nums.num2 = 10
  438. expect(dummy).toEqual({ num1: 4, num2: 10, num3: 2 })
  439. expect(parentSpy).toHaveBeenCalledTimes(2)
  440. expect(childSpy).toHaveBeenCalledTimes(4)
  441. // this calls the parenteffect, which calls the childeffect once
  442. nums.num3 = 7
  443. expect(dummy).toEqual({ num1: 4, num2: 10, num3: 7 })
  444. expect(parentSpy).toHaveBeenCalledTimes(3)
  445. expect(childSpy).toHaveBeenCalledTimes(5)
  446. })
  447. it('should observe json methods', () => {
  448. let dummy = <Record<string, number>>{}
  449. const obj = reactive<Record<string, number>>({})
  450. effect(() => {
  451. dummy = JSON.parse(JSON.stringify(obj))
  452. })
  453. obj.a = 1
  454. expect(dummy.a).toBe(1)
  455. })
  456. it('should observe class method invocations', () => {
  457. class Model {
  458. count: number
  459. constructor() {
  460. this.count = 0
  461. }
  462. inc() {
  463. this.count++
  464. }
  465. }
  466. const model = reactive(new Model())
  467. let dummy
  468. effect(() => {
  469. dummy = model.count
  470. })
  471. expect(dummy).toBe(0)
  472. model.inc()
  473. expect(dummy).toBe(1)
  474. })
  475. it('lazy', () => {
  476. const obj = reactive({ foo: 1 })
  477. let dummy
  478. const runner = effect(() => (dummy = obj.foo), { lazy: true })
  479. expect(dummy).toBe(undefined)
  480. expect(runner()).toBe(1)
  481. expect(dummy).toBe(1)
  482. obj.foo = 2
  483. expect(dummy).toBe(2)
  484. })
  485. it('scheduler', () => {
  486. let runner: any, dummy
  487. const scheduler = jest.fn(_runner => {
  488. runner = _runner
  489. })
  490. const obj = reactive({ foo: 1 })
  491. effect(
  492. () => {
  493. dummy = obj.foo
  494. },
  495. { scheduler }
  496. )
  497. expect(scheduler).not.toHaveBeenCalled()
  498. expect(dummy).toBe(1)
  499. // should be called on first trigger
  500. obj.foo++
  501. expect(scheduler).toHaveBeenCalledTimes(1)
  502. // should not run yet
  503. expect(dummy).toBe(1)
  504. // manually run
  505. runner()
  506. // should have run
  507. expect(dummy).toBe(2)
  508. })
  509. it('events: onTrack', () => {
  510. let events: DebuggerEvent[] = []
  511. let dummy
  512. const onTrack = jest.fn((e: DebuggerEvent) => {
  513. events.push(e)
  514. })
  515. const obj = reactive({ foo: 1, bar: 2 })
  516. const runner = effect(
  517. () => {
  518. dummy = obj.foo
  519. dummy = 'bar' in obj
  520. dummy = Object.keys(obj)
  521. },
  522. { onTrack }
  523. )
  524. expect(dummy).toEqual(['foo', 'bar'])
  525. expect(onTrack).toHaveBeenCalledTimes(3)
  526. expect(events).toEqual([
  527. {
  528. effect: runner,
  529. target: toRaw(obj),
  530. type: OperationTypes.GET,
  531. key: 'foo'
  532. },
  533. {
  534. effect: runner,
  535. target: toRaw(obj),
  536. type: OperationTypes.HAS,
  537. key: 'bar'
  538. },
  539. {
  540. effect: runner,
  541. target: toRaw(obj),
  542. type: OperationTypes.ITERATE,
  543. key: ITERATE_KEY
  544. }
  545. ])
  546. })
  547. it('events: onTrigger', () => {
  548. let events: DebuggerEvent[] = []
  549. let dummy
  550. const onTrigger = jest.fn((e: DebuggerEvent) => {
  551. events.push(e)
  552. })
  553. const obj = reactive({ foo: 1 })
  554. const runner = effect(
  555. () => {
  556. dummy = obj.foo
  557. },
  558. { onTrigger }
  559. )
  560. obj.foo++
  561. expect(dummy).toBe(2)
  562. expect(onTrigger).toHaveBeenCalledTimes(1)
  563. expect(events[0]).toEqual({
  564. effect: runner,
  565. target: toRaw(obj),
  566. type: OperationTypes.SET,
  567. key: 'foo',
  568. oldValue: 1,
  569. newValue: 2
  570. })
  571. delete obj.foo
  572. expect(dummy).toBeUndefined()
  573. expect(onTrigger).toHaveBeenCalledTimes(2)
  574. expect(events[1]).toEqual({
  575. effect: runner,
  576. target: toRaw(obj),
  577. type: OperationTypes.DELETE,
  578. key: 'foo',
  579. oldValue: 2
  580. })
  581. })
  582. it('stop', () => {
  583. let dummy
  584. const obj = reactive({ prop: 1 })
  585. const runner = effect(() => {
  586. dummy = obj.prop
  587. })
  588. obj.prop = 2
  589. expect(dummy).toBe(2)
  590. stop(runner)
  591. obj.prop = 3
  592. expect(dummy).toBe(2)
  593. // stopped effect should still be manually callable
  594. runner()
  595. expect(dummy).toBe(3)
  596. })
  597. it('events: onStop', () => {
  598. const onStop = jest.fn()
  599. const runner = effect(() => {}, {
  600. onStop
  601. })
  602. stop(runner)
  603. expect(onStop).toHaveBeenCalled()
  604. })
  605. it('stop: a stopped effect is nested in a normal effect', () => {
  606. let dummy
  607. const obj = reactive({ prop: 1 })
  608. const runner = effect(() => {
  609. dummy = obj.prop
  610. })
  611. stop(runner)
  612. obj.prop = 2
  613. expect(dummy).toBe(1)
  614. // observed value in inner stopped effect
  615. // will track outer effect as an dependency
  616. effect(() => {
  617. runner()
  618. })
  619. expect(dummy).toBe(2)
  620. // notify outer effect to run
  621. obj.prop = 3
  622. expect(dummy).toBe(3)
  623. })
  624. it('markNonReactive', () => {
  625. const obj = reactive({
  626. foo: markNonReactive({
  627. prop: 0
  628. })
  629. })
  630. let dummy
  631. effect(() => {
  632. dummy = obj.foo.prop
  633. })
  634. expect(dummy).toBe(0)
  635. obj.foo.prop++
  636. expect(dummy).toBe(0)
  637. obj.foo = { prop: 1 }
  638. expect(dummy).toBe(1)
  639. })
  640. it('should not be trigger when the value and the old value both are NaN', () => {
  641. const obj = reactive({
  642. foo: NaN
  643. })
  644. const fnSpy = jest.fn(() => obj.foo)
  645. effect(fnSpy)
  646. obj.foo = NaN
  647. expect(fnSpy).toHaveBeenCalledTimes(1)
  648. })
  649. })