| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- import {
- type InjectionKey,
- type Ref,
- h,
- hasInjectionContext,
- inject,
- nextTick,
- provide,
- reactive,
- readonly,
- ref,
- renderSlot,
- toDisplayString,
- } from '@vue/runtime-dom'
- import {
- createComponent,
- createSlot,
- createTextNode,
- createVaporApp,
- defineVaporComponent,
- renderEffect,
- template,
- vaporInteropPlugin,
- withVaporCtx,
- } from '../src'
- import { makeRender } from './_utils'
- import { setElementText, setText } from '../src/dom/prop'
- const define = makeRender<any>()
- // reference: https://vue-composition-api-rfc.netlify.com/api.html#provide-inject
- describe('api: provide/inject', () => {
- it('string keys', () => {
- const Provider = define({
- setup() {
- provide('foo', 1)
- return createComponent(Middle)
- },
- })
- const Middle = {
- render() {
- return createComponent(Consumer)
- },
- }
- const Consumer = {
- setup() {
- const foo = inject('foo')
- return (() => {
- const n0 = createTextNode()
- setElementText(n0, foo)
- return n0
- })()
- },
- }
- Provider.render()
- expect(Provider.host.innerHTML).toBe('1')
- })
- it('symbol keys', () => {
- // also verifies InjectionKey type sync
- const key: InjectionKey<number> = Symbol()
- const Provider = define({
- setup() {
- provide(key, 1)
- return createComponent(Middle)
- },
- })
- const Middle = {
- render: () => createComponent(Consumer),
- }
- const Consumer = {
- setup() {
- const foo = inject(key)
- return (() => {
- const n0 = createTextNode()
- setElementText(n0, foo)
- return n0
- })()
- },
- }
- Provider.render()
- expect(Provider.host.innerHTML).toBe('1')
- })
- it('default values', () => {
- const Provider = define({
- setup() {
- provide('foo', 'foo')
- return createComponent(Middle)
- },
- })
- const Middle = {
- render: () => createComponent(Consumer),
- }
- const Consumer = {
- setup() {
- // default value should be ignored if value is provided
- const foo = inject('foo', 'fooDefault')
- // default value should be used if value is not provided
- const bar = inject('bar', 'bar')
- return (() => {
- const n0 = createTextNode()
- setElementText(n0, foo + bar)
- return n0
- })()
- },
- }
- Provider.render()
- expect(Provider.host.innerHTML).toBe('foobar')
- })
- // NOTE: Options API is not supported
- // it('bound to instance', () => {})
- it('nested providers', () => {
- const ProviderOne = define({
- setup() {
- provide('foo', 'foo')
- provide('bar', 'bar')
- return createComponent(ProviderTwo)
- },
- })
- const ProviderTwo = {
- setup() {
- // override parent value
- provide('foo', 'fooOverride')
- provide('baz', 'baz')
- return createComponent(Consumer)
- },
- }
- const Consumer = {
- setup() {
- const foo = inject('foo')
- const bar = inject('bar')
- const baz = inject('baz')
- return (() => {
- const n0 = createTextNode()
- setElementText(n0, [foo, bar, baz].join(','))
- return n0
- })()
- },
- }
- ProviderOne.render()
- expect(ProviderOne.host.innerHTML).toBe('fooOverride,bar,baz')
- })
- it('reactivity with refs', async () => {
- const count = ref(1)
- const Provider = define({
- setup() {
- provide('count', count)
- return createComponent(Middle)
- },
- })
- const Middle = {
- render: () => createComponent(Consumer),
- }
- const Consumer = {
- setup() {
- const count = inject<Ref<number>>('count')!
- return (() => {
- const n0 = createTextNode()
- renderEffect(() => {
- setElementText(n0, count.value)
- })
- return n0
- })()
- },
- }
- Provider.render()
- expect(Provider.host.innerHTML).toBe('1')
- count.value++
- await nextTick()
- expect(Provider.host.innerHTML).toBe('2')
- })
- it('reactivity with readonly refs', async () => {
- const count = ref(1)
- const Provider = define({
- setup() {
- provide('count', readonly(count))
- return createComponent(Middle)
- },
- })
- const Middle = {
- render: () => createComponent(Consumer),
- }
- const Consumer = {
- setup() {
- const count = inject<Ref<number>>('count')!
- // should not work
- count.value++
- return (() => {
- const n0 = createTextNode()
- renderEffect(() => {
- setElementText(n0, count.value)
- })
- return n0
- })()
- },
- }
- Provider.render()
- expect(Provider.host.innerHTML).toBe('1')
- expect(
- `Set operation on key "value" failed: target is readonly`,
- ).toHaveBeenWarned()
- count.value++
- await nextTick()
- expect(Provider.host.innerHTML).toBe('2')
- })
- it('reactivity with objects', async () => {
- const rootState = reactive({ count: 1 })
- const Provider = define({
- setup() {
- provide('state', rootState)
- return createComponent(Middle)
- },
- })
- const Middle = {
- render: () => createComponent(Consumer),
- }
- const Consumer = {
- setup() {
- const state = inject<typeof rootState>('state')!
- return (() => {
- const n0 = createTextNode()
- renderEffect(() => {
- setElementText(n0, state.count)
- })
- return n0
- })()
- },
- }
- Provider.render()
- expect(Provider.host.innerHTML).toBe('1')
- rootState.count++
- await nextTick()
- expect(Provider.host.innerHTML).toBe('2')
- })
- it('reactivity with readonly objects', async () => {
- const rootState = reactive({ count: 1 })
- const Provider = define({
- setup() {
- provide('state', readonly(rootState))
- return createComponent(Middle)
- },
- })
- const Middle = {
- render: () => createComponent(Consumer),
- }
- const Consumer = {
- setup() {
- const state = inject<typeof rootState>('state')!
- // should not work
- state.count++
- return (() => {
- const n0 = createTextNode()
- renderEffect(() => {
- setElementText(n0, state.count)
- })
- return n0
- })()
- },
- }
- Provider.render()
- expect(Provider.host.innerHTML).toBe('1')
- expect(
- `Set operation on key "count" failed: target is readonly`,
- ).toHaveBeenWarned()
- rootState.count++
- await nextTick()
- expect(Provider.host.innerHTML).toBe('2')
- })
- it('should warn unfound', () => {
- const Provider = define({
- setup() {
- return createComponent(Middle)
- },
- })
- const Middle = {
- render: () => createComponent(Consumer),
- }
- const Consumer = {
- setup() {
- const foo = inject('foo')
- expect(foo).toBeUndefined()
- return (() => {
- const n0 = createTextNode()
- setElementText(n0, foo)
- return n0
- })()
- },
- }
- Provider.render()
- expect(Provider.host.innerHTML).toBe('')
- expect(`injection "foo" not found.`).toHaveBeenWarned()
- })
- it('should not warn when default value is undefined', () => {
- const Provider = define({
- setup() {
- return createComponent(Middle)
- },
- })
- const Middle = {
- render: () => createComponent(Consumer),
- }
- const Consumer = {
- setup() {
- const foo = inject('foo', undefined)
- return (() => {
- const n0 = createTextNode()
- setElementText(n0, foo)
- return n0
- })()
- },
- }
- Provider.render()
- expect(`injection "foo" not found.`).not.toHaveBeenWarned()
- })
- // #2400
- it('should not self-inject', () => {
- const { host } = define({
- setup() {
- provide('foo', 'foo')
- const injection = inject('foo', null)
- return createTextNode(toDisplayString(injection))
- },
- }).render()
- expect(host.innerHTML).toBe('')
- })
- it('should work with slots', () => {
- const Parent = defineVaporComponent({
- setup() {
- provide('test', 'hello')
- return createSlot('default', null)
- },
- })
- const Child = defineVaporComponent({
- setup() {
- const test = inject('test')
- return createTextNode(toDisplayString(test))
- },
- })
- const { host } = define({
- setup() {
- return createComponent(Parent, null, {
- default: withVaporCtx(() => createComponent(Child)),
- })
- },
- }).render()
- expect(host.innerHTML).toBe('hello<!--slot-->')
- })
- describe('hasInjectionContext', () => {
- it('should be false outside of setup', () => {
- expect(hasInjectionContext()).toBe(false)
- })
- it('should be true within setup', () => {
- expect.assertions(1)
- const Comp = define({
- setup() {
- expect(hasInjectionContext()).toBe(true)
- return []
- },
- })
- Comp.render()
- })
- it('should be true within app.runWithContext()', () => {
- expect.assertions(1)
- createVaporApp({}).runWithContext(() => {
- expect(hasInjectionContext()).toBe(true)
- })
- })
- })
- })
- describe('vdom interop', () => {
- beforeEach(() => {
- document.body.innerHTML = ''
- })
- test('should inject value from vapor parent', async () => {
- const VdomChild = {
- setup() {
- const foo = inject('foo')
- return () => h('div', null, [toDisplayString(foo)])
- },
- }
- const value = ref('foo')
- const App = defineVaporComponent({
- setup() {
- provide('foo', value)
- return createComponent(VdomChild as any)
- },
- })
- const root = document.createElement('div')
- document.body.appendChild(root)
- const app = createVaporApp(App)
- app.use(vaporInteropPlugin)
- app.mount(root)
- expect(root.innerHTML).toBe('<div>foo</div>')
- value.value = 'bar'
- await nextTick()
- expect(root.innerHTML).toBe('<div>bar</div>')
- app.unmount()
- })
- test('slotted vapor child should inject value from vdom parent', async () => {
- const value = ref('foo')
- const VdomParent = {
- setup(_: any, { slots }: any) {
- provide('foo', value)
- return () => renderSlot(slots, 'default')
- },
- }
- const VaporChild = defineVaporComponent({
- setup() {
- const foo = inject('foo')
- const n0 = template(' ')() as any
- renderEffect(() => setText(n0, toDisplayString(foo)))
- return n0
- },
- })
- const App = defineVaporComponent({
- setup() {
- return createComponent(VdomParent, null, {
- default: () => createComponent(VaporChild),
- })
- },
- })
- const root = document.createElement('div')
- document.body.appendChild(root)
- const app = createVaporApp(App)
- app.use(vaporInteropPlugin)
- app.mount(root)
- expect(root.innerHTML).toBe('foo')
- value.value = 'bar'
- await nextTick()
- expect(root.innerHTML).toBe('bar')
- app.unmount()
- })
- })
|