apiInject.spec.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. // NOTE: This test is implemented based on the case of `runtime-core/__test__/apiInject.spec.ts`.
  2. import {
  3. type InjectionKey,
  4. type Ref,
  5. createComponent,
  6. createTextNode,
  7. createVaporApp,
  8. getCurrentInstance,
  9. hasInjectionContext,
  10. inject,
  11. nextTick,
  12. provide,
  13. reactive,
  14. readonly,
  15. ref,
  16. renderEffect,
  17. setText,
  18. } from '../src'
  19. import { makeRender } from './_utils'
  20. const define = makeRender<any>()
  21. // reference: https://vue-composition-api-rfc.netlify.com/api.html#provide-inject
  22. describe('api: provide/inject', () => {
  23. it('string keys', () => {
  24. const Provider = define({
  25. setup() {
  26. provide('foo', 1)
  27. return createComponent(Middle)
  28. },
  29. })
  30. const Middle = {
  31. render() {
  32. return createComponent(Consumer)
  33. },
  34. }
  35. const Consumer = {
  36. setup() {
  37. const foo = inject('foo')
  38. return (() => {
  39. const n0 = createTextNode()
  40. setText(n0, foo)
  41. return n0
  42. })()
  43. },
  44. }
  45. Provider.render()
  46. expect(Provider.host.innerHTML).toBe('1')
  47. })
  48. it('symbol keys', () => {
  49. // also verifies InjectionKey type sync
  50. const key: InjectionKey<number> = Symbol()
  51. const Provider = define({
  52. setup() {
  53. provide(key, 1)
  54. return createComponent(Middle)
  55. },
  56. })
  57. const Middle = {
  58. render: () => createComponent(Consumer),
  59. }
  60. const Consumer = {
  61. setup() {
  62. const foo = inject(key)
  63. return (() => {
  64. const n0 = createTextNode()
  65. setText(n0, foo)
  66. return n0
  67. })()
  68. },
  69. }
  70. Provider.render()
  71. expect(Provider.host.innerHTML).toBe('1')
  72. })
  73. it('default values', () => {
  74. const Provider = define({
  75. setup() {
  76. provide('foo', 'foo')
  77. return createComponent(Middle)
  78. },
  79. })
  80. const Middle = {
  81. render: () => createComponent(Consumer),
  82. }
  83. const Consumer = {
  84. setup() {
  85. // default value should be ignored if value is provided
  86. const foo = inject('foo', 'fooDefault')
  87. // default value should be used if value is not provided
  88. const bar = inject('bar', 'bar')
  89. return (() => {
  90. const n0 = createTextNode()
  91. setText(n0, foo + bar)
  92. return n0
  93. })()
  94. },
  95. }
  96. Provider.render()
  97. expect(Provider.host.innerHTML).toBe('foobar')
  98. })
  99. // NOTE: Options API is not supported
  100. // it('bound to instance', () => {})
  101. it('nested providers', () => {
  102. const ProviderOne = define({
  103. setup() {
  104. provide('foo', 'foo')
  105. provide('bar', 'bar')
  106. return createComponent(ProviderTwo)
  107. },
  108. })
  109. const ProviderTwo = {
  110. setup() {
  111. // override parent value
  112. provide('foo', 'fooOverride')
  113. provide('baz', 'baz')
  114. return createComponent(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 (() => {
  123. const n0 = createTextNode()
  124. setText(n0, [foo, bar, baz].join(','))
  125. return n0
  126. })()
  127. },
  128. }
  129. ProviderOne.render()
  130. expect(ProviderOne.host.innerHTML).toBe('fooOverride,bar,baz')
  131. })
  132. it('reactivity with refs', async () => {
  133. const count = ref(1)
  134. const Provider = define({
  135. setup() {
  136. provide('count', count)
  137. return createComponent(Middle)
  138. },
  139. })
  140. const Middle = {
  141. render: () => createComponent(Consumer),
  142. }
  143. const Consumer = {
  144. setup() {
  145. const count = inject<Ref<number>>('count')!
  146. return (() => {
  147. const n0 = createTextNode()
  148. renderEffect(() => {
  149. setText(n0, count.value)
  150. })
  151. return n0
  152. })()
  153. },
  154. }
  155. Provider.render()
  156. expect(Provider.host.innerHTML).toBe('1')
  157. count.value++
  158. await nextTick()
  159. expect(Provider.host.innerHTML).toBe('2')
  160. })
  161. it('reactivity with readonly refs', async () => {
  162. const count = ref(1)
  163. const Provider = define({
  164. setup() {
  165. provide('count', readonly(count))
  166. return createComponent(Middle)
  167. },
  168. })
  169. const Middle = {
  170. render: () => createComponent(Consumer),
  171. }
  172. const Consumer = {
  173. setup() {
  174. const count = inject<Ref<number>>('count')!
  175. // should not work
  176. count.value++
  177. return (() => {
  178. const n0 = createTextNode()
  179. renderEffect(() => {
  180. setText(n0, count.value)
  181. })
  182. return n0
  183. })()
  184. },
  185. }
  186. Provider.render()
  187. expect(Provider.host.innerHTML).toBe('1')
  188. expect(
  189. `Set operation on key "value" failed: target is readonly`,
  190. ).toHaveBeenWarned()
  191. count.value++
  192. await nextTick()
  193. expect(Provider.host.innerHTML).toBe('2')
  194. })
  195. it('reactivity with objects', async () => {
  196. const rootState = reactive({ count: 1 })
  197. const Provider = define({
  198. setup() {
  199. provide('state', rootState)
  200. return createComponent(Middle)
  201. },
  202. })
  203. const Middle = {
  204. render: () => createComponent(Consumer),
  205. }
  206. const Consumer = {
  207. setup() {
  208. const state = inject<typeof rootState>('state')!
  209. return (() => {
  210. const n0 = createTextNode()
  211. renderEffect(() => {
  212. setText(n0, state.count)
  213. })
  214. return n0
  215. })()
  216. },
  217. }
  218. Provider.render()
  219. expect(Provider.host.innerHTML).toBe('1')
  220. rootState.count++
  221. await nextTick()
  222. expect(Provider.host.innerHTML).toBe('2')
  223. })
  224. it('reactivity with readonly objects', async () => {
  225. const rootState = reactive({ count: 1 })
  226. const Provider = define({
  227. setup() {
  228. provide('state', readonly(rootState))
  229. return createComponent(Middle)
  230. },
  231. })
  232. const Middle = {
  233. render: () => createComponent(Consumer),
  234. }
  235. const Consumer = {
  236. setup() {
  237. const state = inject<typeof rootState>('state')!
  238. // should not work
  239. state.count++
  240. return (() => {
  241. const n0 = createTextNode()
  242. renderEffect(() => {
  243. setText(n0, state.count)
  244. })
  245. return n0
  246. })()
  247. },
  248. }
  249. Provider.render()
  250. expect(Provider.host.innerHTML).toBe('1')
  251. expect(
  252. `Set operation on key "count" failed: target is readonly`,
  253. ).toHaveBeenWarned()
  254. rootState.count++
  255. await nextTick()
  256. expect(Provider.host.innerHTML).toBe('2')
  257. })
  258. it('should warn unfound', () => {
  259. const Provider = define({
  260. setup() {
  261. return createComponent(Middle)
  262. },
  263. })
  264. const Middle = {
  265. render: () => createComponent(Consumer),
  266. }
  267. const Consumer = {
  268. setup() {
  269. const foo = inject('foo')
  270. expect(foo).toBeUndefined()
  271. return (() => {
  272. const n0 = createTextNode()
  273. setText(n0, foo)
  274. return n0
  275. })()
  276. },
  277. }
  278. Provider.render()
  279. expect(Provider.host.innerHTML).toBe('')
  280. expect(`injection "foo" not found.`).toHaveBeenWarned()
  281. })
  282. it('should not warn when default value is undefined', () => {
  283. const Provider = define({
  284. setup() {
  285. return createComponent(Middle)
  286. },
  287. })
  288. const Middle = {
  289. render: () => createComponent(Consumer),
  290. }
  291. const Consumer = {
  292. setup() {
  293. const foo = inject('foo', undefined)
  294. return (() => {
  295. const n0 = createTextNode()
  296. setText(n0, foo)
  297. return n0
  298. })()
  299. },
  300. }
  301. Provider.render()
  302. expect(`injection "foo" not found.`).not.toHaveBeenWarned()
  303. })
  304. // #2400
  305. it.todo('should not self-inject', () => {
  306. const Comp = define({
  307. setup() {
  308. provide('foo', 'foo')
  309. const injection = inject('foo', null)
  310. return () => injection
  311. },
  312. })
  313. Comp.render()
  314. expect(Comp.host.innerHTML).toBe('')
  315. })
  316. describe('hasInjectionContext', () => {
  317. it('should be false outside of setup', () => {
  318. expect(hasInjectionContext()).toBe(false)
  319. })
  320. it('should be true within setup', () => {
  321. expect.assertions(1)
  322. const Comp = define({
  323. setup() {
  324. expect(hasInjectionContext()).toBe(true)
  325. return () => null
  326. },
  327. })
  328. Comp.render()
  329. })
  330. it('should be true within app.runWithContext()', () => {
  331. expect.assertions(1)
  332. createVaporApp({}).runWithContext(() => {
  333. expect(hasInjectionContext()).toBe(true)
  334. })
  335. })
  336. })
  337. })