PreactX Jest/Enzyme Functional Test
My login component redirects to A2b component on successful login (which it does). A2b components will make a request to an API on componentDidMount (via useEffect hook) and display a loading indicator while waiting for network request completion (which it does). On API request completion, the A2b component rerenders to display the form for access code verification (which it does).
While trying to test the login component. I have to call route('/users/a2b') twice and wrapper.update() twice to be able to test the functionality.
Please, can someone point me in the right direction? NB: This is Preact v10 not React.
==App.tsx component===
import { h, FunctionComponent } from 'preact';
import Router from 'preact-router';
import { useState } from 'preact/hooks';
import '../style.scss';
import { StoreProvider, useAction, useStore } from '@preact-hooks/unistore';
import store from '../store';
import { Props } from './parts/types';
import Index from '../routes/index/index';
import A2b from '../routes/users/a2b';
import Footer from './parts/footer';
import Header from './parts/header';
import createAction from '../actions';
const App: FunctionComponent<Props> = props => {
const [url, setUrl] = useState('');
const actions = createAction(useStore());
const updateStoreItems = useAction(actions.updateStoreItems);
const handleRoute = (e: any) => {
setUrl(e.url);
};
return (
<div id="app">
<div id="schul">
<Header url={url} />
<Router onChange={handleRoute}>
<Index path="/" />
<A2b path="/users/a2b" />
</Router>
<Footer url={url} />
</div>
</div>
)};
export default () => (
<StoreProvider value={store}>
<App />
</StoreProvider>
);
==Index.tsx containing form for login==
import { h, FunctionalComponent } from 'preact';
import Inputs from '../../forms/inputs';
import orms_img from '../../assets/orms_img.jpg';
import Login from './partials/login';
import { useAction, useSelector, useStore } from '@preact-hooks/unistore';
import { useEffect } from 'preact/hooks';
import createAction from '../../actions';
import { isLogdIn } from '../../components/parts/auth';
import { Elems, Props } from '../../components/parts/types';
const Index: FunctionalComponent<Props> = (props) =>{
const actions = createAction(useStore());
let usr = isLogdIn();
const createLogin = useAction(actions.createLogin);
const removeStoreItems = useAction(actions.removeStoreItems);
useEffect(() => {
createLogin();
return () => removeStoreItems("logn_form");
}, []);
const { logn_form } = useSelector("logn_form");
const createLoginForm = (elems: Elems) => {
return Object.entries(elems).map(([key, elem]) => {
return <Inputs {...props} control={elem} elements={elems} />;
});
};
return (
<div className="home container">
<div id="orms">
<p style={{ fontSize: '50px', margin: '5px' }}>EXAM CENTER</p>
<p className="orms">University</p>
<p className="orms">Exams & Records</p>
</div>
<Login orms_img={orms_img} elems={logn_form} createLoginForm={createLoginForm} />
</div>
);
};
export default Index;
On successful login the user will be routed to "A2b" component
==A2b.tsx component==
import { useAction, useSelector, useStore } from '@preact-hooks/unistore';
import { h, FunctionalComponent } from 'preact';
import { useEffect } from 'preact/hooks';
import createAction from '../../actions';
import { isLogdIn } from '../../components/parts/auth';
import Loading from '../../components/parts/loading';
import { Props } from '../../components/parts/types';
import Inputs from '../../forms/inputs';
const A2b: FunctionalComponent<Props> = (props) => {
const actions = createAction(useStore())
const { elems, msg, kode } = useSelector('elems,msg,kode');
const xGet = useAction(actions.xGet);
const removeStoreItems = useAction(actions.removeStoreItems)
let usr = isLogdIn();
useEffect(() => {
let config = {
fetched: 'msg,elems,user',
url: '/users/key'
}
if (usr.a2b != 'ok') {
console.log('in a2b')
xGet(config);
}
return () => { removeStoreItems('form_data')}
}, [usr.a2b]);
const getKey = (e: Event) => {
e.preventDefault();
let config = {
spin: 'kode',
fetched: 'msg,user',
url: '/users/key'
}
xGet(config);
}
if (!elems) return <Loading />;
let anim = kode ? ' load' : ' okam';
let widthe = kode ? '105px' : '85px';
return (
<div class="actvty container align-center">
<div className="inline-block" style={{ width: '200px', marginTop: "10%", height: "400px" }}>
<p> Please enter your access code below: </p>
<Inputs {...props} control={elems['kode']} elements={elems} />
<Inputs {...props} control={elems['action']} elements={elems} />
</div>
</div>
);
}
export default A2b;
As I said before, this A2b component will make a request to the server on componentDidMount (via useEffect hook) (to create access code at the server and send the code as SMS to the users phone for 2FA). During the API request, a loading page will be displayed.
===== the test using jest and enzyme=====
import { h } from 'preact';
import { mount } from 'enzyme';
import { xhr } from '../../src/components/parts/http'; //wrapper around axios
import { resp_data } from './data'; //mocked data
import App from '../../src/components/app'
import { route } from 'preact-router';
const nextTick = async () => {
return new Promise(resolve => {
setTimeout(resolve, 0)
})
}
describe('Test for Index/Login', () => {
it('should redirect to from Index to A2b on successfull login', async () => {
jest.spyOn(xhr, 'post').mockResolvedValue({
data: resp_data,
status: 200
})
const wrapper = mount(<App />);
expect(wrapper.find('p').length).toEqual(3);
expect(wrapper.find('Login').length).toEqual(1);
const username = wrapper.find('input.username')
username.getDOMNode().setAttribute('value','[email protected]')
username.simulate('input')
const password = wrapper.find('input.password')
password.getDOMNode().setAttribute('value', 'intell')
password.simulate('input')
wrapper.find('button#elems').simulate('click')
route('/users/a2b') // called the first time?
wrapper.update() // called the first time
expect(wrapper.contains(<div className='loading' />)).toBeTruthy()
await nextTick() // a delay to wait for the mocked API call & update store
route('/users/a2b') // Have to call this again for it to work
wrapper.update() // and again
expect(wrapper.find('p').text()).toContain('Please enter your access code below:');
});
});