diff --git a/packages/runtime-core/__tests__/apiInject.spec.ts b/packages/runtime-core/__tests__/apiInject.spec.ts index a11163a6..a5d09a4f 100644 --- a/packages/runtime-core/__tests__/apiInject.spec.ts +++ b/packages/runtime-core/__tests__/apiInject.spec.ts @@ -1,5 +1,261 @@ +import { + h, + provide, + inject, + InjectionKey, + ref, + nextTick, + Ref, + readonly, + reactive +} from '../src/index' +import { render, nodeOps, serialize, mockWarn } from '@vue/runtime-test' + // reference: https://vue-composition-api-rfc.netlify.com/api.html#provide-inject describe('api: provide/inject', () => { - test.todo('should work') + mockWarn() + + it('string keys', () => { + const Provider = { + setup() { + provide('foo', 1) + return () => h(Middle) + } + } + + const Middle = { + render: () => h(Consumer) + } + + const Consumer = { + setup() { + const foo = inject('foo') + return () => foo + } + } + + const root = nodeOps.createElement('div') + render(h(Provider), root) + expect(serialize(root)).toBe(`
1
`) + }) + + it('symbol keys', () => { + // also verifies InjectionKey type sync + const key: InjectionKey = Symbol() + + const Provider = { + setup() { + provide(key, 1) + return () => h(Middle) + } + } + + const Middle = { + render: () => h(Consumer) + } + + const Consumer = { + setup() { + const foo = inject(key) || 1 + return () => foo + 1 + } + } + + const root = nodeOps.createElement('div') + render(h(Provider), root) + expect(serialize(root)).toBe(`
2
`) + }) + + it('default values', () => { + const Provider = { + setup() { + provide('foo', 'foo') + return () => h(Middle) + } + } + + const Middle = { + render: () => h(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 () => foo + bar + } + } + + const root = nodeOps.createElement('div') + render(h(Provider), root) + expect(serialize(root)).toBe(`
foobar
`) + }) + + it('nested providers', () => { + const ProviderOne = { + setup() { + provide('foo', 'foo') + provide('bar', 'bar') + return () => h(ProviderTwo) + } + } + + const ProviderTwo = { + setup() { + // override parent value + provide('foo', 'fooOverride') + provide('baz', 'baz') + return () => h(Consumer) + } + } + + const Consumer = { + setup() { + const foo = inject('foo') + const bar = inject('bar') + const baz = inject('baz') + return () => [foo, bar, baz].join(',') + } + } + + const root = nodeOps.createElement('div') + render(h(ProviderOne), root) + expect(serialize(root)).toBe(`
fooOverride,bar,baz
`) + }) + + it('reactivity with refs', async () => { + const count = ref(1) + + const Provider = { + setup() { + provide('count', count) + return () => h(Middle) + } + } + + const Middle = { + render: () => h(Consumer) + } + + const Consumer = { + setup() { + const count = inject('count') as Ref + return () => count.value + } + } + + const root = nodeOps.createElement('div') + render(h(Provider), root) + expect(serialize(root)).toBe(`
1
`) + + count.value++ + await nextTick() + expect(serialize(root)).toBe(`
2
`) + }) + + it('reactivity with readonly refs', async () => { + const count = ref(1) + + const Provider = { + setup() { + provide('count', readonly(count)) + return () => h(Middle) + } + } + + const Middle = { + render: () => h(Consumer) + } + + const Consumer = { + setup() { + const count = inject('count') as Ref + // should not work + count.value++ + return () => count.value + } + } + + const root = nodeOps.createElement('div') + render(h(Provider), root) + expect(serialize(root)).toBe(`
1
`) + + expect( + `Set operation on key "value" failed: target is readonly` + ).toHaveBeenWarned() + + // source mutation should still work + count.value++ + await nextTick() + expect(serialize(root)).toBe(`
2
`) + }) + + it('reactivity with objects', async () => { + const rootState = reactive({ count: 1 }) + + const Provider = { + setup() { + provide('state', rootState) + return () => h(Middle) + } + } + + const Middle = { + render: () => h(Consumer) + } + + const Consumer = { + setup() { + const state = inject('state') as typeof rootState + return () => state.count + } + } + + const root = nodeOps.createElement('div') + render(h(Provider), root) + expect(serialize(root)).toBe(`
1
`) + + rootState.count++ + await nextTick() + expect(serialize(root)).toBe(`
2
`) + }) + + it('reactivity with readonly objects', async () => { + const rootState = reactive({ count: 1 }) + + const Provider = { + setup() { + provide('state', readonly(rootState)) + return () => h(Middle) + } + } + + const Middle = { + render: () => h(Consumer) + } + + const Consumer = { + setup() { + const state = inject('state') as typeof rootState + // should not work + state.count++ + return () => state.count + } + } + + const root = nodeOps.createElement('div') + render(h(Provider), root) + expect(serialize(root)).toBe(`
1
`) + + expect( + `Set operation on key "count" failed: target is readonly` + ).toHaveBeenWarned() + + rootState.count++ + await nextTick() + expect(serialize(root)).toBe(`
2
`) + }) })