2

I am trying to launch the basic example from the Jest Tutorial Page, using Jest, React and Typescript

Link.tsx

import * as React from 'react';

const STATUS = {
    NORMAL: 'normal',
    HOVERED: 'hovered',
};

interface theProps {
    page:string
}

export default class Link extends React.Component<theProps,any> {

    constructor(props) {
        super(props);

        this._onMouseEnter = this._onMouseEnter.bind(this);
        this._onMouseLeave = this._onMouseLeave.bind(this);

        this.state = {
            class: STATUS.NORMAL,
        };
    }

    _onMouseEnter() {
        this.setState({class: STATUS.HOVERED});
    }

    _onMouseLeave() {
        this.setState({class: STATUS.NORMAL});
    }

    render() {
        return (
            <a
                className={this.state.class}
                href={this.props.page || '#'}
                onMouseEnter={this._onMouseEnter}
                onMouseLeave={this._onMouseLeave}>
                {this.props.children}
            </a>
        );
    }

}

test.tsx

import * as React from 'react';
import Link from '../app/Link';
import * as renderer from 'react-test-renderer';

it('Link changes the class when hovered', () => {
    const component = renderer.create(
        <Link page="http://www.facebook.com">Facebook</Link>
    );
    let tree = component.toJSON();
    expect(tree).toMatchSnapshot();

    // manually trigger the callback
    tree.props.onMouseEnter();
    // re-renderingf
    tree = component.toJSON();
    expect(tree).toMatchSnapshot();

    // manually trigger the callback
    tree.props.onMouseLeave();
    // re-rendering
    tree = component.toJSON();
    expect(tree).toMatchSnapshot();
});

But even though the test run fine with jest, both Webpack and IntelliJ complain about the line tree.props.onMouseEnter();: Unresolved function or method onMouseLeave()

This kind of makes sense as the props object has the type { [propName: string]: string }

Is there anything I can include the skip those warning/error messages ?

3 Answers 3

1

I'm not sure why that even works for you.
The props for Link do not have onMouseEnter nor onMouseLeave, it's the a that returns from Link.render that has it.

It should probably be more like:

const component = renderer.create(
    <Link page="http://www.facebook.com">Facebook</Link>
) as Link;

...

tree._onMouseEnter();
...
tree._onMouseLeave();
Sign up to request clarification or add additional context in comments.

2 Comments

With this solution the test don't compile anymore. With ´link._onMouseEnter();´, IntelliJ is happy but test still doesn't compile. I also think it's weird it works in the first place ...
TypeError: tree._onMouseEnter is not a function
0
+50

"Is there anything I can include the skip those warning/error messages ?"

yes

let tree: any = component.toJSON();

or

let tree = Component.toJSON();
let props = tree.props as any;

or maybe could this be close enought ? ...

import * as React from "react";
import * as renderer from "react-test-renderer";
import * as TestUtil from "react-addons-test-utils";

interface HTMLProps extends React.HTMLProps<any> {
    [key: string]: any;
}

interface ITree /* reimplements ReactTestRendererJSON */ {
    type: string;
    // I'm not sure about this but ... is it close enought ? 
    props: HTMLProps;
    children: null | Array<string | ITree>;
    $$typeof?: any;
}

function isTree(x: string | ITree): x is ITree {
    return x && (typeof x !== "string")
        && x.type !== "undefined"
        && typeof x.children !== "undefined" // .. or  === ("string" || "array"
        && typeof x.props !== "undefined"; // === "object"
}

function isString(x: any): x is string {
    return typeof x === "string";
}

describe("react-test-renderer", () => {

    it("Should/Could be typed?", () => {

        let All = (props: any) =>
            (<div {...props.divProps}>
                Hello1
                <span {...props.spanProps}>
                    Hello2
                    <a {...props.aProps}>
                        Hello3
                    </a>
                </span>
            </div>);

        const X3 = (props?: any) => {
            return <All {...props}></All>;
        }

        const X2 = (props?: any) => {
            return <X3 {...props} />;
        };

        const X1 = (props?: any) => {
            return <X2 {...props} />;
        };

        let result = {
            onDrag: false,
            onDrop: false,
            onMouseEnter: false
        };

        let onDrag = () => result.onDrag = true;
        let onDrop = () => result.onDrop = true;
        let onMouseEnter = () => result.onMouseEnter = true;

        let _render /*: ReactTestInstance*/ = renderer.create(X1({
            divProps: {
                onDrag
            },
            spanProps: {
                onDrop
            },
            aProps: {
                onMouseEnter
            }
        }));

        // 1st Rendered component its a Div
        let divTree = _render.toJSON() as ITree;

        // is Not an Element
        expect(TestUtil.isDOMComponent(divTree as any))
            .toBeFalsy();
        // is Not a Component
        expect(TestUtil.isCompositeComponent(divTree as any))
            .toBeFalsy();
        // is a Tree 
        expect(isTree(divTree))
            .toBeTruthy();

        // tree.props = 1st rendered DOMElement props , in this case a <div/>
        expect(divTree.type).toEqual("div");
        // not created 
        expect(divTree.props.accept).toBeUndefined();
        // should be there, beacuse of divProps
        expect(typeof divTree.props.onDrag).toEqual("function");

        //  TODO: ReactTestRenderer.js => Symbol['for']('react.test.json')
        // expect(tree.$$typeof).toBe("?");

        // trigger !
        divTree.props.onDrag(null);

        // Children ... 
        expect(divTree.children.length).toEqual(2);

        // String children 
        {
            let text = divTree.children.filter(isString)[0];
            if (!isString(text)) { throw "Never"; }
            expect(text).toEqual("Hello1");
        }

        // For <Span/>
        let spanTree = divTree.children.filter(isTree)[0];

        if (!isTree(spanTree)) {
            // make peace with the compiler ... 
            throw "never";
        }

        expect(isTree(spanTree)).toBeTruthy();
        expect(spanTree.type).toEqual("span");
        // String children 
        {
            let text = spanTree.children.filter(isString)[0];
            if (!isString(text)) { throw "Never"; }
            expect(text).toEqual("Hello2");
        }

        // trigger, [div][span onDrop]
        spanTree.props.onDrop(null);

        // For <A/>
        let aTree = spanTree.children.filter(isTree)[0];
        expect(isTree(aTree)).toBeTruthy();
        if (!isTree(aTree)) {
            // make peace with the compiler 
            // ...Its a ITree 
            throw "never";
        }

        expect(aTree.type).toEqual("a");
        aTree.props.onMouseEnter(null);
        let text = aTree.children.filter(isString)[0];
        if (!isString(text)) { throw "Never"; }
        expect(text).toEqual("Hello3");


        expect(result).toEqual(
            {
                onDrag: true,
                onDrop: true,
                onMouseEnter: true
            }
        );
    });
    // ...
});

3 Comments

putting 'any' made it work. Feels dirty and I am still confused about that ´tree.props.onMouseEnter´ ...
yes, and my use of React.HTMLProps<any> interface for the tree.props is quite hacky and misleading, but its kind of what I was expecting to find/ finding; what I learned from the exercise is that props will be populated with props 'fulfilled' when calling/creating the component/element so you can test its existence, trigger its handlers, also tree/tree-items will be populated with DOM elements only, or string... and it's children 'inner-elements' should be found on the 'treeItem' children so could 'query?' the result.
if(such feature existed) we could query it in in pseudo sqlx: "from tree.children select item where item.type == 'a' and item.props.href == '#HOME'" or else injsLike: expect( tree.children .ofType<ITree>() .find(x=> x.type === 'a' && x.props.href == '#HOME') ).toBeSomething();
0

2019.09.15

Dependencies:

"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-test-renderer": "^16.9.0",
"jest": "^24.8.0",
"ts-jest": "^24.0.2",
"tslint": "^5.18.0",
"typescript": "^3.5.3"

Below example works for me.

import React from 'react';

enum STATUS {
  HOVERED = 'hovered',
  NORMAL = 'normal'
}

interface ILinkState {
  class: STATUS;
}

export default class Link extends React.Component<any, ILinkState> {
  constructor(props) {
    super(props);

    this._onMouseEnter = this._onMouseEnter.bind(this);
    this._onMouseLeave = this._onMouseLeave.bind(this);

    this.state = {
      class: STATUS.NORMAL
    };
  }

  public render() {
    return (
      <a
        className={this.state.class}
        href={this.props.page || '#'}
        onMouseEnter={this._onMouseEnter}
        onMouseLeave={this._onMouseLeave}>
        {this.props.children}
      </a>
    );
  }
  private _onMouseEnter() {
    this.setState({ class: STATUS.HOVERED });
  }

  private _onMouseLeave() {
    this.setState({ class: STATUS.NORMAL });
  }
}

Snapshot testing:

import React from 'react';
import Link from './';
import renderer, { ReactTestRendererJSON } from 'react-test-renderer';

test('Link changes the class when hovered', () => {
  const component = renderer.create(<Link page="http://www.facebook.com">Facebook</Link>);
  let tree: ReactTestRendererJSON | null = component.toJSON();
  expect(tree).toMatchSnapshot();

  if (tree) {
    // manually trigger the callback
    tree.props.onMouseEnter();
  }
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();

  if (tree) {
    // manually trigger the callback
    tree.props.onMouseLeave();
  }
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

Snapshot testing with coverage report:

 PASS  src/react-test-renderer-examples/01-quick-start/index.spec.tsx
  ✓ Link changes the class when hovered (32ms)

-----------|----------|----------|----------|----------|-------------------|
File       |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files  |      100 |    83.33 |      100 |      100 |                   |
 index.tsx |      100 |    83.33 |      100 |      100 |                28 |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   3 passed, 3 total
Time:        3.614s, estimated 5s

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.