Skip to content

Commit

Permalink
fix(reactivity): fix shallow readonly behavior for collections (#3003)
Browse files Browse the repository at this point in the history
fix #3007
  • Loading branch information
HcySunYang authored Mar 26, 2021
1 parent 9cb21d0 commit 68de9f4
Show file tree
Hide file tree
Showing 5 changed files with 393 additions and 35 deletions.
165 changes: 165 additions & 0 deletions packages/reactivity/__tests__/collections/shallowReadonly.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { isReactive, isReadonly, shallowReadonly } from '../../src'

describe('reactivity/collections', () => {
describe('shallowReadonly/Map', () => {
;[Map, WeakMap].forEach(Collection => {
test('should make the map/weak-map readonly', () => {
const key = {}
const val = { foo: 1 }
const original = new Collection([[key, val]])
const sroMap = shallowReadonly(original)
expect(isReadonly(sroMap)).toBe(true)
expect(isReactive(sroMap)).toBe(false)
expect(sroMap.get(key)).toBe(val)

sroMap.set(key, {} as any)
expect(
`Set operation on key "[object Object]" failed: target is readonly.`
).toHaveBeenWarned()
})

test('should not make nested values readonly', () => {
const key = {}
const val = { foo: 1 }
const original = new Collection([[key, val]])
const sroMap = shallowReadonly(original)
expect(isReadonly(sroMap.get(key))).toBe(false)
expect(isReactive(sroMap.get(key))).toBe(false)

sroMap.get(key)!.foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})
})

test('should not make the value generated by the iterable method readonly', () => {
const key = {}
const val = { foo: 1 }
const original = new Map([[key, val]])
const sroMap = shallowReadonly(original)

const values1 = [...sroMap.values()]
const values2 = [...sroMap.entries()]

expect(isReadonly(values1[0])).toBe(false)
expect(isReactive(values1[0])).toBe(false)
expect(values1[0]).toBe(val)

values1[0].foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()

expect(isReadonly(values2[0][1])).toBe(false)
expect(isReactive(values2[0][1])).toBe(false)
expect(values2[0][1]).toBe(val)

values2[0][1].foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})

test('should not make the value generated by the forEach method readonly', () => {
const val = { foo: 1 }
const original = new Map([['key', val]])
const sroMap = shallowReadonly(original)

sroMap.forEach(val => {
expect(isReadonly(val)).toBe(false)
expect(isReactive(val)).toBe(false)
expect(val).toBe(val)

val.foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})
})
})

describe('shallowReadonly/Set', () => {
test('should make the set/weak-set readonly', () => {
;[Set, WeakSet].forEach(Collection => {
const obj = { foo: 1 }
const original = new Collection([obj])
const sroSet = shallowReadonly(original)
expect(isReadonly(sroSet)).toBe(true)
expect(isReactive(sroSet)).toBe(false)
expect(sroSet.has(obj)).toBe(true)

sroSet.add({} as any)
expect(
`Add operation on key "[object Object]" failed: target is readonly.`
).toHaveBeenWarned()
})
})

test('should not make nested values readonly', () => {
const obj = { foo: 1 }
const original = new Set([obj])
const sroSet = shallowReadonly(original)

const values = [...sroSet.values()]

expect(values[0]).toBe(obj)
expect(isReadonly(values[0])).toBe(false)
expect(isReactive(values[0])).toBe(false)

sroSet.add({} as any)
expect(
`Add operation on key "[object Object]" failed: target is readonly.`
).toHaveBeenWarned()

values[0].foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})

test('should not make the value generated by the iterable method readonly', () => {
const val = { foo: 1 }
const original = new Set([val])
const sroSet = shallowReadonly(original)

const values1 = [...sroSet.values()]
const values2 = [...sroSet.entries()]

expect(isReadonly(values1[0])).toBe(false)
expect(isReactive(values1[0])).toBe(false)
expect(values1[0]).toBe(val)

values1[0].foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()

expect(isReadonly(values2[0][1])).toBe(false)
expect(isReactive(values2[0][1])).toBe(false)
expect(values2[0][1]).toBe(val)

values2[0][1].foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})

test('should not make the value generated by the forEach method readonly', () => {
const val = { foo: 1 }
const original = new Set([val])
const sroSet = shallowReadonly(original)

sroSet.forEach(val => {
expect(isReadonly(val)).toBe(false)
expect(isReactive(val)).toBe(false)
expect(val).toBe(val)

val.foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})
})
})
})
29 changes: 0 additions & 29 deletions packages/reactivity/__tests__/readonly.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
markRaw,
effect,
ref,
shallowReadonly,
isProxy,
computed
} from '../src'
Expand Down Expand Up @@ -455,32 +454,4 @@ describe('reactivity/readonly', () => {
'Set operation on key "randomProperty" failed: target is readonly.'
).toHaveBeenWarned()
})

describe('shallowReadonly', () => {
test('should not make non-reactive properties reactive', () => {
const props = shallowReadonly({ n: { foo: 1 } })
expect(isReactive(props.n)).toBe(false)
})

test('should make root level properties readonly', () => {
const props = shallowReadonly({ n: 1 })
// @ts-ignore
props.n = 2
expect(props.n).toBe(1)
expect(
`Set operation on key "n" failed: target is readonly.`
).toHaveBeenWarned()
})

// to retain 2.x behavior.
test('should NOT make nested properties readonly', () => {
const props = shallowReadonly({ n: { foo: 1 } })
// @ts-ignore
props.n.foo = 2
expect(props.n.foo).toBe(2)
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})
})
})
191 changes: 191 additions & 0 deletions packages/reactivity/__tests__/shallowReadonly.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { isReactive, isReadonly, shallowReadonly } from '../src'

describe('reactivity/shallowReadonly', () => {
test('should not make non-reactive properties reactive', () => {
const props = shallowReadonly({ n: { foo: 1 } })
expect(isReactive(props.n)).toBe(false)
})

test('should make root level properties readonly', () => {
const props = shallowReadonly({ n: 1 })
// @ts-ignore
props.n = 2
expect(props.n).toBe(1)
expect(
`Set operation on key "n" failed: target is readonly.`
).toHaveBeenWarned()
})

// to retain 2.x behavior.
test('should NOT make nested properties readonly', () => {
const props = shallowReadonly({ n: { foo: 1 } })
// @ts-ignore
props.n.foo = 2
expect(props.n.foo).toBe(2)
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})

describe('collection/Map', () => {
;[Map, WeakMap].forEach(Collection => {
test('should make the map/weak-map readonly', () => {
const key = {}
const val = { foo: 1 }
const original = new Collection([[key, val]])
const sroMap = shallowReadonly(original)
expect(isReadonly(sroMap)).toBe(true)
expect(isReactive(sroMap)).toBe(false)
expect(sroMap.get(key)).toBe(val)

sroMap.set(key, {} as any)
expect(
`Set operation on key "[object Object]" failed: target is readonly.`
).toHaveBeenWarned()
})

test('should not make nested values readonly', () => {
const key = {}
const val = { foo: 1 }
const original = new Collection([[key, val]])
const sroMap = shallowReadonly(original)
expect(isReadonly(sroMap.get(key))).toBe(false)
expect(isReactive(sroMap.get(key))).toBe(false)

sroMap.get(key)!.foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})
})

test('should not make the value generated by the iterable method readonly', () => {
const key = {}
const val = { foo: 1 }
const original = new Map([[key, val]])
const sroMap = shallowReadonly(original)

const values1 = [...sroMap.values()]
const values2 = [...sroMap.entries()]

expect(isReadonly(values1[0])).toBe(false)
expect(isReactive(values1[0])).toBe(false)
expect(values1[0]).toBe(val)

values1[0].foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()

expect(isReadonly(values2[0][1])).toBe(false)
expect(isReactive(values2[0][1])).toBe(false)
expect(values2[0][1]).toBe(val)

values2[0][1].foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})

test('should not make the value generated by the forEach method readonly', () => {
const val = { foo: 1 }
const original = new Map([['key', val]])
const sroMap = shallowReadonly(original)

sroMap.forEach(val => {
expect(isReadonly(val)).toBe(false)
expect(isReactive(val)).toBe(false)
expect(val).toBe(val)

val.foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})
})
})

describe('collection/Set', () => {
test('should make the set/weak-set readonly', () => {
;[Set, WeakSet].forEach(Collection => {
const obj = { foo: 1 }
const original = new Collection([obj])
const sroSet = shallowReadonly(original)
expect(isReadonly(sroSet)).toBe(true)
expect(isReactive(sroSet)).toBe(false)
expect(sroSet.has(obj)).toBe(true)

sroSet.add({} as any)
expect(
`Add operation on key "[object Object]" failed: target is readonly.`
).toHaveBeenWarned()
})
})

test('should not make nested values readonly', () => {
const obj = { foo: 1 }
const original = new Set([obj])
const sroSet = shallowReadonly(original)

const values = [...sroSet.values()]

expect(values[0]).toBe(obj)
expect(isReadonly(values[0])).toBe(false)
expect(isReactive(values[0])).toBe(false)

sroSet.add({} as any)
expect(
`Add operation on key "[object Object]" failed: target is readonly.`
).toHaveBeenWarned()

values[0].foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})

test('should not make the value generated by the iterable method readonly', () => {
const val = { foo: 1 }
const original = new Set([val])
const sroSet = shallowReadonly(original)

const values1 = [...sroSet.values()]
const values2 = [...sroSet.entries()]

expect(isReadonly(values1[0])).toBe(false)
expect(isReactive(values1[0])).toBe(false)
expect(values1[0]).toBe(val)

values1[0].foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()

expect(isReadonly(values2[0][1])).toBe(false)
expect(isReactive(values2[0][1])).toBe(false)
expect(values2[0][1]).toBe(val)

values2[0][1].foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})

test('should not make the value generated by the forEach method readonly', () => {
const val = { foo: 1 }
const original = new Set([val])
const sroSet = shallowReadonly(original)

sroSet.forEach(val => {
expect(isReadonly(val)).toBe(false)
expect(isReactive(val)).toBe(false)
expect(val).toBe(val)

val.foo = 2
expect(
`Set operation on key "foo" failed: target is readonly.`
).not.toHaveBeenWarned()
})
})
})
})
Loading

0 comments on commit 68de9f4

Please sign in to comment.