diff --git a/.gitignore b/.gitignore index 8d8448a53..ea082a76f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ dist/ Thumbs.db npm-debug.log package-lock.json +yarn.lock .vscode .cache diff --git a/examples/ft-ui-async-stylesheets/.gitignore b/examples/ft-ui-async-stylesheets/.gitignore new file mode 100644 index 000000000..364fdec1a --- /dev/null +++ b/examples/ft-ui-async-stylesheets/.gitignore @@ -0,0 +1 @@ +public/ diff --git a/examples/ft-ui-async-stylesheets/.npmrc b/examples/ft-ui-async-stylesheets/.npmrc new file mode 100644 index 000000000..6e8708804 --- /dev/null +++ b/examples/ft-ui-async-stylesheets/.npmrc @@ -0,0 +1,2 @@ +shrinkwrap = false +package-lock = false diff --git a/examples/ft-ui-async-stylesheets/__test__/integration.test.js b/examples/ft-ui-async-stylesheets/__test__/integration.test.js new file mode 100644 index 000000000..c656d3765 --- /dev/null +++ b/examples/ft-ui-async-stylesheets/__test__/integration.test.js @@ -0,0 +1,74 @@ +describe('examples/ft-ui', () => { + beforeAll(async () => { + await page.setViewport({ width: 1920, height: 1080 }) + await page.goto('http://localhost:3456', { waitUntil: 'load' }) + }) + + describe('JavaScript bootstrap', () => { + it('flags that JavaScript is enabled', async () => { + await expect(page).toMatchElement('html.js') + }) + + it('flags that the browser passes the cut the mustard test', async () => { + await expect(page).toMatchElement('html.enhanced') + }) + + it('loads the configured scripts', async () => { + await expect(page).toMatchElement('script[src^="https://polyfill.io"]') + await expect(page).toMatchElement('script[src="public/scripts.bundle.js"]') + }) + }) + + describe('UI components', () => { + it('renders a11y skip links', async () => { + await expect(page).toMatchElement('a[href="#site-navigation"]') + await expect(page).toMatchElement('a[href="#site-content"]') + await expect(page).toMatchElement('a[href="#site-footer"]') + }) + + it('renders the site header', async () => { + await expect(page).toMatchElement('#site-navigation .o-header__top') + await expect(page).toMatchElement('#site-navigation .o-header__top-logo') + }) + + it('renders the desktop navigation elements', async () => { + await expect(page).toMatchElement('.o-header__nav--desktop') + await expect(page).toMatchElement('.o-header__mega-column--articles') + await expect(page).toMatchElement('.o-header__mega-column--subsections') + }) + + it('renders the desktop search bar', async () => { + await expect(page).toMatchElement('#o-header-search-primary') + }) + + it('renders the small screen navigation elements', async () => { + await expect(page).toMatchElement('#site-navigation .o-header__nav--mobile') + }) + + it('renders the sticky header', async () => { + await expect(page).toMatchElement('.o-header--sticky') + }) + + it('renders the site footer', async () => { + await expect(page).toMatchElement('#site-footer') + }) + + it('renders the drawer menu', async () => { + await expect(page).toMatchElement('#o-header-drawer') + }) + }) + + describe('basic UI interactivity', () => { + it('enables the drawer menu to be toggled', async () => { + await expect(page).toMatchElement('#o-header-drawer[aria-hidden="true"]') + await page.click('a[aria-controls="o-header-drawer"]') + await expect(page).toMatchElement('#o-header-drawer[aria-hidden="false"]') + }) + + it('enables the search bar to be toggled', async () => { + await expect(page).toMatchElement('#o-header-search-primary[aria-hidden="true"]') + await page.click('a[aria-controls="o-header-search-primary"]') + await expect(page).toMatchElement('#o-header-search-primary[aria-hidden="false"]') + }) + }) +}) diff --git a/examples/ft-ui-async-stylesheets/client/async.scss b/examples/ft-ui-async-stylesheets/client/async.scss new file mode 100644 index 000000000..150d52f96 --- /dev/null +++ b/examples/ft-ui-async-stylesheets/client/async.scss @@ -0,0 +1,16 @@ +// This will already have been output in blocking.scss +$n-ui-foundations-applied: true; +@import "n-ui-foundations/main"; + +// Output anything which isn't visible on page load +@import "o-header/main"; +@include oHeader($features: ('drawer', 'megamenu', 'sticky'), $include-base-styles: false); + +// Megamenu z-indexes etc. +@import "@financial-times/dotcom-ui-header/styles"; + +@import "n-topic-search/main"; +@include nTopicSearch(); + +$o-footer-is-silent: false; +@import "o-footer/main"; diff --git a/examples/ft-ui-async-stylesheets/client/main.js b/examples/ft-ui-async-stylesheets/client/main.js new file mode 100644 index 000000000..5c3562da7 --- /dev/null +++ b/examples/ft-ui-async-stylesheets/client/main.js @@ -0,0 +1,20 @@ +import domLoaded from 'dom-loaded' +import * as layout from '@financial-times/dotcom-ui-layout' + +/** + * + * Load stylesheets asyncronously. See: + * https://www.filamentgroup.com/lab/load-css-simpler/ + * https://w3c.github.io/preload/#example-5 + */ +const asyncStylesheetsInit = () => { + const asyncStylesheets = document.getElementsByClassName('asyncStylesheet') + Array.from(asyncStylesheets).forEach((element) => { + element.media = 'all' + }) +} + +domLoaded.then(() => { + asyncStylesheetsInit() + layout.init() +}) diff --git a/examples/ft-ui-async-stylesheets/client/main.scss b/examples/ft-ui-async-stylesheets/client/main.scss new file mode 100644 index 000000000..a07ff681c --- /dev/null +++ b/examples/ft-ui-async-stylesheets/client/main.scss @@ -0,0 +1,8 @@ +@import "n-ui-foundations/main"; + +// We don't need the sub-brand or transparent header styles so disable them. +// NOTE: Don't output anything which isn't visible on page load! +@import "o-header/main"; +@include oHeader($features: ('nav', 'subnav', 'search', 'anon', 'simple'), $include-base-styles: true); + +@import '@financial-times/dotcom-ui-layout/styles'; diff --git a/examples/ft-ui-async-stylesheets/jest-puppeteer.config.js b/examples/ft-ui-async-stylesheets/jest-puppeteer.config.js new file mode 100644 index 000000000..65b07a1f3 --- /dev/null +++ b/examples/ft-ui-async-stylesheets/jest-puppeteer.config.js @@ -0,0 +1,10 @@ +module.exports = { + server: { + command: 'npm run start', + port: process.env.PORT || 3456 + }, + launch: { + // https://discuss.circleci.com/t/puppeteer-fails-on-circleci/22650 + args: ['--no-sandbox', '--disable-setuid-sandbox'] + } +} diff --git a/examples/ft-ui-async-stylesheets/jest.config.js b/examples/ft-ui-async-stylesheets/jest.config.js new file mode 100644 index 000000000..1688ea979 --- /dev/null +++ b/examples/ft-ui-async-stylesheets/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + preset: 'jest-puppeteer' +} diff --git a/examples/ft-ui-async-stylesheets/package.json b/examples/ft-ui-async-stylesheets/package.json new file mode 100644 index 000000000..ff461a73b --- /dev/null +++ b/examples/ft-ui-async-stylesheets/package.json @@ -0,0 +1,31 @@ +{ + "name": "example-ft-ui-async-stylesheets", + "private": true, + "version": "0.0.0", + "license": "MIT", + "scripts": { + "build": "page-kit build -d", + "dev": "nodemon --ext js,jsx", + "start": "node server/start.js", + "pretest": "npm run build", + "test": "../../node_modules/.bin/jest" + }, + "dependencies": { + "@financial-times/dotcom-middleware-navigation": "file:../../packages/dotcom-middleware-navigation", + "@financial-times/dotcom-ui-layout": "file:../../packages/dotcom-ui-layout", + "@financial-times/dotcom-ui-shell": "file:../../packages/dotcom-ui-shell", + "@financial-times/dotcom-ui-header": "file:../../packages/dotcom-ui-header", + "express": "^4.16.2", + "react": "^16.8.6", + "react-dom": "^16.8.6", + "sucrase": "^3.10.1" + }, + "devDependencies": { + "@financial-times/dotcom-page-kit-cli": "file:../../packages/dotcom-page-kit-cli", + "@financial-times/dotcom-build-bower-resolve": "file:../../packages/dotcom-build-bower-resolve", + "@financial-times/dotcom-build-js": "file:../../packages/dotcom-build-js", + "@financial-times/dotcom-build-sass": "file:../../packages/dotcom-build-sass", + "dom-loaded": "^1.2.0", + "nodemon": "^1.18.9" + } +} diff --git a/examples/ft-ui-async-stylesheets/page-kit.config.js b/examples/ft-ui-async-stylesheets/page-kit.config.js new file mode 100644 index 000000000..1faa4b3cd --- /dev/null +++ b/examples/ft-ui-async-stylesheets/page-kit.config.js @@ -0,0 +1,22 @@ +const path = require('path') +const bower = require('@financial-times/dotcom-build-bower-resolve') +const sass = require('@financial-times/dotcom-build-sass') +const js = require('@financial-times/dotcom-build-js') + +module.exports = { + plugins: [ + bower.plugin(), + sass.plugin({ includePaths: [path.resolve('../../bower_components')] }), + js.plugin(), + ], + settings: { + build: { + entry: { + scripts: './client/main.js', + styles: './client/main.scss', + async: './client/async.scss' + }, + outputPath: path.resolve('./public') + } + } +} diff --git a/examples/ft-ui-async-stylesheets/readme.md b/examples/ft-ui-async-stylesheets/readme.md new file mode 100644 index 000000000..89d807b66 --- /dev/null +++ b/examples/ft-ui-async-stylesheets/readme.md @@ -0,0 +1,5 @@ +# FT UI + +This example demonstrates all the FT.com layout which includes the header, navigation, and footer components. It uses [Sucrase] to provide support for JSX syntax and ESM. + +[Sucrase]: https://github.com/alangpierce/sucrase diff --git a/examples/ft-ui-async-stylesheets/server/app.js b/examples/ft-ui-async-stylesheets/server/app.js new file mode 100644 index 000000000..04d984083 --- /dev/null +++ b/examples/ft-ui-async-stylesheets/server/app.js @@ -0,0 +1,11 @@ +import express from 'express' +import * as navigation from '@financial-times/dotcom-middleware-navigation' +import { homeController } from './controllers/home.jsx' + +export const app = express() + +app.use(navigation.init()) + +app.use('/public', express.static('./public')) + +app.get('/', homeController) diff --git a/examples/ft-ui-async-stylesheets/server/controllers/home.jsx b/examples/ft-ui-async-stylesheets/server/controllers/home.jsx new file mode 100644 index 000000000..20cb74eb2 --- /dev/null +++ b/examples/ft-ui-async-stylesheets/server/controllers/home.jsx @@ -0,0 +1,40 @@ +import React from 'react' +import ReactDOM from 'react-dom/server' +import { Shell } from '@financial-times/dotcom-ui-shell' +import { Layout } from '@financial-times/dotcom-ui-layout' + +export function homeController(_, response, next) { + const appContext = { + appName: 'ft-ui', + edition: response.locals.navigation.editions.current.id + } + + const pageData = { + title: 'Hello World!', + contents: '
Hello, welcome to Page Kit.