-
-
Notifications
You must be signed in to change notification settings - Fork 102
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(runtime-vapor): provide and inject (#158)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
- Loading branch information
Showing
5 changed files
with
541 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,397 @@ | ||
// NOTE: This test is implemented based on the case of `runtime-core/__test__/apiInject.spec.ts`. | ||
|
||
import { | ||
type InjectionKey, | ||
type Ref, | ||
createComponent, | ||
createTextNode, | ||
createVaporApp, | ||
getCurrentInstance, | ||
hasInjectionContext, | ||
inject, | ||
nextTick, | ||
provide, | ||
reactive, | ||
readonly, | ||
ref, | ||
renderEffect, | ||
setText, | ||
} from '../src' | ||
import { makeRender } from './_utils' | ||
|
||
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() | ||
setText(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() | ||
setText(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() | ||
setText(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() | ||
setText(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(() => { | ||
setText(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(() => { | ||
setText(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(() => { | ||
setText(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(() => { | ||
setText(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() | ||
setText(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() | ||
setText(n0, foo) | ||
return n0 | ||
})() | ||
}, | ||
} | ||
|
||
Provider.render() | ||
expect(`injection "foo" not found.`).not.toHaveBeenWarned() | ||
}) | ||
|
||
// #2400 | ||
it.todo('should not self-inject', () => { | ||
const Comp = define({ | ||
setup() { | ||
provide('foo', 'foo') | ||
const injection = inject('foo', null) | ||
return () => injection | ||
}, | ||
}) | ||
|
||
Comp.render() | ||
expect(Comp.host.innerHTML).toBe('') | ||
}) | ||
|
||
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 () => null | ||
}, | ||
}) | ||
|
||
Comp.render() | ||
}) | ||
|
||
it('should be true within app.runWithContext()', () => { | ||
expect.assertions(1) | ||
createVaporApp({}).runWithContext(() => { | ||
expect(hasInjectionContext()).toBe(true) | ||
}) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.