apiInject.spec.ts 6.7 KB

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