reactiveArray.spec.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import { vi } from 'vitest'
  2. import { reactive, isReactive, toRaw } from '../src/reactive'
  3. import { ref, isRef } 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. test('delete on Array should not trigger length dependency', () => {
  84. const arr = reactive([1, 2, 3])
  85. const fn = vi.fn()
  86. effect(() => {
  87. fn(arr.length)
  88. })
  89. expect(fn).toHaveBeenCalledTimes(1)
  90. delete arr[1]
  91. expect(fn).toHaveBeenCalledTimes(1)
  92. })
  93. test('add existing index on Array should not trigger length dependency', () => {
  94. const array = new Array(3)
  95. const observed = reactive(array)
  96. const fn = vi.fn()
  97. effect(() => {
  98. fn(observed.length)
  99. })
  100. expect(fn).toHaveBeenCalledTimes(1)
  101. observed[1] = 1
  102. expect(fn).toHaveBeenCalledTimes(1)
  103. })
  104. test('add non-integer prop on Array should not trigger length dependency', () => {
  105. const array: any[] & { x?: string } = new Array(3)
  106. const observed = reactive(array)
  107. const fn = vi.fn()
  108. effect(() => {
  109. fn(observed.length)
  110. })
  111. expect(fn).toHaveBeenCalledTimes(1)
  112. observed.x = 'x'
  113. expect(fn).toHaveBeenCalledTimes(1)
  114. observed[-1] = 'x'
  115. expect(fn).toHaveBeenCalledTimes(1)
  116. observed[NaN] = 'x'
  117. expect(fn).toHaveBeenCalledTimes(1)
  118. })
  119. // #2427
  120. test('track length on for ... in iteration', () => {
  121. const array = reactive([1])
  122. let length = ''
  123. effect(() => {
  124. length = ''
  125. for (const key in array) {
  126. length += key
  127. }
  128. })
  129. expect(length).toBe('0')
  130. array.push(1)
  131. expect(length).toBe('01')
  132. })
  133. describe('Array methods w/ refs', () => {
  134. let original: any[]
  135. beforeEach(() => {
  136. original = reactive([1, ref(2)])
  137. })
  138. // read + copy
  139. test('read only copy methods', () => {
  140. const raw = original.concat([3, ref(4)])
  141. expect(isRef(raw[1])).toBe(true)
  142. expect(isRef(raw[3])).toBe(true)
  143. })
  144. // read + write
  145. test('read + write mutating methods', () => {
  146. const res = original.copyWithin(0, 1, 2)
  147. const raw = toRaw(res)
  148. expect(isRef(raw[0])).toBe(true)
  149. expect(isRef(raw[1])).toBe(true)
  150. })
  151. test('read + identity', () => {
  152. const ref = original[1]
  153. expect(ref).toBe(toRaw(original)[1])
  154. expect(original.indexOf(ref)).toBe(1)
  155. })
  156. })
  157. describe('Array subclasses', () => {
  158. class SubArray<T> extends Array<T> {
  159. lastPushed: undefined | T
  160. lastSearched: undefined | T
  161. push(item: T) {
  162. this.lastPushed = item
  163. return super.push(item)
  164. }
  165. indexOf(searchElement: T, fromIndex?: number | undefined): number {
  166. this.lastSearched = searchElement
  167. return super.indexOf(searchElement, fromIndex)
  168. }
  169. }
  170. test('calls correct mutation method on Array subclass', () => {
  171. const subArray = new SubArray(4, 5, 6)
  172. const observed = reactive(subArray)
  173. subArray.push(7)
  174. expect(subArray.lastPushed).toBe(7)
  175. observed.push(9)
  176. expect(observed.lastPushed).toBe(9)
  177. })
  178. test('calls correct identity-sensitive method on Array subclass', () => {
  179. const subArray = new SubArray(4, 5, 6)
  180. const observed = reactive(subArray)
  181. let index
  182. index = subArray.indexOf(4)
  183. expect(index).toBe(0)
  184. expect(subArray.lastSearched).toBe(4)
  185. index = observed.indexOf(6)
  186. expect(index).toBe(2)
  187. expect(observed.lastSearched).toBe(6)
  188. })
  189. })
  190. })