From f63fd34a214528fe84d36dc7b75ab91dc39055ec Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Wed, 18 Jul 2018 20:32:53 +0100 Subject: [PATCH 01/13] =?UTF-8?q?Install=20jest,=20add=20a=20simple=20spec?= =?UTF-8?q?=20for=20the=20update-versions=20module=20=20=F0=9F=90=BF=20v2.?= =?UTF-8?q?10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + test/src/update-versions.spec.js | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 test/src/update-versions.spec.js diff --git a/package.json b/package.json index 0e3d69b..03e1eef 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,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" diff --git a/test/src/update-versions.spec.js b/test/src/update-versions.spec.js new file mode 100644 index 0000000..d7683a9 --- /dev/null +++ b/test/src/update-versions.spec.js @@ -0,0 +1,38 @@ +const subject = require('../../src/update-versions'); + +const fixture = Object.freeze({ + version: '0.0.0', + dependencies: { + foo: '0.0.0', + bar: '1.2.3', + }, + devDependencies: { + baz: '0.0.0' + } +}); + +describe('src/update-versions', () => { + it('returns a new object', () => { + const result = subject(fixture); + expect(result).not.toEqual(fixture); + }); + + it('updates the version number', () => { + const result = subject(fixture, '1.0.0'); + expect(result.version).toEqual('1.0.0'); + }); + + it('updates the version numbers of any local dependencies', () => { + const result = subject(fixture, '1.0.0', [ 'foo', 'baz' ]); + + expect(result.dependencies.foo).not.toEqual('0.0.0'); + expect(result.devDependencies.baz).not.toEqual('0.0.0'); + }); + + it('uses a caret range for local dependencies version numbers', () => { + const result = subject(fixture, '1.0.0', [ 'foo', 'baz' ]); + + expect(result.dependencies.foo).toEqual('^1.0.0'); + expect(result.devDependencies.baz).toEqual('^1.0.0'); + }); +}); From df54731a2a335e327d3f1361087ae15649c4c41a Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Thu, 19 Jul 2018 08:52:06 +0100 Subject: [PATCH 02/13] =?UTF-8?q?Add=20simple=20spec=20for=20package=20mod?= =?UTF-8?q?ule=20WIP=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 ++- src/package.js | 11 ++++++++-- test/src/package.spec.js | 46 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 test/src/package.spec.js diff --git a/package.json b/package.json index 03e1eef..ff94fc4 100644 --- a/package.json +++ b/package.json @@ -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": [], diff --git a/src/package.js b/src/package.js index 7cf2907..909a3b9 100644 --- a/src/package.js +++ b/src/package.js @@ -7,8 +7,15 @@ const writeFile = util.promisify(fs.writeFile); class Package { constructor (manifest, location) { - this.manifest = manifest; - this.location = location; + // define read-only properties + Object.defineProperties(this, { + manifest: { + value: Object.freeze(manifest) + }, + location: { + value: path.normalize(location) + } + }); } get name () { diff --git a/test/src/package.spec.js b/test/src/package.spec.js new file mode 100644 index 0000000..935fb6d --- /dev/null +++ b/test/src/package.spec.js @@ -0,0 +1,46 @@ +const path = require('path'); +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 #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'); + }); + }); +}); From dbe334e02df3504f2c1d35d5e33aed1579209f3a Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Thu, 26 Jul 2018 09:10:43 +0100 Subject: [PATCH 03/13] Add spec for sort packages module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🐿 v2.10.0 --- Makefile | 6 ++-- test/src/package.spec.js | 8 ++++- test/src/sort-packages.spec.js | 59 ++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 test/src/sort-packages.spec.js diff --git a/Makefile b/Makefile index 256a4e2..44e597c 100644 --- a/Makefile +++ b/Makefile @@ -6,10 +6,10 @@ 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 # unit-test-coverage: # nyc --reporter=$(if $(CIRCLECI),lcovonly,lcov) make unit-test -test: verify +test: verify unit-test diff --git a/test/src/package.spec.js b/test/src/package.spec.js index 935fb6d..cb0a5df 100644 --- a/test/src/package.spec.js +++ b/test/src/package.spec.js @@ -1,4 +1,3 @@ -const path = require('path'); const Subject = require('../../src/package'); const fixture = Object.freeze({ @@ -30,6 +29,13 @@ describe('src/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); diff --git a/test/src/sort-packages.spec.js b/test/src/sort-packages.spec.js new file mode 100644 index 0000000..7dcbc16 --- /dev/null +++ b/test/src/sort-packages.spec.js @@ -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); + }); + }); +}); From 67dc34bd78ca19ab176e8e21732e2b7c9e2685da Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Mon, 3 Sep 2018 17:48:37 +0100 Subject: [PATCH 04/13] =?UTF-8?q?Add=20spec=20for=20get-packages=20module?= =?UTF-8?q?=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 3 ++- src/get-packages.js | 1 + test/src/get-packages.spec.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 test/src/get-packages.spec.js diff --git a/Makefile b/Makefile index 44e597c..f4225a6 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,8 @@ node_modules/@financial-times/n-gage/index.mk: -include node_modules/@financial-times/n-gage/index.mk unit-test: - export NODE_ENV=test; jest + export NODE_ENV=test; jest --verbose --env node + @$(DONE) # unit-test-coverage: # nyc --reporter=$(if $(CIRCLECI),lcovonly,lcov) make unit-test diff --git a/src/get-packages.js b/src/get-packages.js index f3c42fd..fb94a28 100644 --- a/src/get-packages.js +++ b/src/get-packages.js @@ -3,5 +3,6 @@ const glob = promisify(require('glob')); module.exports = (patterns = []) => { const opts = { realpath: true }; + // return glob(patterns.length > 1 ? `{${patterns.join(',')}}` : patterns[0], opts); }; diff --git a/test/src/get-packages.spec.js b/test/src/get-packages.spec.js new file mode 100644 index 0000000..c0c9642 --- /dev/null +++ b/test/src/get-packages.spec.js @@ -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) + ); + }); +}); From 86b14c2fea4d48c6aa523db630d7d4e636892572 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Mon, 3 Sep 2018 22:46:05 +0100 Subject: [PATCH 05/13] =?UTF-8?q?Add=20spec=20for=20filter=20packages=20mo?= =?UTF-8?q?dule=20=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/src/filter-packages.spec.js | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/src/filter-packages.spec.js diff --git a/test/src/filter-packages.spec.js b/test/src/filter-packages.spec.js new file mode 100644 index 0000000..d642e40 --- /dev/null +++ b/test/src/filter-packages.spec.js @@ -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'); + }); +}); From e6d7bdd51247ec4b1f47cd6f2245cd3eb84f7757 Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Mon, 3 Sep 2018 23:35:36 +0100 Subject: [PATCH 06/13] =?UTF-8?q?Enable=20test=20coverage=20make=20task=20?= =?UTF-8?q?=20=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Makefile | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 23120a2..4b8e795 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /node* .circleci/ +coverage/ *.env* .eslintrc.js .editorconfig diff --git a/Makefile b/Makefile index f4225a6..2c4ecca 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,8 @@ 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 unit-test From f4041718b4baf9b2c35cc6e772fc321d26cca2df Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Tue, 4 Sep 2018 08:56:01 +0100 Subject: [PATCH 07/13] Add spec for exec task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🐿 v2.10.0 --- src/tasks/exec.js | 6 +++-- test/src/tasks/exec.spec.js | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 test/src/tasks/exec.spec.js diff --git a/src/tasks/exec.js b/src/tasks/exec.js index d6f7d1d..a987cf4 100644 --- a/src/tasks/exec.js +++ b/src/tasks/exec.js @@ -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 [args...]') .description('Runs an arbitrary command in the scope of each package') diff --git a/test/src/tasks/exec.spec.js b/test/src/tasks/exec.spec.js new file mode 100644 index 0000000..f5afecc --- /dev/null +++ b/test/src/tasks/exec.spec.js @@ -0,0 +1,51 @@ +const mockRun = jest.fn(); +jest.mock('../../../src/run-package', () => mockRun); + +const { task: subject } = require('../../../src/tasks/exec'); + +const createPackage = (name) => ( + { + name, + location: `/Path/to/${name}` + } +); + +describe('src/tasks/exec', () => { + const packages = [ + createPackage('foo'), + createPackage('bar'), + createPackage('baz'), + ]; + + const command = 'test-command'; + + const args = ['--flag', '--named', 'value']; + + let result; + + beforeEach(() => { + result = subject(packages, command, args); + }); + + afterEach(() => { + mockRun.mockReset(); + }); + + it('it returns an array of functions', () => { + expect(result.length).toEqual(packages.length); + + result.forEach((item) => { + expect(item).toBeInstanceOf(Function); + }); + }); + + it('provides the correct arguments to run helper', () => { + result.forEach((item, i) => { + const pkg = packages[i]; + + item(); + + expect(mockRun).toHaveBeenCalledWith(command, args, pkg.location); + }); + }); +}); From 7d6b022f43966f0c79bd3e8338fbbbfe7427862c Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Tue, 4 Sep 2018 18:03:28 +0100 Subject: [PATCH 08/13] =?UTF-8?q?Add=20spec=20for=20publish=20task=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tasks/publish.js | 6 ++-- test/src/tasks/publish.spec.js | 54 ++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 test/src/tasks/publish.spec.js diff --git a/src/tasks/publish.js b/src/tasks/publish.js index feb0502..a611428 100644 --- a/src/tasks/publish.js +++ b/src/tasks/publish.js @@ -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); @@ -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') diff --git a/test/src/tasks/publish.spec.js b/test/src/tasks/publish.spec.js new file mode 100644 index 0000000..d553122 --- /dev/null +++ b/test/src/tasks/publish.spec.js @@ -0,0 +1,54 @@ +const mockRun = jest.fn(); +jest.mock('../../../src/run-package', () => mockRun); + +const { task: subject } = require('../../../src/tasks/publish'); + +const createPackage = (name, options = {}) => ( + { + name, + location: `/Path/to/${name}`, + ...options + } +); + +describe('src/tasks/publish', () => { + const packages = [ + createPackage('foo'), + createPackage('bar'), + createPackage('baz', { private: true }), + ]; + + const args = ['--access', 'public']; + + let result; + + beforeEach(() => { + result = subject(packages, args); + }); + + afterEach(() => { + mockRun.mockReset(); + }); + + it('it returns an array of functions', () => { + expect(result).toBeInstanceOf(Array); + + result.forEach((item) => { + expect(item).toBeInstanceOf(Function); + }); + }); + + it('it filters out any private packages', () => { + expect(result.length).toEqual(2); + }); + + it('provides the correct arguments to run helper', () => { + result.forEach((item, i) => { + const pkg = packages[i]; + + item(); + + expect(mockRun).toHaveBeenCalledWith('npm', ['publish'].concat(args), pkg.location); + }); + }); +}); From 9809db8783a7c2d091ccec24e9fa60bc4e5034ad Mon Sep 17 00:00:00 2001 From: Matt Hinchliffe Date: Wed, 5 Sep 2018 08:49:55 +0100 Subject: [PATCH 09/13] =?UTF-8?q?Add=20spec=20for=20run=20task=20=20?= =?UTF-8?q?=F0=9F=90=BF=20v2.10.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tasks/run.js | 6 ++-- test/src/tasks/exec.spec.js | 6 +++- test/src/tasks/publish.spec.js | 2 +- test/src/tasks/run.spec.js | 54 ++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 test/src/tasks/run.spec.js diff --git a/src/tasks/run.js b/src/tasks/run.js index aca2c66..998fb3c 100644 --- a/src/tasks/run.js +++ b/src/tasks/run.js @@ -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); @@ -16,7 +16,9 @@ async function run (packages = [], script) { }); }; -module.exports.register = (program) => { +exports.task = run; + +exports.register = (program) => { program .command('run