For the web, we want to ensure that we can run UI tests, regression tests and functional tests.
// Example function to test function add(x, y) { return x + y; } // Test suite describe('JavaScript functionality for a file', () => { // Test spec it('expects true to be true', () => { expect(true).to.be.true; // evaluates to true - test passes }); // Test spec it('expects 1 + 2 to equal 3', () => { const result = add(1, 2); expect(result).to.equal(3); // evaluates to true - test passes }); });
For UI Tests, reference manual/Testing/mocha-and-chai.md
for more information. It is best to run this using mochacinno
and in watch mode. These are best use for anything found under the js
controller module banner.
/** * Regression tests * @author Dennis O'Keeffe */ require('babel-polyfill'); const expect = require('chai').expect; const cwd = process.cwd(); const PixelDiff = require('controllers/pixeldiff'); const puppeteer = require('puppeteer'); console.log(PixelDiff); console.log(cwd); const screenshot = async (selector, savePath, location = '/') => { const browser = await puppeteer.launch(); const page = await browser.newPage(); console.log('Opening browser'); await page.goto('http://localhost:3000'); const el = await page.$('.homeSplashFade'); await el.screenshot({ path: savePath }); console.log('Closing browser'); await browser.close(); }; describe('It works functionality', () => { it('Expects true to be true', () => { expect(true).to.be.true; }); }); describe('Image regression testing', () => { it('has no pixel difference', async () => { console.log('Comparing images'); await screenshot('.homeSplashFade', cwd + '/regression/temp/test.png'); const res = await PixelDiff.diff({ imgOnePath: cwd + '/regression/src/test.png', imgTwoPath: cwd + '/regression/temp/test.png', dest: cwd + '/regression/diff/test.png', output: true }); expect(res).to.equal(0); }); });
For more information, reference manual/Testing/Regression-Testing.md
.
const puppeteer = require('puppeteer'); // Test suite describe('PageHome functionality', () => { // Test suite within another test suite - useful for subsectioning tests within a component/page describe('Simple Puppeteer UI test for form elements', () => { it('firstName and lastName from Puppeteer deep equal expected schema', async () => { const expected = { firstName: 'Hello', lastName: 'World' }; const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); await page.goto('http://localhost:3000'); await page.screenshot({ path: 'example.png' }); // create an example screenshot of current UI state await page.type('input[name="firstName"]', 'Hello'); const firstName = await page.$eval( 'input[name="firstName"]', (el) => el.value ); await page.waitFor(500); await page.click('#next'); await page.waitForSelector('input[name="lastName"]'); await page.type('input[name="lastName"]', 'World'); const lastName = await page.$eval( 'input[name="lastName"]', (el) => el.value ); await page.waitFor(500); const formData = { firstName: firstName, lastName: lastName }; await browser.close(); expect(formData).to.deep.equal(expected); // evauates to true if form fields hold correct value }); }); });
For more information, reference manual/Testing/puppeteer.md
.
Run the site through the accessibility checker and ensure that there are no errors.
Useful links:
Type checking is an integral part of the process to ensure that what we are providing is the correct type.
TypeScript with VSCode offers a great to do this from our doc blocks.
This Medium article has a great into into it.
To get started on VSCode for .js files, head to settings and update the "Check JS" box.
Check JS
Now we can get into type setting simply by using our doc blocks with the @type
attribute!
Basic example
The deep you delve into the type, the deeper the error outlines go.
More complex example
Checkout the Typescript page
Here is an example using @typedefs.
import React, { Component } from 'react'; import Config from 'src/app.json'; import Emitter from 'common/Emitter'; /* user imports */ /** * Render the ComponentALFooterOne component * * @class ComponentALFooterOne * @extends {Component} */ class ComponentALFooterOne extends Component { /** * @typedef {Object} Link Defines the main links * @property {String} copy Copy string * @property {String} link Link URL */ /** * @typedef {Object} Social Social links * @property {String} type Type of social linke * @property {String} link Link URL */ /** * @typedef {Object} State The state object * @property {Link[]} linksTop Links that go on the top * @property {Social[]} linksSocial Social links * @property {Link[]} linksBottom Links that go to the bottom */ /** * @type {State} state */ state = { linksTop: [ { copy: 'Link', link: '/' }, { copy: 'Link', link: '/' }, { copy: 'Link', link: '/' }, { copy: 'Link', link: '/' } ], linksSocial: [ { type: 'Link', link: '/' }, { type: 'Link', link: '/' }, { type: 'Link', link: '/' }, { type: 'Link', link: '/' } ], linksBottom: [ { copy: 'Link', link: '/' }, { copy: 'Link', link: '/' }, { copy: 'Link', link: '/' } ] }; handleLink(e, d) { Emitter.emit('event', { event: 'ComponentALFooterOne.handleLink', e: e.target }); if (d.href[0] === '/') { e.preventDefault(); const { router } = this.props; router.push(Config.baseUrl + d.href); } } renderLinksTopLeft = () => { const { linksTopLeft } = this.props.copy; if (!linksTopLeft) { return; } return linksTopLeft.map((d, i) => ( <a key={i} onClick={(e) => this.handleLink(e, d)} href={d.href} className="text f-primary link margin opaque animate" > {d.name} </a> )); }; renderLinksBottomLeft = () => { const { linksBottomLeft } = this.props.copy; if (!linksBottomLeft) { return; } return linksBottomLeft.map((d, i) => ( <a key={i} onClick={(e) => this.handleLink(e, d)} href={d.href} className="text f-primary link social margin opaque animate" > {d.name} </a> )); }; renderLinksBottomRight = () => { const { linksBottomRight } = this.props.copy; if (!linksBottomRight) { return; } return linksBottomRight.map((d, i) => ( <a key={i} onClick={(e) => this.handleLink(e, d)} href={d.href} className="text f-primary link social margin opaque animate" > {d.name} </a> )); }; /** * Render ComponentALFooterOne component * @memberof ComponentALFooterOne * @var {function} render Render ComponentALFooterOne component * @returns {Object} component */ render() { return ( <div className="component-al-footer-one"> <div className="container content"> <nav className="nav footer"> <div className="block-main links">{this.renderLinksTopLeft()}</div> <div className="block-lower"> <div className="block-social">{this.renderLinksBottomLeft()}</div> <div className="block-footer links"> {this.renderLinksBottomRight()} </div> </div> </nav> </div> </div> ); } } export default ComponentALFooterOne;
Decorators are a useful way to cut code and wrap classes. A good example is the usage with redux. Take note though: decorators are deprecated and may be removed.
The example of a decorator used for Redux:
import { connect } from 'react-redux'; const DecoratorRedux = (component) => { const mapStateToProps = (state) => { return { routing: state.routing, copy: state.copy, current: state.routing.locationBeforeTransitions.pathname }; }; return connect(mapStateToProps)(component); }; export default DecoratorRedux;
In use:
@DecoratorRedux class PageBlog extends Component { /** * Render PageBlog component * @memberof PageBlog * @var {function} render Render PageBlog component * @returns {PageBlog} component */ render() { return (<div><p>Hello!</p></div>); }