/
reactive.spec.ts
240 lines (216 loc) · 6.32 KB
/
reactive.spec.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
import {
ref,
isRef,
reactive,
isReactive,
computed,
toRaw,
shallowReactive,
set,
markRaw,
} from '../../../src'
describe('reactivity/reactive', () => {
let warn: jest.SpyInstance
beforeEach(() => {
warn = jest.spyOn(global.console, 'error').mockImplementation(() => null)
warn.mockReset()
})
afterEach(() => {
expect(warn).not.toBeCalled()
warn.mockRestore()
})
test('Object', () => {
const original = { foo: 1 }
const observed = reactive(original)
expect(observed).toBe(original)
expect(isReactive(observed)).toBe(true)
expect(isReactive(original)).toBe(true) // this is false in v3 but true in v2
// get
expect(observed.foo).toBe(1)
// has
expect('foo' in observed).toBe(true)
// ownKeys
expect(Object.keys(observed)).toEqual(['foo'])
})
test('proto', () => {
const obj = {}
const reactiveObj = reactive(obj)
expect(isReactive(reactiveObj)).toBe(true)
// read prop of reactiveObject will cause reactiveObj[prop] to be reactive
// @ts-ignore
const prototype = reactiveObj['__proto__']
const otherObj = { data: ['a'] }
expect(isReactive(otherObj)).toBe(false)
const reactiveOther = reactive(otherObj)
expect(isReactive(reactiveOther)).toBe(true)
expect(reactiveOther.data[0]).toBe('a')
})
test('nested reactives', () => {
const original = {
nested: {
foo: 1,
},
array: [{ bar: 2 }],
}
const observed = reactive(original)
expect(isReactive(observed.nested)).toBe(true)
expect(isReactive(observed.array)).toBe(true)
expect(isReactive(observed.array[0])).toBe(true)
})
test('observed value should proxy mutations to original (Object)', () => {
const original: any = { foo: 1 }
const observed = reactive(original)
// set
observed.bar = 1
expect(observed.bar).toBe(1)
expect(original.bar).toBe(1)
// delete
delete observed.foo
expect('foo' in observed).toBe(false)
expect('foo' in original).toBe(false)
})
test('setting a property with an unobserved value should wrap with reactive', () => {
const observed = reactive<{ foo?: object }>({})
const raw = {}
set(observed, 'foo', raw) // v2 limitation
expect(observed.foo).toBe(raw) // v2 limitation
expect(isReactive(observed.foo)).toBe(true)
})
test('observing already observed value should return same Proxy', () => {
const original = { foo: 1 }
const observed = reactive(original)
const observed2 = reactive(observed)
expect(observed2).toBe(observed)
})
test('observing the same value multiple times should return same Proxy', () => {
const original = { foo: 1 }
const observed = reactive(original)
const observed2 = reactive(original)
expect(observed2).toBe(observed)
})
test('should not pollute original object with Proxies', () => {
const original: any = { foo: 1 }
const original2 = { bar: 2 }
const observed = reactive(original)
const observed2 = reactive(original2)
observed.bar = observed2
expect(observed.bar).toBe(observed2)
expect(original.bar).toBe(original2)
})
test('unwrap', () => {
// vue2 mutates the original object
const original = { foo: 1 }
const observed = reactive(original)
expect(toRaw(observed)).toBe(original)
expect(toRaw(original)).toBe(original)
})
test('should not unwrap Ref<T>', () => {
const observedNumberRef = reactive(ref(1))
const observedObjectRef = reactive(ref({ foo: 1 }))
expect(isRef(observedNumberRef)).toBe(true)
expect(isRef(observedObjectRef)).toBe(true)
})
test('should unwrap computed refs', () => {
// readonly
const a = computed(() => 1)
// writable
const b = computed({
get: () => 1,
set: () => {},
})
const obj = reactive({ a, b })
// check type
obj.a + 1
obj.b + 1
expect(typeof obj.a).toBe(`number`)
expect(typeof obj.b).toBe(`number`)
})
test('non-observable values', () => {
const assertValue = (value: any) => {
expect(isReactive(reactive(value))).toBe(false)
expect(reactive(value)).toBe(value)
// expect(warnSpy).toHaveBeenLastCalledWith(`value cannot be made reactive: ${String(value)}`);
}
// number
assertValue(1)
// string
assertValue('foo')
// boolean
assertValue(false)
// null
assertValue(null)
// undefined
assertValue(undefined)
// symbol
const s = Symbol()
assertValue(s)
// built-ins should work and return same value
const p = Promise.resolve()
expect(reactive(p)).toBe(p)
const r = new RegExp('')
expect(reactive(r)).toBe(r)
const d = new Date()
expect(reactive(d)).toBe(d)
expect(warn).toBeCalledTimes(12)
expect(
warn.mock.calls.map((call) => {
expect(call[0]).toBe(
'[Vue warn]: "reactive()" is called without provide an "object".'
)
})
)
warn.mockReset()
})
test('markRaw', () => {
const obj = reactive({
foo: { a: 1 },
bar: markRaw({ b: 2 }),
})
expect(isReactive(obj.foo)).toBe(true)
expect(isReactive(obj.bar)).toBe(false)
})
test('should not observe frozen objects', () => {
const obj = reactive({
foo: Object.freeze({ a: 1 }),
})
expect(isReactive(obj.foo)).toBe(false)
})
describe('shallowReactive', () => {
test('should not make non-reactive properties reactive', () => {
const props = shallowReactive({ n: { foo: 1 } })
expect(isReactive(props.n)).toBe(false)
})
test('should keep reactive properties reactive', () => {
const props: any = shallowReactive({ n: reactive({ foo: 1 }) })
props.n = reactive({ foo: 2 })
expect(isReactive(props.n)).toBe(true)
})
})
test('should shallowReactive non-observable values', () => {
const assertValue = (value: any) => {
expect(shallowReactive(value)).toBe(value)
}
// number
assertValue(1)
// string
assertValue('foo')
// boolean
assertValue(false)
// null
assertValue(null)
// undefined
assertValue(undefined)
// symbol
const s = Symbol()
assertValue(s)
expect(warn).toBeCalledTimes(6)
expect(
warn.mock.calls.map((call) => {
expect(call[0]).toBe(
'[Vue warn]: "shallowReactive()" is called without provide an "object".'
)
})
)
warn.mockReset()
})
})