Skip to content

Commit 68de9f4

Browse files
authored
fix(reactivity): fix shallow readonly behavior for collections (#3003)
fix #3007
1 parent 9cb21d0 commit 68de9f4

File tree

5 files changed

+393
-35
lines changed

5 files changed

+393
-35
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { isReactive, isReadonly, shallowReadonly } from '../../src'
2+
3+
describe('reactivity/collections', () => {
4+
describe('shallowReadonly/Map', () => {
5+
;[Map, WeakMap].forEach(Collection => {
6+
test('should make the map/weak-map readonly', () => {
7+
const key = {}
8+
const val = { foo: 1 }
9+
const original = new Collection([[key, val]])
10+
const sroMap = shallowReadonly(original)
11+
expect(isReadonly(sroMap)).toBe(true)
12+
expect(isReactive(sroMap)).toBe(false)
13+
expect(sroMap.get(key)).toBe(val)
14+
15+
sroMap.set(key, {} as any)
16+
expect(
17+
`Set operation on key "[object Object]" failed: target is readonly.`
18+
).toHaveBeenWarned()
19+
})
20+
21+
test('should not make nested values readonly', () => {
22+
const key = {}
23+
const val = { foo: 1 }
24+
const original = new Collection([[key, val]])
25+
const sroMap = shallowReadonly(original)
26+
expect(isReadonly(sroMap.get(key))).toBe(false)
27+
expect(isReactive(sroMap.get(key))).toBe(false)
28+
29+
sroMap.get(key)!.foo = 2
30+
expect(
31+
`Set operation on key "foo" failed: target is readonly.`
32+
).not.toHaveBeenWarned()
33+
})
34+
})
35+
36+
test('should not make the value generated by the iterable method readonly', () => {
37+
const key = {}
38+
const val = { foo: 1 }
39+
const original = new Map([[key, val]])
40+
const sroMap = shallowReadonly(original)
41+
42+
const values1 = [...sroMap.values()]
43+
const values2 = [...sroMap.entries()]
44+
45+
expect(isReadonly(values1[0])).toBe(false)
46+
expect(isReactive(values1[0])).toBe(false)
47+
expect(values1[0]).toBe(val)
48+
49+
values1[0].foo = 2
50+
expect(
51+
`Set operation on key "foo" failed: target is readonly.`
52+
).not.toHaveBeenWarned()
53+
54+
expect(isReadonly(values2[0][1])).toBe(false)
55+
expect(isReactive(values2[0][1])).toBe(false)
56+
expect(values2[0][1]).toBe(val)
57+
58+
values2[0][1].foo = 2
59+
expect(
60+
`Set operation on key "foo" failed: target is readonly.`
61+
).not.toHaveBeenWarned()
62+
})
63+
64+
test('should not make the value generated by the forEach method readonly', () => {
65+
const val = { foo: 1 }
66+
const original = new Map([['key', val]])
67+
const sroMap = shallowReadonly(original)
68+
69+
sroMap.forEach(val => {
70+
expect(isReadonly(val)).toBe(false)
71+
expect(isReactive(val)).toBe(false)
72+
expect(val).toBe(val)
73+
74+
val.foo = 2
75+
expect(
76+
`Set operation on key "foo" failed: target is readonly.`
77+
).not.toHaveBeenWarned()
78+
})
79+
})
80+
})
81+
82+
describe('shallowReadonly/Set', () => {
83+
test('should make the set/weak-set readonly', () => {
84+
;[Set, WeakSet].forEach(Collection => {
85+
const obj = { foo: 1 }
86+
const original = new Collection([obj])
87+
const sroSet = shallowReadonly(original)
88+
expect(isReadonly(sroSet)).toBe(true)
89+
expect(isReactive(sroSet)).toBe(false)
90+
expect(sroSet.has(obj)).toBe(true)
91+
92+
sroSet.add({} as any)
93+
expect(
94+
`Add operation on key "[object Object]" failed: target is readonly.`
95+
).toHaveBeenWarned()
96+
})
97+
})
98+
99+
test('should not make nested values readonly', () => {
100+
const obj = { foo: 1 }
101+
const original = new Set([obj])
102+
const sroSet = shallowReadonly(original)
103+
104+
const values = [...sroSet.values()]
105+
106+
expect(values[0]).toBe(obj)
107+
expect(isReadonly(values[0])).toBe(false)
108+
expect(isReactive(values[0])).toBe(false)
109+
110+
sroSet.add({} as any)
111+
expect(
112+
`Add operation on key "[object Object]" failed: target is readonly.`
113+
).toHaveBeenWarned()
114+
115+
values[0].foo = 2
116+
expect(
117+
`Set operation on key "foo" failed: target is readonly.`
118+
).not.toHaveBeenWarned()
119+
})
120+
121+
test('should not make the value generated by the iterable method readonly', () => {
122+
const val = { foo: 1 }
123+
const original = new Set([val])
124+
const sroSet = shallowReadonly(original)
125+
126+
const values1 = [...sroSet.values()]
127+
const values2 = [...sroSet.entries()]
128+
129+
expect(isReadonly(values1[0])).toBe(false)
130+
expect(isReactive(values1[0])).toBe(false)
131+
expect(values1[0]).toBe(val)
132+
133+
values1[0].foo = 2
134+
expect(
135+
`Set operation on key "foo" failed: target is readonly.`
136+
).not.toHaveBeenWarned()
137+
138+
expect(isReadonly(values2[0][1])).toBe(false)
139+
expect(isReactive(values2[0][1])).toBe(false)
140+
expect(values2[0][1]).toBe(val)
141+
142+
values2[0][1].foo = 2
143+
expect(
144+
`Set operation on key "foo" failed: target is readonly.`
145+
).not.toHaveBeenWarned()
146+
})
147+
148+
test('should not make the value generated by the forEach method readonly', () => {
149+
const val = { foo: 1 }
150+
const original = new Set([val])
151+
const sroSet = shallowReadonly(original)
152+
153+
sroSet.forEach(val => {
154+
expect(isReadonly(val)).toBe(false)
155+
expect(isReactive(val)).toBe(false)
156+
expect(val).toBe(val)
157+
158+
val.foo = 2
159+
expect(
160+
`Set operation on key "foo" failed: target is readonly.`
161+
).not.toHaveBeenWarned()
162+
})
163+
})
164+
})
165+
})

packages/reactivity/__tests__/readonly.spec.ts

-29
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
markRaw,
88
effect,
99
ref,
10-
shallowReadonly,
1110
isProxy,
1211
computed
1312
} from '../src'
@@ -455,32 +454,4 @@ describe('reactivity/readonly', () => {
455454
'Set operation on key "randomProperty" failed: target is readonly.'
456455
).toHaveBeenWarned()
457456
})
458-
459-
describe('shallowReadonly', () => {
460-
test('should not make non-reactive properties reactive', () => {
461-
const props = shallowReadonly({ n: { foo: 1 } })
462-
expect(isReactive(props.n)).toBe(false)
463-
})
464-
465-
test('should make root level properties readonly', () => {
466-
const props = shallowReadonly({ n: 1 })
467-
// @ts-ignore
468-
props.n = 2
469-
expect(props.n).toBe(1)
470-
expect(
471-
`Set operation on key "n" failed: target is readonly.`
472-
).toHaveBeenWarned()
473-
})
474-
475-
// to retain 2.x behavior.
476-
test('should NOT make nested properties readonly', () => {
477-
const props = shallowReadonly({ n: { foo: 1 } })
478-
// @ts-ignore
479-
props.n.foo = 2
480-
expect(props.n.foo).toBe(2)
481-
expect(
482-
`Set operation on key "foo" failed: target is readonly.`
483-
).not.toHaveBeenWarned()
484-
})
485-
})
486457
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { isReactive, isReadonly, shallowReadonly } from '../src'
2+
3+
describe('reactivity/shallowReadonly', () => {
4+
test('should not make non-reactive properties reactive', () => {
5+
const props = shallowReadonly({ n: { foo: 1 } })
6+
expect(isReactive(props.n)).toBe(false)
7+
})
8+
9+
test('should make root level properties readonly', () => {
10+
const props = shallowReadonly({ n: 1 })
11+
// @ts-ignore
12+
props.n = 2
13+
expect(props.n).toBe(1)
14+
expect(
15+
`Set operation on key "n" failed: target is readonly.`
16+
).toHaveBeenWarned()
17+
})
18+
19+
// to retain 2.x behavior.
20+
test('should NOT make nested properties readonly', () => {
21+
const props = shallowReadonly({ n: { foo: 1 } })
22+
// @ts-ignore
23+
props.n.foo = 2
24+
expect(props.n.foo).toBe(2)
25+
expect(
26+
`Set operation on key "foo" failed: target is readonly.`
27+
).not.toHaveBeenWarned()
28+
})
29+
30+
describe('collection/Map', () => {
31+
;[Map, WeakMap].forEach(Collection => {
32+
test('should make the map/weak-map readonly', () => {
33+
const key = {}
34+
const val = { foo: 1 }
35+
const original = new Collection([[key, val]])
36+
const sroMap = shallowReadonly(original)
37+
expect(isReadonly(sroMap)).toBe(true)
38+
expect(isReactive(sroMap)).toBe(false)
39+
expect(sroMap.get(key)).toBe(val)
40+
41+
sroMap.set(key, {} as any)
42+
expect(
43+
`Set operation on key "[object Object]" failed: target is readonly.`
44+
).toHaveBeenWarned()
45+
})
46+
47+
test('should not make nested values readonly', () => {
48+
const key = {}
49+
const val = { foo: 1 }
50+
const original = new Collection([[key, val]])
51+
const sroMap = shallowReadonly(original)
52+
expect(isReadonly(sroMap.get(key))).toBe(false)
53+
expect(isReactive(sroMap.get(key))).toBe(false)
54+
55+
sroMap.get(key)!.foo = 2
56+
expect(
57+
`Set operation on key "foo" failed: target is readonly.`
58+
).not.toHaveBeenWarned()
59+
})
60+
})
61+
62+
test('should not make the value generated by the iterable method readonly', () => {
63+
const key = {}
64+
const val = { foo: 1 }
65+
const original = new Map([[key, val]])
66+
const sroMap = shallowReadonly(original)
67+
68+
const values1 = [...sroMap.values()]
69+
const values2 = [...sroMap.entries()]
70+
71+
expect(isReadonly(values1[0])).toBe(false)
72+
expect(isReactive(values1[0])).toBe(false)
73+
expect(values1[0]).toBe(val)
74+
75+
values1[0].foo = 2
76+
expect(
77+
`Set operation on key "foo" failed: target is readonly.`
78+
).not.toHaveBeenWarned()
79+
80+
expect(isReadonly(values2[0][1])).toBe(false)
81+
expect(isReactive(values2[0][1])).toBe(false)
82+
expect(values2[0][1]).toBe(val)
83+
84+
values2[0][1].foo = 2
85+
expect(
86+
`Set operation on key "foo" failed: target is readonly.`
87+
).not.toHaveBeenWarned()
88+
})
89+
90+
test('should not make the value generated by the forEach method readonly', () => {
91+
const val = { foo: 1 }
92+
const original = new Map([['key', val]])
93+
const sroMap = shallowReadonly(original)
94+
95+
sroMap.forEach(val => {
96+
expect(isReadonly(val)).toBe(false)
97+
expect(isReactive(val)).toBe(false)
98+
expect(val).toBe(val)
99+
100+
val.foo = 2
101+
expect(
102+
`Set operation on key "foo" failed: target is readonly.`
103+
).not.toHaveBeenWarned()
104+
})
105+
})
106+
})
107+
108+
describe('collection/Set', () => {
109+
test('should make the set/weak-set readonly', () => {
110+
;[Set, WeakSet].forEach(Collection => {
111+
const obj = { foo: 1 }
112+
const original = new Collection([obj])
113+
const sroSet = shallowReadonly(original)
114+
expect(isReadonly(sroSet)).toBe(true)
115+
expect(isReactive(sroSet)).toBe(false)
116+
expect(sroSet.has(obj)).toBe(true)
117+
118+
sroSet.add({} as any)
119+
expect(
120+
`Add operation on key "[object Object]" failed: target is readonly.`
121+
).toHaveBeenWarned()
122+
})
123+
})
124+
125+
test('should not make nested values readonly', () => {
126+
const obj = { foo: 1 }
127+
const original = new Set([obj])
128+
const sroSet = shallowReadonly(original)
129+
130+
const values = [...sroSet.values()]
131+
132+
expect(values[0]).toBe(obj)
133+
expect(isReadonly(values[0])).toBe(false)
134+
expect(isReactive(values[0])).toBe(false)
135+
136+
sroSet.add({} as any)
137+
expect(
138+
`Add operation on key "[object Object]" failed: target is readonly.`
139+
).toHaveBeenWarned()
140+
141+
values[0].foo = 2
142+
expect(
143+
`Set operation on key "foo" failed: target is readonly.`
144+
).not.toHaveBeenWarned()
145+
})
146+
147+
test('should not make the value generated by the iterable method readonly', () => {
148+
const val = { foo: 1 }
149+
const original = new Set([val])
150+
const sroSet = shallowReadonly(original)
151+
152+
const values1 = [...sroSet.values()]
153+
const values2 = [...sroSet.entries()]
154+
155+
expect(isReadonly(values1[0])).toBe(false)
156+
expect(isReactive(values1[0])).toBe(false)
157+
expect(values1[0]).toBe(val)
158+
159+
values1[0].foo = 2
160+
expect(
161+
`Set operation on key "foo" failed: target is readonly.`
162+
).not.toHaveBeenWarned()
163+
164+
expect(isReadonly(values2[0][1])).toBe(false)
165+
expect(isReactive(values2[0][1])).toBe(false)
166+
expect(values2[0][1]).toBe(val)
167+
168+
values2[0][1].foo = 2
169+
expect(
170+
`Set operation on key "foo" failed: target is readonly.`
171+
).not.toHaveBeenWarned()
172+
})
173+
174+
test('should not make the value generated by the forEach method readonly', () => {
175+
const val = { foo: 1 }
176+
const original = new Set([val])
177+
const sroSet = shallowReadonly(original)
178+
179+
sroSet.forEach(val => {
180+
expect(isReadonly(val)).toBe(false)
181+
expect(isReactive(val)).toBe(false)
182+
expect(val).toBe(val)
183+
184+
val.foo = 2
185+
expect(
186+
`Set operation on key "foo" failed: target is readonly.`
187+
).not.toHaveBeenWarned()
188+
})
189+
})
190+
})
191+
})

0 commit comments

Comments
 (0)