Skip to content

Commit

Permalink
Add test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzable committed Nov 21, 2018
1 parent b6a7d36 commit 5d661f8
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 178 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
stable
lts/*
10 changes: 5 additions & 5 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ const { exec } = require('child_process');
const { promisify } = require('util');

const execAsync = promisify(exec);
const pkgs = [];

async function getLatestVersions(name) {
const { stdout } = await execAsync(`npm view ${name} versions --json`);
try {
return JSON.parse(stdout);
} catch (err) {
return [];
throw new Error(`Failed to parse output from NPM view - ${err.toString()}`);
}
}

Expand All @@ -37,7 +36,7 @@ function getInstalledVersion(currentDir, name) {
}
}

function pushPkgs(dir, logger, deps = {}, type) {
function pushPkgs({ dir, logger, deps = {}, type, pkgs }) {
return Object.keys(deps).map(async name => {
let wanted = deps[name];
if (!wanted.startsWith('^')) wanted = `^${wanted}`;
Expand Down Expand Up @@ -71,9 +70,10 @@ function getPkgIds(filteredPkgs) {
async function verifyDeps({ dir, logger = console }) {
const { dependencies, devDependencies } = require(path.join(dir, 'package.json'));
logger.info(chalk.blue('Checking NPM module versions…\n'));
const pkgs = [];
await Promise.all([
...pushPkgs(dir, logger, dependencies, 'prod'),
...pushPkgs(dir, logger, devDependencies, 'dev')
...pushPkgs({ dir, logger, deps: dependencies, type: 'prod', pkgs }),
...pushPkgs({ dir, logger, deps: devDependencies, type: 'dev', pkgs })
]);
const toInstall = pkgs.filter(({ shouldBeInstalled }) => shouldBeInstalled);
if (toInstall.length > 0) {
Expand Down
183 changes: 180 additions & 3 deletions lib/index.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,186 @@
'use strict';

const chalk = require('chalk');
const Chance = require('chance');
const { promisify: getMockExecAsync } = require('util');
const { join: mockJoin } = require('path');
const verifyDeps = require('.');

describe('verifyDeps', () => {
it('exports as a function', () => {
expect(typeof verifyDeps).toBe('function');
jest.mock('path', () => ({ join: jest.fn() }));
jest.mock('child_process', () => ({}));
jest.mock('util', () => {
const mockExecAsync = jest.fn();
return { promisify: () => mockExecAsync };
});

const chance = new Chance();
const moduleNameRegex = new RegExp('npm view (.*) versions --json');
const pathString = './index.test.js';
const mockExecAsync = getMockExecAsync();
const mockExports = {};

describe('lib/index', () => {
const dir = chance.word();
const outdatedDep = chance.word();
const updatedDep = chance.word();
const outdatedDevDep = chance.word();
const updatedDevDep = chance.word();
const logger = { info: jest.fn(), error: jest.fn() };
let olderVersion;
let newerVersion;

beforeEach(() => {
olderVersion = '1.0.0';
newerVersion = '1.0.1';
mockExports.version = olderVersion;
mockExports.dependencies = {
[outdatedDep]: `^${olderVersion}`,
[updatedDep]: `^${olderVersion}`
};
mockExports.devDependencies = {
[outdatedDevDep]: `^${olderVersion}`,
[updatedDevDep]: `^${olderVersion}`
};
mockExecAsync.mockImplementation(command => {
const moduleName = command.match(moduleNameRegex)[1];
const versions = [olderVersion];
if (moduleName === outdatedDep || moduleName === outdatedDevDep) versions.push(newerVersion);
return Promise.resolve({ stdout: JSON.stringify(versions) });
});
mockJoin.mockImplementation(() => pathString);
});

afterEach(() => {
logger.info.mockClear();
logger.error.mockClear();
mockExecAsync.mockClear();
mockJoin.mockClear();
});

it('should show dependency update required when using semver and later patch version available', async () => {
try {
await verifyDeps({ dir, logger });
} catch (err) {
expect(logger.info).toHaveBeenCalledWith(
`${chalk.red(outdatedDep)} is outdated: ${chalk.red(olderVersion)}${chalk.green(
newerVersion
)}`
);
expect(logger.info).toHaveBeenCalledWith(
`${chalk.red(outdatedDevDep)} is outdated: ${chalk.red(olderVersion)}${chalk.green(
newerVersion
)}`
);
expect(logger.info).toHaveBeenCalledWith(`npm i ${outdatedDep}@${newerVersion}`);
expect(logger.info).toHaveBeenCalledWith(`npm i -D ${outdatedDevDep}@${newerVersion}`);
expect(logger.info).toHaveBeenCalledTimes(7);
expect(err.message).toEqual(chalk.red('Please update your installed modules.'));
}
});

it('should show dependency update required when using semver and later minor version available', async () => {
newerVersion = '1.1.0';
try {
await verifyDeps({ dir, logger });
} catch (err) {
expect(logger.info).toHaveBeenCalledWith(
`${chalk.red(outdatedDep)} is outdated: ${chalk.red(olderVersion)}${chalk.green(
newerVersion
)}`
);
expect(logger.info).toHaveBeenCalledWith(
`${chalk.red(outdatedDevDep)} is outdated: ${chalk.red(olderVersion)}${chalk.green(
newerVersion
)}`
);
expect(logger.info).toHaveBeenCalledWith(`npm i ${outdatedDep}@${newerVersion}`);
expect(logger.info).toHaveBeenCalledWith(`npm i -D ${outdatedDevDep}@${newerVersion}`);
expect(logger.info).toHaveBeenCalledTimes(7);
expect(err.message).toEqual(chalk.red('Please update your installed modules.'));
}
});

it('should not show dependency update required when using semver and later major version available', async () => {
newerVersion = '2.0.0';
await verifyDeps({ dir, logger });
expect(logger.info).toHaveBeenCalledWith(chalk.green('All NPM modules are up to date.'));
expect(logger.info).toHaveBeenCalledTimes(2);
});

it('should not show dependency update required installed version matches locked version', async () => {
mockExports.dependencies[outdatedDep] = olderVersion;
mockExports.devDependencies[outdatedDevDep] = olderVersion;
newerVersion = '2.0.0';
await verifyDeps({ dir, logger });
expect(logger.info).toHaveBeenCalledWith(chalk.green('All NPM modules are up to date.'));
expect(logger.info).toHaveBeenCalledTimes(2);
});

it('should show dependency update required when version is locked if non-major-version update available', async () => {
mockExports.dependencies[outdatedDep] = olderVersion;
mockExports.devDependencies[outdatedDevDep] = olderVersion;
try {
await verifyDeps({ dir, logger });
} catch (err) {
expect(logger.info).toHaveBeenCalledWith(
`${chalk.red(outdatedDep)} is outdated: ${chalk.red(olderVersion)}${chalk.green(
newerVersion
)}`
);
expect(logger.info).toHaveBeenCalledWith(
`${chalk.red(outdatedDevDep)} is outdated: ${chalk.red(olderVersion)}${chalk.green(
newerVersion
)}`
);
expect(logger.info).toHaveBeenCalledWith(`npm i ${outdatedDep}@${newerVersion}`);
expect(logger.info).toHaveBeenCalledWith(`npm i -D ${outdatedDevDep}@${newerVersion}`);
expect(logger.info).toHaveBeenCalledTimes(7);
expect(err.message).toEqual(chalk.red('Please update your installed modules.'));
}
});

it('should not show dependency update required when version is locked if only major-version update available', async () => {
mockExports.dependencies[outdatedDep] = olderVersion;
mockExports.devDependencies[outdatedDevDep] = olderVersion;
newerVersion = '2.0.0';
await verifyDeps({ dir, logger });
expect(logger.info).toHaveBeenCalledWith(chalk.green('All NPM modules are up to date.'));
expect(logger.info).toHaveBeenCalledTimes(2);
});

it('should show dependency install required if module cannot be found', async () => {
mockJoin.mockImplementation((...args) => {
if (args[2] === outdatedDep) throw new Error('module not found');
return pathString;
});
try {
await verifyDeps({ dir, logger });
} catch (err) {
expect(logger.info).toHaveBeenCalledWith(
`${chalk.red(outdatedDep)} is ${chalk.red('not installed')}`
);
expect(logger.info).toHaveBeenCalledWith(`npm i ${outdatedDep}@${newerVersion}`);
expect(logger.info).toHaveBeenCalledWith(`npm i -D ${outdatedDevDep}@${newerVersion}`);
expect(logger.info).toHaveBeenCalledTimes(7);
expect(err.message).toEqual(chalk.red('Please update your installed modules.'));
}
});

it('should show dependency install required if fetching versions does not return valid JSON output', async () => {
const invalidOutput = chance.word();
mockExecAsync.mockImplementation(() => ({ stdout: invalidOutput }));
let syntaxError;
try {
JSON.parse(invalidOutput);
} catch (err) {
syntaxError = err;
}
try {
await verifyDeps({ dir, logger });
} catch (err) {
expect(err.message).toBe(`Failed to parse output from NPM view - ${syntaxError.toString()}`);
}
});
});

module.exports = mockExports;
Loading

0 comments on commit 5d661f8

Please sign in to comment.