effect.spec.ts 18 KB

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