Skip to content
This repository has been archived by the owner on Mar 14, 2022. It is now read-only.

Commit

Permalink
Run pa11y tests when there is no pa11y.mustache template.
Browse files Browse the repository at this point in the history
pa11y tests should run if there is a demo with name `pa11y` which
compiles to `pa11y.html` in the demos directory. The demo may or
may not be based on a template `pa11y.mustache`.

This commit also removes references to "custom config" (it would
have been awkward to commit separately). Custom config is a way
of configuring components outside of origami.json. This is not
documented anywhere in spec v1, moving to spec v2 is a good time
to drop suppport.
  • Loading branch information
notlee committed Jan 29, 2021
1 parent 6f499de commit 1dd0b9c
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 139 deletions.
28 changes: 28 additions & 0 deletions lib/helpers/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,32 @@ function getModuleBrands(cwd) {
});
}

/**
* Get individual demo configuration for the component.
* @param {string} cwd - The component's directory (the current working directory).
* @return {array<object>} - An array of objects representing a component demo.
*/
async function getComponentDemos(cwd) {
const origamiJson = await getOrigamiJson(cwd);
if (origamiJson && origamiJson.demos && Array.isArray(origamiJson.demos)) {
return origamiJson.demos;
}
return [];
}

/**
* Get shared demo configuration.
* @param {string} cwd - The component's directory (the current working directory).
* @return {object} - An object of configuration.
*/
async function getComponentDefaultDemoConfig(cwd) {
const origamiJson = await getOrigamiJson(cwd);
if (origamiJson && origamiJson.demosDefaults && typeof origamiJson.demosDefaults === 'object') {
return origamiJson.demosDefaults;
}
return {};
}

// List mustache files in a directory, recursing over subdirectories
function getMustacheFilesList(basePath) {
const opts = {
Expand Down Expand Up @@ -176,4 +202,6 @@ module.exports.sassSupportsSilent = sassSupportsSilent;
module.exports.getMustacheFilesList = getMustacheFilesList;
module.exports.getSassTestFiles = getSassTestFiles;
module.exports.getModuleBrands = getModuleBrands;
module.exports.getComponentDemos = getComponentDemos;
module.exports.getComponentDefaultDemoConfig = getComponentDefaultDemoConfig;
module.exports.getSassIncludePaths = getSassIncludePaths;
160 changes: 65 additions & 95 deletions lib/tasks/demo-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,110 +206,80 @@ function hasUniqueNames(demos) {
return true;
}

module.exports = function (cfg) {
module.exports = async function (cfg) {
const config = cfg || {};
const cwd = config.cwd || process.cwd();
if (Boolean(config.demoConfig) && config.demoConfig !== 'origami.json') {
return Promise.reject(new Error('Custom demo config files are not supported, please place demo config inside of origami.json.'));
}

const configPath = path.join(cwd, 'origami.json');
return readFile(configPath, 'utf-8')
.then(file => {
let demosConfig;
try {
demosConfig = JSON.parse(file);
} catch (error) {
const e = new Error(`${configPath} is not valid JSON.`);
e.stack = '';
throw e;
}

const demos = [];

if (!Array.isArray(demosConfig.demos)) {
const e = new Error('No demos exist in origami.json file. Reference http://origami.ft.com/docs/syntax/origamijson/ to help configure demos for the component.');
e.stack = '';
throw e;
}
const demoDefaultConfiguration = await files.getComponentDefaultDemoConfig();
const demos = await files.getComponentDemos();

if (demosConfig.demos.length === 0) {
const e = new Error('No demos exist in origami.json file. Reference http://origami.ft.com/docs/syntax/origamijson/ to help configure demos for the component.');
e.stack = '';
throw e;
}
const demoBuildConfig = [];

if (!hasUniqueNames(demosConfig.demos)) {
const e = new Error('Demos with the same name were found. Give them unique names and try again.');
e.stack = '';
throw e;
}
if (demos.length === 0) {
const e = new Error('No demos exist in origami.json file. Reference https://origami.ft.com/spec to help configure demos for the component.');
e.stack = '';
throw e;
}

let demoFilters;
if (config && typeof config.demoFilter === 'string') {
demoFilters = config.demoFilter.split(',');
} else if (config && Array.isArray(config.demoFilter)) {
demoFilters = config.demoFilter;
}
if (!hasUniqueNames(demos)) {
const e = new Error('Demos with the same name were found. Give them unique names and try again.');
e.stack = '';
throw e;
}

if (!configPath.includes('origami.json')) {
const e = new Error('Please move your demo config into origami.json following the spec: http://origami.ft.com/docs/syntax/origamijson');
e.stack = '';
throw e;
}
let demoFilters;
if (config && typeof config.demoFilter === 'string') {
demoFilters = config.demoFilter.split(',');
} else if (config && Array.isArray(config.demoFilter)) {
demoFilters = config.demoFilter;
}

for (const demoConfig of demosConfig.demos) {
if (!demoFilters || demoFilters && demoFilters.includes(demoConfig.name)) {
demos.push(mergeDeep(
{
documentClasses: '',
description: ''
},
demosConfig.demosDefaults || demosConfig.options,
demoConfig
));
}
}
for (const demoConfig of demos) {
if (!demoFilters || demoFilters && demoFilters.includes(demoConfig.name)) {
demoBuildConfig.push(mergeDeep(
{
documentClasses: '',
description: ''
},
demoDefaultConfiguration,
demoConfig
));
}
}

// Create an array of configuration for each demo asset to build.
const htmlBuildsConfig = [];
const sassBuildsConfig = [];
const jsBuildsConfig = [];
// Create build configuration for each demo asset if it doesn't
// already exist. For example two demos may share the same Sass.
for (const demo of demos) {
const buildConfig = {
demo: demo || {},
brand: config.brand,
cwd: cwd
};
// Add demo html config.
htmlBuildsConfig.push(buildConfig);
const newSassBuild = !sassBuildsConfig.find(existingConfig =>
existingConfig.demo.sass === buildConfig.demo.sass
);
if (demo.sass && newSassBuild) {
sassBuildsConfig.push(buildConfig);
}
// Create an array of configuration for each demo asset to build.
const htmlBuildsConfig = [];
const sassBuildsConfig = [];
const jsBuildsConfig = [];
// Create build configuration for each demo asset if it doesn't
// already exist. For example two demos may share the same Sass.
for (const demoBuild of demoBuildConfig) {
const buildConfig = {
demo: demoBuild || {},
brand: config.brand,
cwd: cwd
};
// Add demo html config.
htmlBuildsConfig.push(buildConfig);
const newSassBuild = !sassBuildsConfig.find(existingConfig =>
existingConfig.demo.sass === buildConfig.demo.sass
);
if (demoBuild.sass && newSassBuild) {
sassBuildsConfig.push(buildConfig);
}

const newJsBuild = !jsBuildsConfig.find(existingConfig =>
existingConfig.demo.js === buildConfig.demo.js
);
if (demo.js && newJsBuild) {
jsBuildsConfig.push(buildConfig);
}
}
const newJsBuild = !jsBuildsConfig.find(existingConfig =>
existingConfig.demo.js === buildConfig.demo.js
);
if (demoBuild.js && newJsBuild) {
jsBuildsConfig.push(buildConfig);
}
}

// Return build promises for all demo assets.
return Promise.all([
...htmlBuildsConfig.map(c => buildDemoHtml(c)),
...sassBuildsConfig.map(c => buildDemoSass(c)),
...jsBuildsConfig.map(c => buildDemoJs(c))
]);
}, () => {
const configError = 'Couldn\'t find demos config path, checked: ' + configPath;
const e = new Error(configError);
e.stack = '';
throw e;
});
// Return build promises for all demo assets.
return Promise.all([
...htmlBuildsConfig.map(c => buildDemoHtml(c)),
...sassBuildsConfig.map(c => buildDemoSass(c)),
...jsBuildsConfig.map(c => buildDemoJs(c))
]);
};
17 changes: 6 additions & 11 deletions lib/tasks/pa11y.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
'use strict';

const denodeify = require('util').promisify;
const fs = require('fs');
const path = require('path');
const isCI = require('is-ci');
const files = require('../helpers/files');
const buildDemo = require('./demo-build');

const fileExists = file => denodeify(fs.open)(file, 'r').then(() => true).catch(() => false);

const pa11yTest = async function (config, brand) {
const pa11y = require('pa11y');
const src = path.join(config.cwd, '/demos/local/pa11y.html');
Expand Down Expand Up @@ -87,13 +83,12 @@ module.exports = function (cfg) {
}
}
},
skip: function () {
return fileExists(path.join(config.cwd, '/demos/src/pa11y.mustache'))
.then(exists => {
if (!exists) {
return `No Pa11y demo found. To run Pa11y against this project, create a file at ${path.join(config.cwd, '/demos/local/pa11y.html')}`;
}
});
skip: async function () {
const demos = await files.getComponentDemos();
const hasPa11yDemo = demos.some(d => d.name === 'pa11y');
if (!hasPa11yDemo) {
return `No Pa11y demo found. Create a demo with name "pa11y" to run Pa11y against this project.`;
}
}
};
};
29 changes: 29 additions & 0 deletions test/unit/helpers/files.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,33 @@ describe('Files helper', function () {

});

describe('getComponentDemos', function () {
it('should return the component\'s `demos` configuration from origami.json', async function () {
const demos = await files.getComponentDemos();
proclaim.isArray(demos, 'Expected an array of demos.');
proclaim.ok(demos.some(d => d.name === 'pa11y'), 'Expected to find a test demo with name "pa11y"');
});

it('should return an empty array given no origami.json manifest', async function () {
fs.unlinkSync(path.resolve(filesTestPath, 'origami.json'));
const demos = await files.getComponentDemos();
proclaim.isArray(demos);
proclaim.lengthEquals(demos, 0, 'Expected an empty array.');
});
});

describe('getComponentDefaultDemoConfig', function () {
it('should return the component\'s `demosDefaults` configuration from origami.json', async function () {
const demosDefaults = await files.getComponentDefaultDemoConfig();
proclaim.isObject(demosDefaults, 'Expected an object of default demo configuration.');
proclaim.ok(demosDefaults.sass, 'Expected to confirm demo default configuration by finding "sass" configuration, but found none.');
});

it('should return an empty object given no origami.json manifest', async function () {
fs.unlinkSync(path.resolve(filesTestPath, 'origami.json'));
const demosDefaults = await files.getComponentDefaultDemoConfig();
proclaim.isObject(demosDefaults, {}, 'Expected an object given missing default demo configuration.');
proclaim.lengthEquals(Object.keys(demosDefaults), 0, 'Expected an empty object given missing default demo configuration.');
});
});
});
34 changes: 7 additions & 27 deletions test/unit/tasks/demo-build.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,48 +36,28 @@ describe('Demo task', function () {
});

describe('Build demos', function () {
it('should fail if there is not a config file', function () {
it('should error if no demos are found', function () {
process.chdir(oNoManifestPath);
fs.writeFileSync('package.json', '{"name":"o-test"}', 'utf8');
return demo()
.then(() => {
throw new Error('promise resolved when it should have rejected');
}, function (err) {
proclaim.equal(err.message, `Couldn\'t find demos config path, checked: ${path.join(process.cwd(),'origami.json')}`);
proclaim.equal(
err.message,
'No demos exist in origami.json file. Reference ' +
'https://origami.ft.com/spec to help configure demos ' +
'for the component.'
);
fs.unlinkSync(path.resolve(oNoManifestPath, 'package.json'));
process.chdir(demoTestPath);
});
});

it('should error with a custom config file', function () {
fs.writeFileSync('package.json', '{"name":"o-test"}', 'utf8');
fs.copySync('demos/src/config.json', 'demos/src/mysupercoolconfig.json');
return demo({
demoConfig: 'demos/src/mysupercoolconfig.json'
})
.then(() => {
throw new Error('promise resolved when it should have rejected');
}, function errorHandler(err) {
// It will throw a template not found error which is fixed in "should build html" test
proclaim.notEqual(err.message, 'Couldn\'t find demos config path, checked: demos/src/mysupercoolconfigs.json');
});
});

it('should not fail using origami.json', function () {
return demo();
});

it('should fail if it\'s using the old config format', function () {
return demo({
demoConfig: 'demos/src/oldconfig.json'
})
.then(() => {
throw new Error('promise resolved when it should have rejected');
}, function () {
proclaim.ok(true);
});
});

it('should fail if there are demos with the same name', function () {
const demoConfig = JSON.parse(fs.readFileSync('origami.json', 'utf8'));
demoConfig.demos[1] = demoConfig.demos[0];
Expand Down
18 changes: 12 additions & 6 deletions test/unit/tasks/pa11y.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ describe('Test task', function() {
demo = require('../../../lib/tasks/demo-build');
pa11y = require('../../../lib/tasks/pa11y');
return demo({
demoConfig: 'origami.json',
demoFilter: ['pa11y']
});
});
Expand All @@ -65,14 +64,21 @@ describe('Test task', function() {
});

describe('skip', () => {
it('should return a truthy value if the file does not exist', function() {
fs.removeSync(path.join(process.cwd(), '/demos/src/pa11y.mustache'));
it('should return a truthy value if origami.json has no demo with name "pa11y"', function() {
const origamiJSON = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'origami.json'), 'utf-8'));
origamiJSON.demos = [];
fs.writeFileSync('origami.json', JSON.stringify(origamiJSON), 'utf8');
return pa11y().skip().then(result => proclaim.isTrue(Boolean(result)));
});

it('should return a helpful message if the file does not exist', function() {
fs.removeSync(path.join(process.cwd(), '/demos/src/pa11y.mustache'));
return pa11y().skip().then(result => proclaim.equal(result, `No Pa11y demo found. To run Pa11y against this project, create a file at ${path.join(process.cwd(), '/demos/local/pa11y.html')}`));
it('should return a helpful message if origami.json has no demo with name "pa11y"', function() {
const origamiJSON = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'origami.json'), 'utf-8'));
origamiJSON.demos = [];
fs.writeFileSync('origami.json', JSON.stringify(origamiJSON), 'utf8');
return pa11y().skip().then(result => proclaim.equal(
result,
'No Pa11y demo found. Create a demo with name "pa11y" to run Pa11y against this project.'
));
});

it('should return a falsey value if the file does exist', function() {
Expand Down

0 comments on commit 1dd0b9c

Please sign in to comment.