Map.spec.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. import { effect, isReactive, reactive, toRaw } from '../../src'
  2. describe('reactivity/collections', () => {
  3. function coverCollectionFn(collection: Map<any, any>, fnName: string) {
  4. const spy = vi.fn()
  5. let proxy = reactive(collection)
  6. ;(collection as any)[fnName] = spy
  7. return [proxy as any, spy]
  8. }
  9. describe('Map', () => {
  10. test('instanceof', () => {
  11. const original = new Map()
  12. const observed = reactive(original)
  13. expect(isReactive(observed)).toBe(true)
  14. expect(original).toBeInstanceOf(Map)
  15. expect(observed).toBeInstanceOf(Map)
  16. })
  17. it('should observe mutations', () => {
  18. let dummy
  19. const map = reactive(new Map())
  20. effect(() => {
  21. dummy = map.get('key')
  22. })
  23. expect(dummy).toBe(undefined)
  24. map.set('key', 'value')
  25. expect(dummy).toBe('value')
  26. map.set('key', 'value2')
  27. expect(dummy).toBe('value2')
  28. map.delete('key')
  29. expect(dummy).toBe(undefined)
  30. })
  31. it('should observe mutations with observed value as key', () => {
  32. let dummy
  33. const key = reactive({})
  34. const value = reactive({})
  35. const map = reactive(new Map())
  36. effect(() => {
  37. dummy = map.get(key)
  38. })
  39. expect(dummy).toBe(undefined)
  40. map.set(key, value)
  41. expect(dummy).toBe(value)
  42. map.delete(key)
  43. expect(dummy).toBe(undefined)
  44. })
  45. it('should observe size mutations', () => {
  46. let dummy
  47. const map = reactive(new Map())
  48. effect(() => (dummy = map.size))
  49. expect(dummy).toBe(0)
  50. map.set('key1', 'value')
  51. map.set('key2', 'value2')
  52. expect(dummy).toBe(2)
  53. map.delete('key1')
  54. expect(dummy).toBe(1)
  55. map.clear()
  56. expect(dummy).toBe(0)
  57. })
  58. it('should observe for of iteration', () => {
  59. let dummy
  60. const map = reactive(new Map())
  61. effect(() => {
  62. dummy = 0
  63. for (let [key, num] of map) {
  64. key
  65. dummy += num
  66. }
  67. })
  68. expect(dummy).toBe(0)
  69. map.set('key1', 3)
  70. expect(dummy).toBe(3)
  71. map.set('key2', 2)
  72. expect(dummy).toBe(5)
  73. // iteration should track mutation of existing entries (#709)
  74. map.set('key1', 4)
  75. expect(dummy).toBe(6)
  76. map.delete('key1')
  77. expect(dummy).toBe(2)
  78. map.clear()
  79. expect(dummy).toBe(0)
  80. })
  81. it('should observe forEach iteration', () => {
  82. let dummy: any
  83. const map = reactive(new Map())
  84. effect(() => {
  85. dummy = 0
  86. map.forEach((num: any) => (dummy += num))
  87. })
  88. expect(dummy).toBe(0)
  89. map.set('key1', 3)
  90. expect(dummy).toBe(3)
  91. map.set('key2', 2)
  92. expect(dummy).toBe(5)
  93. // iteration should track mutation of existing entries (#709)
  94. map.set('key1', 4)
  95. expect(dummy).toBe(6)
  96. map.delete('key1')
  97. expect(dummy).toBe(2)
  98. map.clear()
  99. expect(dummy).toBe(0)
  100. })
  101. it('should observe keys iteration', () => {
  102. let dummy
  103. const map = reactive(new Map())
  104. effect(() => {
  105. dummy = 0
  106. for (let key of map.keys()) {
  107. dummy += key
  108. }
  109. })
  110. expect(dummy).toBe(0)
  111. map.set(3, 3)
  112. expect(dummy).toBe(3)
  113. map.set(2, 2)
  114. expect(dummy).toBe(5)
  115. map.delete(3)
  116. expect(dummy).toBe(2)
  117. map.clear()
  118. expect(dummy).toBe(0)
  119. })
  120. it('should observe values iteration', () => {
  121. let dummy
  122. const map = reactive(new Map())
  123. effect(() => {
  124. dummy = 0
  125. for (let num of map.values()) {
  126. dummy += num
  127. }
  128. })
  129. expect(dummy).toBe(0)
  130. map.set('key1', 3)
  131. expect(dummy).toBe(3)
  132. map.set('key2', 2)
  133. expect(dummy).toBe(5)
  134. // iteration should track mutation of existing entries (#709)
  135. map.set('key1', 4)
  136. expect(dummy).toBe(6)
  137. map.delete('key1')
  138. expect(dummy).toBe(2)
  139. map.clear()
  140. expect(dummy).toBe(0)
  141. })
  142. it('should observe entries iteration', () => {
  143. let dummy
  144. let dummy2
  145. const map = reactive(new Map())
  146. effect(() => {
  147. dummy = ''
  148. dummy2 = 0
  149. for (let [key, num] of map.entries()) {
  150. dummy += key
  151. dummy2 += num
  152. }
  153. })
  154. expect(dummy).toBe('')
  155. expect(dummy2).toBe(0)
  156. map.set('key1', 3)
  157. expect(dummy).toBe('key1')
  158. expect(dummy2).toBe(3)
  159. map.set('key2', 2)
  160. expect(dummy).toBe('key1key2')
  161. expect(dummy2).toBe(5)
  162. // iteration should track mutation of existing entries (#709)
  163. map.set('key1', 4)
  164. expect(dummy).toBe('key1key2')
  165. expect(dummy2).toBe(6)
  166. map.delete('key1')
  167. expect(dummy).toBe('key2')
  168. expect(dummy2).toBe(2)
  169. map.clear()
  170. expect(dummy).toBe('')
  171. expect(dummy2).toBe(0)
  172. })
  173. it('should be triggered by clearing', () => {
  174. let dummy
  175. const map = reactive(new Map())
  176. effect(() => (dummy = map.get('key')))
  177. expect(dummy).toBe(undefined)
  178. map.set('key', 3)
  179. expect(dummy).toBe(3)
  180. map.clear()
  181. expect(dummy).toBe(undefined)
  182. })
  183. it('should not observe custom property mutations', () => {
  184. let dummy
  185. const map: any = reactive(new Map())
  186. effect(() => (dummy = map.customProp))
  187. expect(dummy).toBe(undefined)
  188. map.customProp = 'Hello World'
  189. expect(dummy).toBe(undefined)
  190. })
  191. it('should not observe non value changing mutations', () => {
  192. let dummy
  193. const map = reactive(new Map())
  194. const mapSpy = vi.fn(() => (dummy = map.get('key')))
  195. effect(mapSpy)
  196. expect(dummy).toBe(undefined)
  197. expect(mapSpy).toHaveBeenCalledTimes(1)
  198. map.set('key', undefined)
  199. expect(dummy).toBe(undefined)
  200. expect(mapSpy).toHaveBeenCalledTimes(2)
  201. map.set('key', 'value')
  202. expect(dummy).toBe('value')
  203. expect(mapSpy).toHaveBeenCalledTimes(3)
  204. map.set('key', 'value')
  205. expect(dummy).toBe('value')
  206. expect(mapSpy).toHaveBeenCalledTimes(3)
  207. map.delete('key')
  208. expect(dummy).toBe(undefined)
  209. expect(mapSpy).toHaveBeenCalledTimes(4)
  210. map.delete('key')
  211. expect(dummy).toBe(undefined)
  212. expect(mapSpy).toHaveBeenCalledTimes(4)
  213. map.clear()
  214. expect(dummy).toBe(undefined)
  215. expect(mapSpy).toHaveBeenCalledTimes(4)
  216. })
  217. it('should not observe raw data', () => {
  218. let dummy
  219. const map = reactive(new Map())
  220. effect(() => (dummy = toRaw(map).get('key')))
  221. expect(dummy).toBe(undefined)
  222. map.set('key', 'Hello')
  223. expect(dummy).toBe(undefined)
  224. map.delete('key')
  225. expect(dummy).toBe(undefined)
  226. })
  227. it('should not pollute original Map with Proxies', () => {
  228. const map = new Map()
  229. const observed = reactive(map)
  230. const value = reactive({})
  231. observed.set('key', value)
  232. expect(map.get('key')).not.toBe(value)
  233. expect(map.get('key')).toBe(toRaw(value))
  234. })
  235. it('should return observable versions of contained values', () => {
  236. const observed = reactive(new Map())
  237. const value = {}
  238. observed.set('key', value)
  239. const wrapped = observed.get('key')
  240. expect(isReactive(wrapped)).toBe(true)
  241. expect(toRaw(wrapped)).toBe(value)
  242. })
  243. it('should observed nested data', () => {
  244. const observed = reactive(new Map())
  245. observed.set('key', { a: 1 })
  246. let dummy
  247. effect(() => {
  248. dummy = observed.get('key').a
  249. })
  250. observed.get('key').a = 2
  251. expect(dummy).toBe(2)
  252. })
  253. it('should observe nested values in iterations (forEach)', () => {
  254. const map = reactive(new Map([[1, { foo: 1 }]]))
  255. let dummy: any
  256. effect(() => {
  257. dummy = 0
  258. map.forEach(value => {
  259. expect(isReactive(value)).toBe(true)
  260. dummy += value.foo
  261. })
  262. })
  263. expect(dummy).toBe(1)
  264. map.get(1)!.foo++
  265. expect(dummy).toBe(2)
  266. })
  267. it('should observe nested values in iterations (values)', () => {
  268. const map = reactive(new Map([[1, { foo: 1 }]]))
  269. let dummy: any
  270. effect(() => {
  271. dummy = 0
  272. for (const value of map.values()) {
  273. expect(isReactive(value)).toBe(true)
  274. dummy += value.foo
  275. }
  276. })
  277. expect(dummy).toBe(1)
  278. map.get(1)!.foo++
  279. expect(dummy).toBe(2)
  280. })
  281. it('should observe nested values in iterations (entries)', () => {
  282. const key = {}
  283. const map = reactive(new Map([[key, { foo: 1 }]]))
  284. let dummy: any
  285. effect(() => {
  286. dummy = 0
  287. for (const [key, value] of map.entries()) {
  288. key
  289. expect(isReactive(key)).toBe(true)
  290. expect(isReactive(value)).toBe(true)
  291. dummy += value.foo
  292. }
  293. })
  294. expect(dummy).toBe(1)
  295. map.get(key)!.foo++
  296. expect(dummy).toBe(2)
  297. })
  298. it('should observe nested values in iterations (for...of)', () => {
  299. const key = {}
  300. const map = reactive(new Map([[key, { foo: 1 }]]))
  301. let dummy: any
  302. effect(() => {
  303. dummy = 0
  304. for (const [key, value] of map) {
  305. key
  306. expect(isReactive(key)).toBe(true)
  307. expect(isReactive(value)).toBe(true)
  308. dummy += value.foo
  309. }
  310. })
  311. expect(dummy).toBe(1)
  312. map.get(key)!.foo++
  313. expect(dummy).toBe(2)
  314. })
  315. it('should not be trigger when the value and the old value both are NaN', () => {
  316. const map = reactive(new Map([['foo', NaN]]))
  317. const mapSpy = vi.fn(() => map.get('foo'))
  318. effect(mapSpy)
  319. map.set('foo', NaN)
  320. expect(mapSpy).toHaveBeenCalledTimes(1)
  321. })
  322. it('should work with reactive keys in raw map', () => {
  323. const raw = new Map()
  324. const key = reactive({})
  325. raw.set(key, 1)
  326. const map = reactive(raw)
  327. expect(map.has(key)).toBe(true)
  328. expect(map.get(key)).toBe(1)
  329. expect(map.delete(key)).toBe(true)
  330. expect(map.has(key)).toBe(false)
  331. expect(map.get(key)).toBeUndefined()
  332. })
  333. it('should track set of reactive keys in raw map', () => {
  334. const raw = new Map()
  335. const key = reactive({})
  336. raw.set(key, 1)
  337. const map = reactive(raw)
  338. let dummy
  339. effect(() => {
  340. dummy = map.get(key)
  341. })
  342. expect(dummy).toBe(1)
  343. map.set(key, 2)
  344. expect(dummy).toBe(2)
  345. })
  346. it('should track deletion of reactive keys in raw map', () => {
  347. const raw = new Map()
  348. const key = reactive({})
  349. raw.set(key, 1)
  350. const map = reactive(raw)
  351. let dummy
  352. effect(() => {
  353. dummy = map.has(key)
  354. })
  355. expect(dummy).toBe(true)
  356. map.delete(key)
  357. expect(dummy).toBe(false)
  358. })
  359. it('should warn when both raw and reactive versions of the same object is used as key', () => {
  360. const raw = new Map()
  361. const rawKey = {}
  362. const key = reactive(rawKey)
  363. raw.set(rawKey, 1)
  364. raw.set(key, 1)
  365. const map = reactive(raw)
  366. map.set(key, 2)
  367. expect(
  368. `Reactive Map contains both the raw and reactive`,
  369. ).toHaveBeenWarned()
  370. })
  371. // #877
  372. it('should not trigger key iteration when setting existing keys', () => {
  373. const map = reactive(new Map())
  374. const spy = vi.fn()
  375. effect(() => {
  376. const keys = []
  377. for (const key of map.keys()) {
  378. keys.push(key)
  379. }
  380. spy(keys)
  381. })
  382. expect(spy).toHaveBeenCalledTimes(1)
  383. expect(spy.mock.calls[0][0]).toMatchObject([])
  384. map.set('a', 0)
  385. expect(spy).toHaveBeenCalledTimes(2)
  386. expect(spy.mock.calls[1][0]).toMatchObject(['a'])
  387. map.set('b', 0)
  388. expect(spy).toHaveBeenCalledTimes(3)
  389. expect(spy.mock.calls[2][0]).toMatchObject(['a', 'b'])
  390. // keys didn't change, should not trigger
  391. map.set('b', 1)
  392. expect(spy).toHaveBeenCalledTimes(3)
  393. })
  394. it('should trigger Map.has only once for non-reactive keys', () => {
  395. const [proxy, spy] = coverCollectionFn(new Map(), 'has')
  396. proxy.has('k')
  397. expect(spy).toBeCalledTimes(1)
  398. })
  399. it('should trigger Map.set only once for non-reactive keys', () => {
  400. const [proxy, spy] = coverCollectionFn(new Map(), 'set')
  401. proxy.set('k', 'v')
  402. expect(spy).toBeCalledTimes(1)
  403. })
  404. it('should trigger Map.delete only once for non-reactive keys', () => {
  405. const [proxy, spy] = coverCollectionFn(new Map(), 'delete')
  406. proxy.delete('foo')
  407. expect(spy).toBeCalledTimes(1)
  408. })
  409. it('should trigger Map.clear only once for non-reactive keys', () => {
  410. const [proxy, spy] = coverCollectionFn(new Map(), 'clear')
  411. proxy.clear()
  412. expect(spy).toBeCalledTimes(1)
  413. })
  414. it('should return proxy from Map.set call', () => {
  415. const map = reactive(new Map())
  416. const result = map.set('a', 'a')
  417. expect(result).toBe(map)
  418. })
  419. })
  420. })