test: 100% coverage for observer
This commit is contained in:
parent
bf38fea313
commit
bb0e15de4d
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
dist
|
dist
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
coverage
|
||||||
explorations
|
explorations
|
||||||
TODOs.md
|
TODOs.md
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
"build": "node scripts/build.js",
|
"build": "node scripts/build.js",
|
||||||
"lint": "prettier --write --parser typescript 'packages/**/*.ts'"
|
"lint": "prettier --write --parser typescript 'packages/**/*.ts'",
|
||||||
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"gitHooks": {
|
"gitHooks": {
|
||||||
"pre-commit": "lint-staged",
|
"pre-commit": "lint-staged",
|
||||||
|
@ -621,6 +621,10 @@ describe('observer/autorun', () => {
|
|||||||
stop(runner)
|
stop(runner)
|
||||||
obj.prop = 3
|
obj.prop = 3
|
||||||
expect(dummy).toBe(2)
|
expect(dummy).toBe(2)
|
||||||
|
|
||||||
|
// stopped runner should still be manually callable
|
||||||
|
runner()
|
||||||
|
expect(dummy).toBe(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('markNonReactive', () => {
|
it('markNonReactive', () => {
|
||||||
|
@ -52,16 +52,26 @@ describe('observer/immutable', () => {
|
|||||||
observed.bar.baz = 3
|
observed.bar.baz = 3
|
||||||
expect(observed.bar.baz).toBe(2)
|
expect(observed.bar.baz).toBe(2)
|
||||||
expect(warn).toHaveBeenCalledTimes(2)
|
expect(warn).toHaveBeenCalledTimes(2)
|
||||||
|
delete observed.foo
|
||||||
|
expect(observed.foo).toBe(1)
|
||||||
|
expect(warn).toHaveBeenCalledTimes(3)
|
||||||
|
delete observed.bar.baz
|
||||||
|
expect(observed.bar.baz).toBe(2)
|
||||||
|
expect(warn).toHaveBeenCalledTimes(4)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should allow mutation when unlocked', () => {
|
it('should allow mutation when unlocked', () => {
|
||||||
const observed = immutable({ foo: 1, bar: { baz: 2 } })
|
const observed: any = immutable({ foo: 1, bar: { baz: 2 } })
|
||||||
unlock()
|
unlock()
|
||||||
observed.foo = 2
|
observed.prop = 2
|
||||||
observed.bar.baz = 3
|
observed.bar.qux = 3
|
||||||
|
delete observed.bar.baz
|
||||||
|
delete observed.foo
|
||||||
lock()
|
lock()
|
||||||
expect(observed.foo).toBe(2)
|
expect(observed.prop).toBe(2)
|
||||||
expect(observed.bar.baz).toBe(3)
|
expect(observed.foo).toBeUndefined()
|
||||||
|
expect(observed.bar.qux).toBe(3)
|
||||||
|
expect('baz' in observed.bar).toBe(false)
|
||||||
expect(warn).not.toHaveBeenCalled()
|
expect(warn).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -190,7 +200,9 @@ describe('observer/immutable', () => {
|
|||||||
lock()
|
lock()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
;[Map, WeakMap].forEach((Collection: any) => {
|
|
||||||
|
const maps = [Map, WeakMap]
|
||||||
|
maps.forEach((Collection: any) => {
|
||||||
describe(Collection.name, () => {
|
describe(Collection.name, () => {
|
||||||
test('should make nested values immutable', () => {
|
test('should make nested values immutable', () => {
|
||||||
const key1 = {}
|
const key1 = {}
|
||||||
@ -224,22 +236,25 @@ describe('observer/immutable', () => {
|
|||||||
|
|
||||||
test('should allow mutation & trigger autorun when unlocked', () => {
|
test('should allow mutation & trigger autorun when unlocked', () => {
|
||||||
const map = immutable(new Collection())
|
const map = immutable(new Collection())
|
||||||
|
const isWeak = Collection === WeakMap
|
||||||
const key = {}
|
const key = {}
|
||||||
let dummy
|
let dummy
|
||||||
autorun(() => {
|
autorun(() => {
|
||||||
dummy = map.get(key)
|
dummy = map.get(key) + (isWeak ? 0 : map.size)
|
||||||
})
|
})
|
||||||
expect(dummy).toBeUndefined()
|
expect(dummy).toBeNaN()
|
||||||
unlock()
|
unlock()
|
||||||
map.set(key, 1)
|
map.set(key, 1)
|
||||||
lock()
|
lock()
|
||||||
expect(dummy).toBe(1)
|
expect(dummy).toBe(isWeak ? 1 : 2)
|
||||||
expect(map.get(key)).toBe(1)
|
expect(map.get(key)).toBe(1)
|
||||||
expect(warn).not.toHaveBeenCalled()
|
expect(warn).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
;[Set, WeakSet].forEach((Collection: any) => {
|
|
||||||
|
const sets = [Set, WeakSet]
|
||||||
|
sets.forEach((Collection: any) => {
|
||||||
describe(Collection.name, () => {
|
describe(Collection.name, () => {
|
||||||
test('should make nested values immutable', () => {
|
test('should make nested values immutable', () => {
|
||||||
const key1 = {}
|
const key1 = {}
|
||||||
|
@ -11,18 +11,21 @@ const builtInSymbols = new Set(
|
|||||||
.filter(value => typeof value === 'symbol')
|
.filter(value => typeof value === 'symbol')
|
||||||
)
|
)
|
||||||
|
|
||||||
function get(
|
function makeGetter(isImmutable: boolean) {
|
||||||
target: any,
|
return function get(target: any, key: string | symbol, receiver: any) {
|
||||||
key: string | symbol,
|
const res = Reflect.get(target, key, receiver)
|
||||||
receiver: any,
|
if (typeof key === 'symbol' && builtInSymbols.has(key)) {
|
||||||
toObservable: (t: any) => any
|
return res
|
||||||
) {
|
}
|
||||||
const res = Reflect.get(target, key, receiver)
|
track(target, OperationTypes.GET, key)
|
||||||
if (typeof key === 'symbol' && builtInSymbols.has(key)) {
|
return res !== null && typeof res === 'object'
|
||||||
return res
|
? isImmutable
|
||||||
|
? // need to lazy access immutable and observable here to avoid
|
||||||
|
// circular dependency
|
||||||
|
immutable(res)
|
||||||
|
: observable(res)
|
||||||
|
: res
|
||||||
}
|
}
|
||||||
track(target, OperationTypes.GET, key)
|
|
||||||
return res !== null && typeof res === 'object' ? toObservable(res) : res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function set(
|
function set(
|
||||||
@ -37,6 +40,7 @@ function set(
|
|||||||
const result = Reflect.set(target, key, value, receiver)
|
const result = Reflect.set(target, key, value, receiver)
|
||||||
// don't trigger if target is something up in the prototype chain of original
|
// don't trigger if target is something up in the prototype chain of original
|
||||||
if (target === unwrap(receiver)) {
|
if (target === unwrap(receiver)) {
|
||||||
|
/* istanbul ignore else */
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
const extraInfo = { oldValue, newValue: value }
|
const extraInfo = { oldValue, newValue: value }
|
||||||
if (!hadKey) {
|
if (!hadKey) {
|
||||||
@ -60,6 +64,7 @@ function deleteProperty(target: any, key: string | symbol): boolean {
|
|||||||
const oldValue = target[key]
|
const oldValue = target[key]
|
||||||
const result = Reflect.deleteProperty(target, key)
|
const result = Reflect.deleteProperty(target, key)
|
||||||
if (hadKey) {
|
if (hadKey) {
|
||||||
|
/* istanbul ignore else */
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
trigger(target, OperationTypes.DELETE, key, { oldValue })
|
trigger(target, OperationTypes.DELETE, key, { oldValue })
|
||||||
} else {
|
} else {
|
||||||
@ -81,8 +86,7 @@ function ownKeys(target: any): (string | number | symbol)[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const mutableHandlers: ProxyHandler<any> = {
|
export const mutableHandlers: ProxyHandler<any> = {
|
||||||
get: (target: any, key: string | symbol, receiver: any) =>
|
get: makeGetter(false),
|
||||||
get(target, key, receiver, observable),
|
|
||||||
set,
|
set,
|
||||||
deleteProperty,
|
deleteProperty,
|
||||||
has,
|
has,
|
||||||
@ -90,8 +94,7 @@ export const mutableHandlers: ProxyHandler<any> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const immutableHandlers: ProxyHandler<any> = {
|
export const immutableHandlers: ProxyHandler<any> = {
|
||||||
get: (target: any, key: string | symbol, receiver: any) =>
|
get: makeGetter(true),
|
||||||
get(target, key, receiver, LOCKED ? immutable : observable),
|
|
||||||
|
|
||||||
set(target: any, key: string | symbol, value: any, receiver: any): boolean {
|
set(target: any, key: string | symbol, value: any, receiver: any): boolean {
|
||||||
if (LOCKED) {
|
if (LOCKED) {
|
||||||
|
@ -34,6 +34,7 @@ function add(value: any) {
|
|||||||
const hadKey = proto.has.call(target, value)
|
const hadKey = proto.has.call(target, value)
|
||||||
const result = proto.add.call(target, value)
|
const result = proto.add.call(target, value)
|
||||||
if (!hadKey) {
|
if (!hadKey) {
|
||||||
|
/* istanbul ignore else */
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
trigger(target, OperationTypes.ADD, value, { value })
|
trigger(target, OperationTypes.ADD, value, { value })
|
||||||
} else {
|
} else {
|
||||||
@ -51,6 +52,7 @@ function set(key: any, value: any) {
|
|||||||
const oldValue = proto.get.call(target, key)
|
const oldValue = proto.get.call(target, key)
|
||||||
const result = proto.set.call(target, key, value)
|
const result = proto.set.call(target, key, value)
|
||||||
if (value !== oldValue) {
|
if (value !== oldValue) {
|
||||||
|
/* istanbul ignore else */
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
const extraInfo = { oldValue, newValue: value }
|
const extraInfo = { oldValue, newValue: value }
|
||||||
if (!hadKey) {
|
if (!hadKey) {
|
||||||
@ -77,6 +79,7 @@ function deleteEntry(key: any) {
|
|||||||
// forward the operation before queueing reactions
|
// forward the operation before queueing reactions
|
||||||
const result = proto.delete.call(target, key)
|
const result = proto.delete.call(target, key)
|
||||||
if (hadKey) {
|
if (hadKey) {
|
||||||
|
/* istanbul ignore else */
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
trigger(target, OperationTypes.DELETE, key, { oldValue })
|
trigger(target, OperationTypes.DELETE, key, { oldValue })
|
||||||
} else {
|
} else {
|
||||||
@ -94,6 +97,7 @@ function clear() {
|
|||||||
// forward the operation before queueing reactions
|
// forward the operation before queueing reactions
|
||||||
const result = proto.clear.call(target)
|
const result = proto.clear.call(target)
|
||||||
if (hadItems) {
|
if (hadItems) {
|
||||||
|
/* istanbul ignore else */
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
trigger(target, OperationTypes.CLEAR, void 0, { oldTarget })
|
trigger(target, OperationTypes.CLEAR, void 0, { oldTarget })
|
||||||
} else {
|
} else {
|
||||||
@ -158,22 +162,21 @@ const immutableInstrumentations: any = {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function getInstrumented(
|
function makeInstrumentationGetter(instrumentations: any) {
|
||||||
target: any,
|
return function getInstrumented(
|
||||||
key: string | symbol,
|
target: any,
|
||||||
receiver: any,
|
key: string | symbol,
|
||||||
instrumentations: any
|
receiver: any
|
||||||
) {
|
) {
|
||||||
target = instrumentations.hasOwnProperty(key) ? instrumentations : target
|
target = instrumentations.hasOwnProperty(key) ? instrumentations : target
|
||||||
return Reflect.get(target, key, receiver)
|
return Reflect.get(target, key, receiver)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutableCollectionHandlers: ProxyHandler<any> = {
|
export const mutableCollectionHandlers: ProxyHandler<any> = {
|
||||||
get: (target: any, key: string | symbol, receiver: any) =>
|
get: makeInstrumentationGetter(mutableInstrumentations)
|
||||||
getInstrumented(target, key, receiver, mutableInstrumentations)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const immutableCollectionHandlers: ProxyHandler<any> = {
|
export const immutableCollectionHandlers: ProxyHandler<any> = {
|
||||||
get: (target: any, key: string | symbol, receiver: any) =>
|
get: makeInstrumentationGetter(immutableInstrumentations)
|
||||||
getInstrumented(target, key, receiver, immutableInstrumentations)
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user