SolvedDefinitelyTyped Enzyme instance() method should be able to carry type of wrapped class

This is an issue to track the problem meant to be addressed by #18043

If you know how to fix the issue, make a pull request instead.

Issue

Calls to shallow(..).instance() should have the type of the component they wrap.

Example

(thanks to @mohsen1 - took straight from the test PR)

class MyComponentWithMethods extends React.Component<{}, {}> {
    someMethod() {
        return 'some string';
    }
}
function test_instance_method_inferring() {
    let wrapper = shallow(<MyComponentWithMethods />);
    let instance: MyComponentWithMethods = wrapper.instance();
    let b: string = wrapper.instance().someMethod();
}

Result:

Property 'someMethod' is missing in type 'Component<any, any, any>'.

Workaround: cast the output of instance

function test_instance_method_inferring() {
    let wrapper = shallow(<MyComponentWithMethods />);
    let instance = wrapper.instance() as MyComponentWithMethods;
    let b: string = wrapper.instance().someMethod();  // works
}

Possible solution: add a third property parameter to shallow

export function shallow<P>(node: ReactElement<P>, options?: ShallowRendererProps): ShallowWrapper<P, any>;
< export function shallow<P, S>(node: ReactElement<P>, options?: ShallowRendererProps): ShallowWrapper<P, S>;
> export function shallow<P, S, C = Component<P, S>>(node: ReactElement<P>, options?: ShallowRendererProps): ShallowWrapper<P, S, C>;

This would mean that the test above would pass with the following change:

function test_instance_method_inferring() {
    let wrapper = shallow<{}, {}, MyComponentWithMethods>(<MyComponentWithMethods />);
    let instance: MyComponentWithMethods = wrapper.instance();
}

I can try to put together a PR if that seems like a sensible idea.

16 Answers

✔️Accepted Answer

that is unnecessary boilerplate @mohsen1

all you need to do from now on is in demonstrated in test https://github.com/DefinitelyTyped/DefinitelyTyped/pull/26565/files#diff-2931280905f283a37c4795c53167755cR354

class MyComponentWithMethods extends React.Component<{}, {}> {
    someMethod() {
        return 'some string';
    }
}
test('instance type by providing component type explicitly works', () => {
    const wrapper = shallow<MyComponentWithMethods>(<MyComponentWithMethods />);
    expect(wrapper.instance().someMethod()).toBe('some string');
  }
}

Other Answers:

It's not possible to get the component type from a JSX expression because the return type of all JSX expressions is currently just JSX.Element, so inference is a no-go. This may be worth raising with the TS team.

The third instance type parameter isn't great because the first two are actually on that type. One option is to drop down to one parameter, the component type, and use index types to get props and state. It would be a fairly massive breaking change, but it seems to work pretty well, and could even give you methods off of .find(Component).instance():

declare class Test extends React.Component<{ a?: number }> {
  method(): void;
}

declare function shallow<C extends React.Component = React.Component>(node: JSX.Element): ShallowWrapper<C>;

interface ShallowWrapper<C extends React.Component> {
  props(): C['props'];
  state(): C['state'];
  instance(): C;
}

const wrapper = shallow<Test>(<Test />);

wrapper.props().a; // ok, number
wrapper.instance().method; // ok, () => void;

This works well with const. Does anyone know how to do this for an implicitly typed any let?

e.g.

let component;  // typed any
beforeEach(() => {
  component = mount<MyComponent>(<MyComponent>);
});

test('my test', () => {
 //component is still typed any
});

I mean if you don't want to repeat component type in the generic you can do that runtime function :)

More Issues: