React/NextJS application to render inews.co.uk as powered by a WordPress WP-JSON API
- Install this application's dependencies with
yarn
- Start the server with
yarn start
, this will additionally watch and compile the app code as you develop. - View the front end at
http://localhost:3333
By default the application consumes the inewsint.co.uk API to populate the front end with content. All links will be rewritten to use http://localhost:3333
.
The application can consume the inews.co.uk API by starting the app with yarn start:dev:prod
.
The application can consume the API generated by a local instance of inews-wordpress by setting up a custom .env
file with details of the local instance and then starting this app with yarn start:dev:local
.
Your local .env
will need to define the following variables (edit values as needed) for this app to work as expected.
INEWS_FE_DOMAIN=http://localhost:3333
INEWS_PUBLIC_API_DOMAIN=http://inews.local
INEWS_PRIVATE_API_DOMAIN=http://inews.local
- Using the correct context to render a component:
import { useContext } from 'react'
import PageContext from '../PageContext'
const ctx = useContext(PageContext)
return (
<div className="inews__ad_container">
{
ctx.dirtyAmp
? <div id="advert"></div>
: <amp-ad></amp-ad>
}
</div>
);
This will render an <amp-ad>
if we are on an AMP page, and a placeholder <div>
on non-amp pages.
This is all pre-rendered on the server so we can not for example include a component here that interacts with the DOM.
The PageContext contains child props such as post data, e.g. ctx.post
. PageContext also available on the client window.PageContext
.
-
Clone mol-adverts
-
Run
npm run inews-dev
-
For local development point to the dev ads bundle:
http://inews.local:3333/?abl=https://localhost:8123/dist/DEV
-
Run
npm run inews-build-prod
, to create a release in/dist/inews-adverts.js
, copy this to/public/static
in the inews-react project
inews-adverts.js
is a bundle of plugins defined in: https://github.com/MailOnline/mol-adverts/blob/inews-bundle/src/inews-entry.js
The plugins referenced in that entry file can either be shared plugins, imported from /src/plugins
, or plugins specifically written for inews in /src/plugins-inews
.
Use PlainTextTools to generate the ascii tables to demonstrate the component layout in its docblock section.
To "throw" a 400/500 error from a page template simply return an error
object with a statusCode
property from the getInitialProps
method of your page component.
This will cause the app to rendor the pages/_error.js
template with the status code passed over in error.statusCode
static async getInitialProps(context) {
const { slug } = context.query;
const tags = await wp
.tags()
.slug(slug)
.embed();
if(tags.length === 0){
return {error: {statusCode: 404}};
}
}
Run npm version patch
before deploying a branch to production. This will ensure static assets are fresh on the client.
Unit/Integration testing is done with Jest as the testing framework and Enzyme as the testing utlity library.
All of the test files live under the tests
directory and the file structure of tests
matches the file structure of the root directory. Thus the test for components/post-items/post/PostPuff.js
can be found at tests/components/post-items/post/PostPuff.js
.
The test configuration, setup and linting code can be found in these files:
In general the relevant tests from the test suite are perfomed against any staged code via the pre-commit hook
If the tests fail it will block the commit. Patch the code to ensure the test passes and if need be fix the test if its broken. If a test has failed because a snapshot needs updating run the following yarn test -o -u
.
Never comment out or skip the test; always address the core problem.
The entire test suite can be run with yarn test
. If wanted, generate a local copy of the code coverage report using yarn test --coverage
.
View the coverage report by opening up the generated coverage/lcov-report/index.html
file in your browser
When it comes to testing server side components you should only need to use the shallow()
rendering method from Enzyme to transform your JSX into something you can query against using the Enzyme selector API
The shallow()
method does not render/transform any child components in your target component thus allowing you to test it as a unit.
Jest snapshots can also be used for general litmus testing of a component however be aware that snapshots are brittle and need regenerating anytime the output of a component changes.
Use mocks over imported methods. This will allow you to confirm that a method has been called and with what without relying on the actual implementation of it.
Anything that can differ across environments should always be mocked to ensure reliable tests.
//Import is relative from your test file
import { getRelativePublicApiUrl } from '../../utils/URL';
/* Mock config.js (which is used within `getRelativePublicApiUrl()`) to ensure
* a consistent return value across envs
*/
jest.mock('../../config', () => ({
publicApiUrl: 'https://api.inewstest.co.uk/wp-json',
}));
describe('getRelativePublicApiUrl', () => {
it(' shouldreturn the protocol relative version of config.publicApiUrl', () => {
//Expecting against the value defined in the mock above
expect(getRelativePublicApiUrl()).toBe('//api.inewstest.co.uk/wp-json');
});
});
import React from 'react';
//Import is relative from your test file
import PostItemTitle from '../../../components/post-items/PostItemTitle';
//Import the mock we're defining below so that we can test against it
import * as mockUtilPost from '../../../utils/post';
/*
* The path of the module being mocked is relative to this file.
*
* Define "_esModule: true" on the returned object to have it behave
* as an ES6 module with named exports.
*
* Use jest.fn() in the named export to return a function can be tested
* against.
*
*/
jest.mock('../../../utils/post', () => ({
__esModule: true,
default: {},
getSocialTitle: jest.fn(() => 'Social Title'),
}));
//Tidy up between tests
beforeEach(() => {
jest.clearAllMocks();
});
//Dummy post object
const post = {
id: 1,
title: {
rendered: 'title',
},
breadcrumbs: [
{ name: 'News' },
],
link: 'https://inews-article.com/test-article',
};
test('renders the snapshot', () => {
const postItemTitle = shallow(<PostItemTitle post={post} />);
//Jest will generate a .snap file the first time toMatchSnapShot() is called
expect(postItemTitle).toMatchSnapshot();
});
it('should output a linked headline using the title returned from getSocialTitle', () => {
const postItemTitle = shallow(<PostItemTitle post={post} />);
expect(mockUtilPost.getSocialTitle).toHaveBeenCalled();
const header = postItemTitle.find('h2');
const anchor = header.find('a');
expect(anchor.prop('href')).toBe(post.link);
expect(anchor.prop('title')).toBe(post.title.rendered);
/* Due to shallow() not rendering child components we need check the
* dangerouslySetInnerHTML prop for the correct title.
*/
expect(anchor.prop('dangerouslySetInnerHTML')).toMatchObject({
__html: 'Social Title',
});
});
Testing client side components and code is much the same as testing the server side components and code.
If you need to access to any DOM APIs in your tests then you'll need to use the mount()
rendering method from Enzyme and not shallow()
method.
If you're using mount()
then you'll need a browser like environment for it to work as expected. Globally this project uses node
as its test environment however this can be overriden on a per test file basis to use jsdom
instead by including the following docblock at the top of your test file:
/**
* @jest-environment jsdom
*/
When process.env.NODE_ENV !== 'production'
is true the following demo pages are accessible:
endpoint | file | purpose |
---|---|---|
/demo/all-post-sections | ./pages/_demo-pages/all-post-sections.js | Outputs all post sections with basic data |