apiInject.spec.ts 6.0 KB

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