reactiveArray.spec.ts 25 KB

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