- Only use platform-specific runtimes if the test is asserting platform-specific behavior.
+### Testing Type Definition Correctness
+
+This project uses [tsd](https://github.com/SamVerschueren/tsd) to test the built definition files (`*.d.ts`).
+
+Type tests are located in the `test-dts` directory. To run the dts tests, run `yarn test-dts`. Note that the type test requires all relevant `*.d.ts` files to be built first (and the script does it for you). Once the `d.ts` files are built and up-to-date, the tests can be re-run by simply running `./node_modules/.bin/tsd`.
+
## Financial Contribution
As a pure community-driven project without major corporate backing, we also welcome financial contributions via Patreon and OpenCollective.
+++ /dev/null
-import { createComponent } from '../src/apiCreateComponent'
-import { ref } from '@vue/reactivity'
-import { PropType } from '../src/componentProps'
-import { h } from '../src/h'
-
-// mock React just for TSX testing purposes
-const React = {
- createElement: () => {}
-}
-
-test('createComponent type inference', () => {
- const MyComponent = createComponent({
- props: {
- a: Number,
- // required should make property non-void
- b: {
- type: String,
- required: true
- },
- // default value should infer type and make it non-void
- bb: {
- default: 'hello'
- },
- // explicit type casting
- cc: Array as PropType<string[]>,
- // required + type casting
- dd: {
- type: Array as PropType<string[]>,
- required: true
- },
- // explicit type casting with constructor
- ccc: Array as () => string[],
- // required + contructor type casting
- ddd: {
- type: Array as () => string[],
- required: true
- }
- },
- setup(props) {
- props.a && props.a * 2
- props.b.slice()
- props.bb.slice()
- props.cc && props.cc.push('hoo')
- props.dd.push('dd')
- return {
- c: ref(1),
- d: {
- e: ref('hi')
- }
- }
- },
- render() {
- const props = this.$props
- props.a && props.a * 2
- props.b.slice()
- props.bb.slice()
- props.cc && props.cc.push('hoo')
- props.dd.push('dd')
- this.a && this.a * 2
- this.b.slice()
- this.bb.slice()
- this.c * 2
- this.d.e.slice()
- this.cc && this.cc.push('hoo')
- this.dd.push('dd')
- return h('div', this.bb)
- }
- })
- // test TSX props inference
- ;<MyComponent
- a={1}
- b="foo"
- dd={['foo']}
- ddd={['foo']}
- // should allow extraneous as attrs
- class="bar"
- />
-})
-
-test('type inference w/ optional props declaration', () => {
- const Comp = createComponent({
- setup(props: { msg: string }) {
- props.msg
- return {
- a: 1
- }
- },
- render() {
- this.$props.msg
- this.msg
- this.a * 2
- return h('div', this.msg)
- }
- })
- ;<Comp msg="hello" />
-})
-
-test('type inference w/ direct setup function', () => {
- const Comp = createComponent((props: { msg: string }) => {
- return () => <div>{props.msg}</div>
- })
- ;<Comp msg="hello" />
-})
-
-test('type inference w/ array props declaration', () => {
- const Comp = createComponent({
- props: ['a', 'b'],
- setup(props) {
- props.a
- props.b
- return {
- c: 1
- }
- },
- render() {
- this.$props.a
- this.$props.b
- this.a
- this.b
- this.c
- }
- })
- ;<Comp a={1} b={2} />
-})
-
-test('with legacy options', () => {
- createComponent({
- props: { a: Number },
- setup() {
- return {
- b: 123
- }
- },
- data() {
- // Limitation: we cannot expose the return result of setup() on `this`
- // here in data() - somehow that would mess up the inference
- return {
- c: this.a || 123
- }
- },
- computed: {
- d(): number {
- return this.b + 1
- }
- },
- watch: {
- a() {
- this.b + 1
- }
- },
- created() {
- this.a && this.a * 2
- this.b * 2
- this.c * 2
- this.d * 2
- },
- methods: {
- doSomething() {
- this.a && this.a * 2
- this.b * 2
- this.c * 2
- this.d * 2
- return (this.a || 0) + this.b + this.c + this.d
- }
- },
- render() {
- this.a && this.a * 2
- this.b * 2
- this.c * 2
- this.d * 2
- return h('div', (this.a || 0) + this.b + this.c + this.d)
- }
- })
-})
one = {
name: 'one',
data: () => ({ msg: 'one' }),
- render() {
+ render(this: any) {
return h('div', this.msg)
},
created: jest.fn(),
two = {
name: 'two',
data: () => ({ msg: 'two' }),
- render() {
+ render(this: any) {
return h('div', this.msg)
},
created: jest.fn(),
M extends MethodOptions = {}
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
props?: undefined
-} & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M>>
+} & ThisType<ComponentPublicInstance<{}, RawBindings, D, C, M, Props>>
export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
ctx: ComponentPublicInstance,
key: string
) {
- const getter = () => ctx[key]
+ const getter = () => (ctx as Data)[key]
if (isString(raw)) {
const handler = renderContext[raw]
if (isFunction(handler)) {
import {
currentInstance,
ComponentInternalInstance,
- currentSuspense
+ currentSuspense,
+ Data
} from './component'
import {
ErrorCodes,
cb: Function,
options?: WatchOptions
): StopHandle {
- const ctx = this.renderProxy!
+ const ctx = this.renderProxy as Data
const getter = isString(source) ? () => ctx[source] : source.bind(ctx)
const stop = watch(getter, cb.bind(ctx), options)
onBeforeUnmount(stop, this)
makeMap,
isPromise
} from '@vue/shared'
-import { SuspenseBoundary } from './rendererSuspense'
+import { SuspenseBoundary } from './components/Suspense'
import {
CompilerError,
CompilerOptions,
M extends MethodOptions = {},
PublicProps = P
> = {
- [key: string]: any
$data: D
$props: PublicProps
$attrs: Data
import { isString, isArray } from '@vue/shared'
import { watch } from '../apiWatch'
import { ShapeFlags } from '../shapeFlags'
-import { SuspenseBoundary } from '../rendererSuspense'
+import { SuspenseBoundary } from './Suspense'
import {
RendererInternals,
queuePostRenderEffect,
deactivate: (vnode: VNode) => void
}
-export const KeepAlive = {
+const KeepAliveImpl = {
name: `KeepAlive`,
// Marker for special handling inside the renderer. We are not using a ===
}
if (__DEV__) {
- ;(KeepAlive as any).props = {
+ ;(KeepAliveImpl as any).props = {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number]
}
}
+// export the public type for h/tsx inference
+export const KeepAlive = (KeepAliveImpl as any) as {
+ new (): {
+ $props: KeepAliveProps
+ }
+}
+
function getName(comp: Component): string | void {
return (comp as FunctionalComponent).displayName || comp.name
}
if (target) {
let current = target.parent
while (current && current.parent) {
- if (current.parent.type === KeepAlive) {
+ if (current.parent.type === KeepAliveImpl) {
injectToKeepAliveRoot(wrappedHook, type, target, current)
}
current = current.parent
-import { VNode, normalizeVNode, VNodeChild } from './vnode'
-import { ShapeFlags } from './shapeFlags'
+import { VNode, normalizeVNode, VNodeChild } from '../vnode'
+import { ShapeFlags } from '../shapeFlags'
import { isFunction, isArray } from '@vue/shared'
-import { ComponentInternalInstance, handleSetupResult } from './component'
-import { Slots } from './componentSlots'
-import { RendererInternals } from './renderer'
-import { queuePostFlushCb, queueJob } from './scheduler'
-import { updateHOCHostEl } from './componentRenderUtils'
-import { handleError, ErrorCodes } from './errorHandling'
-import { pushWarningContext, popWarningContext } from './warning'
+import { ComponentInternalInstance, handleSetupResult } from '../component'
+import { Slots } from '../componentSlots'
+import { RendererInternals } from '../renderer'
+import { queuePostFlushCb, queueJob } from '../scheduler'
+import { updateHOCHostEl } from '../componentRenderUtils'
+import { handleError, ErrorCodes } from '../errorHandling'
+import { pushWarningContext, popWarningContext } from '../warning'
-export const Suspense = {
+export interface SuspenseProps {
+ onResolve?: () => void
+ onRecede?: () => void
+}
+
+// Suspense exposes a component-like API, and is treated like a component
+// in the compiler, but internally it's a special built-in type that hooks
+// directly into the renderer.
+export const SuspenseImpl = {
+ // In order to make Suspense tree-shakable, we need to avoid importing it
+ // directly in the renderer. The renderer checks for the __isSuspense flag
+ // on a vnode's type and calls the `process` method, passing in renderer
+ // internals.
__isSuspense: true,
process(
n1: VNode | null,
}
}
+// Force-casted public typing for h and TSX props inference
+export const Suspense = ((__FEATURE_SUSPENSE__
+ ? SuspenseImpl
+ : null) as any) as {
+ __isSuspense: true
+ new (): { $props: SuspenseProps }
+}
+
function mountSuspense(
n2: VNode,
container: object,
VNodeChildren,
Fragment,
Portal,
- isVNode,
- Suspense
+ isVNode
} from './vnode'
+import { Suspense, SuspenseProps } from './components/Suspense'
import { isObject, isArray } from '@vue/shared'
import { RawSlots } from './componentSlots'
import { FunctionalComponent } from './component'
// fake constructor type returned from `createComponent`
interface Constructor<P = any> {
+ __isFragment?: never
+ __isPortal?: never
+ __isSuspense?: never
new (): { $props: P }
}
export function h(type: typeof Suspense, children?: RawChildren): VNode
export function h(
type: typeof Suspense,
- props?:
- | (RawProps & {
- onResolve?: () => void
- onRecede?: () => void
- })
- | null,
+ props?: (RawProps & SuspenseProps) | null,
children?: RawChildren | RawSlots
): VNode
// Public API ------------------------------------------------------------------
+export const version = __VERSION__
export * from './apiReactivity'
export * from './apiWatch'
export * from './apiLifecycle'
createBlock
} from './vnode'
// VNode type symbols
-export { Text, Comment, Fragment, Portal, Suspense } from './vnode'
+export { Text, Comment, Fragment, Portal } from './vnode'
// Internal Components
-export { KeepAlive } from './components/KeepAlive'
+export { Suspense, SuspenseProps } from './components/Suspense'
+export { KeepAlive, KeepAliveProps } from './components/KeepAlive'
// VNode flags
export { PublicShapeFlags as ShapeFlags } from './shapeFlags'
import { PublicPatchFlags } from '@vue/shared'
FunctionDirective,
DirectiveArguments
} from './directives'
-export { SuspenseBoundary } from './rendererSuspense'
-
-export const version = __VERSION__
+export { SuspenseBoundary } from './components/Suspense'
import { App, createAppAPI } from './apiApp'
import {
SuspenseBoundary,
- Suspense,
- queueEffectWithSuspense
-} from './rendererSuspense'
+ queueEffectWithSuspense,
+ SuspenseImpl
+} from './components/Suspense'
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
import { KeepAliveSink } from './components/KeepAlive'
optimized
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
- ;(type as typeof Suspense).process(
+ ;(type as typeof SuspenseImpl).process(
n1,
n2,
container,
import { ShapeFlags } from './shapeFlags'
import { isReactive, Ref } from '@vue/reactivity'
import { AppContext } from './apiApp'
-import { SuspenseBoundary } from './rendererSuspense'
+import { SuspenseBoundary } from './components/Suspense'
import { DirectiveBinding } from './directives'
-import { Suspense as SuspenseImpl } from './rendererSuspense'
+import { SuspenseImpl } from './components/Suspense'
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
- // type differentiator for h()
__isFragment: true
+ new (): {
+ $props: VNodeProps
+ }
}
export const Portal = (Symbol(__DEV__ ? 'Portal' : undefined) as any) as {
- // type differentiator for h()
__isPortal: true
+ new (): {
+ $props: VNodeProps & { target: string | object }
+ }
}
export const Text = Symbol(__DEV__ ? 'Text' : undefined)
export const Comment = Symbol(__DEV__ ? 'Comment' : undefined)
-// Export Suspense with casting to avoid circular type dependency between
-// `suspense.ts` and `createRenderer.ts` in exported types.
-// A circular type dependency causes tsc to generate d.ts with dynmaic import()
-// calls using realtive paths, which works for separate d.ts files, but will
-// fail after d.ts rollup with API Extractor.
-const Suspense = ((__FEATURE_SUSPENSE__ ? SuspenseImpl : null) as any) as {
- __isSuspense: true
-}
-export { Suspense }
-
export type VNodeTypes =
| string
| Component
| typeof Portal
| typeof Text
| typeof Comment
- | typeof Suspense
+ | typeof SuspenseImpl
export interface VNodeProps {
[key: string]: any
key?: string | number
- ref?: string | Ref | ((ref: object) => void)
+ ref?: string | Ref | ((ref: object | null) => void)
}
type VNodeChildAtom<HostNode, HostElement> =
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
function formatComponentName(vnode: ComponentVNode, file?: string): string {
- const Component = vnode.type
+ const Component = vnode.type as Component
let name = isFunction(Component)
? Component.displayName || Component.name
: Component.name
+import { Ref, ComponentPublicInstance } from '@vue/runtime-core'
+
// This code is based on https://github.com/wonderful-panda/vue-tsx-support
// published under the MIT license.
// Copyright by @wonderful-panda
[K in StringKeyOf<E>]?: E[K] extends Function ? E[K] : (payload: E[K]) => void
}
-type ElementAttrs<T> = T & EventHandlers<Events>
+type ReservedProps = {
+ key?: string | number
+ ref?: string | Ref | ((ref: Element | ComponentPublicInstance | null) => void)
+}
+
+type ElementAttrs<T> = T & EventHandlers<Events> & ReservedProps
type NativeElements = {
[K in StringKeyOf<IntrinsicElementAttributes>]: ElementAttrs<
>
}
-declare namespace JSX {
- interface Element {}
- interface ElementClass {
- $props: {}
- }
- interface ElementAttributesProperty {
- $props: {}
- }
- interface IntrinsicElements extends NativeElements {
- // allow arbitrary elements
- [name: string]: any
+declare global {
+ namespace JSX {
+ interface Element {}
+ interface ElementClass {
+ $props: {}
+ }
+ interface ElementAttributesProperty {
+ $props: {}
+ }
+ interface IntrinsicElements extends NativeElements {
+ // allow arbitrary elements
+ [name: string]: any
+ }
}
}
+
+// suppress ts:2669
+export {}
declaration: shouldEmitDeclarations,
declarationMap: shouldEmitDeclarations
},
- exclude: ['**/__tests__']
+ exclude: ['**/__tests__', 'test-dts']
}
})
// we only need to check TS and generate declarations once for each build.
--- /dev/null
+import { describe } from './util'
+import { expectError, expectType } from 'tsd'
+import { createComponent, PropType, ref } from './index'
+
+describe('with object props', () => {
+ interface ExpectedProps {
+ a?: number | undefined
+ b: string
+ bb: string
+ cc?: string[] | undefined
+ dd: string[]
+ ccc?: string[] | undefined
+ ddd: string[]
+ }
+
+ const MyComponent = createComponent({
+ props: {
+ a: Number,
+ // required should make property non-void
+ b: {
+ type: String,
+ required: true
+ },
+ // default value should infer type and make it non-void
+ bb: {
+ default: 'hello'
+ },
+ // explicit type casting
+ cc: Array as PropType<string[]>,
+ // required + type casting
+ dd: {
+ type: Array as PropType<string[]>,
+ required: true
+ },
+ // explicit type casting with constructor
+ ccc: Array as () => string[],
+ // required + contructor type casting
+ ddd: {
+ type: Array as () => string[],
+ required: true
+ }
+ },
+ setup(props) {
+ // type assertion. See https://github.com/SamVerschueren/tsd
+ expectType<ExpectedProps['a']>(props.a)
+ expectType<ExpectedProps['b']>(props.b)
+ expectType<ExpectedProps['bb']>(props.bb)
+ expectType<ExpectedProps['cc']>(props.cc)
+ expectType<ExpectedProps['dd']>(props.dd)
+ expectType<ExpectedProps['ccc']>(props.ccc)
+ expectType<ExpectedProps['ddd']>(props.ddd)
+
+ // setup context
+ return {
+ c: ref(1),
+ d: {
+ e: ref('hi')
+ }
+ }
+ },
+ render() {
+ const props = this.$props
+ expectType<ExpectedProps['a']>(props.a)
+ expectType<ExpectedProps['b']>(props.b)
+ expectType<ExpectedProps['bb']>(props.bb)
+ expectType<ExpectedProps['cc']>(props.cc)
+ expectType<ExpectedProps['dd']>(props.dd)
+ expectType<ExpectedProps['ccc']>(props.ccc)
+ expectType<ExpectedProps['ddd']>(props.ddd)
+
+ // should also expose declared props on `this`
+ expectType<ExpectedProps['a']>(this.a)
+ expectType<ExpectedProps['b']>(this.b)
+ expectType<ExpectedProps['bb']>(this.bb)
+ expectType<ExpectedProps['cc']>(this.cc)
+ expectType<ExpectedProps['dd']>(this.dd)
+ expectType<ExpectedProps['ccc']>(this.ccc)
+ expectType<ExpectedProps['ddd']>(this.ddd)
+
+ // assert setup context unwrapping
+ expectType<number>(this.c)
+ expectType<string>(this.d.e)
+
+ return null
+ }
+ })
+
+ // Test TSX
+ expectType<JSX.Element>(
+ <MyComponent
+ a={1}
+ b="b"
+ bb="bb"
+ cc={['cc']}
+ dd={['dd']}
+ ccc={['ccc']}
+ ddd={['ddd']}
+ // should allow extraneous as attrs
+ class="bar"
+ // should allow key
+ key={'foo'}
+ // should allow ref
+ ref={'foo'}
+ />
+ )
+
+ // missing required props
+ expectError(<MyComponent />)
+
+ // wrong prop types
+ expectError(
+ <MyComponent a={'wrong type'} b="foo" dd={['foo']} ddd={['foo']} />
+ )
+ expectError(<MyComponent b="foo" dd={[123]} ddd={['foo']} />)
+})
+
+describe('type inference w/ optional props declaration', () => {
+ const MyComponent = createComponent({
+ setup(_props: { msg: string }) {
+ return {
+ a: 1
+ }
+ },
+ render() {
+ expectType<string>(this.$props.msg)
+ expectError(this.msg)
+ expectType<number>(this.a)
+ return null
+ }
+ })
+
+ expectType<JSX.Element>(<MyComponent msg="foo" />)
+ expectError(<MyComponent />)
+ expectError(<MyComponent msg={1} />)
+})
+
+describe('type inference w/ direct setup function', () => {
+ const MyComponent = createComponent((_props: { msg: string }) => {})
+ expectType<JSX.Element>(<MyComponent msg="foo" />)
+ expectError(<MyComponent />)
+ expectError(<MyComponent msg={1} />)
+})
+
+describe('type inference w/ array props declaration', () => {
+ createComponent({
+ props: ['a', 'b'],
+ setup(props) {
+ props.a
+ props.b
+ return {
+ c: 1
+ }
+ },
+ render() {
+ expectType<{ a?: any; b?: any }>(this.$props)
+ expectType<any>(this.a)
+ expectType<any>(this.b)
+ expectType<number>(this.c)
+ }
+ })
+})
+
+describe('type inference w/ options API', () => {
+ createComponent({
+ props: { a: Number },
+ setup() {
+ return {
+ b: 123
+ }
+ },
+ data() {
+ // Limitation: we cannot expose the return result of setup() on `this`
+ // here in data() - somehow that would mess up the inference
+ expectType<number | undefined>(this.a)
+ return {
+ c: this.a || 123
+ }
+ },
+ computed: {
+ d(): number {
+ expectType<number>(this.b)
+ return this.b + 1
+ }
+ },
+ watch: {
+ a() {
+ expectType<number>(this.b)
+ this.b + 1
+ }
+ },
+ created() {
+ // props
+ expectType<number | undefined>(this.a)
+ // returned from setup()
+ expectType<number>(this.b)
+ // returned from data()
+ expectType<number>(this.c)
+ // computed
+ expectType<number>(this.d)
+ },
+ methods: {
+ doSomething() {
+ // props
+ expectType<number | undefined>(this.a)
+ // returned from setup()
+ expectType<number>(this.b)
+ // returned from data()
+ expectType<number>(this.c)
+ // computed
+ expectType<number>(this.d)
+ }
+ },
+ render() {
+ // props
+ expectType<number | undefined>(this.a)
+ // returned from setup()
+ expectType<number>(this.b)
+ // returned from data()
+ expectType<number>(this.c)
+ // computed
+ expectType<number>(this.d)
+ }
+ })
+})
-// This file tests a number of cases that *should* fail using tsd:
-// https://github.com/SamVerschueren/tsd
-// It will probably show up red in VSCode, and it's intended. We cannot use
-// directives like @ts-ignore or @ts-nocheck since that would suppress the
-// errors that should be caught.
-
+import { describe } from './util'
import { expectError } from 'tsd'
import { h, createComponent, ref, Fragment, Portal, Suspense } from './index'
-// h inference w/ element
-// key
-h('div', { key: 1 })
-h('div', { key: 'foo' })
-expectError(h('div', { key: [] }))
-expectError(h('div', { key: {} }))
-// ref
-h('div', { ref: 'foo' })
-h('div', { ref: ref(null) })
-h('div', { ref: el => {} })
-expectError(h('div', { ref: [] }))
-expectError(h('div', { ref: {} }))
-expectError(h('div', { ref: 123 }))
+describe('h inference w/ element', () => {
+ // key
+ h('div', { key: 1 })
+ h('div', { key: 'foo' })
+ expectError(h('div', { key: [] }))
+ expectError(h('div', { key: {} }))
+ // ref
+ h('div', { ref: 'foo' })
+ h('div', { ref: ref(null) })
+ h('div', { ref: el => {} })
+ expectError(h('div', { ref: [] }))
+ expectError(h('div', { ref: {} }))
+ expectError(h('div', { ref: 123 }))
+})
-// h inference w/ Fragment
-// only accepts array children
-h(Fragment, ['hello'])
-h(Fragment, { key: 123 }, ['hello'])
-expectError(h(Fragment, 'foo'))
-expectError(h(Fragment, { key: 123 }, 'bar'))
+describe('h inference w/ Fragment', () => {
+ // only accepts array children
+ h(Fragment, ['hello'])
+ h(Fragment, { key: 123 }, ['hello'])
+ expectError(h(Fragment, 'foo'))
+ expectError(h(Fragment, { key: 123 }, 'bar'))
+})
-// h inference w/ Portal
-h(Portal, { target: '#foo' }, 'hello')
-expectError(h(Portal))
-expectError(h(Portal, {}))
-expectError(h(Portal, { target: '#foo' }))
+describe('h inference w/ Portal', () => {
+ h(Portal, { target: '#foo' }, 'hello')
+ expectError(h(Portal))
+ expectError(h(Portal, {}))
+ expectError(h(Portal, { target: '#foo' }))
+})
-// h inference w/ Suspense
-h(Suspense, { onRecede: () => {}, onResolve: () => {} }, 'hello')
-h(Suspense, 'foo')
-h(Suspense, () => 'foo')
-h(Suspense, null, {
- default: () => 'foo'
+describe('h inference w/ Suspense', () => {
+ h(Suspense, { onRecede: () => {}, onResolve: () => {} }, 'hello')
+ h(Suspense, 'foo')
+ h(Suspense, () => 'foo')
+ h(Suspense, null, {
+ default: () => 'foo'
+ })
+ expectError(h(Suspense, { onResolve: 1 }))
})
-expectError(h(Suspense, { onResolve: 1 }))
-// h inference w/ functional component
-const Func = (_props: { foo: string; bar?: number }) => ''
-h(Func, { foo: 'hello' })
-h(Func, { foo: 'hello', bar: 123 })
-expectError(h(Func, { foo: 123 }))
-expectError(h(Func, {}))
-expectError(h(Func, { bar: 123 }))
+describe('h inference w/ functional component', () => {
+ const Func = (_props: { foo: string; bar?: number }) => ''
+ h(Func, { foo: 'hello' })
+ h(Func, { foo: 'hello', bar: 123 })
+ expectError(h(Func, { foo: 123 }))
+ expectError(h(Func, {}))
+ expectError(h(Func, { bar: 123 }))
+})
-// h inference w/ plain object component
-const Foo = {
- props: {
- foo: String
+describe('h inference w/ plain object component', () => {
+ const Foo = {
+ props: {
+ foo: String
+ }
}
-}
-h(Foo, { foo: 'ok' })
-h(Foo, { foo: 'ok', class: 'extra' })
-// should fail on wrong type
-expectError(h(Foo, { foo: 1 }))
+ h(Foo, { foo: 'ok' })
+ h(Foo, { foo: 'ok', class: 'extra' })
+ // should fail on wrong type
+ expectError(h(Foo, { foo: 1 }))
+})
-// h inference w/ createComponent
-const Bar = createComponent({
- props: {
- foo: String,
- bar: {
- type: Number,
- required: true
+describe('h inference w/ createComponent', () => {
+ const Foo = createComponent({
+ props: {
+ foo: String,
+ bar: {
+ type: Number,
+ required: true
+ }
}
- }
+ })
+
+ h(Foo, { bar: 1 })
+ h(Foo, { bar: 1, foo: 'ok' })
+ // should allow extraneous props (attrs fallthrough)
+ h(Foo, { bar: 1, foo: 'ok', class: 'extra' })
+ // should fail on missing required prop
+ expectError(h(Foo, {}))
+ expectError(h(Foo, { foo: 'ok' }))
+ // should fail on wrong type
+ expectError(h(Foo, { bar: 1, foo: 1 }))
})
-h(Bar, { bar: 1 })
-h(Bar, { bar: 1, foo: 'ok' })
-// should allow extraneous props (attrs fallthrough)
-h(Bar, { bar: 1, foo: 'ok', class: 'extra' })
-// should fail on missing required prop
-expectError(h(Bar, {}))
-expectError(h(Bar, { foo: 'ok' }))
-// should fail on wrong type
-expectError(h(Bar, { bar: 1, foo: 1 }))
+describe('h inference w/ createComponent + optional props', () => {
+ const Foo = createComponent({
+ setup(_props: { foo?: string; bar: number }) {}
+ })
+
+ h(Foo, { bar: 1 })
+ h(Foo, { bar: 1, foo: 'ok' })
+ // should allow extraneous props (attrs fallthrough)
+ h(Foo, { bar: 1, foo: 'ok', class: 'extra' })
+ // should fail on missing required prop
+ expectError(h(Foo, {}))
+ expectError(h(Foo, { foo: 'ok' }))
+ // should fail on wrong type
+ expectError(h(Foo, { bar: 1, foo: 1 }))
+})
+
+describe('h inference w/ createComponent + direct function', () => {
+ const Foo = createComponent((_props: { foo?: string; bar: number }) => {})
+
+ h(Foo, { bar: 1 })
+ h(Foo, { bar: 1, foo: 'ok' })
+ // should allow extraneous props (attrs fallthrough)
+ h(Foo, { bar: 1, foo: 'ok', class: 'extra' })
+ // should fail on missing required prop
+ expectError(h(Foo, {}))
+ expectError(h(Foo, { foo: 'ok' }))
+ // should fail on wrong type
+ expectError(h(Foo, { bar: 1, foo: 1 }))
+})
+// This directory contains a number of d.ts assertions using tsd:
+// https://github.com/SamVerschueren/tsd
+// The tests checks type errors and will probably show up red in VSCode, and
+// it's intended. We cannot use directives like @ts-ignore or @ts-nocheck since
+// that would suppress the errors that should be caught.
+
export * from '@vue/runtime-dom'
--- /dev/null
+// TSX w/ createComponent is tested in createComponent.test-d.tsx
+
+import { expectError, expectType } from 'tsd'
+import { KeepAlive, Suspense, Fragment, Portal } from '@vue/runtime-dom'
+
+expectType<JSX.Element>(<div />)
+expectType<JSX.Element>(<div id="foo" />)
+expectType<JSX.Element>(<input value="foo" />)
+
+// unknown prop
+expectError(<div foo="bar" />)
+
+// allow key/ref on arbitrary element
+expectType<JSX.Element>(<div key="foo" />)
+expectType<JSX.Element>(<div ref="bar" />)
+
+expectType<JSX.Element>(
+ <input
+ onInput={e => {
+ // infer correct event type
+ expectType<EventTarget | null>(e.target)
+ }}
+ />
+)
+
+// built-in types
+expectType<JSX.Element>(<Fragment />)
+expectType<JSX.Element>(<Fragment key="1" />)
+
+expectType<JSX.Element>(<Portal target="#foo" />)
+// target is required
+expectError(<Portal />)
+
+// KeepAlive
+expectType<JSX.Element>(<KeepAlive include="foo" exclude={['a']} />)
+expectError(<KeepAlive include={123} />)
+
+// Suspense
+expectType<JSX.Element>(<Suspense />)
+expectType<JSX.Element>(<Suspense onResolve={() => {}} onRecede={() => {}} />)
+expectError(<Suspense onResolve={123} />)
--- /dev/null
+// aesthetic utility for making test-d.ts look more like actual tests
+// and makes it easier to navigate test cases with folding
+// it's a noop since test-d.ts files are not actually run.
+export function describe(_name: string, _fn: () => void) {}
"resolveJsonModule": true,
"esModuleInterop": true,
"removeComments": false,
- "jsx": "react",
+ "jsx": "preserve",
"lib": ["esnext", "dom"],
"types": ["jest", "node"],
"rootDir": ".",
"packages/global.d.ts",
"packages/runtime-dom/jsx.d.ts",
"packages/*/src",
- "packages/*/__tests__"
+ "packages/*/__tests__",
+ "test-dts"
]
}