Skip to content
This repository has been archived by the owner on Oct 18, 2023. It is now read-only.

Commit

Permalink
Merge pull request #16 from Financial-Times/matth/write-some-tests
Browse files Browse the repository at this point in the history
Write some tests
  • Loading branch information
i-like-robots authored Sep 25, 2018
2 parents 4f588d4 + 9d9cfc9 commit d3a5805
Show file tree
Hide file tree
Showing 20 changed files with 560 additions and 21 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/node*
.circleci/
coverage/
*.env*
.eslintrc.js
.editorconfig
Expand Down
12 changes: 7 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ node_modules/@financial-times/n-gage/index.mk:

-include node_modules/@financial-times/n-gage/index.mk

# unit-test:
# export NODE_ENV=test; mocha 'tests/**/*.spec.js'
unit-test:
export NODE_ENV=test; jest --verbose --env node
@$(DONE)

# unit-test-coverage:
# nyc --reporter=$(if $(CIRCLECI),lcovonly,lcov) make unit-test
unit-test-coverage:
export NODE_ENV=test; jest --coverage --env node
@$(DONE)

test: verify
test: verify unit-test
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"scripts": {
"precommit": "node_modules/.bin/secret-squirrel",
"commitmsg": "node_modules/.bin/secret-squirrel-commitmsg",
"prepush": "make verify -j3"
"prepush": "make verify -j3",
"test": "jest"
},
"bin": "src/bin/cli",
"keywords": [],
Expand All @@ -30,6 +31,7 @@
"commander": "^2.16.0",
"find-up": "^3.0.0",
"glob": "^7.1.2",
"jest": "^23.4.1",
"log-symbols": "^2.2.0",
"semver": "^5.5.0",
"toposort": "^2.0.2"
Expand Down
1 change: 1 addition & 0 deletions src/get-packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ const glob = promisify(require('glob'));

module.exports = (patterns = []) => {
const opts = { realpath: true };
// <https://en.wikipedia.org/wiki/Glob_(programming)>
return glob(patterns.length > 1 ? `{${patterns.join(',')}}` : patterns[0], opts);
};
6 changes: 3 additions & 3 deletions src/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const writeFile = util.promisify(fs.writeFile);

class Package {
constructor (manifest, location) {
this.manifest = manifest;
this.location = location;
this.manifest = Object.freeze(manifest);
this.location = path.normalize(location);
}

get name () {
Expand All @@ -34,7 +34,7 @@ class Package {
async writeManifest (manifest) {
const json = JSON.stringify(manifest, null, 2);
await writeFile(this.manifestLocation, json);
this.manifest = manifest;
this.manifest = Object.freeze(manifest);
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/tasks/exec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
const taskify = require('../cli-task');
const runPackage = require('../run-package');

async function exec (packages = [], command, args = []) {
function exec (packages = [], command, args = []) {
return packages.map((pkg) => {
return () => runPackage(command, args, pkg.location);
});
};

module.exports.register = (program) => {
exports.task = exec;

exports.register = (program) => {
program
.command('exec <path> [args...]')
.description('Runs an arbitrary command in the scope of each package')
Expand Down
6 changes: 4 additions & 2 deletions src/tasks/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const logger = require('../logger');
const taskify = require('../cli-task');
const runPackage = require('../run-package');

async function publish (packages = [], args = []) {
function publish (packages = [], args = []) {
// filter out any private packages
const filteredPackages = packages.filter((pkg) => !pkg.private);

Expand All @@ -14,7 +14,9 @@ async function publish (packages = [], args = []) {
});
};

module.exports.register = (program) => {
exports.task = publish;

exports.register = (program) => {
program
.command('publish [args...]')
.description('Runs npm publish in the scope of each public package')
Expand Down
6 changes: 4 additions & 2 deletions src/tasks/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const logger = require('../logger');
const taskify = require('../cli-task');
const runPackage = require('../run-package');

async function run (packages = [], script) {
function run (packages = [], script) {
// filter out packages without the requested command
const filteredPackages = packages.filter(({ manifest }) => {
return manifest.scripts && manifest.scripts.hasOwnProperty(script);
Expand All @@ -16,7 +16,9 @@ async function run (packages = [], script) {
});
};

module.exports.register = (program) => {
exports.task = run;

exports.register = (program) => {
program
.command('run <script>')
.description('Runs an npm script in each package that contains that script.')
Expand Down
9 changes: 5 additions & 4 deletions src/tasks/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ const path = require('path');
const taskify = require('../cli-task');
const runPackage = require('../run-package');

async function script (packages = [], scriptPath) {
// solve path to script file
function script (packages = [], scriptPath) {
const resolvedScript = path.resolve(process.cwd(), scriptPath);

return packages.map((pkg) => {
return () => runPackage('node', [resolvedScript], pkg.location);
});
};
}

exports.task = script;

module.exports.register = (program) => {
exports.register = (program) => {
program
.command('script <path>')
.description('Runs the given Node script in the scope of each package')
Expand Down
6 changes: 4 additions & 2 deletions src/tasks/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const logger = require('../logger');
const taskify = require('../cli-task');
const updateVersions = require('../update-versions');

async function version (packages = [], tag) {
function version (packages = [], tag) {
// Projects may use different tag formats
const number = semver.clean(tag);

Expand All @@ -23,7 +23,9 @@ async function version (packages = [], tag) {
});
};

module.exports.register = (program) => {
exports.task = version;

exports.register = (program) => {
program
.command('version <tag>')
.description('Updates the release number for all packages and writes the new data back to package.json')
Expand Down
46 changes: 46 additions & 0 deletions test/src/filter-packages.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const subject = require('../../src/filter-packages');

describe('src/filter-packages', () => {
const manifests = [
{
name: 'foo',
private: true
},
{
name: 'bar',
author: 'Joe Bloggs'
},
{
name: 'baz',
private: false
}
];

const fixture = manifests.map((manifest) => ({ manifest }));

it('matches manifests with a matching field and value', () => {
const result = subject('private:false', fixture);

expect(result.length).toEqual(1);
expect(result[0].manifest.name).toEqual('baz');
});

it('coerces different types of value', () => {
const a = subject('private:true', fixture);

expect(a.length).toEqual(1);
expect(a[0].manifest.name).toEqual('foo');

const b = subject('author:"Joe Bloggs"', fixture);

expect(b.length).toEqual(1);
expect(b[0].manifest.name).toEqual('bar');
});

it('defaults to matching the package name', () => {
const result = subject('baz', fixture);

expect(result.length).toEqual(1);
expect(result[0].manifest.name).toEqual('baz');
});
});
33 changes: 33 additions & 0 deletions test/src/get-packages.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const mockGlob = jest.fn();
jest.mock('glob', () => mockGlob);

const subject = require('../../src/get-packages');

describe('src/get-packages', () => {
afterEach(() => {
mockGlob.mockReset();
});

it('munges multiple patterns', () => {
subject([
'components/*',
'packages/*'
]);

expect(mockGlob).toHaveBeenCalledWith(
'{components/*,packages/*}',
expect.any(Object),
expect.any(Function)
);
});

it('does not munge a single pattern', () => {
subject([ 'components/*' ]);

expect(mockGlob).toHaveBeenCalledWith(
'components/*',
expect.any(Object),
expect.any(Function)
);
});
});
80 changes: 80 additions & 0 deletions test/src/package.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
jest.mock('fs');

const fs = require('fs');
const Subject = require('../../src/package');

const fixture = Object.freeze({
name: 'my-package',
version: '0.0.0',
});

describe('src/package', () => {
const factory = (json) => {
return new Subject(json, '/root/path/to/package');
};

describe('constructor', () => {
it('stores the given manifest', () => {
const instance = factory(fixture);
expect(instance.manifest).toBe(fixture);
});

it('stores the given location', () => {
const instance = factory(fixture);
expect(instance.location).toBe('/root/path/to/package');
});
});

describe('get #name', () => {
it('gets the manifest name', () => {
const instance = factory(fixture);
expect(instance.name).toBe('my-package');
});
});

describe('get #private', () => {
it('returns a boolean', () => {
const instance = factory(fixture);
expect(instance.private).toBe(false);
});
});

describe('get #manifestLocation', () => {
it('gets the manifest name', () => {
const instance = factory(fixture);
expect(instance.manifestLocation).toBe('/root/path/to/package/package.json');
});
});

describe('get #nodeModulesLocation', () => {
it('gets the manifest name', () => {
const instance = factory(fixture);
expect(instance.nodeModulesLocation).toEqual('/root/path/to/package/node_modules');
});
});

describe('#writeManifest', () => {
beforeEach(() => {
// The final arg is a callback that needs calling!
fs.writeFile.mockImplementation((...args) => args[args.length - 1]());
});

it('writes the new manifest', async () => {
const instance = factory(fixture);
await instance.writeManifest({ ...fixture, version: '1.0.0' });

expect(fs.writeFile).toHaveBeenCalledWith(
'/root/path/to/package/package.json',
JSON.stringify({ name : 'my-package', version: '1.0.0' }, null, 2),
expect.any(Function)
);
});

it('updates the manifest property', async () => {
const instance = factory(fixture);
await instance.writeManifest({ ...fixture, version: '1.0.0' });

expect(instance.manifest.version).toEqual('1.0.0');
});
});
});
59 changes: 59 additions & 0 deletions test/src/sort-packages.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const subject = require('../../src/sort-packages');

const fixture = Object.freeze([
{
name: 'foo',
manifest: {
dependencies: {
qux: '0.0.0'
}
}
},
{
name: 'bar',
manifest: {
dependencies: {
baz: '0.0.0'
}
}
},
{
name: 'baz',
manifest: {
dependencies: {
foo: '0.0.0',
qux: '0.0.0'
}
},
},
{
name: 'qux',
manifest: {
dependencies: {
}
}
}
]);

describe('src/sort-packages', () => {
it('returns a new array', () => {
const result = subject(null, fixture);
expect(result).not.toEqual(fixture);
});

it('sorts packages topologically', () => {
const result = subject(null, fixture);

['qux', 'foo', 'baz', 'bar'].forEach((name, i) => {
expect(result[i].name).toEqual(name);
});
});

it('can reverse the sort order', () => {
const result = subject(true, fixture);

['bar', 'baz', 'foo', 'qux'].forEach((name, i) => {
expect(result[i].name).toEqual(name);
});
});
});
Loading

0 comments on commit d3a5805

Please sign in to comment.