Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internal improvements, add more tests #689

Merged
merged 66 commits into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
d3df4f8
chore: start separating tests
tommy-mitchell Apr 6, 2023
636eb0b
tests(`git`): add tests for `git-util.js`
tommy-mitchell Apr 7, 2023
e822ae9
further separate tests, add `npm` tests, style
tommy-mitchell Apr 8, 2023
22cac30
fix(`git`): move tests to integration, fix `defaultBranch` tests
tommy-mitchell Apr 8, 2023
796595d
tests(`util`): add more tests
tommy-mitchell Apr 8, 2023
af48c4e
feat(`pretty-version-diff`): simplify and add tests
tommy-mitchell Apr 9, 2023
dce03f5
update `Version` API, remove `pretty-version-diff.js`, consolidate ta…
tommy-mitchell Apr 11, 2023
49378f4
fix(`test/index`): incorrect message expectation
tommy-mitchell Apr 11, 2023
993a53e
fix(`version`): passing test marked as failing
tommy-mitchell Apr 11, 2023
f6b988e
chore(`version`): rename `setNewVersionFrom` to `setFrom`
tommy-mitchell Apr 11, 2023
76dd173
chore: bump `semver`
tommy-mitchell Apr 11, 2023
75a2156
fix(`version`): get correct diff
tommy-mitchell Apr 11, 2023
722370b
chore(`version`): use new `semver.RELEASE_TYPES`
tommy-mitchell Apr 11, 2023
9097485
fix(`packed-files`): don't rename `.gitignore`
tommy-mitchell Apr 11, 2023
68997f1
feat(`version`): improved prerelease diffs, more docs
tommy-mitchell Apr 12, 2023
280c66e
feat(`version`): update/document API
tommy-mitchell Apr 13, 2023
21eeab1
tests(`prerequisite-tasks`): test is passing now?
tommy-mitchell Apr 13, 2023
d234532
tests: add more `git` and `npm` tests
tommy-mitchell Apr 14, 2023
d258504
fix: comment out unused imports
tommy-mitchell Apr 14, 2023
7a39e51
docs: update old info on readme
tommy-mitchell Apr 14, 2023
12a3650
add `ui`/`cli` tests, improve `customVersion` validation, style impro…
tommy-mitchell Apr 18, 2023
ef1d2e9
update CI to Node.js 20
tommy-mitchell Apr 18, 2023
b854b81
tests(`cli`): try to fix failure on CI
tommy-mitchell Apr 19, 2023
d675d1f
Merge branch 'main' into improve-tests
tommy-mitchell Apr 19, 2023
9d810a1
feat: add changes from #932, add tests
tommy-mitchell Apr 19, 2023
f2bc525
undo Node.js 20 on CI, issues with `esmock`
tommy-mitchell Apr 19, 2023
2b502c3
Merge branch 'main' into improve-tests
tommy-mitchell Jul 2, 2023
e403bf9
update deps
tommy-mitchell Jul 2, 2023
26accf9
fix(`npm`): incorrect version handling, missing async, style fix
tommy-mitchell Jul 4, 2023
366c8c3
suppress ESM warnings on tests
tommy-mitchell Jul 4, 2023
f5331c4
tests(`npm`): finish adding tests, seperate into own files
tommy-mitchell Jul 4, 2023
bab7bb2
mark tests as failing
tommy-mitchell Jul 4, 2023
438db1e
fix(`npm.collaborators`): revert version change
tommy-mitchell Jul 4, 2023
1ed67ac
remove reminders (added to #684)
tommy-mitchell Jul 4, 2023
da36e87
refactor(`cli`): move defaults into `meow` configuration
tommy-mitchell Jul 4, 2023
7534032
remove todos, update `chalk` usage, grammar tweak
tommy-mitchell Jul 4, 2023
649f7ef
update `esmock` to fix `ui` tests
tommy-mitchell Jul 30, 2023
f3c09b2
fix lint
tommy-mitchell Jul 30, 2023
0d262ea
fix lint
tommy-mitchell Jul 30, 2023
0413d1f
move `util` tests to separate files, fix failing tests, add test for…
tommy-mitchell Jul 31, 2023
676bdfd
move `git-util` tests to separate files
tommy-mitchell Jul 31, 2023
2eefd96
use `git add .` in tests
tommy-mitchell Jul 31, 2023
80cfc9d
fix: await create file
tommy-mitchell Jul 31, 2023
86c3ffd
tests(`hyperlink`): use `esmock`
tommy-mitchell Jul 31, 2023
213d374
fix(`hyperlinks`): mark tests as serial
tommy-mitchell Jul 31, 2023
f869534
tests(`hyperlinks`): properly mock `terminal-link`
tommy-mitchell Jul 31, 2023
a1618c1
rename `test/git` to `test/git-util`
tommy-mitchell Jul 31, 2023
784b2cd
tests(`git-util`): finish testing
tommy-mitchell Jul 31, 2023
53657bf
tests(`inquirer`): log debug messages with AVA
tommy-mitchell Jul 31, 2023
44a045a
handle todos, style tweaks
tommy-mitchell Jul 31, 2023
1cdf40b
fix lint
tommy-mitchell Jul 31, 2023
57c9df4
fix: correct config name
tommy-mitchell Jul 31, 2023
8d5ede5
document test helpers
tommy-mitchell Jul 31, 2023
bdc92fb
Merge branch 'improve-tests' of https://github.com/tommy-mitchell/np …
tommy-mitchell Jul 31, 2023
b286a28
remove todos
tommy-mitchell Jul 31, 2023
4aa52f8
fix: move `chalk-template` to regular deps
tommy-mitchell Jul 31, 2023
f140762
try resolving `util.readPkg` error
tommy-mitchell Aug 2, 2023
b06fc98
better resolution of issue
tommy-mitchell Aug 2, 2023
6ba3b4b
fixes
tommy-mitchell Aug 31, 2023
cd16b94
update deps
tommy-mitchell Aug 31, 2023
c92873c
fix: lint
tommy-mitchell Aug 31, 2023
361b609
fix: missing option in test
tommy-mitchell Aug 31, 2023
e3c5e5c
fix: ignore some checks when `--release-draft-only`
tommy-mitchell Aug 31, 2023
f98b85d
fix(`prerequisite-tasks`): don't double parse version
tommy-mitchell Sep 1, 2023
c6249a2
version style tweaks
tommy-mitchell Sep 1, 2023
a682b51
semver order
tommy-mitchell Sep 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@
"xo": "^0.53.1"
},
"ava": {
"files": [
"!test/fixtures",
"!test/_helpers"
],
"environmentVariables": {
"FORCE_HYPERLINK": "1"
},
Expand Down
28 changes: 14 additions & 14 deletions source/git-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export const readFileFromLastRelease = async file => {
return oldFile;
};

const tagList = async () => {
// Returns the list of tags, sorted by creation date in ascending order.
const {stdout} = await execa('git', ['tag', '--sort=creatordate']);
return stdout.split('\n');
};

const firstCommit = async () => {
const {stdout} = await execa('git', ['rev-list', '--max-parents=0', 'HEAD']);
return stdout;
Expand Down Expand Up @@ -97,12 +103,6 @@ export const verifyCurrentBranchIsReleaseBranch = async releaseBranch => {
}
};

export const tagList = async () => {
// Returns the list of tags, sorted by creation date in ascending order.
const {stdout} = await execa('git', ['tag', '--sort=creatordate']);
return stdout.split('\n');
};

export const isHeadDetached = async () => {
try {
// Command will fail with code 1 if the HEAD is detached.
Expand All @@ -113,7 +113,7 @@ export const isHeadDetached = async () => {
}
};

export const isWorkingTreeClean = async () => {
const isWorkingTreeClean = async () => {
try {
const {stdout: status} = await execa('git', ['status', '--porcelain']);
if (status !== '') {
Expand Down Expand Up @@ -182,7 +182,7 @@ export const fetch = async () => {
await execa('git', ['fetch']);
};

export const tagExistsOnRemote = async tagName => {
const tagExistsOnRemote = async tagName => {
try {
const {stdout: revInfo} = await execa('git', ['rev-parse', '--quiet', '--verify', `refs/tags/${tagName}`]);

Expand All @@ -202,14 +202,14 @@ export const tagExistsOnRemote = async tagName => {
}
};

async function hasLocalBranch(branch) {
const hasLocalBranch = async branch => {
try {
await execa('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]);
return true;
} catch {
return false;
}
}
};

export const defaultBranch = async () => {
for (const branch of ['main', 'master', 'gh-pages']) {
Expand All @@ -233,6 +233,10 @@ export const commitLogFromRevision = async revision => {
return stdout;
};

const push = async () => {
await execa('git', ['push', '--follow-tags']);
};

export const pushGraceful = async remoteIsOnGitHub => {
try {
await push();
Expand All @@ -247,10 +251,6 @@ export const pushGraceful = async remoteIsOnGitHub => {
}
};

export const push = async () => {
await execa('git', ['push', '--follow-tags']);
};

export const deleteTag = async tagName => {
await execa('git', ['tag', '--delete', tagName]);
};
Expand Down
2 changes: 1 addition & 1 deletion source/npm/handle-npm-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const handleNpmError = (error, task, message, executor) => {
// Attempting to privately publish a scoped package without the correct npm plan
// https://stackoverflow.com/a/44862841/10292952
if (error.code === 402 || error.stderr.includes('npm ERR! 402 Payment Required')) {
throw new Error('You cannot publish a privately scoped package without a paid plan. Did you mean to publish publicly?');
throw new Error('You cannot publish a scoped package privately without a paid plan. Did you mean to publish publicly?');
}

return throwError(() => error);
Expand Down
16 changes: 10 additions & 6 deletions source/npm/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,24 @@ export const username = async ({externalRegistry}) => {
const {stdout} = await execa('npm', args);
return stdout;
} catch (error) {
throw new Error(/ENEEDAUTH/.test(error.stderr)
const message = /ENEEDAUTH/.test(error.stderr)
? 'You must be logged in. Use `npm login` and try again.'
: 'Authentication error. Use `npm whoami` to troubleshoot.');
: 'Authentication error. Use `npm whoami` to troubleshoot.';
throw new Error(message);
}
};

export const isExternalRegistry = pkg => typeof pkg.publishConfig === 'object' && typeof pkg.publishConfig.registry === 'string';

export const collaborators = async pkg => {
const packageName = pkg.name;
ow(packageName, ow.string);

const npmVersion = await version();
const args = semver.satisfies(npmVersion, '>=9.0.0') ? ['access', 'list', 'collaborators', packageName, '--json'] : ['access', 'ls-collaborators', packageName];
const args = semver.satisfies(npmVersion, '>=9.0.0')
? ['access', 'list', 'collaborators', packageName, '--json']
: ['access', 'ls-collaborators', packageName];

if (isExternalRegistry(pkg)) {
args.push('--registry', pkg.publishConfig.registry);
}
Expand Down Expand Up @@ -116,9 +122,7 @@ export const isPackageNameAvailable = async pkg => {
return availability;
};

export const isExternalRegistry = pkg => typeof pkg.publishConfig === 'object' && typeof pkg.publishConfig.registry === 'string';

export const version = async () => {
const version = async () => {
const {stdout} = await execa('npm', ['--version']);
return stdout;
};
Expand Down
24 changes: 24 additions & 0 deletions test/_helpers/integration-test.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type {Macro, ExecutionContext} from 'ava';
import type {Execa$} from 'execa';

type CommandsFnParameters = [{
t: ExecutionContext;
$$: Execa$<string>;
temporaryDir: string;
}];

type AssertionsFnParameters<MockType> = [{
t: ExecutionContext;
testedModule: MockType;
$$: Execa$<string>;
temporaryDir: string;
}];

export type CreateFixtureMacro<MockType> = Macro<[
commands: (...arguments_: CommandsFnParameters) => Promise<void>,
assertions: (...arguments_: AssertionsFnParameters<MockType>) => Promise<void>,
], {
createFile: (file: string, content?: string) => Promise<void>;
}>;

export function _createFixture<MockType>(source: string): CreateFixtureMacro<MockType>;
41 changes: 41 additions & 0 deletions test/_helpers/integration-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable ava/no-ignored-test-files */
import path from 'node:path';
import fs from 'fs-extra';
import test from 'ava';
import esmock from 'esmock';
import {$, execa} from 'execa';
import {temporaryDirectoryTask} from 'tempy';

const createEmptyGitRepo = async ($$, temporaryDir) => {
await $$`git init`;

// `git tag` needs an initial commit
await fs.createFile(path.resolve(temporaryDir, 'temp'));
await $$`git add temp`;
await $$`git commit -m "init1"`;
await $$`git rm temp`;
await $$`git commit -m "init2"`;
};

export const createIntegrationTest = async (t, assertions) => {
await temporaryDirectoryTask(async temporaryDir => {
const $$ = $({cwd: temporaryDir});

await createEmptyGitRepo($$, temporaryDir);

t.context.createFile = async (file, content = '') => fs.writeFile(path.resolve(temporaryDir, file), content);
await assertions($$, temporaryDir);
});
};

export const _createFixture = source => test.macro(async (t, commands, assertions) => {
await createIntegrationTest(t, async ($$, temporaryDir) => {
const testedModule = await esmock(source, {}, {
'node:process': {cwd: () => temporaryDir},
execa: {execa: async (...args) => execa(...args, {cwd: temporaryDir})},
});

await commands({t, $$, temporaryDir});
await assertions({t, testedModule, $$, temporaryDir});
});
});
File renamed without changes.
20 changes: 20 additions & 0 deletions test/_helpers/listr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {SilentRenderer} from './listr-renderer.js';

export const run = async listr => {
listr.setRenderer(SilentRenderer);
await listr.run();
};

export const assertTaskFailed = (t, taskTitle) => {
const task = SilentRenderer.tasks.find(task => task.title === taskTitle);
t.true(task.hasFailed(), `Task '${taskTitle}' did not fail!`);
};

export const assertTaskDisabled = (t, taskTitle) => {
const task = SilentRenderer.tasks.find(task => task.title === taskTitle);
t.true(!task.isEnabled(), `Task '${taskTitle}' was enabled!`);
};

export const assertTaskDoesntExist = (t, taskTitle) => {
t.true(SilentRenderer.tasks.every(task => task.title !== taskTitle), `Task '${taskTitle}' exists!`);
};
14 changes: 14 additions & 0 deletions test/_helpers/stub-execa.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type {Macro, ExecutionContext} from 'ava';
import type {ExecaReturnValue} from 'execa';

type AssertionsFnParameters<MockType> = [{
t: ExecutionContext;
testedModule: MockType;
}];

export type CreateFixtureMacro<MockType> = Macro<[
commands: ExecaReturnValue[],
assertions: (...arguments_: AssertionsFnParameters<MockType>) => Promise<void>,
]>;

export function _createFixture<MockType>(source: string, importMeta: string): CreateFixtureMacro<MockType>;
40 changes: 40 additions & 0 deletions test/_helpers/stub-execa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable ava/no-ignored-test-files */
import test from 'ava';
import esmock from 'esmock';
import sinon from 'sinon';
import {execa} from 'execa';

const makeExecaStub = commands => {
const stub = sinon.stub();

for (const result of commands) {
const [command, ...commandArgs] = result.command.split(' ');

// Command passes if the exit code is 0, or if there's no exit code and no stderr.
const passes = result.exitCode === 0 || (!result.exitCode && !result.stderr);

if (passes) {
stub.withArgs(command, commandArgs).resolves(result);
} else {
stub.withArgs(command, commandArgs).rejects(Object.assign(new Error(), result)); // eslint-disable-line unicorn/error-message
}
}

return stub;
};

const _stubExeca = (source, importMeta) => async commands => {
const execaStub = makeExecaStub(commands);

return esmock(source, importMeta, {}, {
execa: {
execa: async (...args) => execaStub.resolves(execa(...args))(...args),
},
});
};

export const _createFixture = (source, importMeta) => test.macro(async (t, commands, assertions) => {
const stubExeca = _stubExeca(source, importMeta);
const testedModule = await stubExeca(commands);
await assertions({t, testedModule});
});
5 changes: 5 additions & 0 deletions test/_helpers/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const runIfExists = async (func, ...args) => {
if (typeof func === 'function') {
await func(...args);
}
};
58 changes: 0 additions & 58 deletions test/_utils.js

This file was deleted.

8 changes: 3 additions & 5 deletions test/config.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import path from 'node:path';
import test from 'ava';
import sinon from 'sinon';
import esmock from 'esmock';

const testedModulePath = '../source/config.js';

const getFixture = fixture => path.resolve('test', 'fixtures', 'config', fixture);
const getFixtures = fixtures => fixtures.map(fixture => getFixture(fixture));

const getConfigsWhenGlobalBinaryIsUsed = async homedirStub => {
const getConfigsWhenGlobalBinaryIsUsed = async homedir => {
const pathsPkgDir = getFixtures(['pkg-dir', 'local1', 'local2', 'local3']);

const promises = pathsPkgDir.map(async pathPkgDir => {
const getConfig = await esmock(testedModulePath, {
'is-installed-globally': true,
'node:os': {homedir: homedirStub},
'node:os': {homedir: () => homedir},
});
return getConfig(pathPkgDir);
});
Expand All @@ -37,8 +36,7 @@ const getConfigsWhenLocalBinaryIsUsed = async pathPkgDir => {
};

const useGlobalBinary = test.macro(async (t, homedir, source) => {
const homedirStub = sinon.stub().returns(getFixture(homedir));
const configs = await getConfigsWhenGlobalBinaryIsUsed(homedirStub);
const configs = await getConfigsWhenGlobalBinaryIsUsed(getFixture(homedir));

for (const config of configs) {
t.deepEqual(config, {source});
Expand Down
Loading