observable.spec.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import { observable, isObservable, unwrap, markNonReactive } from '../src/index'
  2. describe('observer/observable', () => {
  3. test('Object', () => {
  4. const original = { foo: 1 }
  5. const observed = observable(original)
  6. expect(observed).not.toBe(original)
  7. expect(isObservable(observed)).toBe(true)
  8. expect(isObservable(original)).toBe(false)
  9. // get
  10. expect(observed.foo).toBe(1)
  11. // has
  12. expect('foo' in observed).toBe(true)
  13. // ownKeys
  14. expect(Object.keys(observed)).toEqual(['foo'])
  15. })
  16. test('Array', () => {
  17. const original: any[] = [{ foo: 1 }]
  18. const observed = observable(original)
  19. expect(observed).not.toBe(original)
  20. expect(isObservable(observed)).toBe(true)
  21. expect(isObservable(original)).toBe(false)
  22. expect(isObservable(observed[0])).toBe(true)
  23. // get
  24. expect(observed[0].foo).toBe(1)
  25. // has
  26. expect(0 in observed).toBe(true)
  27. // ownKeys
  28. expect(Object.keys(observed)).toEqual(['0'])
  29. })
  30. test('cloned observable Array should point to observed values', () => {
  31. const original = [{ foo: 1 }]
  32. const observed = observable(original)
  33. const clone = observed.slice()
  34. expect(isObservable(clone[0])).toBe(true)
  35. expect(clone[0]).not.toBe(original[0])
  36. expect(clone[0]).toBe(observed[0])
  37. })
  38. test('nested observables', () => {
  39. const original = {
  40. nested: {
  41. foo: 1
  42. },
  43. array: [{ bar: 2 }]
  44. }
  45. const observed = observable(original)
  46. expect(isObservable(observed.nested)).toBe(true)
  47. expect(isObservable(observed.array)).toBe(true)
  48. expect(isObservable(observed.array[0])).toBe(true)
  49. })
  50. test('observed value should proxy mutations to original (Object)', () => {
  51. const original: any = { foo: 1 }
  52. const observed = observable(original)
  53. // set
  54. observed.bar = 1
  55. expect(observed.bar).toBe(1)
  56. expect(original.bar).toBe(1)
  57. // delete
  58. delete observed.foo
  59. expect('foo' in observed).toBe(false)
  60. expect('foo' in original).toBe(false)
  61. })
  62. test('observed value should proxy mutations to original (Array)', () => {
  63. const original: any[] = [{ foo: 1 }, { bar: 2 }]
  64. const observed = observable(original)
  65. // set
  66. const value = { baz: 3 }
  67. const observableValue = observable(value)
  68. observed[0] = value
  69. expect(observed[0]).toBe(observableValue)
  70. expect(original[0]).toBe(value)
  71. // delete
  72. delete observed[0]
  73. expect(observed[0]).toBeUndefined()
  74. expect(original[0]).toBeUndefined()
  75. // mutating methods
  76. observed.push(value)
  77. expect(observed[2]).toBe(observableValue)
  78. expect(original[2]).toBe(value)
  79. })
  80. test('setting a property with an unobserved value should wrap with observable', () => {
  81. const observed: any = observable({})
  82. const raw = {}
  83. observed.foo = raw
  84. expect(observed.foo).not.toBe(raw)
  85. expect(isObservable(observed.foo)).toBe(true)
  86. })
  87. test('observing already observed value should return same Proxy', () => {
  88. const original = { foo: 1 }
  89. const observed = observable(original)
  90. const observed2 = observable(observed)
  91. expect(observed2).toBe(observed)
  92. })
  93. test('observing the same value multiple times should return same Proxy', () => {
  94. const original = { foo: 1 }
  95. const observed = observable(original)
  96. const observed2 = observable(original)
  97. expect(observed2).toBe(observed)
  98. })
  99. test('should not pollute original object with Proxies', () => {
  100. const original: any = { foo: 1 }
  101. const original2 = { bar: 2 }
  102. const observed = observable(original)
  103. const observed2 = observable(original2)
  104. observed.bar = observed2
  105. expect(observed.bar).toBe(observed2)
  106. expect(original.bar).toBe(original2)
  107. })
  108. test('unwrap', () => {
  109. const original = { foo: 1 }
  110. const observed = observable(original)
  111. expect(unwrap(observed)).toBe(original)
  112. expect(unwrap(original)).toBe(original)
  113. })
  114. test('unobservable values', () => {
  115. const warn = jest.spyOn(console, 'warn')
  116. let lastMsg: string
  117. warn.mockImplementation(msg => {
  118. lastMsg = msg
  119. })
  120. const getMsg = (value: any) => `value is not observable: ${String(value)}`
  121. const assertValue = (value: any) => {
  122. observable(value)
  123. expect(lastMsg).toMatch(getMsg(value))
  124. }
  125. // number
  126. assertValue(1)
  127. // string
  128. assertValue('foo')
  129. // boolean
  130. assertValue(false)
  131. // null
  132. assertValue(null)
  133. // undefined should work because it returns empty object observable
  134. lastMsg = ''
  135. observable(undefined)
  136. expect(lastMsg).toBe('')
  137. // symbol
  138. const s = Symbol()
  139. assertValue(s)
  140. warn.mockRestore()
  141. // built-ins should work and return same value
  142. const p = Promise.resolve()
  143. expect(observable(p)).toBe(p)
  144. const r = new RegExp('')
  145. expect(observable(r)).toBe(r)
  146. const d = new Date()
  147. expect(observable(d)).toBe(d)
  148. })
  149. test('markNonReactive', () => {
  150. const obj = observable({
  151. foo: { a: 1 },
  152. bar: markNonReactive({ b: 2 })
  153. })
  154. expect(isObservable(obj.foo)).toBe(true)
  155. expect(isObservable(obj.bar)).toBe(false)
  156. })
  157. })