apiInject.spec.ts 6.9 KB

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