Skip to content

Instantly share code, notes, and snippets.

@philmander
Last active August 1, 2020 15:49
Show Gist options
  • Save philmander/29eb95e7992eb360ed7f537ea29db17b to your computer and use it in GitHub Desktop.
Save philmander/29eb95e7992eb360ed7f537ea29db17b to your computer and use it in GitHub Desktop.
Simple Preact Unit Testing with Jest (with shallow rendering)
import { h, Component } from 'preact';
import { Link } from 'preact-router/match';
class Nav extends Component {
constructor() {
super();
this.state.title = 'Navigation'
}
componentWillReceiveProps(nextProps) {
const { title } = nextProps;
if(title && title !== this.props.title) {
this.setState({ title });
}
}
render() {
const { title } = this.state;
return (
<nav onClick={() => {this.setState({ title: 'Menu' })}}>
<p>{ title }</p>
<ul>
<li><Link activeClassName="active" href="/one">One</Link></li>
<li><Link activeClassName="active" href="/two">Two</Link></li>
<li><Link activeClassName="active" href="/three">Three</Link></li>
</ul>
</nav>
);
}
}
export default Nav;
import { h } from 'preact';
import { createShallowRenderer, simulate } from '../../preact-test-helpers';
import Nav from './nav';
test('Basic Preact component testing', async () => {
// shallow rendering
let render = createShallowRenderer();
render(<Nav />, document.body);
// with shallow rendering, the <Link> becomes <h-link>
expect(document.querySelector('nav > p').innerHTML).toBe('Navigation');
expect(document.body.querySelectorAll('h-link').length).toBe(3);
// dispatch an event that change state (the title)
await simulate('nav', 'click');
expect(document.querySelector('nav > p').innerHTML).toBe('Menu');
// update props that change state
render = createShallowRenderer();
render(<Nav title="Links"/>, document.body, document.body.firstChild);
expect(document.querySelector('nav > p').innerHTML).toBe('Links');
expect(document.querySelectorAll('h-link').length).toBe(3);
})
import { options as preactOptions, render } from 'preact';
let shouldRenderShallow;
let previousVNodeHook;
// wrap the Preact.render() to make it render shallow with a vnode hook
// adapted from https://gist.github.com/robertknight/88e9d10cff9269c55d453e5fb8364f47
export function createShallowRenderer({ prefix = 'h-', context = {} } = {}) {
shouldRenderShallow = 2;
const vnodeHook = node => {
if (previousVNodeHook) {
previousVNodeHook(node);
}
if (typeof node.nodeName === 'string') {
return;
}
if(shouldRenderShallow <= 0) {
node.nodeName = prefix + node.nodeName.name;
}
shouldRenderShallow--;
};
preactOptions.vnode = vnodeHook;
return (vnode, parent, merge) => {
const vnodeWithContext = h(ContextWrapper, { context }, vnode);
render(vnodeWithContext, parent, merge);
preactOptions.vnode = previousVNodeHook;
};
}
// handy function to dispatch events and return a promise which is resolved after next tick
export function simulate(node, event) {
// pass a selector or a dom node
if(typeof node === 'string') {
node = document.querySelector(node);
}
// pass an event object or an event name
event = event instanceof window.Event ? event : new window.Event(event);
node.dispatchEvent(event);
// return a promise that resolves on next tick so the invoking code see the update
return new Promise(resolve => {
process.nextTick(() => { resolve() });
});
}
// wraps the node under test so a test context can be injected
function ContextWrapper({ context, children }) {
this.context = Object.assign(this.context, context);
return <div> { children } </div>;
}
@philmander
Copy link
Author

philmander commented Nov 14, 2017

This example uses just Jest and Babel (using the standard Preact setup).

preact-test-helper.js exports two functions:

  • createShallowRenderer() wraps `Preact.render() with a hook to support shallow rendering.
  • simulate() is a helper function to dispatch reguler dom events on the jsdom supplied by Jest.

@philmander
Copy link
Author

philmander commented Nov 14, 2017

For comparing snapshots, (e.g. renderer.create(<Nav />).toJSON()) see preact-render-to-json

@developit
Copy link

wow.

@RobbieTheWagner
Copy link

@philmander is there not a package released for this? I think having the simulate function or a way to fire events is very helpful.

@philmander
Copy link
Author

Didn't seem worth it, but feel free to do so if it helps

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment