Skip to content

Commit

Permalink
Update component data libs, tests etc for ES modules
Browse files Browse the repository at this point in the history
Component data YAML has now been replaced with ES modules
  • Loading branch information
colinrotherham committed Jan 25, 2024
1 parent 9a93d1f commit b2a6d0f
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 102 deletions.
2 changes: 1 addition & 1 deletion docs/contributing/coding-standards/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ When creating your component, you should create the following files in the compo
- `README.md` - Summary documentation with links to the installation instructions and component documentation on <https://design-system.service.gov.uk/>
- `_[component-name].scss` - An SCSS file to generate the styles for this component only. It delegates the CSS generation to the \_index.scss file.
- `_index.scss` - The actual styles for the component that you can import in 2 ways - [on their own using `[component-name].scss`](https://frontend.design-system.service.gov.uk/importing-css-assets-and-javascript/#import-specific-parts-of-the-css) or [alongside other components in `components/_all.scss`](https://frontend.design-system.service.gov.uk/importing-css-assets-and-javascript/#import-specific-parts-of-the-css)
- `[component-name].yaml` - Lists the component's Nunjucks macro options and includes examples using these options. Both the options and examples are used to generate component documentation in the review app. The examples are also used to test component behaviour, and to generate [fixtures for testing alternative implementations of the design system](https://frontend.design-system.service.gov.uk/testing-your-html/).
- `options/data.mjs` - Lists the component's Nunjucks macro options (or params) and includes examples using these options. Both the options and examples are used to generate component documentation in the review app. The examples are also used to test component behaviour, and to generate [fixtures for testing alternative implementations of the design system](https://frontend.design-system.service.gov.uk/testing-your-html/).
- `macro.njk` - The main entry point for rendering the component. It provides a `govuk[ComponentName](params)` macro, delegating render to the `template.njk` file
- `template.njk` - The template used for rendering the component using any `params` provided to the macro
- `template.test.js` - Tests to ensure the component renders as intended with its various options
Expand Down
4 changes: 2 additions & 2 deletions docs/contributing/coding-standards/nunjucks-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ We have chosen as Nunjucks as the templating language for GOV.UK Frontend compon

To provide a level of consistency for developers we have standardised option names, their expected input, use and placement. There are exceptions, and if so they are documented accordingly.

The options (arguments) accepted by the component macro are specified in a `[component-name].yaml` file as `params`. Each option should have the following attributes: `name`, `type`, `required`, `description`.
The macro options (or params) accepted by the component macro are specified in an `options/data.mjs` file. Each option should have the following attributes: `name`, `type`, `required`, `description`.

An option can additionally contain `params` that denotes nested items in the option (see [breadcrumbs component](/packages/govuk-frontend/src/govuk/components/breadcrumbs/breadcrumbs.yaml#L6)) and `isComponent: true` where the option is another component (see [checkboxes component](/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.yaml#L10)).
An option can additionally contain `params` that denotes nested items in the option (see [breadcrumbs component](/packages/govuk-frontend/src/govuk/components/breadcrumbs/options/params.mjs#L12)) and `isComponent: true` where the option is another component (see [checkboxes component](/packages/govuk-frontend/src/govuk/components/checkboxes/options/params.mjs#L16)).

Component macro options are shipped as `macro-options.json` in `packages/govuk-frontend/dist`.

Expand Down
2 changes: 1 addition & 1 deletion docs/contributing/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ npm scripts are defined in `package.json`. These trigger a number of Gulp tasks.
**`npm start` will trigger `npm run dev` that will:**

- runs `npm run build`
- starts the review app, restarting when `.mjs`, `.json` or `.yaml` files change
- starts the review app, restarting when `.js`, `.mjs` or `.json` files change
- compile again when frontend `.mjs` and `.scss` files change

**`npm test` will do the following:**
Expand Down
8 changes: 4 additions & 4 deletions docs/contributing/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ Check that:

You should add an example to the review app if the existing examples do not reflect the changes you've made.

1. Open `packages/govuk-frontend/src/govuk/components/<COMPONENT>/<COMPONENT>.yaml`, where `<COMPONENT>` is the component you've changed.
2. Add or update examples in the `examples` list.
1. Open `packages/govuk-frontend/src/govuk/components/<COMPONENT>/options/data.mjs`, where `<COMPONENT>` is the component you've changed.
2. Add or update examples in the `examples` export.

If you've created a new component, create a new `packages/govuk-frontend/src/govuk/<COMPONENT>/<COMPONENT>.yaml` file instead, where `<COMPONENT>` is the name of the component you've created.
If you've created a new component, create a new `packages/govuk-frontend/src/govuk/<COMPONENT>/options/data.mjs` file instead, where `<COMPONENT>` is the name of the component you've created.

## 4. Test in supported browsers and assistive technology

Expand Down Expand Up @@ -84,7 +84,7 @@ You should write new tests if you’ve created a new component, or changed the w

If you're new to testing, see existing test files for examples of things to do. Do not let the tests keep you from submitting your contribution! If you're not sure which tests are needed or are having trouble updating them, submit your pull request anyway. We will help you create the tests and solve problems during code review.

Some test files use examples from each component’s `.yaml` file, for example `packages/govuk-frontend/src/govuk/components/button/button.yaml`. When you add or update these tests, you can use the existing examples or add new ones.
Some test files use examples from each component’s `options/data.mjs` file, for example `packages/govuk-frontend/src/govuk/components/button/options/data.mjs`. When you add or update these tests, you can use the existing examples or add new ones.

Use `hidden: true` in a new example if you do not want to include the example in the review app. The example will still appear in our [test fixtures](http://frontend.design-system.service.gov.uk/testing-your-html/).

Expand Down
2 changes: 1 addition & 1 deletion docs/releasing/testing-and-linting.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Tests should be written using ES modules (`*.mjs`) by default, but use CommonJS

### Component tests

We write functional tests for every component to check the output of our Nunjucks code. These are found in `template.test.js` files in each component directory. These Nunjucks tests render the component examples defined in the component yaml files, and assert that the HTML tags, attributes and classes are as expected. For example: checking that when you pass in an `id` to the component using the Nunjucks macro, it outputs the component with an `id` attribute equal to that value.
We write functional tests for every component to check the output of our Nunjucks code. These are found in `template.test.js` files in each component directory. These Nunjucks tests render the component examples defined in the component options, and assert that the HTML tags, attributes and classes are as expected. For example: checking that when you pass in an `id` to the component using the Nunjucks macro, it outputs the component with an `id` attribute equal to that value.

If a component uses JavaScript, we also write functional tests in a `[component name].test.js` file, for example [checkboxes.test.js](/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.test.js). These component tests check that interactions, such as a mouse click, have the expected result.

Expand Down
2 changes: 1 addition & 1 deletion jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const config = {

// Transform some `*.js` to compatible CommonJS
...Object.fromEntries(
['slash'].map((packagePath) => [
['@govuk-frontend/lib/components', 'slash'].map((packagePath) => [
replacePathSepForRegex(`${packageResolveToPath(packagePath)}$`),
[
'babel-jest',
Expand Down
1 change: 1 addition & 0 deletions packages/govuk-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"build": "npm run build:package",
"build:package": "gulp build:package --color",
"build:release": "gulp build:release --color",
"build:fixtures": "gulp fixtures --color",
"build:stats": "npm run stats --workspace @govuk-frontend/stats",
"build:types": "tsc --build tsconfig.build.json",
"clean": "npm run clean:package",
Expand Down
24 changes: 12 additions & 12 deletions packages/govuk-frontend/tasks/build/package.unit.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ describe('packages/govuk-frontend/dist/', () => {
'!**/*.test.*',
'!**/__snapshots__/',
'!**/__snapshots__/**',
'!**/govuk-prototype-kit.config.mjs',
'!**/options/examples.mjs',
'!**/options/params.mjs',
'!**/tsconfig?(.build).json',
'!README.md'
]
Expand All @@ -67,8 +70,15 @@ describe('packages/govuk-frontend/dist/', () => {
const listingExpected = listingSource
.filter(filterPath(filterPatterns))

// Removes GOV.UK Prototype kit config (moved to package top level)
.flatMap(mapPathTo(['**/govuk-prototype-kit.config.mjs'], () => []))
// Replaces source component '**/options/data.mjs' with:
// - `fixtures.json` fixtures for tests
// - `macro-options.json` component options
.flatMap(
mapPathTo(['**/options/data.mjs'], ({ dir: requirePath }) => [
join(requirePath, '../fixtures.json'),
join(requirePath, '../macro-options.json')
])
)

// All source `**/*.mjs` files compiled to ES modules
.flatMap(
Expand Down Expand Up @@ -125,16 +135,6 @@ describe('packages/govuk-frontend/dist/', () => {
join(requirePath, `${name}.scss.map`) // with source map
])
)

// Replaces source component '*.yaml' with:
// - `fixtures.json` fixtures for tests
// - `macro-options.json` component options
.flatMap(
mapPathTo(['**/*.yaml'], ({ dir: requirePath }) => [
join(requirePath, 'fixtures.json'),
join(requirePath, 'macro-options.json')
])
)
.sort()

// Compare output files with '.npmignore' filter
Expand Down
15 changes: 11 additions & 4 deletions packages/govuk-frontend/tasks/fixtures.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { join } from 'path'

import { components, task } from '@govuk-frontend/tasks'
import gulp from 'gulp'

Expand All @@ -9,16 +11,21 @@ import gulp from 'gulp'
export const compile = (options) =>
gulp.series(
/**
* Generate GOV.UK Frontend fixtures.json from ${componentName}.yaml
* Generate GOV.UK Frontend fixtures.json from ${componentName}/options/data.mjs
*/
task.name('compile:fixtures', () =>
components.generateFixtures('**/*.yaml', options)
components.generateFixtures({
srcPath: options.srcPath,
destPath: join(options.destPath, 'govuk/components')
})
),

/**
* Generate GOV.UK Frontend macro-options.json from ${componentName}.yaml
* Generate GOV.UK Frontend macro-options.json from ${componentName}/options/data.mjs
*/
task.name('compile:macro-options', () =>
components.generateMacroOptions('**/*.yaml', options)
components.generateMacroOptions({
destPath: join(options.destPath, 'govuk/components')
})
)
)
16 changes: 11 additions & 5 deletions packages/govuk-frontend/tasks/watch.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { join } from 'path'

import { paths } from '@govuk-frontend/config'
import { npm, task } from '@govuk-frontend/tasks'
import gulp from 'gulp'
import slash from 'slash'

import { assets, fixtures, scripts, styles, templates } from './index.mjs'
import { assets, scripts, styles, templates } from './index.mjs'

/**
* Watch task
Expand Down Expand Up @@ -76,7 +77,10 @@ export const watch = (options) =>
*/
task.name('compile:js watch', () =>
gulp.watch(
'**/*.{cjs,js,mjs}',
[
'**/*.{cjs,js,mjs}', // Watch all script files
`!**/options/**` // Except component options
],
{ cwd: options.srcPath, ignored: ['**/*.test.*'] },

// Run JavaScripts compile
Expand All @@ -89,11 +93,13 @@ export const watch = (options) =>
*/
task.name('compile:fixtures watch', () =>
gulp.watch(
'govuk/components/*/*.yaml',
'**/options/**',
{ cwd: options.srcPath },

// Run fixtures compile
fixtures(options)
// Build fixtures and macro options via npm script otherwise
// imported component data is cached by the ES module loader
// https://nodejs.org/api/esm.html#no-requirecache
npm.script('build:fixtures', ['--silent'], { basePath: paths.package })
)
),

Expand Down
32 changes: 30 additions & 2 deletions shared/lib/components.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
const { dirname, join } = require('path')
const { dirname, join, relative } = require('path')
const { pathToFileURL } = require('url')

const nunjucks = require('nunjucks')
const { outdent } = require('outdent')
const slash = require('slash')

const { getListing, getDirectories } = require('./files')
const { packageTypeToPath, componentNameToMacroName } = require('./names')
const {
packageNameToPath,
packageTypeToPath,
componentNameToMacroName
} = require('./names')

// Nunjucks default environment
const env = nunjucksEnv()
Expand Down Expand Up @@ -43,6 +48,28 @@ function nunjucksEnv(searchPaths = [], nunjucksOptions = {}, packageOptions) {
})
}

/**
* Load single component data (from source)
*
* @param {string} componentName - Component name
* @returns {Promise<ComponentData & { name: string }>} Component data
*/
async function getComponentData(componentName) {
const componentDataPath = join(
packageNameToPath('govuk-frontend'),
`src/govuk/components/${componentName}/options/data.mjs`
)

const { default: componentData } = await import(
slash(relative(__dirname, componentDataPath))
)

return {
name: componentName,
...componentData
}
}

/**
* Load single component fixtures
*
Expand Down Expand Up @@ -274,6 +301,7 @@ function renderTemplate(templatePath, options) {
}

module.exports = {
getComponentData,
getComponentFixtures,
getComponentsFixtures,
getComponentFiles,
Expand Down
Loading

0 comments on commit b2a6d0f

Please sign in to comment.