Skip to content

Commit

Permalink
feat: add dependency map tests
Browse files Browse the repository at this point in the history
  • Loading branch information
EvanLovely committed Jul 3, 2019
1 parent 4431f5c commit b422ffa
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 51 deletions.
109 changes: 105 additions & 4 deletions __tests__/monorepo.test.js
@@ -1,13 +1,60 @@
const path = require('path');
const { join, resolve } = require('path');
const fs = require('fs-extra');
const globby = require('globby');
const rootPkg = require(path.join(__dirname, '../package.json'));
const {
getPkgPathFromName,
getPkgDependents,
getPkgDependencies,
getPkgList,
getPkgFiles,
getFilesPkgSync,
getFilesChanged,
findTwigFilesUsedInFile,
getFilesPkg,
getTwigFilePath,
} = require('@bolt/testing-utils');
const rootPkg = require(join(__dirname, '../package.json'));
const assert = require('assert');
const chalk = require('chalk');
const { promisify } = require('util');
const readdir = promisify(fs.readdir);
const lstat = promisify(fs.lstat);

/**
* @param {string} pkgName
* @param {string[]} deps
*/
function pkgToHaveDependenciesOn(pkgName, deps) {
const listedDeps = getPkgDependencies(pkgName);
const missingDeps = [];
deps.forEach(dep => {
if (!listedDeps.some(d => d === dep)) {
if (dep !== pkgName) {
if (dep !== '@bolt/global') {
missingDeps.push(dep);
}
}
}
});

const pass = missingDeps.length === 0;

return pass
? {
pass,
message: () => `${pkgName} has all dependencies listed correctly`,
}
: {
pass,
message: () =>
`${pkgName} is missing these dependencies in package.json: ${missingDeps.join(
', ',
)}`,
};
}

expect.extend({ pkgToHaveDependenciesOn });

function flattenDeep(arr1) {
return arr1.reduce(
(acc, val) =>
Expand All @@ -16,6 +63,9 @@ function flattenDeep(arr1) {
);
}

/** @type {BoltPkg[]} */
const boltPkgs = getPkgList();

describe('check the config for monorepo packages', () => {
/**
* Array of object containing the package.json of each monorepo package
Expand Down Expand Up @@ -97,13 +147,13 @@ describe('check the config for monorepo packages', () => {
});

test('`@bolt` dependencies are symlinked to the packages folder', async () => {
const baseDir = path.resolve(__dirname, '../node_modules/@bolt');
const baseDir = resolve(__dirname, '../node_modules/@bolt');
readdir(baseDir)
.then(dirNames =>
Promise.all(
dirNames.map(dirName => {
const item = {
path: path.join(baseDir, dirName),
path: join(baseDir, dirName),
};
return new Promise((resolve, reject) => {
lstat(item.path)
Expand Down Expand Up @@ -131,4 +181,55 @@ describe('check the config for monorepo packages', () => {
});
});
});

describe('Bolt Components declare dependencies in package.json if used in Twig files', () => {
const excludedPkgs = ['generator-bolt'];

boltPkgs
.filter(boltPkg => !excludedPkgs.includes(boltPkg.name))
.forEach(
/** @type {BoltPkg} */
boltPkg => {
test(`pkg: ${boltPkg.name}`, async () => {
const twigFilePaths = await globby(
[join(boltPkg.location, '**/*.twig')],
{
gitignore: true,
ignore: ['**/__tests__/**'],
},
);

if (twigFilePaths.length === 0) {
expect(true).toBe(true);
} else {
/** @type {Set<string>} */
const twigDeps = new Set();
/** @type {Set<string>} */
const twigDepPkgs = new Set();

await Promise.all(
twigFilePaths.map(async twigFilePath => {
const theseTwigDeps = await findTwigFilesUsedInFile(
twigFilePath,
);
theseTwigDeps.forEach(x => twigDeps.add(x));
}),
);

await Promise.all(
[...twigDeps].map(twigDep => {
return getTwigFilePath(twigDep)
.then(getFilesPkg)
.then(pkgName => twigDepPkgs.add(pkgName));
}),
);

twigDepPkgs.delete(boltPkg.name);

expect(boltPkg.name).pkgToHaveDependenciesOn([...twigDepPkgs]);
}
});
},
);
});
});
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -65,7 +65,7 @@
"pretest:js": "npm run setup:php",
"test:js": "NODE_ENV='test' jest --all --colors",
"test:js:update": "yarn run test:js -u",
"test:monorepo": "jest ./__tests__/{**/*.test.js,*.test.js} -c jest.config.quick.js",
"test:monorepo": "jest ./__tests__/monorepo.test.js -c jest.config.quick.js",
"test:php": "cd packages/core-php -- composer run test",
"test:pkgs": "lerna run test",
"test:types": "tsc"
Expand Down
34 changes: 33 additions & 1 deletion packages/testing/testing-utils/__tests__/dependency-map.test.js
Expand Up @@ -10,12 +10,37 @@ describe('dependency mapper', () => {
);

expect(results).toStrictEqual([
'@x/button.twig',
'@x/button.twig',
'@x/button/button.twig',
'@x/icon/icon.twig',
'button.twig',
]);
});

test('findTwigFilesUsedInString', () => {
const twigString = `
<div class="card">
<h2>{{ title }}</h2>
<footer>
{% include "@x/button.twig" %}
<div>{% include "@x/button.twig" %}</div>
{% include '@x/button/button.twig' %}
{% include '@x/icon/icon.twig' %}
{% include "button.twig" %}
{% include "@x/button.twig" with {
text: "Hi",
} %}
</footer>
</div>
`;

const results = dm.findTwigFilesUsedInString(twigString);

expect(results).toStrictEqual([
'@x/button.twig',
'@x/button/button.twig',
'@x/icon/icon.twig',
'button.twig',
]);
});

Expand All @@ -28,4 +53,11 @@ describe('dependency mapper', () => {
join(repoRoot, 'packages/components/bolt-button/src/button.twig'),
);
});

test('getFilesPkg', async () => {
const pkgName = await dm.getFilesPkg(
join(repoRoot, 'packages/components/bolt-band/src/band.js'),
);
expect(pkgName).toEqual('@bolt/components-band');
});
});
11 changes: 2 additions & 9 deletions packages/testing/testing-utils/__tests__/test-utils.test.js
Expand Up @@ -4,13 +4,6 @@ const tu = require('../test-utils');
const repoRoot = join(__dirname, '../../../..');

describe('test-utils', () => {
test('getPkgNameFromPath', () => {
const name = tu.getPkgNameFromPath(
join(repoRoot, 'packages/components/bolt-band'),
);
expect(name).toBe('@bolt/components-band');
});

test('getPkgPathFromName', () => {
const path = tu.getPkgPathFromName('@bolt/components-band');
expect(path).toBe(join(repoRoot, 'packages/components/bolt-band'));
Expand All @@ -27,8 +20,8 @@ describe('test-utils', () => {
expect(dependents).toEqual(['@bolt/components-band']);
});

test('getFilesPkg', () => {
const pkgName = tu.getFilesPkg(
test('getFilesPkgSync', () => {
const pkgName = tu.getFilesPkgSync(
join(repoRoot, 'packages/components/bolt-band/src/band.js'),
);
expect(pkgName).toEqual('@bolt/components-band');
Expand Down
73 changes: 60 additions & 13 deletions packages/testing/testing-utils/dependency-map.js
@@ -1,6 +1,7 @@
const globby = require('globby');
const findPkg = require('find-pkg');
const { readFile, readJSON, readJSONSync, existsSync } = require('fs-extra');
const { join } = require('path');
const { join, dirname } = require('path');

const repoRoot = join(__dirname, '../../..');

Expand Down Expand Up @@ -29,6 +30,11 @@ const twigNamespaces = readJSONSync(twigNamespacesManifestPath);
* @returns {Promise<string>} file path to template file
*/
async function getTwigFilePath(templateName) {
if (!templateName.startsWith('@')) {
throw new Error(
`This function only resolves Twig files using namespaces (i.e. starts with "@" or "@bolt") and instead this (probably a file path) was passed in: ${templateName}`,
);
}
let [namespace, ...paths] = templateName.split('/');
namespace = namespace.replace('@', '');
const relPath = paths.join('/');
Expand Down Expand Up @@ -62,31 +68,72 @@ async function getTwigFilePath(templateName) {
}

/**
* @param {string} twigFilePath i.e. `path/to/file.twig`
* @returns {Promise<string[]>} list of other Twig files used in it via `include`, `embed`, or `extend`. i.e. `['@bolt/button.twig']`
* @see {getTwigFilePath}
* @param {string} twigString - Twig code as a string to parse
* @param {Object} [opt]
* @param {boolean} [opt.unique=true]
* @returns {string[]} list of other Twig files used in it via `include`, `embed`, or `extend`. i.e. `['@bolt/button.twig']`
* @see {findTwigFilesUsedInFile}
*/
async function findTwigFilesUsedInFile(twigFilePath) {
const twigString = await readFile(twigFilePath, {
encoding: 'utf8',
});

function findTwigFilesUsedInString(twigString, { unique = true } = {}) {
/* eslint-disable prettier/prettier */
const twigRegex = new RegExp(
"(?<=" + // begin lookahead assertion; these patterns must appear before and will not be included in result
"[include|extends|embed] " + // any of these words and then a space
"[\"|']" + // either `"` or `'`
"[include|extends|embed] " + // any of these words and then a space
"[\"|']" + // either `"` or `'`
")" + // end lookahead assertion
"(.*\.twig)" // this will be captured as result. any character `.` infinite times `*` followed by a literal period `\.` and then `twig`
, 'g'); // Regex flags - `g`: global
/* eslint-enable prettier/prettier */

const results = twigString.match(twigRegex);
let results = twigString.match(twigRegex);
// if nothing found, results `null` and we want to consistently return same types, in this case, an array
return results || [];
results = results || [];
if (unique) {
results = [...new Set(results)];
}
return (
results
// don't include any Yeoman generator files
.filter(r => !r.includes('<%='))
// don't include any dynamic file includes
.filter(r => !r.includes('~'))
);
}

/**
* @param {string} twigFilePath i.e. `path/to/file.twig`
* @param {Object} [opt]
* @param {boolean} [opt.unique=true]
* @returns {Promise<string[]>} list of other Twig files used in it via `include`, `embed`, or `extend`. i.e. `['@bolt/button.twig']`
* @see {findTwigFilesUsedInString}
*/
async function findTwigFilesUsedInFile(twigFilePath, { unique = true } = {}) {
const twigString = await readFile(twigFilePath, {
encoding: 'utf8',
});

return findTwigFilesUsedInString(twigString, { unique });
}

/**
* Get a package's name from a path to any file in the package
* @param {string} file
* @return {Promise<string>} name of package, i.e. '@bolt/components-band'
*/
async function getFilesPkg(file) {
const pkgPath = await findPkg(dirname(file));
let pkg = await readJSON(pkgPath);
// grab parent package of package.json that are used by Yeoman `generator-*`s
if (pkg.name.includes('<%=')) {
const pkgPath2 = findPkg(dirname(join(pkgPath, '..')));
pkg = await readJSON(pkgPath2);
}
return pkg ? pkg.name : '';
}

module.exports = {
findTwigFilesUsedInFile,
findTwigFilesUsedInString,
getTwigFilePath,
getFilesPkg,
};
27 changes: 27 additions & 0 deletions packages/testing/testing-utils/index.js
@@ -0,0 +1,27 @@
const {
getPkgPathFromName,
getPkgDependents,
getPkgDependencies,
getPkgList,
getPkgFiles,
getFilesPkgSync,
getFilesChanged,
} = require('./test-utils');
const {
findTwigFilesUsedInFile,
getFilesPkg,
getTwigFilePath,
} = require('./dependency-map');

module.exports = {
getPkgPathFromName,
getPkgDependents,
getPkgDependencies,
getPkgList,
getPkgFiles,
getFilesPkgSync,
getFilesChanged,
findTwigFilesUsedInFile,
getFilesPkg,
getTwigFilePath,
};
2 changes: 1 addition & 1 deletion packages/testing/testing-utils/package.json
Expand Up @@ -28,4 +28,4 @@
"email": "me@salemghoweri.com",
"web": "https://github.com/sghoweri"
}]
}
}

0 comments on commit b422ffa

Please sign in to comment.