apiInject.spec.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import {
  2. h,
  3. provide,
  4. inject,
  5. InjectionKey,
  6. ref,
  7. nextTick,
  8. Ref,
  9. readonly,
  10. reactive
  11. } from '../src/index'
  12. import { render, nodeOps, serialize, mockWarn } from '@vue/runtime-test'
  13. // reference: https://vue-composition-api-rfc.netlify.com/api.html#provide-inject
  14. describe('api: provide/inject', () => {
  15. mockWarn()
  16. it('string keys', () => {
  17. const Provider = {
  18. setup() {
  19. provide('foo', 1)
  20. return () => h(Middle)
  21. }
  22. }
  23. const Middle = {
  24. render: () => h(Consumer)
  25. }
  26. const Consumer = {
  27. setup() {
  28. const foo = inject('foo')
  29. return () => foo
  30. }
  31. }
  32. const root = nodeOps.createElement('div')
  33. render(h(Provider), root)
  34. expect(serialize(root)).toBe(`<div>1</div>`)
  35. })
  36. it('symbol keys', () => {
  37. // also verifies InjectionKey type sync
  38. const key: InjectionKey<number> = Symbol()
  39. const Provider = {
  40. setup() {
  41. provide(key, 1)
  42. return () => h(Middle)
  43. }
  44. }
  45. const Middle = {
  46. render: () => h(Consumer)
  47. }
  48. const Consumer = {
  49. setup() {
  50. const foo = inject(key) || 1
  51. return () => foo + 1
  52. }
  53. }
  54. const root = nodeOps.createElement('div')
  55. render(h(Provider), root)
  56. expect(serialize(root)).toBe(`<div>2</div>`)
  57. })
  58. it('default values', () => {
  59. const Provider = {
  60. setup() {
  61. provide('foo', 'foo')
  62. return () => h(Middle)
  63. }
  64. }
  65. const Middle = {
  66. render: () => h(Consumer)
  67. }
  68. const Consumer = {
  69. setup() {
  70. // default value should be ignored if value is provided
  71. const foo = inject('foo', 'fooDefault')
  72. // default value should be used if value is not provided
  73. const bar = inject('bar', 'bar')
  74. return () => foo + bar
  75. }
  76. }
  77. const root = nodeOps.createElement('div')
  78. render(h(Provider), root)
  79. expect(serialize(root)).toBe(`<div>foobar</div>`)
  80. })
  81. it('nested providers', () => {
  82. const ProviderOne = {
  83. setup() {
  84. provide('foo', 'foo')
  85. provide('bar', 'bar')
  86. return () => h(ProviderTwo)
  87. }
  88. }
  89. const ProviderTwo = {
  90. setup() {
  91. // override parent value
  92. provide('foo', 'fooOverride')
  93. provide('baz', 'baz')
  94. return () => h(Consumer)
  95. }
  96. }
  97. const Consumer = {
  98. setup() {
  99. const foo = inject('foo')
  100. const bar = inject('bar')
  101. const baz = inject('baz')
  102. return () => [foo, bar, baz].join(',')
  103. }
  104. }
  105. const root = nodeOps.createElement('div')
  106. render(h(ProviderOne), root)
  107. expect(serialize(root)).toBe(`<div>fooOverride,bar,baz</div>`)
  108. })
  109. it('reactivity with refs', async () => {
  110. const count = ref(1)
  111. const Provider = {
  112. setup() {
  113. provide('count', count)
  114. return () => h(Middle)
  115. }
  116. }
  117. const Middle = {
  118. render: () => h(Consumer)
  119. }
  120. const Consumer = {
  121. setup() {
  122. const count = inject('count') as Ref<number>
  123. return () => count.value
  124. }
  125. }
  126. const root = nodeOps.createElement('div')
  127. render(h(Provider), root)
  128. expect(serialize(root)).toBe(`<div>1</div>`)
  129. count.value++
  130. await nextTick()
  131. expect(serialize(root)).toBe(`<div>2</div>`)
  132. })
  133. it('reactivity with readonly refs', async () => {
  134. const count = ref(1)
  135. const Provider = {
  136. setup() {
  137. provide('count', readonly(count))
  138. return () => h(Middle)
  139. }
  140. }
  141. const Middle = {
  142. render: () => h(Consumer)
  143. }
  144. const Consumer = {
  145. setup() {
  146. const count = inject('count') as Ref<number>
  147. // should not work
  148. count.value++
  149. return () => count.value
  150. }
  151. }
  152. const root = nodeOps.createElement('div')
  153. render(h(Provider), root)
  154. expect(serialize(root)).toBe(`<div>1</div>`)
  155. expect(
  156. `Set operation on key "value" failed: target is readonly`
  157. ).toHaveBeenWarned()
  158. // source mutation should still work
  159. count.value++
  160. await nextTick()
  161. expect(serialize(root)).toBe(`<div>2</div>`)
  162. })
  163. it('reactivity with objects', async () => {
  164. const rootState = reactive({ count: 1 })
  165. const Provider = {
  166. setup() {
  167. provide('state', rootState)
  168. return () => h(Middle)
  169. }
  170. }
  171. const Middle = {
  172. render: () => h(Consumer)
  173. }
  174. const Consumer = {
  175. setup() {
  176. const state = inject('state') as typeof rootState
  177. return () => state.count
  178. }
  179. }
  180. const root = nodeOps.createElement('div')
  181. render(h(Provider), root)
  182. expect(serialize(root)).toBe(`<div>1</div>`)
  183. rootState.count++
  184. await nextTick()
  185. expect(serialize(root)).toBe(`<div>2</div>`)
  186. })
  187. it('reactivity with readonly objects', async () => {
  188. const rootState = reactive({ count: 1 })
  189. const Provider = {
  190. setup() {
  191. provide('state', readonly(rootState))
  192. return () => h(Middle)
  193. }
  194. }
  195. const Middle = {
  196. render: () => h(Consumer)
  197. }
  198. const Consumer = {
  199. setup() {
  200. const state = inject('state') as typeof rootState
  201. // should not work
  202. state.count++
  203. return () => state.count
  204. }
  205. }
  206. const root = nodeOps.createElement('div')
  207. render(h(Provider), root)
  208. expect(serialize(root)).toBe(`<div>1</div>`)
  209. expect(
  210. `Set operation on key "count" failed: target is readonly`
  211. ).toHaveBeenWarned()
  212. rootState.count++
  213. await nextTick()
  214. expect(serialize(root)).toBe(`<div>2</div>`)
  215. })
  216. it('should warn unfound', () => {
  217. const Provider = {
  218. setup() {
  219. return () => h(Middle)
  220. }
  221. }
  222. const Middle = {
  223. render: () => h(Consumer)
  224. }
  225. const Consumer = {
  226. setup() {
  227. const foo = inject('foo')
  228. expect(foo).toBeUndefined()
  229. return () => foo
  230. }
  231. }
  232. const root = nodeOps.createElement('div')
  233. render(h(Provider), root)
  234. expect(serialize(root)).toBe(`<div><!----></div>`)
  235. expect(`injection "foo" not found.`).toHaveBeenWarned()
  236. })
  237. })