readonly.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. import {
  2. reactive,
  3. readonly,
  4. toRaw,
  5. isReactive,
  6. isReadonly,
  7. markNonReactive,
  8. markReadonly,
  9. lock,
  10. unlock,
  11. effect,
  12. ref
  13. } from '../src'
  14. import { mockWarn } from '@vue/runtime-test'
  15. describe('reactivity/readonly', () => {
  16. mockWarn()
  17. describe('Object', () => {
  18. it('should make nested values readonly', () => {
  19. const original = { foo: 1, bar: { baz: 2 } }
  20. const observed = readonly(original)
  21. expect(observed).not.toBe(original)
  22. expect(isReactive(observed)).toBe(true)
  23. expect(isReadonly(observed)).toBe(true)
  24. expect(isReactive(original)).toBe(false)
  25. expect(isReadonly(original)).toBe(false)
  26. expect(isReactive(observed.bar)).toBe(true)
  27. expect(isReadonly(observed.bar)).toBe(true)
  28. expect(isReactive(original.bar)).toBe(false)
  29. expect(isReadonly(original.bar)).toBe(false)
  30. // get
  31. expect(observed.foo).toBe(1)
  32. // has
  33. expect('foo' in observed).toBe(true)
  34. // ownKeys
  35. expect(Object.keys(observed)).toEqual(['foo', 'bar'])
  36. })
  37. it('should not allow mutation', () => {
  38. const observed: any = readonly({ foo: 1, bar: { baz: 2 } })
  39. observed.foo = 2
  40. expect(observed.foo).toBe(1)
  41. expect(
  42. `Set operation on key "foo" failed: target is readonly.`
  43. ).toHaveBeenWarnedLast()
  44. observed.bar.baz = 3
  45. expect(observed.bar.baz).toBe(2)
  46. expect(
  47. `Set operation on key "baz" failed: target is readonly.`
  48. ).toHaveBeenWarnedLast()
  49. delete observed.foo
  50. expect(observed.foo).toBe(1)
  51. expect(
  52. `Delete operation on key "foo" failed: target is readonly.`
  53. ).toHaveBeenWarnedLast()
  54. delete observed.bar.baz
  55. expect(observed.bar.baz).toBe(2)
  56. expect(
  57. `Delete operation on key "baz" failed: target is readonly.`
  58. ).toHaveBeenWarnedLast()
  59. })
  60. it('should allow mutation when unlocked', () => {
  61. const observed: any = readonly({ foo: 1, bar: { baz: 2 } })
  62. unlock()
  63. observed.prop = 2
  64. observed.bar.qux = 3
  65. delete observed.bar.baz
  66. delete observed.foo
  67. lock()
  68. expect(observed.prop).toBe(2)
  69. expect(observed.foo).toBeUndefined()
  70. expect(observed.bar.qux).toBe(3)
  71. expect('baz' in observed.bar).toBe(false)
  72. expect(`target is readonly`).not.toHaveBeenWarned()
  73. })
  74. it('should not trigger effects when locked', () => {
  75. const observed: any = readonly({ a: 1 })
  76. let dummy
  77. effect(() => {
  78. dummy = observed.a
  79. })
  80. expect(dummy).toBe(1)
  81. observed.a = 2
  82. expect(observed.a).toBe(1)
  83. expect(dummy).toBe(1)
  84. })
  85. it('should trigger effects when unlocked', () => {
  86. const observed: any = readonly({ a: 1 })
  87. let dummy
  88. effect(() => {
  89. dummy = observed.a
  90. })
  91. expect(dummy).toBe(1)
  92. unlock()
  93. observed.a = 2
  94. lock()
  95. expect(observed.a).toBe(2)
  96. expect(dummy).toBe(2)
  97. })
  98. })
  99. describe('Array', () => {
  100. it('should make nested values readonly', () => {
  101. const original: any[] = [{ foo: 1 }]
  102. const observed = readonly(original)
  103. expect(observed).not.toBe(original)
  104. expect(isReactive(observed)).toBe(true)
  105. expect(isReadonly(observed)).toBe(true)
  106. expect(isReactive(original)).toBe(false)
  107. expect(isReadonly(original)).toBe(false)
  108. expect(isReactive(observed[0])).toBe(true)
  109. expect(isReadonly(observed[0])).toBe(true)
  110. expect(isReactive(original[0])).toBe(false)
  111. expect(isReadonly(original[0])).toBe(false)
  112. // get
  113. expect(observed[0].foo).toBe(1)
  114. // has
  115. expect(0 in observed).toBe(true)
  116. // ownKeys
  117. expect(Object.keys(observed)).toEqual(['0'])
  118. })
  119. it('should not allow mutation', () => {
  120. const observed: any = readonly([{ foo: 1 }])
  121. observed[0] = 1
  122. expect(observed[0]).not.toBe(1)
  123. expect(
  124. `Set operation on key "0" failed: target is readonly.`
  125. ).toHaveBeenWarned()
  126. observed[0].foo = 2
  127. expect(observed[0].foo).toBe(1)
  128. expect(
  129. `Set operation on key "foo" failed: target is readonly.`
  130. ).toHaveBeenWarned()
  131. // should block length mutation
  132. observed.length = 0
  133. expect(observed.length).toBe(1)
  134. expect(observed[0].foo).toBe(1)
  135. expect(
  136. `Set operation on key "length" failed: target is readonly.`
  137. ).toHaveBeenWarned()
  138. // mutation methods invoke set/length internally and thus are blocked as well
  139. observed.push(2)
  140. expect(observed.length).toBe(1)
  141. // push triggers two warnings on [1] and .length
  142. expect(`target is readonly.`).toHaveBeenWarnedTimes(5)
  143. })
  144. it('should allow mutation when unlocked', () => {
  145. const observed: any = readonly([{ foo: 1, bar: { baz: 2 } }])
  146. unlock()
  147. observed[1] = 2
  148. observed.push(3)
  149. observed[0].foo = 2
  150. observed[0].bar.baz = 3
  151. lock()
  152. expect(observed.length).toBe(3)
  153. expect(observed[1]).toBe(2)
  154. expect(observed[2]).toBe(3)
  155. expect(observed[0].foo).toBe(2)
  156. expect(observed[0].bar.baz).toBe(3)
  157. expect(`target is readonly`).not.toHaveBeenWarned()
  158. })
  159. it('should not trigger effects when locked', () => {
  160. const observed: any = readonly([{ a: 1 }])
  161. let dummy
  162. effect(() => {
  163. dummy = observed[0].a
  164. })
  165. expect(dummy).toBe(1)
  166. observed[0].a = 2
  167. expect(observed[0].a).toBe(1)
  168. expect(dummy).toBe(1)
  169. observed[0] = { a: 2 }
  170. expect(observed[0].a).toBe(1)
  171. expect(dummy).toBe(1)
  172. })
  173. it('should trigger effects when unlocked', () => {
  174. const observed: any = readonly([{ a: 1 }])
  175. let dummy
  176. effect(() => {
  177. dummy = observed[0].a
  178. })
  179. expect(dummy).toBe(1)
  180. unlock()
  181. observed[0].a = 2
  182. expect(observed[0].a).toBe(2)
  183. expect(dummy).toBe(2)
  184. observed[0] = { a: 3 }
  185. expect(observed[0].a).toBe(3)
  186. expect(dummy).toBe(3)
  187. observed.unshift({ a: 4 })
  188. expect(observed[0].a).toBe(4)
  189. expect(dummy).toBe(4)
  190. lock()
  191. })
  192. })
  193. const maps = [Map, WeakMap]
  194. maps.forEach((Collection: any) => {
  195. describe(Collection.name, () => {
  196. test('should make nested values readonly', () => {
  197. const key1 = {}
  198. const key2 = {}
  199. const original = new Collection([[key1, {}], [key2, {}]])
  200. const observed = readonly(original)
  201. expect(observed).not.toBe(original)
  202. expect(isReactive(observed)).toBe(true)
  203. expect(isReadonly(observed)).toBe(true)
  204. expect(isReactive(original)).toBe(false)
  205. expect(isReadonly(original)).toBe(false)
  206. expect(isReactive(observed.get(key1))).toBe(true)
  207. expect(isReadonly(observed.get(key1))).toBe(true)
  208. expect(isReactive(original.get(key1))).toBe(false)
  209. expect(isReadonly(original.get(key1))).toBe(false)
  210. })
  211. test('should not allow mutation & not trigger effect', () => {
  212. const map = readonly(new Collection())
  213. const key = {}
  214. let dummy
  215. effect(() => {
  216. dummy = map.get(key)
  217. })
  218. expect(dummy).toBeUndefined()
  219. map.set(key, 1)
  220. expect(dummy).toBeUndefined()
  221. expect(map.has(key)).toBe(false)
  222. expect(
  223. `Set operation on key "${key}" failed: target is readonly.`
  224. ).toHaveBeenWarned()
  225. })
  226. test('should allow mutation & trigger effect when unlocked', () => {
  227. const map = readonly(new Collection())
  228. const isWeak = Collection === WeakMap
  229. const key = {}
  230. let dummy
  231. effect(() => {
  232. dummy = map.get(key) + (isWeak ? 0 : map.size)
  233. })
  234. expect(dummy).toBeNaN()
  235. unlock()
  236. map.set(key, 1)
  237. lock()
  238. expect(dummy).toBe(isWeak ? 1 : 2)
  239. expect(map.get(key)).toBe(1)
  240. expect(`target is readonly`).not.toHaveBeenWarned()
  241. })
  242. if (Collection === Map) {
  243. test('should retrive readonly values on iteration', () => {
  244. const key1 = {}
  245. const key2 = {}
  246. const original = new Collection([[key1, {}], [key2, {}]])
  247. const observed: any = readonly(original)
  248. for (const [key, value] of observed) {
  249. expect(isReadonly(key)).toBe(true)
  250. expect(isReadonly(value)).toBe(true)
  251. }
  252. observed.forEach((value: any) => {
  253. expect(isReadonly(value)).toBe(true)
  254. })
  255. for (const value of observed.values()) {
  256. expect(isReadonly(value)).toBe(true)
  257. }
  258. })
  259. }
  260. })
  261. })
  262. const sets = [Set, WeakSet]
  263. sets.forEach((Collection: any) => {
  264. describe(Collection.name, () => {
  265. test('should make nested values readonly', () => {
  266. const key1 = {}
  267. const key2 = {}
  268. const original = new Collection([key1, key2])
  269. const observed = readonly(original)
  270. expect(observed).not.toBe(original)
  271. expect(isReactive(observed)).toBe(true)
  272. expect(isReadonly(observed)).toBe(true)
  273. expect(isReactive(original)).toBe(false)
  274. expect(isReadonly(original)).toBe(false)
  275. expect(observed.has(reactive(key1))).toBe(true)
  276. expect(original.has(reactive(key1))).toBe(false)
  277. })
  278. test('should not allow mutation & not trigger effect', () => {
  279. const set = readonly(new Collection())
  280. const key = {}
  281. let dummy
  282. effect(() => {
  283. dummy = set.has(key)
  284. })
  285. expect(dummy).toBe(false)
  286. set.add(key)
  287. expect(dummy).toBe(false)
  288. expect(set.has(key)).toBe(false)
  289. expect(
  290. `Add operation on key "${key}" failed: target is readonly.`
  291. ).toHaveBeenWarned()
  292. })
  293. test('should allow mutation & trigger effect when unlocked', () => {
  294. const set = readonly(new Collection())
  295. const key = {}
  296. let dummy
  297. effect(() => {
  298. dummy = set.has(key)
  299. })
  300. expect(dummy).toBe(false)
  301. unlock()
  302. set.add(key)
  303. lock()
  304. expect(dummy).toBe(true)
  305. expect(set.has(key)).toBe(true)
  306. expect(`target is readonly`).not.toHaveBeenWarned()
  307. })
  308. if (Collection === Set) {
  309. test('should retrive readonly values on iteration', () => {
  310. const original = new Collection([{}, {}])
  311. const observed: any = readonly(original)
  312. for (const value of observed) {
  313. expect(isReadonly(value)).toBe(true)
  314. }
  315. observed.forEach((value: any) => {
  316. expect(isReadonly(value)).toBe(true)
  317. })
  318. for (const value of observed.values()) {
  319. expect(isReadonly(value)).toBe(true)
  320. }
  321. for (const [v1, v2] of observed.entries()) {
  322. expect(isReadonly(v1)).toBe(true)
  323. expect(isReadonly(v2)).toBe(true)
  324. }
  325. })
  326. }
  327. })
  328. })
  329. test('calling reactive on an readonly should return readonly', () => {
  330. const a = readonly({})
  331. const b = reactive(a)
  332. expect(isReadonly(b)).toBe(true)
  333. // should point to same original
  334. expect(toRaw(a)).toBe(toRaw(b))
  335. })
  336. test('calling readonly on a reactive object should return readonly', () => {
  337. const a = reactive({})
  338. const b = readonly(a)
  339. expect(isReadonly(b)).toBe(true)
  340. // should point to same original
  341. expect(toRaw(a)).toBe(toRaw(b))
  342. })
  343. test('observing already observed value should return same Proxy', () => {
  344. const original = { foo: 1 }
  345. const observed = readonly(original)
  346. const observed2 = readonly(observed)
  347. expect(observed2).toBe(observed)
  348. })
  349. test('observing the same value multiple times should return same Proxy', () => {
  350. const original = { foo: 1 }
  351. const observed = readonly(original)
  352. const observed2 = readonly(original)
  353. expect(observed2).toBe(observed)
  354. })
  355. test('markNonReactive', () => {
  356. const obj = readonly({
  357. foo: { a: 1 },
  358. bar: markNonReactive({ b: 2 })
  359. })
  360. expect(isReactive(obj.foo)).toBe(true)
  361. expect(isReactive(obj.bar)).toBe(false)
  362. })
  363. test('markReadonly', () => {
  364. const obj = reactive({
  365. foo: { a: 1 },
  366. bar: markReadonly({ b: 2 })
  367. })
  368. expect(isReactive(obj.foo)).toBe(true)
  369. expect(isReactive(obj.bar)).toBe(true)
  370. expect(isReadonly(obj.foo)).toBe(false)
  371. expect(isReadonly(obj.bar)).toBe(true)
  372. })
  373. test('should make ref readonly', () => {
  374. const n: any = readonly(ref(1))
  375. n.value = 2
  376. expect(n.value).toBe(1)
  377. expect(
  378. `Set operation on key "value" failed: target is readonly.`
  379. ).toHaveBeenWarned()
  380. })
  381. })