reactiveArray.spec.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. import { type ComputedRef, computed } from '../src/computed'
  2. import { isReactive, reactive, shallowReactive, toRaw } from '../src/reactive'
  3. import { isRef, ref } from '../src/ref'
  4. import { effect } from '../src/effect'
  5. describe('reactivity/reactive/Array', () => {
  6. test('should make Array reactive', () => {
  7. const original = [{ foo: 1 }]
  8. const observed = reactive(original)
  9. expect(observed).not.toBe(original)
  10. expect(isReactive(observed)).toBe(true)
  11. expect(isReactive(original)).toBe(false)
  12. expect(isReactive(observed[0])).toBe(true)
  13. // get
  14. expect(observed[0].foo).toBe(1)
  15. // has
  16. expect(0 in observed).toBe(true)
  17. // ownKeys
  18. expect(Object.keys(observed)).toEqual(['0'])
  19. })
  20. test('cloned reactive Array should point to observed values', () => {
  21. const original = [{ foo: 1 }]
  22. const observed = reactive(original)
  23. const clone = observed.slice()
  24. expect(isReactive(clone[0])).toBe(true)
  25. expect(clone[0]).not.toBe(original[0])
  26. expect(clone[0]).toBe(observed[0])
  27. })
  28. test('observed value should proxy mutations to original (Array)', () => {
  29. const original: any[] = [{ foo: 1 }, { bar: 2 }]
  30. const observed = reactive(original)
  31. // set
  32. const value = { baz: 3 }
  33. const reactiveValue = reactive(value)
  34. observed[0] = value
  35. expect(observed[0]).toBe(reactiveValue)
  36. expect(original[0]).toBe(value)
  37. // delete
  38. delete observed[0]
  39. expect(observed[0]).toBeUndefined()
  40. expect(original[0]).toBeUndefined()
  41. // mutating methods
  42. observed.push(value)
  43. expect(observed[2]).toBe(reactiveValue)
  44. expect(original[2]).toBe(value)
  45. })
  46. test('Array identity methods should work with raw values', () => {
  47. const raw = {}
  48. const arr = reactive([{}, {}])
  49. arr.push(raw)
  50. expect(arr.indexOf(raw)).toBe(2)
  51. expect(arr.indexOf(raw, 3)).toBe(-1)
  52. expect(arr.includes(raw)).toBe(true)
  53. expect(arr.includes(raw, 3)).toBe(false)
  54. expect(arr.lastIndexOf(raw)).toBe(2)
  55. expect(arr.lastIndexOf(raw, 1)).toBe(-1)
  56. // should work also for the observed version
  57. const observed = arr[2]
  58. expect(arr.indexOf(observed)).toBe(2)
  59. expect(arr.indexOf(observed, 3)).toBe(-1)
  60. expect(arr.includes(observed)).toBe(true)
  61. expect(arr.includes(observed, 3)).toBe(false)
  62. expect(arr.lastIndexOf(observed)).toBe(2)
  63. expect(arr.lastIndexOf(observed, 1)).toBe(-1)
  64. })
  65. test('Array identity methods should work if raw value contains reactive objects', () => {
  66. const raw = []
  67. const obj = reactive({})
  68. raw.push(obj)
  69. const arr = reactive(raw)
  70. expect(arr.includes(obj)).toBe(true)
  71. })
  72. test('Array identity methods should be reactive', () => {
  73. const obj = {}
  74. const arr = reactive([obj, {}])
  75. let index: number = -1
  76. effect(() => {
  77. index = arr.indexOf(obj)
  78. })
  79. expect(index).toBe(0)
  80. arr.reverse()
  81. expect(index).toBe(1)
  82. })
  83. // only non-existent reactive will try to search by using its raw value
  84. describe('Array identity methods should not be called more than necessary', () => {
  85. const identityMethods = ['includes', 'indexOf', 'lastIndexOf'] as const
  86. function instrumentArr(rawTarget: any[]) {
  87. const mutableTarget = rawTarget as Record<
  88. (typeof identityMethods)[number],
  89. any
  90. >
  91. identityMethods.forEach(key => {
  92. const spy = vi.fn(rawTarget[key] as any)
  93. mutableTarget[key] = spy
  94. })
  95. }
  96. function searchValue(target: any[], ...args: unknown[]) {
  97. return identityMethods.map(key => (target[key] as any)(...args))
  98. }
  99. function unInstrumentArr(rawTarget: any[]) {
  100. identityMethods.forEach(key => {
  101. ;(rawTarget[key] as any).mockClear()
  102. // relink to prototype method
  103. rawTarget[key] = Array.prototype[key] as any
  104. })
  105. }
  106. function expectHaveBeenCalledTimes(rawTarget: any[], times: number) {
  107. identityMethods.forEach(key => {
  108. expect(rawTarget[key]).toHaveBeenCalledTimes(times)
  109. })
  110. }
  111. test('should be called once with a non-existent raw value', () => {
  112. const reactiveArr = reactive([])
  113. instrumentArr(toRaw(reactiveArr))
  114. const searchResult = searchValue(reactiveArr, {})
  115. expectHaveBeenCalledTimes(toRaw(reactiveArr), 1)
  116. expect(searchResult).toStrictEqual([false, -1, -1])
  117. unInstrumentArr(toRaw(reactiveArr))
  118. })
  119. test('should be called once with an existent reactive value', () => {
  120. const existReactiveValue = reactive({})
  121. const reactiveArr = reactive([existReactiveValue, existReactiveValue])
  122. instrumentArr(toRaw(reactiveArr))
  123. const searchResult = searchValue(reactiveArr, existReactiveValue)
  124. expectHaveBeenCalledTimes(toRaw(reactiveArr), 1)
  125. expect(searchResult).toStrictEqual([true, 0, 1])
  126. unInstrumentArr(toRaw(reactiveArr))
  127. })
  128. test('should be called twice with a non-existent reactive value', () => {
  129. const reactiveArr = reactive([])
  130. instrumentArr(toRaw(reactiveArr))
  131. const searchResult = searchValue(reactiveArr, reactive({}))
  132. expectHaveBeenCalledTimes(toRaw(reactiveArr), 2)
  133. expect(searchResult).toStrictEqual([false, -1, -1])
  134. unInstrumentArr(toRaw(reactiveArr))
  135. })
  136. test('should be called twice with a non-existent reactive value, but the raw value exists', () => {
  137. const existRaw = {}
  138. const reactiveArr = reactive([existRaw, existRaw])
  139. instrumentArr(toRaw(reactiveArr))
  140. const searchResult = searchValue(reactiveArr, reactive(existRaw))
  141. expectHaveBeenCalledTimes(toRaw(reactiveArr), 2)
  142. expect(searchResult).toStrictEqual([true, 0, 1])
  143. unInstrumentArr(toRaw(reactiveArr))
  144. })
  145. })
  146. test('delete on Array should not trigger length dependency', () => {
  147. const arr = reactive([1, 2, 3])
  148. const fn = vi.fn()
  149. effect(() => {
  150. fn(arr.length)
  151. })
  152. expect(fn).toHaveBeenCalledTimes(1)
  153. delete arr[1]
  154. expect(fn).toHaveBeenCalledTimes(1)
  155. })
  156. test('should track hasOwnProperty call with index', () => {
  157. const original = [1, 2, 3]
  158. const observed = reactive(original)
  159. let dummy
  160. effect(() => {
  161. dummy = observed.hasOwnProperty(0)
  162. })
  163. expect(dummy).toBe(true)
  164. delete observed[0]
  165. expect(dummy).toBe(false)
  166. })
  167. test('shift on Array should trigger dependency once', () => {
  168. const arr = reactive([1, 2, 3])
  169. const fn = vi.fn()
  170. effect(() => {
  171. for (let i = 0; i < arr.length; i++) {
  172. arr[i]
  173. }
  174. fn()
  175. })
  176. expect(fn).toHaveBeenCalledTimes(1)
  177. arr.shift()
  178. expect(fn).toHaveBeenCalledTimes(2)
  179. })
  180. //#6018
  181. test('edge case: avoid trigger effect in deleteProperty when array length-decrease mutation methods called', () => {
  182. const arr = ref([1])
  183. const fn1 = vi.fn()
  184. const fn2 = vi.fn()
  185. effect(() => {
  186. fn1()
  187. if (arr.value.length > 0) {
  188. arr.value.slice()
  189. fn2()
  190. }
  191. })
  192. expect(fn1).toHaveBeenCalledTimes(1)
  193. expect(fn2).toHaveBeenCalledTimes(1)
  194. arr.value.splice(0)
  195. expect(fn1).toHaveBeenCalledTimes(2)
  196. expect(fn2).toHaveBeenCalledTimes(1)
  197. })
  198. test('add existing index on Array should not trigger length dependency', () => {
  199. const array = new Array(3)
  200. const observed = reactive(array)
  201. const fn = vi.fn()
  202. effect(() => {
  203. fn(observed.length)
  204. })
  205. expect(fn).toHaveBeenCalledTimes(1)
  206. observed[1] = 1
  207. expect(fn).toHaveBeenCalledTimes(1)
  208. })
  209. test('add non-integer prop on Array should not trigger length dependency', () => {
  210. const array: any[] & { x?: string } = new Array(3)
  211. const observed = reactive(array)
  212. const fn = vi.fn()
  213. effect(() => {
  214. fn(observed.length)
  215. })
  216. expect(fn).toHaveBeenCalledTimes(1)
  217. observed.x = 'x'
  218. expect(fn).toHaveBeenCalledTimes(1)
  219. observed[-1] = 'x'
  220. expect(fn).toHaveBeenCalledTimes(1)
  221. observed[NaN] = 'x'
  222. expect(fn).toHaveBeenCalledTimes(1)
  223. })
  224. // #2427
  225. test('track length on for ... in iteration', () => {
  226. const array = reactive([1])
  227. let length = ''
  228. effect(() => {
  229. length = ''
  230. for (const key in array) {
  231. length += key
  232. }
  233. })
  234. expect(length).toBe('0')
  235. array.push(1)
  236. expect(length).toBe('01')
  237. })
  238. // #9742
  239. test('mutation on user proxy of reactive Array', () => {
  240. const array = reactive<number[]>([])
  241. const proxy = new Proxy(array, {})
  242. proxy.push(1)
  243. expect(array).toHaveLength(1)
  244. expect(proxy).toHaveLength(1)
  245. })
  246. describe('Array methods w/ refs', () => {
  247. let original: any[]
  248. beforeEach(() => {
  249. original = reactive([1, ref(2)])
  250. })
  251. // read + copy
  252. test('read only copy methods', () => {
  253. const raw = original.concat([3, ref(4)])
  254. expect(isRef(raw[1])).toBe(true)
  255. expect(isRef(raw[3])).toBe(true)
  256. })
  257. // read + write
  258. test('read + write mutating methods', () => {
  259. const res = original.copyWithin(0, 1, 2)
  260. const raw = toRaw(res)
  261. expect(isRef(raw[0])).toBe(true)
  262. expect(isRef(raw[1])).toBe(true)
  263. })
  264. test('read + identity', () => {
  265. const ref = original[1]
  266. expect(ref).toBe(toRaw(original)[1])
  267. expect(original.indexOf(ref)).toBe(1)
  268. })
  269. })
  270. describe('Array subclasses', () => {
  271. class SubArray<T> extends Array<T> {
  272. lastPushed: undefined | T
  273. lastSearched: undefined | T
  274. push(item: T) {
  275. this.lastPushed = item
  276. return super.push(item)
  277. }
  278. indexOf(searchElement: T, fromIndex?: number | undefined): number {
  279. this.lastSearched = searchElement
  280. return super.indexOf(searchElement, fromIndex)
  281. }
  282. }
  283. test('calls correct mutation method on Array subclass', () => {
  284. const subArray = new SubArray(4, 5, 6)
  285. const observed = reactive(subArray)
  286. subArray.push(7)
  287. expect(subArray.lastPushed).toBe(7)
  288. observed.push(9)
  289. expect(observed.lastPushed).toBe(9)
  290. })
  291. test('calls correct identity-sensitive method on Array subclass', () => {
  292. const subArray = new SubArray(4, 5, 6)
  293. const observed = reactive(subArray)
  294. let index
  295. index = subArray.indexOf(4)
  296. expect(index).toBe(0)
  297. expect(subArray.lastSearched).toBe(4)
  298. index = observed.indexOf(6)
  299. expect(index).toBe(2)
  300. expect(observed.lastSearched).toBe(6)
  301. })
  302. })
  303. describe('Optimized array methods:', () => {
  304. test('iterator', () => {
  305. const shallow = shallowReactive([1, 2, 3, 4])
  306. let result = computed(() => {
  307. let sum = 0
  308. for (let x of shallow) {
  309. sum += x ** 2
  310. }
  311. return sum
  312. })
  313. expect(result.value).toBe(30)
  314. shallow[2] = 0
  315. expect(result.value).toBe(21)
  316. const deep = reactive([{ val: 1 }, { val: 2 }])
  317. result = computed(() => {
  318. let sum = 0
  319. for (let x of deep) {
  320. sum += x.val ** 2
  321. }
  322. return sum
  323. })
  324. expect(result.value).toBe(5)
  325. deep[1].val = 3
  326. expect(result.value).toBe(10)
  327. })
  328. test('concat', () => {
  329. const a1 = shallowReactive([1, { val: 2 }])
  330. const a2 = reactive([{ val: 3 }])
  331. const a3 = [4, 5]
  332. let result = computed(() => a1.concat(a2, a3, 6, { val: 7 }))
  333. expect(result.value).toStrictEqual([
  334. 1,
  335. { val: 2 },
  336. { val: 3 },
  337. 4,
  338. 5,
  339. 6,
  340. { val: 7 },
  341. ])
  342. expect(isReactive(result.value[1])).toBe(false)
  343. expect(isReactive(result.value[2])).toBe(true)
  344. expect(isReactive(result.value[6])).toBe(false)
  345. a1.shift()
  346. expect(result.value).toStrictEqual([
  347. { val: 2 },
  348. { val: 3 },
  349. 4,
  350. 5,
  351. 6,
  352. { val: 7 },
  353. ])
  354. a2.pop()
  355. expect(result.value).toStrictEqual([{ val: 2 }, 4, 5, 6, { val: 7 }])
  356. a3.pop()
  357. expect(result.value).toStrictEqual([{ val: 2 }, 4, 5, 6, { val: 7 }])
  358. })
  359. test('entries', () => {
  360. const shallow = shallowReactive([0, 1])
  361. const result1 = computed(() => Array.from(shallow.entries()))
  362. expect(result1.value).toStrictEqual([
  363. [0, 0],
  364. [1, 1],
  365. ])
  366. shallow[1] = 10
  367. expect(result1.value).toStrictEqual([
  368. [0, 0],
  369. [1, 10],
  370. ])
  371. const deep = reactive([{ val: 0 }, { val: 1 }])
  372. const result2 = computed(() => Array.from(deep.entries()))
  373. expect(result2.value).toStrictEqual([
  374. [0, { val: 0 }],
  375. [1, { val: 1 }],
  376. ])
  377. expect(isReactive(result2.value[0][1])).toBe(true)
  378. deep.pop()
  379. expect(Array.from(result2.value)).toStrictEqual([[0, { val: 0 }]])
  380. })
  381. test('every', () => {
  382. const shallow = shallowReactive([1, 2, 5])
  383. let result = computed(() => shallow.every(x => x < 5))
  384. expect(result.value).toBe(false)
  385. shallow.pop()
  386. expect(result.value).toBe(true)
  387. const deep = reactive([{ val: 1 }, { val: 5 }])
  388. result = computed(() => deep.every(x => x.val < 5))
  389. expect(result.value).toBe(false)
  390. deep[1].val = 2
  391. expect(result.value).toBe(true)
  392. })
  393. test('filter', () => {
  394. const shallow = shallowReactive([1, 2, 3, 4])
  395. const result1 = computed(() => shallow.filter(x => x < 3))
  396. expect(result1.value).toStrictEqual([1, 2])
  397. shallow[2] = 0
  398. expect(result1.value).toStrictEqual([1, 2, 0])
  399. const deep = reactive([{ val: 1 }, { val: 2 }])
  400. const result2 = computed(() => deep.filter(x => x.val < 2))
  401. expect(result2.value).toStrictEqual([{ val: 1 }])
  402. expect(isReactive(result2.value[0])).toBe(true)
  403. deep[1].val = 0
  404. expect(result2.value).toStrictEqual([{ val: 1 }, { val: 0 }])
  405. })
  406. test('find and co.', () => {
  407. const shallow = shallowReactive([{ val: 1 }, { val: 2 }])
  408. let find = computed(() => shallow.find(x => x.val === 2))
  409. // @ts-expect-error tests are not limited to es2016
  410. let findLast = computed(() => shallow.findLast(x => x.val === 2))
  411. let findIndex = computed(() => shallow.findIndex(x => x.val === 2))
  412. let findLastIndex = computed(() =>
  413. // @ts-expect-error tests are not limited to es2016
  414. shallow.findLastIndex(x => x.val === 2),
  415. )
  416. expect(find.value).toBe(shallow[1])
  417. expect(isReactive(find.value)).toBe(false)
  418. expect(findLast.value).toBe(shallow[1])
  419. expect(isReactive(findLast.value)).toBe(false)
  420. expect(findIndex.value).toBe(1)
  421. expect(findLastIndex.value).toBe(1)
  422. shallow[1].val = 0
  423. expect(find.value).toBe(shallow[1])
  424. expect(findLast.value).toBe(shallow[1])
  425. expect(findIndex.value).toBe(1)
  426. expect(findLastIndex.value).toBe(1)
  427. shallow.pop()
  428. expect(find.value).toBe(undefined)
  429. expect(findLast.value).toBe(undefined)
  430. expect(findIndex.value).toBe(-1)
  431. expect(findLastIndex.value).toBe(-1)
  432. const deep = reactive([{ val: 1 }, { val: 2 }])
  433. find = computed(() => deep.find(x => x.val === 2))
  434. // @ts-expect-error tests are not limited to es2016
  435. findLast = computed(() => deep.findLast(x => x.val === 2))
  436. findIndex = computed(() => deep.findIndex(x => x.val === 2))
  437. // @ts-expect-error tests are not limited to es2016
  438. findLastIndex = computed(() => deep.findLastIndex(x => x.val === 2))
  439. expect(find.value).toBe(deep[1])
  440. expect(isReactive(find.value)).toBe(true)
  441. expect(findLast.value).toBe(deep[1])
  442. expect(isReactive(findLast.value)).toBe(true)
  443. expect(findIndex.value).toBe(1)
  444. expect(findLastIndex.value).toBe(1)
  445. deep[1].val = 0
  446. expect(find.value).toBe(undefined)
  447. expect(findLast.value).toBe(undefined)
  448. expect(findIndex.value).toBe(-1)
  449. expect(findLastIndex.value).toBe(-1)
  450. })
  451. test('forEach', () => {
  452. const shallow = shallowReactive([1, 2, 3, 4])
  453. let result = computed(() => {
  454. let sum = 0
  455. shallow.forEach(x => (sum += x ** 2))
  456. return sum
  457. })
  458. expect(result.value).toBe(30)
  459. shallow[2] = 0
  460. expect(result.value).toBe(21)
  461. const deep = reactive([{ val: 1 }, { val: 2 }])
  462. result = computed(() => {
  463. let sum = 0
  464. deep.forEach(x => (sum += x.val ** 2))
  465. return sum
  466. })
  467. expect(result.value).toBe(5)
  468. deep[1].val = 3
  469. expect(result.value).toBe(10)
  470. })
  471. test('join', () => {
  472. function toString(this: { val: number }) {
  473. return this.val
  474. }
  475. const shallow = shallowReactive([
  476. { val: 1, toString },
  477. { val: 2, toString },
  478. ])
  479. let result = computed(() => shallow.join('+'))
  480. expect(result.value).toBe('1+2')
  481. shallow[1].val = 23
  482. expect(result.value).toBe('1+2')
  483. shallow.pop()
  484. expect(result.value).toBe('1')
  485. const deep = reactive([
  486. { val: 1, toString },
  487. { val: 2, toString },
  488. ])
  489. result = computed(() => deep.join())
  490. expect(result.value).toBe('1,2')
  491. deep[1].val = 23
  492. expect(result.value).toBe('1,23')
  493. })
  494. test('map', () => {
  495. const shallow = shallowReactive([1, 2, 3, 4])
  496. let result = computed(() => shallow.map(x => x ** 2))
  497. expect(result.value).toStrictEqual([1, 4, 9, 16])
  498. shallow[2] = 0
  499. expect(result.value).toStrictEqual([1, 4, 0, 16])
  500. const deep = reactive([{ val: 1 }, { val: 2 }])
  501. result = computed(() => deep.map(x => x.val ** 2))
  502. expect(result.value).toStrictEqual([1, 4])
  503. deep[1].val = 3
  504. expect(result.value).toStrictEqual([1, 9])
  505. })
  506. test('reduce left and right', () => {
  507. function toString(this: any) {
  508. return this.val + '-'
  509. }
  510. const shallow = shallowReactive([
  511. { val: 1, toString },
  512. { val: 2, toString },
  513. ] as any[])
  514. expect(shallow.reduce((acc, x) => acc + '' + x.val, undefined)).toBe(
  515. 'undefined12',
  516. )
  517. let left = computed(() => shallow.reduce((acc, x) => acc + '' + x.val))
  518. let right = computed(() =>
  519. shallow.reduceRight((acc, x) => acc + '' + x.val),
  520. )
  521. expect(left.value).toBe('1-2')
  522. expect(right.value).toBe('2-1')
  523. shallow[1].val = 23
  524. expect(left.value).toBe('1-2')
  525. expect(right.value).toBe('2-1')
  526. shallow.pop()
  527. expect(left.value).toBe(shallow[0])
  528. expect(right.value).toBe(shallow[0])
  529. let deep = reactive([{ val: 1 }, { val: 2 }])
  530. left = computed(() => deep.reduce((acc, x) => acc + x.val, '0'))
  531. right = computed(() => deep.reduceRight((acc, x) => acc + x.val, '3'))
  532. expect(left.value).toBe('012')
  533. expect(right.value).toBe('321')
  534. deep[1].val = 23
  535. expect(left.value).toBe('0123')
  536. expect(right.value).toBe('3231')
  537. deep = reactive([{ val: 1 }, { val: 2 }])
  538. const maxBy = (prev: any, cur: any) => {
  539. expect(isReactive(prev)).toBe(true)
  540. expect(isReactive(cur)).toBe(true)
  541. return prev.val > cur.val ? prev : cur
  542. }
  543. left = computed(() => deep.reduce(maxBy))
  544. right = computed(() => deep.reduceRight(maxBy))
  545. expect(left.value).toMatchObject({ val: 2 })
  546. expect(right.value).toMatchObject({ val: 2 })
  547. deep[0].val = 23
  548. expect(left.value).toMatchObject({ val: 23 })
  549. expect(right.value).toMatchObject({ val: 23 })
  550. deep[1].val = 24
  551. expect(left.value).toMatchObject({ val: 24 })
  552. expect(right.value).toMatchObject({ val: 24 })
  553. })
  554. test('reduce left and right with single deep reactive element and no initial value', () => {
  555. const deep = reactive([{ val: 1 }])
  556. const left = computed(() => deep.reduce(prev => prev))
  557. const right = computed(() => deep.reduceRight(prev => prev))
  558. expect(isReactive(left.value)).toBe(true)
  559. expect(isReactive(right.value)).toBe(true)
  560. expect(left.value).toMatchObject({ val: 1 })
  561. expect(right.value).toMatchObject({ val: 1 })
  562. deep[0].val = 2
  563. expect(left.value).toMatchObject({ val: 2 })
  564. expect(right.value).toMatchObject({ val: 2 })
  565. })
  566. test('some', () => {
  567. const shallow = shallowReactive([1, 2, 5])
  568. let result = computed(() => shallow.some(x => x > 4))
  569. expect(result.value).toBe(true)
  570. shallow.pop()
  571. expect(result.value).toBe(false)
  572. const deep = reactive([{ val: 1 }, { val: 5 }])
  573. result = computed(() => deep.some(x => x.val > 4))
  574. expect(result.value).toBe(true)
  575. deep[1].val = 2
  576. expect(result.value).toBe(false)
  577. })
  578. // Node 20+
  579. // @ts-expect-error tests are not limited to es2016
  580. test.skipIf(!Array.prototype.toReversed)('toReversed', () => {
  581. const array = reactive([1, { val: 2 }])
  582. const result = computed(() => (array as any).toReversed())
  583. expect(result.value).toStrictEqual([{ val: 2 }, 1])
  584. expect(isReactive(result.value[0])).toBe(true)
  585. array.splice(1, 1, 2)
  586. expect(result.value).toStrictEqual([2, 1])
  587. })
  588. // Node 20+
  589. // @ts-expect-error tests are not limited to es2016
  590. test.skipIf(!Array.prototype.toSorted)('toSorted', () => {
  591. // No comparer
  592. // @ts-expect-error
  593. expect(shallowReactive([2, 1, 3]).toSorted()).toStrictEqual([1, 2, 3])
  594. const shallow = shallowReactive([{ val: 2 }, { val: 1 }, { val: 3 }])
  595. let result: ComputedRef<{ val: number }[]>
  596. // @ts-expect-error
  597. result = computed(() => shallow.toSorted((a, b) => a.val - b.val))
  598. expect(result.value.map(x => x.val)).toStrictEqual([1, 2, 3])
  599. expect(isReactive(result.value[0])).toBe(false)
  600. shallow[0].val = 4
  601. expect(result.value.map(x => x.val)).toStrictEqual([1, 4, 3])
  602. shallow.pop()
  603. expect(result.value.map(x => x.val)).toStrictEqual([1, 4])
  604. const deep = reactive([{ val: 2 }, { val: 1 }, { val: 3 }])
  605. // @ts-expect-error
  606. result = computed(() => deep.toSorted((a, b) => a.val - b.val))
  607. expect(result.value.map(x => x.val)).toStrictEqual([1, 2, 3])
  608. expect(isReactive(result.value[0])).toBe(true)
  609. deep[0].val = 4
  610. expect(result.value.map(x => x.val)).toStrictEqual([1, 3, 4])
  611. })
  612. // Node 20+
  613. // @ts-expect-error tests are not limited to es2016
  614. test.skipIf(!Array.prototype.toSpliced)('toSpliced', () => {
  615. const array = reactive([1, 2, 3])
  616. // @ts-expect-error
  617. const result = computed(() => array.toSpliced(1, 1, -2))
  618. expect(result.value).toStrictEqual([1, -2, 3])
  619. array[0] = 0
  620. expect(result.value).toStrictEqual([0, -2, 3])
  621. })
  622. test('values', () => {
  623. const shallow = shallowReactive([{ val: 1 }, { val: 2 }])
  624. const result = computed(() => Array.from(shallow.values()))
  625. expect(result.value).toStrictEqual([{ val: 1 }, { val: 2 }])
  626. expect(isReactive(result.value[0])).toBe(false)
  627. shallow.pop()
  628. expect(result.value).toStrictEqual([{ val: 1 }])
  629. const deep = reactive([{ val: 1 }, { val: 2 }])
  630. const firstItem = Array.from(deep.values())[0]
  631. expect(isReactive(firstItem)).toBe(true)
  632. })
  633. test('extend methods', () => {
  634. class Collection extends Array {
  635. // @ts-expect-error
  636. every(foo: any, bar: any, baz: any) {
  637. expect(foo).toBe('foo')
  638. expect(bar).toBe('bar')
  639. expect(baz).toBe('baz')
  640. return super.every(obj => obj.id === foo)
  641. }
  642. // @ts-expect-error
  643. filter(foo: any, bar: any, baz: any) {
  644. expect(foo).toBe('foo')
  645. expect(bar).toBe('bar')
  646. expect(baz).toBe('baz')
  647. return super.filter(obj => obj.id === foo)
  648. }
  649. // @ts-expect-error
  650. find(foo: any, bar: any, baz: any) {
  651. expect(foo).toBe('foo')
  652. expect(bar).toBe('bar')
  653. expect(baz).toBe('baz')
  654. return super.find(obj => obj.id === foo)
  655. }
  656. // @ts-expect-error
  657. findIndex(foo: any, bar: any, baz: any) {
  658. expect(foo).toBe('foo')
  659. expect(bar).toBe('bar')
  660. expect(baz).toBe('baz')
  661. return super.findIndex(obj => obj.id === bar)
  662. }
  663. findLast(foo: any, bar: any, baz: any) {
  664. expect(foo).toBe('foo')
  665. expect(bar).toBe('bar')
  666. expect(baz).toBe('baz')
  667. // @ts-expect-error our code is limited to es2016 but user code is not
  668. return super.findLast(obj => obj.id === bar)
  669. }
  670. findLastIndex(foo: any, bar: any, baz: any) {
  671. expect(foo).toBe('foo')
  672. expect(bar).toBe('bar')
  673. expect(baz).toBe('baz')
  674. return super.findIndex(obj => obj.id === bar)
  675. }
  676. // @ts-expect-error
  677. forEach(foo: any, bar: any, baz: any) {
  678. expect(foo).toBe('foo')
  679. expect(bar).toBe('bar')
  680. expect(baz).toBe('baz')
  681. }
  682. // @ts-expect-error
  683. map(foo: any, bar: any, baz: any) {
  684. expect(foo).toBe('foo')
  685. expect(bar).toBe('bar')
  686. expect(baz).toBe('baz')
  687. return super.map(obj => obj.value)
  688. }
  689. // @ts-expect-error
  690. some(foo: any, bar: any, baz: any) {
  691. expect(foo).toBe('foo')
  692. expect(bar).toBe('bar')
  693. expect(baz).toBe('baz')
  694. return super.some(obj => obj.id === baz)
  695. }
  696. }
  697. const state = reactive({
  698. things: new Collection(),
  699. })
  700. const foo = { id: 'foo', value: '1' }
  701. const bar = { id: 'bar', value: '2' }
  702. const baz = { id: 'baz', value: '3' }
  703. state.things.push(foo)
  704. state.things.push(bar)
  705. state.things.push(baz)
  706. expect(state.things.every('foo', 'bar', 'baz')).toBe(false)
  707. expect(state.things.filter('foo', 'bar', 'baz')).toEqual([foo])
  708. const _foo = state.things.find('foo', 'bar', 'baz')
  709. expect(isReactive(_foo)).toBe(true)
  710. expect(foo).toStrictEqual(_foo)
  711. expect(state.things.findIndex('foo', 'bar', 'baz')).toBe(1)
  712. const _bar = state.things.findLast('foo', 'bar', 'baz')
  713. expect(isReactive(_bar)).toBe(true)
  714. expect(bar).toStrictEqual(_bar)
  715. expect(state.things.findLastIndex('foo', 'bar', 'baz')).toBe(1)
  716. expect(state.things.forEach('foo', 'bar', 'baz')).toBeUndefined()
  717. expect(state.things.map('foo', 'bar', 'baz')).toEqual(['1', '2', '3'])
  718. expect(state.things.some('foo', 'bar', 'baz')).toBe(true)
  719. {
  720. class Collection extends Array {
  721. find(matcher: any) {
  722. return super.find(matcher)
  723. }
  724. }
  725. const state = reactive({
  726. // @ts-expect-error
  727. things: new Collection({ foo: '' }),
  728. })
  729. const bar = computed(() => {
  730. return state.things.find((obj: any) => obj.foo === 'bar')
  731. })
  732. bar.value
  733. state.things[0].foo = 'bar'
  734. expect(bar.value).toEqual({ foo: 'bar' })
  735. }
  736. })
  737. })
  738. })