map.delete('key')
expect(dummy).toBe(undefined)
})
+
+ it('should not pollute original Map with Proxies', () => {
+ const map = new Map()
+ const observed = observable(map)
+ const value = observable({})
+ observed.set('key', value)
+ expect(map.get('key')).not.toBe(value)
+ expect(map.get('key')).toBe(unwrap(value))
+ })
+
+ it('should return observable versions of contained values', () => {
+ const observed = observable(new Map())
+ const value = {}
+ observed.set('key', value)
+ const wrapped = observed.get('key')
+ expect(isObservable(wrapped)).toBe(true)
+ expect(unwrap(wrapped)).toBe(value)
+ })
+
+ it('should observed nested data', () => {
+ const observed = observable(new Map())
+ observed.set('key', { a: 1 })
+ let dummy
+ autorun(() => {
+ dummy = observed.get('key').a
+ })
+ observed.get('key').a = 2
+ expect(dummy).toBe(2)
+ })
})
})
import { observable, autorun, isObservable, unwrap } from '../../src'
-describe('Set', () => {
- it('instanceof', () => {
- const original = new Set()
- const observed = observable(original)
- expect(isObservable(observed)).toBe(true)
- expect(original instanceof Set).toBe(true)
- expect(observed instanceof Set).toBe(true)
- })
+describe('observer/collections', () => {
+ describe('Set', () => {
+ it('instanceof', () => {
+ const original = new Set()
+ const observed = observable(original)
+ expect(isObservable(observed)).toBe(true)
+ expect(original instanceof Set).toBe(true)
+ expect(observed instanceof Set).toBe(true)
+ })
- it('should observe mutations', () => {
- let dummy
- const set = observable(new Set())
- autorun(() => (dummy = set.has('value')))
+ it('should observe mutations', () => {
+ let dummy
+ const set = observable(new Set())
+ autorun(() => (dummy = set.has('value')))
- expect(dummy).toBe(false)
- set.add('value')
- expect(dummy).toBe(true)
- set.delete('value')
- expect(dummy).toBe(false)
- })
-
- it('should observe for of iteration', () => {
- let dummy
- const set = observable(new Set())
- autorun(() => {
- dummy = 0
- for (let num of set) {
- dummy += num
- }
+ expect(dummy).toBe(false)
+ set.add('value')
+ expect(dummy).toBe(true)
+ set.delete('value')
+ expect(dummy).toBe(false)
})
- expect(dummy).toBe(0)
- set.add(2)
- set.add(1)
- expect(dummy).toBe(3)
- set.delete(2)
- expect(dummy).toBe(1)
- set.clear()
- expect(dummy).toBe(0)
- })
+ it('should observe for of iteration', () => {
+ let dummy
+ const set = observable(new Set())
+ autorun(() => {
+ dummy = 0
+ for (let num of set) {
+ dummy += num
+ }
+ })
- it('should observe forEach iteration', () => {
- let dummy: any
- const set = observable(new Set())
- autorun(() => {
- dummy = 0
- set.forEach(num => (dummy += num))
+ expect(dummy).toBe(0)
+ set.add(2)
+ set.add(1)
+ expect(dummy).toBe(3)
+ set.delete(2)
+ expect(dummy).toBe(1)
+ set.clear()
+ expect(dummy).toBe(0)
})
- expect(dummy).toBe(0)
- set.add(2)
- set.add(1)
- expect(dummy).toBe(3)
- set.delete(2)
- expect(dummy).toBe(1)
- set.clear()
- expect(dummy).toBe(0)
- })
+ it('should observe forEach iteration', () => {
+ let dummy: any
+ const set = observable(new Set())
+ autorun(() => {
+ dummy = 0
+ set.forEach(num => (dummy += num))
+ })
- it('should observe values iteration', () => {
- let dummy
- const set = observable(new Set())
- autorun(() => {
- dummy = 0
- for (let num of set.values()) {
- dummy += num
- }
+ expect(dummy).toBe(0)
+ set.add(2)
+ set.add(1)
+ expect(dummy).toBe(3)
+ set.delete(2)
+ expect(dummy).toBe(1)
+ set.clear()
+ expect(dummy).toBe(0)
})
- expect(dummy).toBe(0)
- set.add(2)
- set.add(1)
- expect(dummy).toBe(3)
- set.delete(2)
- expect(dummy).toBe(1)
- set.clear()
- expect(dummy).toBe(0)
- })
+ it('should observe values iteration', () => {
+ let dummy
+ const set = observable(new Set())
+ autorun(() => {
+ dummy = 0
+ for (let num of set.values()) {
+ dummy += num
+ }
+ })
- it('should observe keys iteration', () => {
- let dummy
- const set = observable(new Set())
- autorun(() => {
- dummy = 0
- for (let num of set.keys()) {
- dummy += num
- }
+ expect(dummy).toBe(0)
+ set.add(2)
+ set.add(1)
+ expect(dummy).toBe(3)
+ set.delete(2)
+ expect(dummy).toBe(1)
+ set.clear()
+ expect(dummy).toBe(0)
})
- expect(dummy).toBe(0)
- set.add(2)
- set.add(1)
- expect(dummy).toBe(3)
- set.delete(2)
- expect(dummy).toBe(1)
- set.clear()
- expect(dummy).toBe(0)
- })
+ it('should observe keys iteration', () => {
+ let dummy
+ const set = observable(new Set())
+ autorun(() => {
+ dummy = 0
+ for (let num of set.keys()) {
+ dummy += num
+ }
+ })
- it('should observe entries iteration', () => {
- let dummy
- const set = observable(new Set())
- autorun(() => {
- dummy = 0
- // eslint-disable-next-line no-unused-vars
- for (let [key, num] of set.entries()) {
- key
- dummy += num
- }
+ expect(dummy).toBe(0)
+ set.add(2)
+ set.add(1)
+ expect(dummy).toBe(3)
+ set.delete(2)
+ expect(dummy).toBe(1)
+ set.clear()
+ expect(dummy).toBe(0)
})
- expect(dummy).toBe(0)
- set.add(2)
- set.add(1)
- expect(dummy).toBe(3)
- set.delete(2)
- expect(dummy).toBe(1)
- set.clear()
- expect(dummy).toBe(0)
- })
+ it('should observe entries iteration', () => {
+ let dummy
+ const set = observable(new Set())
+ autorun(() => {
+ dummy = 0
+ // eslint-disable-next-line no-unused-vars
+ for (let [key, num] of set.entries()) {
+ key
+ dummy += num
+ }
+ })
- it('should be triggered by clearing', () => {
- let dummy
- const set = observable(new Set())
- autorun(() => (dummy = set.has('key')))
+ expect(dummy).toBe(0)
+ set.add(2)
+ set.add(1)
+ expect(dummy).toBe(3)
+ set.delete(2)
+ expect(dummy).toBe(1)
+ set.clear()
+ expect(dummy).toBe(0)
+ })
- expect(dummy).toBe(false)
- set.add('key')
- expect(dummy).toBe(true)
- set.clear()
- expect(dummy).toBe(false)
- })
+ it('should be triggered by clearing', () => {
+ let dummy
+ const set = observable(new Set())
+ autorun(() => (dummy = set.has('key')))
- it('should not observe custom property mutations', () => {
- let dummy
- const set: any = observable(new Set())
- autorun(() => (dummy = set.customProp))
+ expect(dummy).toBe(false)
+ set.add('key')
+ expect(dummy).toBe(true)
+ set.clear()
+ expect(dummy).toBe(false)
+ })
- expect(dummy).toBe(undefined)
- set.customProp = 'Hello World'
- expect(dummy).toBe(undefined)
- })
+ it('should not observe custom property mutations', () => {
+ let dummy
+ const set: any = observable(new Set())
+ autorun(() => (dummy = set.customProp))
- it('should observe size mutations', () => {
- let dummy
- const set = observable(new Set())
- autorun(() => (dummy = set.size))
-
- expect(dummy).toBe(0)
- set.add('value')
- set.add('value2')
- expect(dummy).toBe(2)
- set.delete('value')
- expect(dummy).toBe(1)
- set.clear()
- expect(dummy).toBe(0)
- })
+ expect(dummy).toBe(undefined)
+ set.customProp = 'Hello World'
+ expect(dummy).toBe(undefined)
+ })
- it('should not observe non value changing mutations', () => {
- let dummy
- const set = observable(new Set())
- const setSpy = jest.fn(() => (dummy = set.has('value')))
- autorun(setSpy)
-
- expect(dummy).toBe(false)
- expect(setSpy).toHaveBeenCalledTimes(1)
- set.add('value')
- expect(dummy).toBe(true)
- expect(setSpy).toHaveBeenCalledTimes(2)
- set.add('value')
- expect(dummy).toBe(true)
- expect(setSpy).toHaveBeenCalledTimes(2)
- set.delete('value')
- expect(dummy).toBe(false)
- expect(setSpy).toHaveBeenCalledTimes(3)
- set.delete('value')
- expect(dummy).toBe(false)
- expect(setSpy).toHaveBeenCalledTimes(3)
- set.clear()
- expect(dummy).toBe(false)
- expect(setSpy).toHaveBeenCalledTimes(3)
- })
+ it('should observe size mutations', () => {
+ let dummy
+ const set = observable(new Set())
+ autorun(() => (dummy = set.size))
+
+ expect(dummy).toBe(0)
+ set.add('value')
+ set.add('value2')
+ expect(dummy).toBe(2)
+ set.delete('value')
+ expect(dummy).toBe(1)
+ set.clear()
+ expect(dummy).toBe(0)
+ })
- it('should not observe raw data', () => {
- let dummy
- const set = observable(new Set())
- autorun(() => (dummy = unwrap(set).has('value')))
+ it('should not observe non value changing mutations', () => {
+ let dummy
+ const set = observable(new Set())
+ const setSpy = jest.fn(() => (dummy = set.has('value')))
+ autorun(setSpy)
+
+ expect(dummy).toBe(false)
+ expect(setSpy).toHaveBeenCalledTimes(1)
+ set.add('value')
+ expect(dummy).toBe(true)
+ expect(setSpy).toHaveBeenCalledTimes(2)
+ set.add('value')
+ expect(dummy).toBe(true)
+ expect(setSpy).toHaveBeenCalledTimes(2)
+ set.delete('value')
+ expect(dummy).toBe(false)
+ expect(setSpy).toHaveBeenCalledTimes(3)
+ set.delete('value')
+ expect(dummy).toBe(false)
+ expect(setSpy).toHaveBeenCalledTimes(3)
+ set.clear()
+ expect(dummy).toBe(false)
+ expect(setSpy).toHaveBeenCalledTimes(3)
+ })
- expect(dummy).toBe(false)
- set.add('value')
- expect(dummy).toBe(false)
- })
+ it('should not observe raw data', () => {
+ let dummy
+ const set = observable(new Set())
+ autorun(() => (dummy = unwrap(set).has('value')))
+
+ expect(dummy).toBe(false)
+ set.add('value')
+ expect(dummy).toBe(false)
+ })
- it('should not observe raw iterations', () => {
- let dummy = 0
- const set = observable(new Set())
- autorun(() => {
- dummy = 0
- for (let [num] of unwrap(set).entries()) {
- dummy += num
- }
- for (let num of unwrap(set).keys()) {
- dummy += num
- }
- for (let num of unwrap(set).values()) {
- dummy += num
- }
- unwrap(set).forEach(num => {
- dummy += num
+ it('should not observe raw iterations', () => {
+ let dummy = 0
+ const set = observable(new Set())
+ autorun(() => {
+ dummy = 0
+ for (let [num] of unwrap(set).entries()) {
+ dummy += num
+ }
+ for (let num of unwrap(set).keys()) {
+ dummy += num
+ }
+ for (let num of unwrap(set).values()) {
+ dummy += num
+ }
+ unwrap(set).forEach(num => {
+ dummy += num
+ })
+ for (let num of unwrap(set)) {
+ dummy += num
+ }
})
- for (let num of unwrap(set)) {
- dummy += num
- }
+
+ expect(dummy).toBe(0)
+ set.add(2)
+ set.add(3)
+ expect(dummy).toBe(0)
+ set.delete(2)
+ expect(dummy).toBe(0)
})
- expect(dummy).toBe(0)
- set.add(2)
- set.add(3)
- expect(dummy).toBe(0)
- set.delete(2)
- expect(dummy).toBe(0)
- })
+ it('should not be triggered by raw mutations', () => {
+ let dummy
+ const set = observable(new Set())
+ autorun(() => (dummy = set.has('value')))
+
+ expect(dummy).toBe(false)
+ unwrap(set).add('value')
+ expect(dummy).toBe(false)
+ dummy = true
+ unwrap(set).delete('value')
+ expect(dummy).toBe(true)
+ unwrap(set).clear()
+ expect(dummy).toBe(true)
+ })
- it('should not be triggered by raw mutations', () => {
- let dummy
- const set = observable(new Set())
- autorun(() => (dummy = set.has('value')))
-
- expect(dummy).toBe(false)
- unwrap(set).add('value')
- expect(dummy).toBe(false)
- dummy = true
- unwrap(set).delete('value')
- expect(dummy).toBe(true)
- unwrap(set).clear()
- expect(dummy).toBe(true)
- })
+ it('should not observe raw size mutations', () => {
+ let dummy
+ const set = observable(new Set())
+ autorun(() => (dummy = unwrap(set).size))
- it('should not observe raw size mutations', () => {
- let dummy
- const set = observable(new Set())
- autorun(() => (dummy = unwrap(set).size))
+ expect(dummy).toBe(0)
+ set.add('value')
+ expect(dummy).toBe(0)
+ })
- expect(dummy).toBe(0)
- set.add('value')
- expect(dummy).toBe(0)
- })
+ it('should not be triggered by raw size mutations', () => {
+ let dummy
+ const set = observable(new Set())
+ autorun(() => (dummy = set.size))
- it('should not be triggered by raw size mutations', () => {
- let dummy
- const set = observable(new Set())
- autorun(() => (dummy = set.size))
+ expect(dummy).toBe(0)
+ unwrap(set).add('value')
+ expect(dummy).toBe(0)
+ })
- expect(dummy).toBe(0)
- unwrap(set).add('value')
- expect(dummy).toBe(0)
- })
+ it('should support objects as key', () => {
+ let dummy
+ const key = {}
+ const set = observable(new Set())
+ const setSpy = jest.fn(() => (dummy = set.has(key)))
+ autorun(setSpy)
- it('should support objects as key', () => {
- let dummy
- const key = {}
- const set = observable(new Set())
- const setSpy = jest.fn(() => (dummy = set.has(key)))
- autorun(setSpy)
+ expect(dummy).toBe(false)
+ expect(setSpy).toHaveBeenCalledTimes(1)
- expect(dummy).toBe(false)
- expect(setSpy).toHaveBeenCalledTimes(1)
+ set.add({})
+ expect(dummy).toBe(false)
+ expect(setSpy).toHaveBeenCalledTimes(1)
- set.add({})
- expect(dummy).toBe(false)
- expect(setSpy).toHaveBeenCalledTimes(1)
+ set.add(key)
+ expect(dummy).toBe(true)
+ expect(setSpy).toHaveBeenCalledTimes(2)
+ })
- set.add(key)
- expect(dummy).toBe(true)
- expect(setSpy).toHaveBeenCalledTimes(2)
+ it('should not pollute original Set with Proxies', () => {
+ const set = new Set()
+ const observed = observable(set)
+ const value = observable({})
+ observed.add(value)
+ expect(observed.has(value)).toBe(true)
+ expect(set.has(value)).toBe(false)
+ })
})
})
import { observable, autorun, unwrap, isObservable } from '../../src'
-describe('observer/collections/WeakMap', () => {
- test('instanceof', () => {
- const original = new WeakMap()
- const observed = observable(original)
- expect(isObservable(observed)).toBe(true)
- expect(original instanceof WeakMap).toBe(true)
- expect(observed instanceof WeakMap).toBe(true)
- })
+describe('observer/collections', () => {
+ describe('WeakMap', () => {
+ test('instanceof', () => {
+ const original = new WeakMap()
+ const observed = observable(original)
+ expect(isObservable(observed)).toBe(true)
+ expect(original instanceof WeakMap).toBe(true)
+ expect(observed instanceof WeakMap).toBe(true)
+ })
- it('should observe mutations', () => {
- let dummy
- const key = {}
- const map = observable(new WeakMap())
- autorun(() => {
- dummy = map.get(key)
+ it('should observe mutations', () => {
+ let dummy
+ const key = {}
+ const map = observable(new WeakMap())
+ autorun(() => {
+ dummy = map.get(key)
+ })
+
+ expect(dummy).toBe(undefined)
+ map.set(key, 'value')
+ expect(dummy).toBe('value')
+ map.set(key, 'value2')
+ expect(dummy).toBe('value2')
+ map.delete(key)
+ expect(dummy).toBe(undefined)
})
- expect(dummy).toBe(undefined)
- map.set(key, 'value')
- expect(dummy).toBe('value')
- map.set(key, 'value2')
- expect(dummy).toBe('value2')
- map.delete(key)
- expect(dummy).toBe(undefined)
- })
+ it('should not observe custom property mutations', () => {
+ let dummy
+ const map: any = observable(new WeakMap())
+ autorun(() => (dummy = map.customProp))
- it('should not observe custom property mutations', () => {
- let dummy
- const map: any = observable(new Map())
- autorun(() => (dummy = map.customProp))
+ expect(dummy).toBe(undefined)
+ map.customProp = 'Hello World'
+ expect(dummy).toBe(undefined)
+ })
- expect(dummy).toBe(undefined)
- map.customProp = 'Hello World'
- expect(dummy).toBe(undefined)
- })
+ it('should not observe non value changing mutations', () => {
+ let dummy
+ const key = {}
+ const map = observable(new WeakMap())
+ const mapSpy = jest.fn(() => (dummy = map.get(key)))
+ autorun(mapSpy)
+
+ expect(dummy).toBe(undefined)
+ expect(mapSpy).toHaveBeenCalledTimes(1)
+ map.set(key, 'value')
+ expect(dummy).toBe('value')
+ expect(mapSpy).toHaveBeenCalledTimes(2)
+ map.set(key, 'value')
+ expect(dummy).toBe('value')
+ expect(mapSpy).toHaveBeenCalledTimes(2)
+ map.delete(key)
+ expect(dummy).toBe(undefined)
+ expect(mapSpy).toHaveBeenCalledTimes(3)
+ map.delete(key)
+ expect(dummy).toBe(undefined)
+ expect(mapSpy).toHaveBeenCalledTimes(3)
+ })
- it('should not observe non value changing mutations', () => {
- let dummy
- const key = {}
- const map = observable(new Map())
- const mapSpy = jest.fn(() => (dummy = map.get(key)))
- autorun(mapSpy)
+ it('should not observe raw data', () => {
+ let dummy
+ const key = {}
+ const map = observable(new WeakMap())
+ autorun(() => (dummy = unwrap(map).get(key)))
- expect(dummy).toBe(undefined)
- expect(mapSpy).toHaveBeenCalledTimes(1)
- map.set(key, 'value')
- expect(dummy).toBe('value')
- expect(mapSpy).toHaveBeenCalledTimes(2)
- map.set(key, 'value')
- expect(dummy).toBe('value')
- expect(mapSpy).toHaveBeenCalledTimes(2)
- map.delete(key)
- expect(dummy).toBe(undefined)
- expect(mapSpy).toHaveBeenCalledTimes(3)
- map.delete(key)
- expect(dummy).toBe(undefined)
- expect(mapSpy).toHaveBeenCalledTimes(3)
- map.clear()
- expect(dummy).toBe(undefined)
- expect(mapSpy).toHaveBeenCalledTimes(3)
- })
+ expect(dummy).toBe(undefined)
+ map.set(key, 'Hello')
+ expect(dummy).toBe(undefined)
+ map.delete(key)
+ expect(dummy).toBe(undefined)
+ })
- it('should not observe raw data', () => {
- let dummy
- const key = {}
- const map = observable(new Map())
- autorun(() => (dummy = unwrap(map).get('key')))
+ it('should not pollute original Map with Proxies', () => {
+ const map = new WeakMap()
+ const observed = observable(map)
+ const key = {}
+ const value = observable({})
+ observed.set(key, value)
+ expect(map.get(key)).not.toBe(value)
+ expect(map.get(key)).toBe(unwrap(value))
+ })
- expect(dummy).toBe(undefined)
- map.set(key, 'Hello')
- expect(dummy).toBe(undefined)
- map.delete(key)
- expect(dummy).toBe(undefined)
+ it('should return observable versions of contained values', () => {
+ const observed = observable(new WeakMap())
+ const key = {}
+ const value = {}
+ observed.set(key, value)
+ const wrapped = observed.get(key)
+ expect(isObservable(wrapped)).toBe(true)
+ expect(unwrap(wrapped)).toBe(value)
+ })
+
+ it('should observed nested data', () => {
+ const observed = observable(new Map())
+ const key = {}
+ observed.set(key, { a: 1 })
+ let dummy
+ autorun(() => {
+ dummy = observed.get(key).a
+ })
+ observed.get(key).a = 2
+ expect(dummy).toBe(2)
+ })
})
})
import { observable, isObservable, autorun, unwrap } from '../../src'
-describe('WeakSet', () => {
- it('instanceof', () => {
- const original = new Set()
- const observed = observable(original)
- expect(isObservable(observed)).toBe(true)
- expect(original instanceof Set).toBe(true)
- expect(observed instanceof Set).toBe(true)
- })
+describe('observer/collections', () => {
+ describe('WeakSet', () => {
+ it('instanceof', () => {
+ const original = new Set()
+ const observed = observable(original)
+ expect(isObservable(observed)).toBe(true)
+ expect(original instanceof Set).toBe(true)
+ expect(observed instanceof Set).toBe(true)
+ })
- it('should observe mutations', () => {
- let dummy
- const value = {}
- const set = observable(new WeakSet())
- autorun(() => (dummy = set.has(value)))
+ it('should observe mutations', () => {
+ let dummy
+ const value = {}
+ const set = observable(new WeakSet())
+ autorun(() => (dummy = set.has(value)))
- expect(dummy).toBe(false)
- set.add(value)
- expect(dummy).toBe(true)
- set.delete(value)
- expect(dummy).toBe(false)
- })
+ expect(dummy).toBe(false)
+ set.add(value)
+ expect(dummy).toBe(true)
+ set.delete(value)
+ expect(dummy).toBe(false)
+ })
- it('should not observe custom property mutations', () => {
- let dummy
- const set: any = observable(new WeakSet())
- autorun(() => (dummy = set.customProp))
+ it('should not observe custom property mutations', () => {
+ let dummy
+ const set: any = observable(new WeakSet())
+ autorun(() => (dummy = set.customProp))
- expect(dummy).toBe(undefined)
- set.customProp = 'Hello World'
- expect(dummy).toBe(undefined)
- })
+ expect(dummy).toBe(undefined)
+ set.customProp = 'Hello World'
+ expect(dummy).toBe(undefined)
+ })
- it('should not observe non value changing mutations', () => {
- let dummy
- const value = {}
- const set = observable(new WeakSet())
- const setSpy = jest.fn(() => (dummy = set.has(value)))
- autorun(setSpy)
+ it('should not observe non value changing mutations', () => {
+ let dummy
+ const value = {}
+ const set = observable(new WeakSet())
+ const setSpy = jest.fn(() => (dummy = set.has(value)))
+ autorun(setSpy)
- expect(dummy).toBe(false)
- expect(setSpy).toHaveBeenCalledTimes(1)
- set.add(value)
- expect(dummy).toBe(true)
- expect(setSpy).toHaveBeenCalledTimes(2)
- set.add(value)
- expect(dummy).toBe(true)
- expect(setSpy).toHaveBeenCalledTimes(2)
- set.delete(value)
- expect(dummy).toBe(false)
- expect(setSpy).toHaveBeenCalledTimes(3)
- set.delete(value)
- expect(dummy).toBe(false)
- expect(setSpy).toHaveBeenCalledTimes(3)
- })
+ expect(dummy).toBe(false)
+ expect(setSpy).toHaveBeenCalledTimes(1)
+ set.add(value)
+ expect(dummy).toBe(true)
+ expect(setSpy).toHaveBeenCalledTimes(2)
+ set.add(value)
+ expect(dummy).toBe(true)
+ expect(setSpy).toHaveBeenCalledTimes(2)
+ set.delete(value)
+ expect(dummy).toBe(false)
+ expect(setSpy).toHaveBeenCalledTimes(3)
+ set.delete(value)
+ expect(dummy).toBe(false)
+ expect(setSpy).toHaveBeenCalledTimes(3)
+ })
- it('should not observe raw data', () => {
- const value = {}
- let dummy
- const set = observable(new WeakSet())
- autorun(() => (dummy = unwrap(set).has(value)))
+ it('should not observe raw data', () => {
+ const value = {}
+ let dummy
+ const set = observable(new WeakSet())
+ autorun(() => (dummy = unwrap(set).has(value)))
- expect(dummy).toBe(false)
- set.add(value)
- expect(dummy).toBe(false)
- })
+ expect(dummy).toBe(false)
+ set.add(value)
+ expect(dummy).toBe(false)
+ })
+
+ it('should not be triggered by raw mutations', () => {
+ const value = {}
+ let dummy
+ const set = observable(new WeakSet())
+ autorun(() => (dummy = set.has(value)))
- it('should not be triggered by raw mutations', () => {
- const value = {}
- let dummy
- const set = observable(new WeakSet())
- autorun(() => (dummy = set.has(value)))
+ expect(dummy).toBe(false)
+ unwrap(set).add(value)
+ expect(dummy).toBe(false)
+ })
- expect(dummy).toBe(false)
- unwrap(set).add(value)
- expect(dummy).toBe(false)
+ it('should not pollute original Set with Proxies', () => {
+ const set = new WeakSet()
+ const observed = observable(set)
+ const value = observable({})
+ observed.add(value)
+ expect(observed.has(value)).toBe(true)
+ expect(set.has(value)).toBe(false)
+ })
})
})
-describe('observer/immutable', () => {})
+import {
+ observable,
+ immutable,
+ unwrap,
+ isObservable,
+ isImmutable,
+ markNonReactive,
+ markImmutable,
+ lock,
+ unlock,
+ autorun
+} from '../src'
+
+describe('observer/immutable', () => {
+ let warn: any
+
+ beforeEach(() => {
+ warn = jest.spyOn(console, 'warn')
+ warn.mockImplementation(() => {})
+ })
+
+ afterEach(() => {
+ warn.mockRestore()
+ })
+
+ describe('Object', () => {
+ it('should make nested values immutable', () => {
+ const original = { foo: 1, bar: { baz: 2 } }
+ const observed = immutable(original)
+ expect(observed).not.toBe(original)
+ expect(isObservable(observed)).toBe(true)
+ expect(isImmutable(observed)).toBe(true)
+ expect(isObservable(original)).toBe(false)
+ expect(isImmutable(original)).toBe(false)
+ expect(isObservable(observed.bar)).toBe(true)
+ expect(isImmutable(observed.bar)).toBe(true)
+ expect(isObservable(original.bar)).toBe(false)
+ expect(isImmutable(original.bar)).toBe(false)
+ // get
+ expect(observed.foo).toBe(1)
+ // has
+ expect('foo' in observed).toBe(true)
+ // ownKeys
+ expect(Object.keys(observed)).toEqual(['foo', 'bar'])
+ })
+
+ it('should not allow mutation', () => {
+ const observed = immutable({ foo: 1, bar: { baz: 2 } })
+ observed.foo = 2
+ expect(observed.foo).toBe(1)
+ expect(warn).toHaveBeenCalledTimes(1)
+ observed.bar.baz = 3
+ expect(observed.bar.baz).toBe(2)
+ expect(warn).toHaveBeenCalledTimes(2)
+ })
+
+ it('should allow mutation when unlocked', () => {
+ const observed = immutable({ foo: 1, bar: { baz: 2 } })
+ unlock()
+ observed.foo = 2
+ observed.bar.baz = 3
+ lock()
+ expect(observed.foo).toBe(2)
+ expect(observed.bar.baz).toBe(3)
+ expect(warn).not.toHaveBeenCalled()
+ })
+
+ it('should not trigger autoruns when locked', () => {
+ const observed = immutable({ a: 1 })
+ let dummy
+ autorun(() => {
+ dummy = observed.a
+ })
+ expect(dummy).toBe(1)
+ observed.a = 2
+ expect(observed.a).toBe(1)
+ expect(dummy).toBe(1)
+ })
+
+ it('should trigger autoruns when unlocked', () => {
+ const observed = immutable({ a: 1 })
+ let dummy
+ autorun(() => {
+ dummy = observed.a
+ })
+ expect(dummy).toBe(1)
+ unlock()
+ observed.a = 2
+ lock()
+ expect(observed.a).toBe(2)
+ expect(dummy).toBe(2)
+ })
+ })
+
+ describe('Array', () => {
+ it('should make nested values immutable', () => {
+ const original: any[] = [{ foo: 1 }]
+ const observed = immutable(original)
+ expect(observed).not.toBe(original)
+ expect(isObservable(observed)).toBe(true)
+ expect(isImmutable(observed)).toBe(true)
+ expect(isObservable(original)).toBe(false)
+ expect(isImmutable(original)).toBe(false)
+ expect(isObservable(observed[0])).toBe(true)
+ expect(isImmutable(observed[0])).toBe(true)
+ expect(isObservable(original[0])).toBe(false)
+ expect(isImmutable(original[0])).toBe(false)
+ // get
+ expect(observed[0].foo).toBe(1)
+ // has
+ expect(0 in observed).toBe(true)
+ // ownKeys
+ expect(Object.keys(observed)).toEqual(['0'])
+ })
+
+ it('should not allow mutation', () => {
+ const observed: any = immutable([{ foo: 1 }])
+ observed[0] = 1
+ expect(observed[0]).not.toBe(1)
+ expect(warn).toHaveBeenCalledTimes(1)
+ observed[0].foo = 2
+ expect(observed[0].foo).toBe(1)
+ expect(warn).toHaveBeenCalledTimes(2)
+
+ // should block length mutation
+ observed.length = 0
+ expect(observed.length).toBe(1)
+ expect(observed[0].foo).toBe(1)
+ expect(warn).toHaveBeenCalledTimes(3)
+
+ // mutation methods invoke set/length internally and thus are blocked as well
+ observed.push(2)
+ expect(observed.length).toBe(1)
+ // push triggers two warnings on [1] and .length
+ expect(warn).toHaveBeenCalledTimes(5)
+ })
+
+ it('should allow mutation when unlocked', () => {
+ const observed: any[] = immutable([{ foo: 1, bar: { baz: 2 } }])
+ unlock()
+ observed[1] = 2
+ observed.push(3)
+ observed[0].foo = 2
+ observed[0].bar.baz = 3
+ lock()
+ expect(observed.length).toBe(3)
+ expect(observed[1]).toBe(2)
+ expect(observed[2]).toBe(3)
+ expect(observed[0].foo).toBe(2)
+ expect(observed[0].bar.baz).toBe(3)
+ expect(warn).not.toHaveBeenCalled()
+ })
+
+ it('should not trigger autoruns when locked', () => {
+ const observed = immutable([{ a: 1 }])
+ let dummy
+ autorun(() => {
+ dummy = observed[0].a
+ })
+ expect(dummy).toBe(1)
+ observed[0].a = 2
+ expect(observed[0].a).toBe(1)
+ expect(dummy).toBe(1)
+ observed[0] = { a: 2 }
+ expect(observed[0].a).toBe(1)
+ expect(dummy).toBe(1)
+ })
+
+ it('should trigger autoruns when unlocked', () => {
+ const observed = immutable([{ a: 1 }])
+ let dummy
+ autorun(() => {
+ dummy = observed[0].a
+ })
+ expect(dummy).toBe(1)
+
+ unlock()
+
+ observed[0].a = 2
+ expect(observed[0].a).toBe(2)
+ expect(dummy).toBe(2)
+
+ observed[0] = { a: 3 }
+ expect(observed[0].a).toBe(3)
+ expect(dummy).toBe(3)
+
+ observed.unshift({ a: 4 })
+ expect(observed[0].a).toBe(4)
+ expect(dummy).toBe(4)
+ lock()
+ })
+ })
+ ;[Map, WeakMap].forEach((Collection: any) => {
+ describe(Collection.name, () => {
+ test('should make nested values immutable', () => {
+ const key1 = {}
+ const key2 = {}
+ const original = new Collection([[key1, {}], [key2, {}]])
+ const observed = immutable(original)
+ expect(observed).not.toBe(original)
+ expect(isObservable(observed)).toBe(true)
+ expect(isImmutable(observed)).toBe(true)
+ expect(isObservable(original)).toBe(false)
+ expect(isImmutable(original)).toBe(false)
+ expect(isObservable(observed.get(key1))).toBe(true)
+ expect(isImmutable(observed.get(key1))).toBe(true)
+ expect(isObservable(original.get(key1))).toBe(false)
+ expect(isImmutable(original.get(key1))).toBe(false)
+ })
+
+ test('should not allow mutation & not trigger autorun', () => {
+ const map = immutable(new Collection())
+ const key = {}
+ let dummy
+ autorun(() => {
+ dummy = map.get(key)
+ })
+ expect(dummy).toBeUndefined()
+ map.set(key, 1)
+ expect(dummy).toBeUndefined()
+ expect(map.has(key)).toBe(false)
+ expect(warn).toHaveBeenCalledTimes(1)
+ })
+
+ test('should allow mutation & trigger autorun when unlocked', () => {
+ const map = immutable(new Collection())
+ const key = {}
+ let dummy
+ autorun(() => {
+ dummy = map.get(key)
+ })
+ expect(dummy).toBeUndefined()
+ unlock()
+ map.set(key, 1)
+ lock()
+ expect(dummy).toBe(1)
+ expect(map.get(key)).toBe(1)
+ expect(warn).not.toHaveBeenCalled()
+ })
+ })
+ })
+ ;[Set, WeakSet].forEach((Collection: any) => {
+ describe(Collection.name, () => {
+ test('should make nested values immutable', () => {
+ const key1 = {}
+ const key2 = {}
+ const original = new Collection([key1, key2])
+ const observed = immutable(original)
+ expect(observed).not.toBe(original)
+ expect(isObservable(observed)).toBe(true)
+ expect(isImmutable(observed)).toBe(true)
+ expect(isObservable(original)).toBe(false)
+ expect(isImmutable(original)).toBe(false)
+ expect(observed.has(observable(key1))).toBe(true)
+ expect(original.has(observable(key1))).toBe(false)
+ })
+
+ test('should not allow mutation & not trigger autorun', () => {
+ const set = immutable(new Collection())
+ const key = {}
+ let dummy
+ autorun(() => {
+ dummy = set.has(key)
+ })
+ expect(dummy).toBe(false)
+ set.add(key)
+ expect(dummy).toBe(false)
+ expect(set.has(key)).toBe(false)
+ expect(warn).toHaveBeenCalledTimes(1)
+ })
+
+ test('should allow mutation & trigger autorun when unlocked', () => {
+ const set = immutable(new Collection())
+ const key = {}
+ let dummy
+ autorun(() => {
+ dummy = set.has(key)
+ })
+ expect(dummy).toBe(false)
+ unlock()
+ set.add(key)
+ lock()
+ expect(dummy).toBe(true)
+ expect(set.has(key)).toBe(true)
+ expect(warn).not.toHaveBeenCalled()
+ })
+ })
+ })
+
+ test('calling observable on an immutable should return immutable', () => {
+ const a = immutable()
+ const b = observable(a)
+ expect(isImmutable(b)).toBe(true)
+ // should point to same original
+ expect(unwrap(a)).toBe(unwrap(b))
+ })
+
+ test('calling immutable on an observable should return immutable', () => {
+ const a = observable()
+ const b = immutable(a)
+ expect(isImmutable(b)).toBe(true)
+ // should point to same original
+ expect(unwrap(a)).toBe(unwrap(b))
+ })
+
+ test('observing already observed value should return same Proxy', () => {
+ const original = { foo: 1 }
+ const observed = immutable(original)
+ const observed2 = immutable(observed)
+ expect(observed2).toBe(observed)
+ })
+
+ test('observing the same value multiple times should return same Proxy', () => {
+ const original = { foo: 1 }
+ const observed = immutable(original)
+ const observed2 = immutable(original)
+ expect(observed2).toBe(observed)
+ })
+
+ test('markNonReactive', () => {
+ const obj = immutable({
+ foo: { a: 1 },
+ bar: markNonReactive({ b: 2 })
+ })
+ expect(isObservable(obj.foo)).toBe(true)
+ expect(isObservable(obj.bar)).toBe(false)
+ })
+
+ test('markImmutable', () => {
+ const obj = observable({
+ foo: { a: 1 },
+ bar: markImmutable({ b: 2 })
+ })
+ expect(isObservable(obj.foo)).toBe(true)
+ expect(isObservable(obj.bar)).toBe(true)
+ expect(isImmutable(obj.foo)).toBe(false)
+ expect(isImmutable(obj.bar)).toBe(true)
+ })
+})
})
} else {
// schedule runs for SET | ADD | DELETE
- addRunners(runners, depsMap.get(key as string | symbol))
+ if (key !== void 0) {
+ addRunners(runners, depsMap.get(key as string | symbol))
+ }
// also run for iteration key on ADD | DELETE
if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
target: any,
key: string | symbol,
receiver: any,
- toObsevable: (t: any) => any
+ toObservable: (t: any) => any
) {
const res = Reflect.get(target, key, receiver)
if (typeof key === 'symbol' && builtInSymbols.has(key)) {
return res
}
track(target, OperationTypes.GET, key)
- return res !== null && typeof res === 'object' ? toObsevable(res) : res
+ return res !== null && typeof res === 'object' ? toObservable(res) : res
}
function set(
set(target: any, key: string | symbol, value: any, receiver: any): boolean {
if (LOCKED) {
if (__DEV__) {
- console.warn(`Set operation failed: target is immutable.`, target)
+ console.warn(
+ `Set operation on key "${key as any}" failed: target is immutable.`,
+ target
+ )
}
return true
} else {
deleteProperty(target: any, key: string | symbol): boolean {
if (LOCKED) {
if (__DEV__) {
- console.warn(`Delete operation failed: target is immutable.`, target)
+ console.warn(
+ `Delete operation on key "${key as any}" failed: target is immutable.`,
+ target
+ )
}
return true
} else {
-import { unwrap } from './index'
+import { unwrap, observable, immutable } from './index'
import { track, trigger } from './autorun'
import { OperationTypes } from './operations'
+import { LOCKED } from './lock'
-function makeInstrumentedMethod(method: string | symbol, type: OperationTypes) {
- return function(...args: any[]) {
- const target = unwrap(this)
- const proto: any = Reflect.getPrototypeOf(target)
- track(target, type, args[0])
- return proto[method].apply(target, args)
- }
+function get(target: any, key: any, toObservable: (t: any) => any): any {
+ target = unwrap(target)
+ key = unwrap(key)
+ const proto: any = Reflect.getPrototypeOf(target)
+ track(target, OperationTypes.GET, key)
+ const res = proto.get.call(target, key)
+ return res !== null && typeof res === 'object' ? toObservable(res) : res
}
-const get = makeInstrumentedMethod('get', OperationTypes.GET)
-const has = makeInstrumentedMethod('has', OperationTypes.HAS)
+function has(key: any): boolean {
+ const target = unwrap(this)
+ key = unwrap(key)
+ const proto: any = Reflect.getPrototypeOf(target)
+ track(target, OperationTypes.HAS, key)
+ return proto.has.call(target, key)
+}
function size(target: any) {
target = unwrap(target)
return Reflect.get(proto, 'size', target)
}
-function makeWarning(type: OperationTypes) {
- return function() {
+function add(value: any) {
+ value = unwrap(value)
+ const target = unwrap(this)
+ const proto: any = Reflect.getPrototypeOf(this)
+ const hadKey = proto.has.call(target, value)
+ const result = proto.add.call(target, value)
+ if (!hadKey) {
if (__DEV__) {
- console.warn(
- `${type} operation failed: target is immutable.`,
- unwrap(this)
- )
+ trigger(target, OperationTypes.ADD, value, { value })
+ } else {
+ trigger(target, OperationTypes.ADD, value)
}
}
+ return result
}
-const mutableInstrumentations: any = {
- get,
- has,
-
- get size() {
- return size(this)
- },
-
- add(key: any) {
- const target = unwrap(this)
- const proto: any = Reflect.getPrototypeOf(this)
- const hadKey = proto.has.call(target, key)
- const result = proto.add.apply(target, arguments)
- if (!hadKey) {
- if (__DEV__) {
- trigger(target, OperationTypes.ADD, key, { value: key })
+function set(key: any, value: any) {
+ value = unwrap(value)
+ const target = unwrap(this)
+ const proto: any = Reflect.getPrototypeOf(this)
+ const hadKey = proto.has.call(target, key)
+ const oldValue = proto.get.call(target, key)
+ const result = proto.set.call(target, key, value)
+ if (value !== oldValue) {
+ if (__DEV__) {
+ const extraInfo = { oldValue, newValue: value }
+ if (!hadKey) {
+ trigger(target, OperationTypes.ADD, key, extraInfo)
} else {
+ trigger(target, OperationTypes.SET, key, extraInfo)
+ }
+ } else {
+ if (!hadKey) {
trigger(target, OperationTypes.ADD, key)
+ } else {
+ trigger(target, OperationTypes.SET, key)
}
}
- return result
- },
+ }
+ return result
+}
- set(key: any, value: any) {
- const target = unwrap(this)
- const proto: any = Reflect.getPrototypeOf(this)
- const hadKey = proto.has.call(target, key)
- const oldValue = proto.get.call(target, key)
- const result = proto.set.apply(target, arguments)
- if (value !== oldValue) {
- if (__DEV__) {
- const extraInfo = { oldValue, newValue: value }
- if (!hadKey) {
- trigger(target, OperationTypes.ADD, key, extraInfo)
- } else {
- trigger(target, OperationTypes.SET, key, extraInfo)
- }
- } else {
- if (!hadKey) {
- trigger(target, OperationTypes.ADD, key)
- } else {
- trigger(target, OperationTypes.SET, key)
- }
- }
+function deleteEntry(key: any) {
+ const target = unwrap(this)
+ const proto: any = Reflect.getPrototypeOf(this)
+ const hadKey = proto.has.call(target, key)
+ const oldValue = proto.get ? proto.get.call(target, key) : undefined
+ // forward the operation before queueing reactions
+ const result = proto.delete.call(target, key)
+ if (hadKey) {
+ if (__DEV__) {
+ trigger(target, OperationTypes.DELETE, key, { oldValue })
+ } else {
+ trigger(target, OperationTypes.DELETE, key)
}
- return result
- },
+ }
+ return result
+}
- delete(key: any) {
- const target = unwrap(this)
- const proto: any = Reflect.getPrototypeOf(this)
- const hadKey = proto.has.call(target, key)
- const oldValue = proto.get ? proto.get.call(target, key) : undefined
- // forward the operation before queueing reactions
- const result = proto.delete.apply(target, arguments)
- if (hadKey) {
- if (__DEV__) {
- trigger(target, OperationTypes.DELETE, key, { oldValue })
- } else {
- trigger(target, OperationTypes.DELETE, key)
- }
+function clear() {
+ const target = unwrap(this)
+ const proto: any = Reflect.getPrototypeOf(this)
+ const hadItems = target.size !== 0
+ const oldTarget = target instanceof Map ? new Map(target) : new Set(target)
+ // forward the operation before queueing reactions
+ const result = proto.clear.call(target)
+ if (hadItems) {
+ if (__DEV__) {
+ trigger(target, OperationTypes.CLEAR, void 0, { oldTarget })
+ } else {
+ trigger(target, OperationTypes.CLEAR)
}
- return result
- },
+ }
+ return result
+}
- clear() {
- const target = unwrap(this)
- const proto: any = Reflect.getPrototypeOf(this)
- const hadItems = target.size !== 0
- const oldTarget = target instanceof Map ? new Map(target) : new Set(target)
- // forward the operation before queueing reactions
- const result = proto.clear.apply(target, arguments)
- if (hadItems) {
+function makeImmutableMethod(method: Function, type: OperationTypes): Function {
+ return function(...args: any[]) {
+ if (LOCKED) {
if (__DEV__) {
- trigger(target, OperationTypes.CLEAR, void 0, { oldTarget })
- } else {
- trigger(target, OperationTypes.CLEAR)
+ const key = args[0] ? `on key "${args[0]}"` : ``
+ console.warn(
+ `${type} operation ${key}failed: target is immutable.`,
+ unwrap(this)
+ )
}
+ return type === OperationTypes.DELETE ? false : this
+ } else {
+ return method.apply(this, args)
}
- return result
}
}
-const immutableInstrumentations: any = {
- get,
+const mutableInstrumentations: any = {
+ get(key: any) {
+ return get(this, key, observable)
+ },
+ get size() {
+ return size(this)
+ },
has,
+ add,
+ set,
+ delete: deleteEntry,
+ clear
+}
+
+const immutableInstrumentations: any = {
+ get(key: any) {
+ return get(this, key, immutable)
+ },
get size() {
return size(this)
},
- add: makeWarning(OperationTypes.ADD),
- set: makeWarning(OperationTypes.SET),
- delete: makeWarning(OperationTypes.DELETE),
- clear: makeWarning(OperationTypes.CLEAR)
+ has,
+ add: makeImmutableMethod(add, OperationTypes.ADD),
+ set: makeImmutableMethod(set, OperationTypes.SET),
+ delete: makeImmutableMethod(deleteEntry, OperationTypes.DELETE),
+ clear: makeImmutableMethod(clear, OperationTypes.CLEAR)
}
-;['forEach', 'keys', 'values', 'entries', Symbol.iterator].forEach(key => {
- mutableInstrumentations[key] = immutableInstrumentations[
- key
- ] = makeInstrumentedMethod(key, OperationTypes.ITERATE)
+;['forEach', 'keys', 'values', 'entries', Symbol.iterator].forEach(method => {
+ mutableInstrumentations[method] = immutableInstrumentations[
+ method
+ ] = function(...args: any[]) {
+ const target = unwrap(this)
+ const proto: any = Reflect.getPrototypeOf(target)
+ track(target, OperationTypes.ITERATE)
+ return proto[method].apply(target, args)
+ }
})
function getInstrumented(
observed = new Proxy(target, handlers)
toProxy.set(target, observed)
toRaw.set(observed, target)
- targetMap.set(target, new Map())
+ if (!targetMap.has(target)) {
+ targetMap.set(target, new Map())
+ }
return observed
}