/News-Web-Nextjs-Node

📰 News Website

Primary LanguageJavaScript

inews React Front-End

React/NextJS application to render inews.co.uk as powered by a WordPress WP-JSON API

Getting Started

  1. Install this application's dependencies with yarn
  2. Start the server with yarn start, this will additionally watch and compile the app code as you develop.
  3. 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.

Using production API data

The application can consume the inews.co.uk API by starting the app with yarn start:dev:prod.

Using local API data

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

Development

  1. 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.

Ads Development

  1. Clone mol-adverts

  2. Run npm run inews-dev

  3. For local development point to the dev ads bundle: http://inews.local:3333/?abl=https://localhost:8123/dist/DEV

  4. 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.

ASCII Tables

Use PlainTextTools to generate the ascii tables to demonstrate the component layout in its docblock section.

HTTP 400/500 statuses

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}};
    }
}

Deployment

Run npm version patch before deploying a branch to production. This will ensure static assets are fresh on the client.

Testing

Unit/Integration testing is done with Jest as the testing framework and Enzyme as the testing utlity library.

Tests directory structure and setup

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:

Pre-commit testing

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.

Running the test suite

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

Server side component/code testing

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.

Example util method test

//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');
  });
});

Example component test with mock

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',
  });
});

Client side component/code testing

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
 */

Demo pages

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