Skip to content

Commit

Permalink
Merge pull request #617 from Financial-Times/CPP-465-improve-x-intera…
Browse files Browse the repository at this point in the history
…ction-test-coverage

CP-465 Added e2e test for hydration and serialisation
  • Loading branch information
serena97 authored Sep 29, 2021
2 parents bd2aac4 + 4a48ddb commit e1307c4
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 5 deletions.
13 changes: 12 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ references:
container_config_node: &container_config_node
working_directory: ~/project/build
docker:
- image: circleci/node:12
- image: circleci/node:12-browsers

workspace_root: &workspace_root
~/project
Expand Down Expand Up @@ -105,6 +105,14 @@ jobs:
- run:
name: Run storybook
command: npm run start-storybook:ci

e2e-test:
<<: *container_config_node
steps:
- *attach_workspace
- run:
name: Run end to end test
command: npm run e2e

publish:
<<: *container_config_node
Expand Down Expand Up @@ -164,6 +172,9 @@ workflows:
- test:
requires:
- build
- e2e-test:
requires:
- build
- deploy:
filters:
<<: *filters_only_main
Expand Down
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
**/public-prod/**
**/blueprints/**
web/static/**
/e2e/**
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ bower_components
npm-debug.log
.DS_Store
dist
.idea
.idea
coverage
2 changes: 1 addition & 1 deletion components/x-interaction/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ When rendered on the server side, components output an extra wrapper element, wi

`x-interaction` exports a function `hydrate`. This should be called on the client side. It inspects the global serialisation data on the page, uses the identifiers to find the wrapper elements, and calls `render` from your chosen `x-engine` client-side runtime to render component instances into the wrappers.

Before calling `hydrate`, you must first `import` any `x-interaction` components that will be rendered on the page. The components register themselves with the `x-interaction` runtime when imported; you don't need to do anything with the imported component. This will also ensure the component is included in your client-side bundle.
Before calling `hydrate`, you must first `import` any `x-interaction` components that will be rendered on the page. The components register themselves with the `x-interaction` runtime when imported; you don't need to do anything with the imported component. This will also ensure the component is included in your client-side bundle. Similarly if the component that you're server side rendering is just a component that you've created through `withActions`, make sure you import that component along with its registerComponent invokation.

Because `hydrate` expects the wrappers to be present in the DOM when called, it should be called after [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded). Depending on your page structure, it might be appropriate to hydrate the component when it's scrolled into view.

Expand Down
5 changes: 5 additions & 0 deletions e2e/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// set up app to host main.js file included in server side rendered html
const express = require('express')
const server = express()
server.use(express.static(__dirname))
exports.app = server
21 changes: 21 additions & 0 deletions e2e/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const { withActions, registerComponent } = require('@financial-times/x-interaction')
const { h } = require('@financial-times/x-engine')

export const greetingActions = withActions({
actionOne() {
return { greeting: 'world' }
}
})

export const GreetingComponent = greetingActions(({ greeting, actions }) => {
return (
<div className="greeting-text">
hello {greeting}
<button className="greeting-button" onClick={actions.actionOne}>
click to add to hello
</button>
</div>
)
})

registerComponent(GreetingComponent, 'GreetingComponent')
62 changes: 62 additions & 0 deletions e2e/e2e.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* @jest-environment node
*/

const { h } = require('@financial-times/x-engine') // required for <GreetingComponent>
const { Serialiser, HydrationData } = require('@financial-times/x-interaction')
const puppeteer = require('puppeteer')
const ReactDOMServer = require('react-dom/server')
const express = require('express')
import React from 'react'
import { GreetingComponent } from './common'

describe('x-interaction-e2e', () => {
let browser
let page
let app
let server

beforeAll(async () => {
app = express()
server = app.listen(3004)
app.use(express.static(__dirname))
browser = await puppeteer.launch()
page = await browser.newPage()
})

it('attaches the event listener to SSR components on hydration', async () => {
const ClientComponent = () => {
// main.js is the transpiled version of index.js, which contains the registered GreetingComponent, and invokes hydrate
return <script type="module" src="./main.js" charset="utf-8"></script>
}

const serialiser = new Serialiser()
const htmlString = ReactDOMServer.renderToString(
<>
<GreetingComponent serialiser={serialiser} />
<HydrationData serialiser={serialiser} />
<ClientComponent />
</>
)

app.get('/', (req, res) => {
res.send(htmlString)
})

// go to page and click button
await page.goto('http://localhost:3004')
await page.waitForSelector('.greeting-button')
await page.click('.greeting-button')
const text = await page.$eval('.greeting-text', (e) => e.textContent)
expect(text).toContain('hello world')
})

afterAll(async () => {
try {
;(await browser) && browser.close()
await server.close()
} catch (e) {
console.log(e)
}
})
})
4 changes: 4 additions & 0 deletions e2e/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { hydrate } from '@financial-times/x-interaction'
import './common'

document.addEventListener('DOMContentLoaded', hydrate)
11 changes: 11 additions & 0 deletions e2e/jest.e2e.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
testMatch: ['<rootDir>/e2e.test.js'],
testPathIgnorePatterns: ['/node_modules/', '/bower_components/'],
transform: {
'^.+\\.jsx?$': '../packages/x-babel-config/jest'
},
moduleNameMapper: {
'^[./a-zA-Z0-9$_-]+\\.scss$': '<rootDir>/__mocks__/styleMock.js'
},
testEnvironment: 'node'
}
43 changes: 43 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "x-dash-e2e",
"version": "0.0.0",
"private": "true",
"description": "This module enables you to write x-dash components that respond to events and change their own data.",
"keywords": [
"x-dash"
],
"author": "",
"license": "ISC",
"x-dash": {
"engine": {
"server": {
"runtime": "react",
"factory": "createElement",
"component": "Component",
"fragment": "Fragment",
"renderModule": "react-dom/server",
"render": "renderToStaticMarkup"
},
"browser": "react"
}
},
"repository": {
"type": "git",
"url": "https://github.com/Financial-Times/x-dash.git"
},
"engines": {
"node": "12.x"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"puppeteer": "^10.4.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"webpack": "^5.54.0",
"webpack-cli": "^4.8.0",
"@financial-times/x-engine": "file:../packages/x-engine",
"@financial-times/x-interaction": "file:../components/x-interaction"
}
}
34 changes: 34 additions & 0 deletions e2e/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const path = require('path')
const xEngine = require('../packages/x-engine/src/webpack')
const webpack = require('webpack')

module.exports = {
entry: './index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname)
},
plugins: [
new webpack.ProvidePlugin({
React: 'react'
}),
xEngine()
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
},
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.js', '.jsx', '.json', '.wasm', '.mjs', '*']
}
}
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ module.exports = {
},
moduleNameMapper: {
'^[./a-zA-Z0-9$_-]+\\.scss$': '<rootDir>/__mocks__/styleMock.js'
}
},
modulePathIgnorePatterns: ['<rootDir>/e2e/']
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"build-only": "athloi run build",
"jest": "jest -c jest.config.js",
"test": "npm run lint && npm run jest",
"e2e": "cd e2e && ./node_modules/.bin/webpack && jest -c jest.e2e.config.js",
"lint": "eslint . --ext=js,jsx",
"blueprint": "node private/scripts/blueprint.js",
"start-storybook": "start-storybook -p ${STORYBOOK_PORT:-9001} -s .storybook/static -h local.ft.com",
Expand Down Expand Up @@ -72,6 +73,7 @@
"workspaces": [
"components/*",
"packages/*",
"tools/*"
"tools/*",
"e2e"
]
}

0 comments on commit e1307c4

Please sign in to comment.