apiInject.spec.ts 8.4 KB

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