summaryrefslogtreecommitdiff
path: root/preact/test/ts
diff options
context:
space:
mode:
Diffstat (limited to 'preact/test/ts')
-rw-r--r--preact/test/ts/Component-test.tsx183
-rw-r--r--preact/test/ts/VNode-test.tsx197
-rw-r--r--preact/test/ts/custom-elements.tsx85
-rw-r--r--preact/test/ts/hoc-test.tsx50
-rw-r--r--preact/test/ts/jsx-namespacce-test.tsx16
-rw-r--r--preact/test/ts/preact-global-test.tsx6
-rw-r--r--preact/test/ts/preact.tsx297
-rw-r--r--preact/test/ts/refs.tsx76
-rw-r--r--preact/test/ts/tsconfig.json15
9 files changed, 925 insertions, 0 deletions
diff --git a/preact/test/ts/Component-test.tsx b/preact/test/ts/Component-test.tsx
new file mode 100644
index 0000000..b037219
--- /dev/null
+++ b/preact/test/ts/Component-test.tsx
@@ -0,0 +1,183 @@
+import 'mocha';
+import { expect } from 'chai';
+import { createElement, Component, RenderableProps, Fragment } from '../../';
+
+// Test `this` binding on event handlers
+function onHandler(this: HTMLInputElement, event: any) {
+ return this.value;
+}
+const foo = <input onChange={onHandler} />;
+
+export class ContextComponent extends Component<{ foo: string }> {
+ getChildContext() {
+ return { something: 2 };
+ }
+
+ render() {
+ return null;
+ }
+}
+
+export interface SimpleComponentProps {
+ initialName: string | null;
+}
+
+export interface SimpleState {
+ name: string | null;
+}
+
+export class SimpleComponent extends Component<
+ SimpleComponentProps,
+ SimpleState
+> {
+ constructor(props: SimpleComponentProps) {
+ super(props);
+ this.state = {
+ name: props.initialName
+ };
+ }
+
+ render() {
+ if (!this.state.name) {
+ return null;
+ }
+ const { initialName, children } = this.props;
+ return (
+ <div>
+ <span>
+ {initialName} / {this.state.name}
+ </span>
+ {children}
+ </div>
+ );
+ }
+}
+
+class DestructuringRenderPropsComponent extends Component<
+ SimpleComponentProps,
+ SimpleState
+> {
+ constructor(props: SimpleComponentProps) {
+ super(props);
+ this.state = {
+ name: props.initialName
+ };
+ }
+
+ render({ initialName, children }: RenderableProps<SimpleComponentProps>) {
+ if (!this.state.name) {
+ return null;
+ }
+ return (
+ <span>
+ {this.props.initialName} / {this.state.name}
+ </span>
+ );
+ }
+}
+
+interface RandomChildrenComponentProps {
+ num?: number;
+ val?: string;
+ span?: boolean;
+}
+
+class RandomChildrenComponent extends Component<RandomChildrenComponentProps> {
+ render() {
+ const { num, val, span } = this.props;
+ if (num) {
+ return num;
+ }
+ if (val) {
+ return val;
+ }
+ if (span) {
+ return <span>hi</span>;
+ }
+ return null;
+ }
+}
+
+class StaticComponent extends Component<SimpleComponentProps, SimpleState> {
+ static getDerivedStateFromProps(
+ props: SimpleComponentProps,
+ state: SimpleState
+ ): Partial<SimpleState> {
+ return {
+ ...props,
+ ...state
+ };
+ }
+
+ static getDerivedStateFromError(err: Error) {
+ return {
+ name: err.message
+ };
+ }
+
+ render() {
+ return null;
+ }
+}
+
+function MapperItem(props: { foo: number }) {
+ return <div />;
+}
+
+function Mapper() {
+ return [1, 2, 3].map(x => <MapperItem foo={x} key={x} />);
+}
+
+describe('Component', () => {
+ const component = new SimpleComponent({ initialName: 'da name' });
+
+ it('has state', () => {
+ expect(component.state.name).to.eq('da name');
+ });
+
+ it('has props', () => {
+ expect(component.props.initialName).to.eq('da name');
+ });
+
+ it('has no base when not mounted', () => {
+ expect(component.base).to.not.exist;
+ });
+
+ describe('setState', () => {
+ // No need to execute these tests. because we only need to check if
+ // the types are working. Executing them would require the DOM.
+ // TODO: Run TS tests in our standard karma setup
+ it.skip('can be used with an object', () => {
+ component.setState({ name: 'another name' });
+ });
+
+ it.skip('can be used with a function', () => {
+ const updater = (state: any, props: any) => ({
+ name: `${state.name} - ${props.initialName}`
+ });
+ component.setState(updater);
+ });
+ });
+
+ describe('render', () => {
+ it('can return null', () => {
+ const comp = new SimpleComponent({ initialName: null });
+ const actual = comp.render();
+
+ expect(actual).to.eq(null);
+ });
+ });
+
+ describe('Fragment', () => {
+ it('should render nested Fragments', () => {
+ var vnode = (
+ <Fragment>
+ <Fragment>foo</Fragment>
+ bar
+ </Fragment>
+ );
+
+ expect(vnode.type).to.be.equal(Fragment);
+ });
+ });
+});
diff --git a/preact/test/ts/VNode-test.tsx b/preact/test/ts/VNode-test.tsx
new file mode 100644
index 0000000..7225901
--- /dev/null
+++ b/preact/test/ts/VNode-test.tsx
@@ -0,0 +1,197 @@
+import 'mocha';
+import { expect } from 'chai';
+import {
+ createElement,
+ Component,
+ toChildArray,
+ FunctionalComponent,
+ ComponentConstructor,
+ ComponentFactory,
+ VNode,
+ ComponentChildren,
+ cloneElement
+} from '../../';
+
+function getDisplayType(vnode: VNode | string | number) {
+ if (typeof vnode === 'string' || typeof vnode == 'number') {
+ return vnode.toString();
+ } else if (typeof vnode.type == 'string') {
+ return vnode.type;
+ } else {
+ return vnode.type.displayName;
+ }
+}
+
+class SimpleComponent extends Component<{}, {}> {
+ render() {
+ return <div>{this.props.children}</div>;
+ }
+}
+
+const SimpleFunctionalComponent = () => <div />;
+
+const a: ComponentFactory = SimpleComponent;
+const b: ComponentFactory = SimpleFunctionalComponent;
+
+describe('VNode TS types', () => {
+ it('is returned by h', () => {
+ const actual = <div className="wow" />;
+ expect(actual).to.include.all.keys('type', 'props', 'key');
+ });
+
+ it('has a nodeName of string when html element', () => {
+ const div = <div>Hi!</div>;
+ expect(div.type).to.equal('div');
+ });
+
+ it('has a nodeName equal to the construction function when SFC', () => {
+ const sfc = <SimpleFunctionalComponent />;
+ expect(sfc.type).to.be.instanceOf(Function);
+ const constructor = sfc.type as FunctionalComponent<any>;
+ expect(constructor.name).to.eq('SimpleFunctionalComponent');
+ });
+
+ it('has a nodeName equal to the constructor of a component', () => {
+ const sfc = <SimpleComponent />;
+ expect(sfc.type).to.be.instanceOf(Function);
+ const constructor = sfc.type as ComponentConstructor<any>;
+ expect(constructor.name).to.eq('SimpleComponent');
+ });
+
+ it('has children which is an array of string or other vnodes', () => {
+ const comp = (
+ <SimpleComponent>
+ <SimpleComponent>child1</SimpleComponent>
+ child2
+ </SimpleComponent>
+ );
+
+ expect(comp.props.children).to.be.instanceOf(Array);
+ expect(comp.props.children[1]).to.be.a('string');
+ });
+
+ it('children type should work with toChildArray', () => {
+ const comp: VNode = <SimpleComponent>child1 {1}</SimpleComponent>;
+
+ const children = toChildArray(comp.props.children);
+ expect(children).to.have.lengthOf(2);
+ });
+
+ it('toChildArray should filter out some types', () => {
+ const compChild = <SimpleComponent />;
+ const comp: VNode = (
+ <SimpleComponent>
+ a{null}
+ {true}
+ {false}
+ {2}
+ {undefined}
+ {['b', 'c']}
+ {compChild}
+ </SimpleComponent>
+ );
+
+ const children = toChildArray(comp.props.children);
+ expect(children).to.deep.equal(['a', 2, 'b', 'c', compChild]);
+ });
+
+ it('functions like getDisplayType should work', () => {
+ function TestComp(props: { children?: ComponentChildren }) {
+ return <div>{props.children}</div>;
+ }
+ TestComp.displayName = 'TestComp';
+
+ const compChild = <TestComp />;
+ const comp: VNode = (
+ <SimpleComponent>
+ a{null}
+ {true}
+ {false}
+ {2}
+ {undefined}
+ {['b', 'c']}
+ {compChild}
+ </SimpleComponent>
+ );
+
+ const types = toChildArray(comp.props.children).map(getDisplayType);
+ expect(types).to.deep.equal(['a', '2', 'b', 'c', 'TestComp']);
+ });
+
+ it('component should work with cloneElement', () => {
+ const comp: VNode = (
+ <SimpleComponent>
+ <div>child 1</div>
+ </SimpleComponent>
+ );
+ const clone: VNode = cloneElement(comp);
+
+ expect(comp.type).to.equal(clone.type);
+ expect(comp.props).not.to.equal(clone.props);
+ expect(comp.props).to.deep.equal(clone.props);
+ });
+
+ it('component should work with cloneElement using generics', () => {
+ const comp: VNode<string> = <SimpleComponent></SimpleComponent>;
+ const clone: VNode<string> = cloneElement<string>(comp);
+
+ expect(comp.type).to.equal(clone.type);
+ expect(comp.props).not.to.equal(clone.props);
+ expect(comp.props).to.deep.equal(clone.props);
+ });
+});
+
+class ComponentWithFunctionChild extends Component<{
+ children: (num: number) => string;
+}> {
+ render() {
+ return null;
+ }
+}
+
+<ComponentWithFunctionChild>
+ {num => num.toFixed(2)}
+</ComponentWithFunctionChild>;
+
+class ComponentWithStringChild extends Component<{ children: string }> {
+ render() {
+ return null;
+ }
+}
+
+<ComponentWithStringChild>child</ComponentWithStringChild>;
+
+class ComponentWithNumberChild extends Component<{ children: number }> {
+ render() {
+ return null;
+ }
+}
+
+<ComponentWithNumberChild>{1}</ComponentWithNumberChild>;
+
+class ComponentWithBooleanChild extends Component<{ children: boolean }> {
+ render() {
+ return null;
+ }
+}
+
+<ComponentWithBooleanChild>{false}</ComponentWithBooleanChild>;
+
+class ComponentWithNullChild extends Component<{ children: null }> {
+ render() {
+ return null;
+ }
+}
+
+<ComponentWithNullChild>{null}</ComponentWithNullChild>;
+
+class ComponentWithNumberChildren extends Component<{ children: number[] }> {
+ render() {
+ return null;
+ }
+}
+
+<ComponentWithNumberChildren>
+ {1}
+ {2}
+</ComponentWithNumberChildren>;
diff --git a/preact/test/ts/custom-elements.tsx b/preact/test/ts/custom-elements.tsx
new file mode 100644
index 0000000..0f8d29e
--- /dev/null
+++ b/preact/test/ts/custom-elements.tsx
@@ -0,0 +1,85 @@
+import { createElement, Component, createContext } from '../../';
+
+declare module '../../' {
+ namespace createElement.JSX {
+ interface IntrinsicElements {
+ // Custom element can use JSX EventHandler definitions
+ 'clickable-ce': {
+ optionalAttr?: string;
+ onClick?: MouseEventHandler<HTMLElement>;
+ };
+
+ // Custom Element that extends HTML attributes
+ 'color-picker': HTMLAttributes & {
+ // Required attribute
+ space: 'rgb' | 'hsl' | 'hsv';
+ // Optional attribute
+ alpha?: boolean;
+ };
+
+ // Custom Element with custom interface definition
+ 'custom-whatever': WhateveElAttributes;
+ }
+ }
+}
+
+// Whatever Element definition
+
+interface WhateverElement {
+ instanceProp: string;
+}
+
+interface WhateverElementEvent {
+ eventProp: number;
+}
+
+// preact.JSX.HTMLAttributes also appears to work here but for consistency,
+// let's use createElement.JSX
+interface WhateveElAttributes extends createElement.JSX.HTMLAttributes {
+ someattribute?: string;
+ onsomeevent?: (this: WhateverElement, ev: WhateverElementEvent) => void;
+}
+
+// Ensure context still works
+const { Provider, Consumer } = createContext({ contextValue: '' });
+
+// Sample component that uses custom elements
+
+class SimpleComponent extends Component {
+ componentProp = 'componentProp';
+ render() {
+ // Render inside div to ensure standard JSX elements still work
+ return (
+ <Provider value={{ contextValue: 'value' }}>
+ <div>
+ <clickable-ce
+ onClick={e => {
+ // `this` should be instance of SimpleComponent since this is an
+ // arrow function
+ console.log(this.componentProp);
+
+ // Validate `currentTarget` is HTMLElement
+ console.log('clicked ', e.currentTarget.style.display);
+ }}
+ ></clickable-ce>
+ <color-picker space="rgb" dir="rtl"></color-picker>
+ <custom-whatever
+ dir="auto" // Inherited prop from HTMLAttributes
+ someattribute="string"
+ onsomeevent={function(e) {
+ // Validate `this` and `e` are the right type
+ console.log('clicked', this.instanceProp, e.eventProp);
+ }}
+ ></custom-whatever>
+
+ {/* Ensure context still works */}
+ <Consumer>
+ {({ contextValue }) => contextValue.toLowerCase()}
+ </Consumer>
+ </div>
+ </Provider>
+ );
+ }
+}
+
+const component = <SimpleComponent />;
diff --git a/preact/test/ts/hoc-test.tsx b/preact/test/ts/hoc-test.tsx
new file mode 100644
index 0000000..455d9e0
--- /dev/null
+++ b/preact/test/ts/hoc-test.tsx
@@ -0,0 +1,50 @@
+import { expect } from 'chai';
+import {
+ createElement,
+ ComponentFactory,
+ ComponentConstructor,
+ Component
+} from '../../';
+import { SimpleComponent, SimpleComponentProps } from './Component-test';
+
+export interface highlightedProps {
+ isHighlighted: boolean;
+}
+
+export function highlighted<T>(
+ Wrappable: ComponentFactory<T>
+): ComponentConstructor<T & highlightedProps> {
+ return class extends Component<T & highlightedProps> {
+ constructor(props: T & highlightedProps) {
+ super(props);
+ }
+
+ render() {
+ let className = this.props.isHighlighted ? 'highlighted' : '';
+ return (
+ <div className={className}>
+ <Wrappable {...this.props} />
+ </div>
+ );
+ }
+
+ toString() {
+ return `Highlighted ${Wrappable.name}`;
+ }
+ };
+}
+
+const HighlightedSimpleComponent = highlighted<SimpleComponentProps>(
+ SimpleComponent
+);
+
+describe('hoc', () => {
+ it('wraps the given component', () => {
+ const highlight = new HighlightedSimpleComponent({
+ initialName: 'initial name',
+ isHighlighted: true
+ });
+
+ expect(highlight.toString()).to.eq('Highlighted SimpleComponent');
+ });
+});
diff --git a/preact/test/ts/jsx-namespacce-test.tsx b/preact/test/ts/jsx-namespacce-test.tsx
new file mode 100644
index 0000000..d6e10bd
--- /dev/null
+++ b/preact/test/ts/jsx-namespacce-test.tsx
@@ -0,0 +1,16 @@
+import { createElement, Component } from '../../';
+
+// declare global JSX types that should not be mixed with preact's internal types
+declare global {
+ namespace JSX {
+ interface Element {
+ unknownProperty: string;
+ }
+ }
+}
+
+class SimpleComponent extends Component {
+ render() {
+ return <div>It works</div>;
+ }
+}
diff --git a/preact/test/ts/preact-global-test.tsx b/preact/test/ts/preact-global-test.tsx
new file mode 100644
index 0000000..e6c3286
--- /dev/null
+++ b/preact/test/ts/preact-global-test.tsx
@@ -0,0 +1,6 @@
+import { createElement } from '../../src';
+
+// Test that preact types are available via the global `preact` namespace.
+
+let component: preact.ComponentChild;
+component = <div>Hello World</div>;
diff --git a/preact/test/ts/preact.tsx b/preact/test/ts/preact.tsx
new file mode 100644
index 0000000..9779d41
--- /dev/null
+++ b/preact/test/ts/preact.tsx
@@ -0,0 +1,297 @@
+import {
+ createElement,
+ render,
+ Component,
+ ComponentProps,
+ FunctionalComponent,
+ AnyComponent,
+ h
+} from '../../';
+
+interface DummyProps {
+ initialInput: string;
+}
+
+interface DummyState {
+ input: string;
+}
+
+class DummyComponent extends Component<DummyProps, DummyState> {
+ constructor(props: DummyProps) {
+ super(props);
+ this.state = {
+ input: `x${this.props}x`
+ };
+ }
+
+ private setRef = (el: AnyComponent<any>) => {
+ console.log(el);
+ };
+
+ render({ initialInput }: DummyProps, { input }: DummyState) {
+ return (
+ <div>
+ <DummerComponent initialInput={initialInput} input={input} />
+ {/* Can specify all Preact attributes on a typed FunctionalComponent */}
+ <ComponentWithChildren
+ initialInput={initialInput}
+ input={input}
+ key="1"
+ ref={this.setRef}
+ />
+ </div>
+ );
+ }
+}
+
+interface DummerComponentProps extends DummyProps, DummyState {}
+
+function DummerComponent({ input, initialInput }: DummerComponentProps) {
+ return (
+ <div>
+ Input: {input}, initial: {initialInput}
+ </div>
+ );
+}
+
+render(createElement('div', { title: 'test', key: '1' }), document);
+render(
+ createElement(DummyComponent, { initialInput: 'The input', key: '1' }),
+ document
+);
+render(
+ createElement(DummerComponent, {
+ initialInput: 'The input',
+ input: 'New input',
+ key: '1'
+ }),
+ document
+);
+render(h('div', { title: 'test', key: '1' }), document);
+render(h(DummyComponent, { initialInput: 'The input', key: '1' }), document);
+render(
+ h(DummerComponent, {
+ initialInput: 'The input',
+ input: 'New input',
+ key: '1'
+ }),
+ document
+);
+
+// Accessing children
+const ComponentWithChildren: FunctionalComponent<DummerComponentProps> = ({
+ input,
+ initialInput,
+ children
+}) => {
+ return (
+ <div>
+ <span>{initialInput}</span>
+ <span>{input}</span>
+ <span>{children}</span>
+ </div>
+ );
+};
+
+const UseOfComponentWithChildren = () => {
+ return (
+ <ComponentWithChildren initialInput="initial" input="input">
+ <span>child 1</span>
+ <span>child 2</span>
+ </ComponentWithChildren>
+ );
+};
+
+// using ref and or jsx
+class ComponentUsingRef extends Component<any, any> {
+ private array: string[];
+ private refs: (Element | null)[] = [];
+
+ constructor() {
+ super();
+ this.array = ['1', '2'];
+ }
+
+ render() {
+ this.refs = [];
+ return (
+ <div jsx>
+ {this.array.map(el => (
+ <span ref={this.setRef}>{el}</span>
+ ))}
+
+ {/* Can specify Preact attributes on a component */}
+ <DummyComponent initialInput="1" key="1" ref={this.setRef} />
+ </div>
+ );
+ }
+
+ private setRef = (el: Element | null) => {
+ this.refs.push(el);
+ };
+}
+
+// using lifecycles
+class ComponentWithLifecycle extends Component<DummyProps, DummyState> {
+ render() {
+ return <div>Hi</div>;
+ }
+
+ componentWillMount() {
+ console.log('componentWillMount');
+ }
+
+ componentDidMount() {
+ console.log('componentDidMount');
+ }
+
+ componentWillUnmount() {
+ console.log('componentWillUnmount');
+ }
+
+ componentWillReceiveProps(nextProps: DummyProps, nextCtx: any) {
+ const { initialInput } = nextProps;
+ console.log('componentWillReceiveProps', initialInput, nextCtx);
+ }
+
+ shouldComponentUpdate(
+ nextProps: DummyProps,
+ nextState: DummyState,
+ nextContext: any
+ ) {
+ return false;
+ }
+
+ componentWillUpdate(
+ nextProps: DummyProps,
+ nextState: DummyState,
+ nextContext: any
+ ) {
+ console.log('componentWillUpdate', nextProps, nextState, nextContext);
+ }
+
+ componentDidUpdate(
+ previousProps: DummyProps,
+ previousState: DummyState,
+ previousContext: any
+ ) {
+ console.log(
+ 'componentDidUpdate',
+ previousProps,
+ previousState,
+ previousContext
+ );
+ }
+}
+
+// Default props: JSX.LibraryManagedAttributes
+
+class DefaultProps extends Component<{ text: string; bool: boolean }> {
+ static defaultProps = {
+ text: 'hello'
+ };
+
+ render() {
+ return <div>{this.props.text}</div>;
+ }
+}
+
+const d1 = <DefaultProps bool={false} text="foo" />;
+const d2 = <DefaultProps bool={false} />;
+
+class DefaultPropsWithUnion extends Component<
+ { default: boolean } & (
+ | {
+ type: 'string';
+ str: string;
+ }
+ | {
+ type: 'number';
+ num: number;
+ }
+ )
+> {
+ static defaultProps = {
+ default: true
+ };
+
+ render() {
+ return <div />;
+ }
+}
+
+const d3 = <DefaultPropsWithUnion type="string" str={'foo'} />;
+const d4 = <DefaultPropsWithUnion type="number" num={0xf00} />;
+const d5 = <DefaultPropsWithUnion type="string" str={'foo'} default={false} />;
+const d6 = <DefaultPropsWithUnion type="number" num={0xf00} default={false} />;
+
+class DefaultUnion extends Component<
+ | {
+ type: 'number';
+ num: number;
+ }
+ | {
+ type: 'string';
+ str: string;
+ }
+> {
+ static defaultProps = {
+ type: 'number',
+ num: 1
+ };
+
+ render() {
+ return <div />;
+ }
+}
+
+const d7 = <DefaultUnion />;
+const d8 = <DefaultUnion num={1} />;
+const d9 = <DefaultUnion type="number" />;
+const d10 = <DefaultUnion type="string" str="foo" />;
+
+class ComponentWithDefaultProps extends Component<{ value: string }> {
+ static defaultProps = { value: '' };
+ render() {
+ return <div>{this.props.value}</div>;
+ }
+}
+
+const withDefaultProps = <ComponentWithDefaultProps />;
+
+interface PartialState {
+ foo: string;
+ bar: number;
+}
+
+class ComponentWithPartialSetState extends Component<{}, PartialState> {
+ render({}, { foo, bar }: PartialState) {
+ return (
+ <button onClick={() => this.handleClick('foo')}>
+ {foo}-{bar}
+ </button>
+ );
+ }
+ handleClick = (value: keyof PartialState) => {
+ this.setState({ [value]: 'updated' });
+ };
+}
+
+const withPartialSetState = <ComponentWithPartialSetState />;
+
+let functionalProps: ComponentProps<typeof DummerComponent> = {
+ initialInput: '',
+ input: ''
+};
+
+let classProps: ComponentProps<typeof DummyComponent> = {
+ initialInput: ''
+};
+
+let elementProps: ComponentProps<'button'> = {
+ type: 'button'
+};
+
+// Typing of style property
+const acceptsNumberAsLength = <div style={{ marginTop: 20 }} />;
+const acceptsStringAsLength = <div style={{ marginTop: '20px' }} />;
diff --git a/preact/test/ts/refs.tsx b/preact/test/ts/refs.tsx
new file mode 100644
index 0000000..9edb730
--- /dev/null
+++ b/preact/test/ts/refs.tsx
@@ -0,0 +1,76 @@
+import {
+ createElement,
+ Component,
+ createRef,
+ FunctionalComponent,
+ Fragment,
+ RefObject,
+ RefCallback
+} from '../../';
+
+// Test Fixtures
+const Foo: FunctionalComponent = () => <span>Foo</span>;
+class Bar extends Component {
+ render() {
+ return <span>Bar</span>;
+ }
+}
+
+// Using Refs
+class CallbackRef extends Component {
+ divRef: RefCallback<HTMLDivElement> = div => {
+ if (div !== null) {
+ console.log(div.tagName);
+ }
+ };
+ fooRef: RefCallback<Component> = foo => {
+ if (foo !== null) {
+ console.log(foo.base);
+ }
+ };
+ barRef: RefCallback<Bar> = bar => {
+ if (bar !== null) {
+ console.log(bar.base);
+ }
+ };
+
+ render() {
+ return (
+ <Fragment>
+ <div ref={this.divRef} />
+ <Foo ref={this.fooRef} />
+ <Bar ref={this.barRef} />
+ </Fragment>
+ );
+ }
+}
+
+class CreateRefComponent extends Component {
+ private divRef: RefObject<HTMLDivElement> = createRef();
+ private fooRef: RefObject<Component> = createRef();
+ private barRef: RefObject<Bar> = createRef();
+
+ componentDidMount() {
+ if (this.divRef.current != null) {
+ console.log(this.divRef.current.tagName);
+ }
+
+ if (this.fooRef.current != null) {
+ console.log(this.fooRef.current.base);
+ }
+
+ if (this.barRef.current != null) {
+ console.log(this.barRef.current.base);
+ }
+ }
+
+ render() {
+ return (
+ <Fragment>
+ <div ref={this.divRef} />
+ <Foo ref={this.fooRef} />
+ <Bar ref={this.barRef} />
+ </Fragment>
+ );
+ }
+}
diff --git a/preact/test/ts/tsconfig.json b/preact/test/ts/tsconfig.json
new file mode 100644
index 0000000..36621f3
--- /dev/null
+++ b/preact/test/ts/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "module": "es6",
+ "moduleResolution": "node",
+ "lib": ["es6", "dom"],
+ "strict": true,
+ "typeRoots": ["../../"],
+ "types": [],
+ "forceConsistentCasingInFileNames": true,
+ "jsx": "react",
+ "jsxFactory": "createElement"
+ },
+ "include": ["./**/*.ts", "./**/*.tsx"]
+}