refactor(CLI): modularize more, test more
This commit is contained in:
@@ -1,12 +1,60 @@
|
||||
// Tests in this file actually run the CLI and attempt to validate its behavior.
|
||||
// Git must be installed on the PATH of the testing machine.
|
||||
/**
|
||||
* Tests in this file actually run the CLI and attempt to validate its behavior.
|
||||
* Git must be installed on the PATH of the testing machine.
|
||||
*
|
||||
* We hash every file in the directories after each test, and compare the hashes
|
||||
* to the "approved" hashes in this file.
|
||||
*
|
||||
* When making a change to this project, run the tests and note which files have
|
||||
* been modified. After manually reviewing the file for accuracy, simply update
|
||||
* the relevant hash below. You may find it helpful to view the differences
|
||||
* between a certain file in each test project. E.g.:
|
||||
*
|
||||
* `diff build/test-one/package.json build/test-two/package.json`
|
||||
*/
|
||||
|
||||
// tslint:disable:no-expression-statement
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import del from 'del';
|
||||
import execa from 'execa';
|
||||
import globby from 'globby';
|
||||
import md5File from 'md5-file';
|
||||
import meow from 'meow';
|
||||
import { join } from 'path';
|
||||
import { join, relative } from 'path';
|
||||
import { cloneRepo, Placeholders, Tasks } from '../tasks';
|
||||
import { typescriptStarter } from '../typescript-starter';
|
||||
import { Runner } from '../utils';
|
||||
// import { Runner, TypescriptStarterOptions } from '../primitives';
|
||||
|
||||
/**
|
||||
* NOTE: many of the tests below validate file modification. The filesystem is
|
||||
* not mocked, and these tests make real changes. Proceed with caution.
|
||||
*
|
||||
* Filesystem changes made by these tests should be contained in the `build`
|
||||
* directory for easier clean up.
|
||||
*/
|
||||
|
||||
const repoURL = process.cwd();
|
||||
const buildDir = join(process.cwd(), 'build');
|
||||
|
||||
enum TestDirectories {
|
||||
one = 'test-one',
|
||||
two = 'test-two',
|
||||
three = 'test-three',
|
||||
four = 'test-four',
|
||||
five = 'test-five'
|
||||
}
|
||||
|
||||
// If the tests all pass, the TestDirectories will automatically be cleaned up.
|
||||
test.after(async () => {
|
||||
await del([
|
||||
`./build/${TestDirectories.one}`,
|
||||
`./build/${TestDirectories.two}`,
|
||||
`./build/${TestDirectories.three}`,
|
||||
`./build/${TestDirectories.four}`,
|
||||
`./build/${TestDirectories.five}`
|
||||
]);
|
||||
});
|
||||
|
||||
test('returns version', async t => {
|
||||
const expected = meow('').pkg.version;
|
||||
@@ -20,32 +68,6 @@ test('returns help/usage', async t => {
|
||||
t.regex(stdout, /Usage/);
|
||||
});
|
||||
|
||||
/**
|
||||
* NOTE: many of the tests below validate file modification. The filesystem is
|
||||
* not mocked, and these tests make real changes. Proceed with caution.
|
||||
*
|
||||
* TODO: mock the filesystem - https://github.com/avajs/ava/issues/665
|
||||
*
|
||||
* Until the filesystem is mocked, filesystem changes made by these tests should
|
||||
* be contained in the `build` directory for easier clean up.
|
||||
*/
|
||||
|
||||
enum testDirectories {
|
||||
one = 'test-one',
|
||||
two = 'test-two',
|
||||
three = 'test-three',
|
||||
four = 'test-four'
|
||||
}
|
||||
|
||||
test.after(async () => {
|
||||
await del([
|
||||
`./build/${testDirectories.one}`,
|
||||
`./build/${testDirectories.two}`,
|
||||
`./build/${testDirectories.three}`,
|
||||
`./build/${testDirectories.four}`
|
||||
]);
|
||||
});
|
||||
|
||||
test('errors if project name collides with an existing path', async t => {
|
||||
const existingDir = 'build';
|
||||
const error = await t.throws(
|
||||
@@ -61,45 +83,103 @@ test('errors if project name is not in kebab-case', async t => {
|
||||
t.regex(error.stderr, /should be in-kebab-case/);
|
||||
});
|
||||
|
||||
test('integration test 1: parses CLI arguments, handles options properly', async t => {
|
||||
async function hashAllTheThings(
|
||||
projectName: string
|
||||
): Promise<{ readonly [filename: string]: string }> {
|
||||
const projectDir = join(buildDir, projectName);
|
||||
const filePaths: ReadonlyArray<string> = await globby(projectDir);
|
||||
const hashAll = filePaths.map<Promise<string>>(
|
||||
path =>
|
||||
new Promise<string>((resolve, reject) => {
|
||||
md5File(path, (err: Error, result: string) => {
|
||||
err ? reject(err) : resolve(result);
|
||||
});
|
||||
})
|
||||
);
|
||||
const hashes = await Promise.all(hashAll);
|
||||
return hashes.reduce<{ readonly [filename: string]: string }>(
|
||||
(acc, hash, i) => {
|
||||
const trimmedFilePath = relative(buildDir, filePaths[i]);
|
||||
return {
|
||||
...acc,
|
||||
[trimmedFilePath]: hash
|
||||
};
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
test(`${
|
||||
TestDirectories.one
|
||||
}: parses CLI arguments, handles default options`, async t => {
|
||||
const description = 'example description 1';
|
||||
const { stdout } = await execa(
|
||||
`../bin/typescript-starter`,
|
||||
[
|
||||
`${testDirectories.one}`,
|
||||
'-description "example description 1"',
|
||||
'--noinstall'
|
||||
],
|
||||
[`${TestDirectories.one}`, `-description "${description}"`, '--noinstall'],
|
||||
{
|
||||
cwd: join(process.cwd(), 'build'),
|
||||
cwd: buildDir,
|
||||
env: {
|
||||
TYPESCRIPT_STARTER_REPO_URL: process.cwd()
|
||||
TYPESCRIPT_STARTER_REPO_URL: repoURL
|
||||
}
|
||||
}
|
||||
);
|
||||
t.regex(stdout, new RegExp(`Created ${testDirectories.one} 🎉`));
|
||||
// TODO: validate contents of testDirectories.one
|
||||
t.regex(stdout, new RegExp(`Created ${TestDirectories.one} 🎉`));
|
||||
const map = await hashAllTheThings(TestDirectories.one);
|
||||
t.deepEqual(map, {
|
||||
'test-one/LICENSE': 'd814c164ff6999405ccc7bf14dcdb50a',
|
||||
'test-one/README.md': '2ab1b6b3e434be0cef6c2b947954198e',
|
||||
'test-one/bin/typescript-starter': 'a4ad3923f37f50df986b43b1adb9f6b3',
|
||||
'test-one/package.json': 'f8eb20e261b3af91e122f85d8abc6b8d',
|
||||
'test-one/src/index.ts': '5991bedc40ac87a01d880c6db16fe349',
|
||||
'test-one/src/lib/number.spec.ts': '40ebb014eb7871d1f810c618aba1d589',
|
||||
'test-one/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
|
||||
'test-one/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
|
||||
'test-one/tsconfig.json': 'f36dc6407fc898f41a23cb620b2f4884',
|
||||
'test-one/tsconfig.module.json': 'e452fd6ff2580347077ae3fff2443e34',
|
||||
'test-one/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
|
||||
});
|
||||
});
|
||||
|
||||
test('integration test 2: parses CLI arguments, handles options properly', async t => {
|
||||
test(`${
|
||||
TestDirectories.two
|
||||
}: parses CLI arguments, handles all options`, async t => {
|
||||
const description = 'example description 2';
|
||||
const { stdout } = await execa(
|
||||
`../bin/typescript-starter`,
|
||||
[
|
||||
`${testDirectories.two}`,
|
||||
'-description "example description 2"',
|
||||
`${TestDirectories.two}`,
|
||||
`-description "${description}"`,
|
||||
'--yarn',
|
||||
'--node',
|
||||
'--dom',
|
||||
'--noinstall'
|
||||
],
|
||||
{
|
||||
cwd: join(process.cwd(), 'build'),
|
||||
cwd: buildDir,
|
||||
env: {
|
||||
TYPESCRIPT_STARTER_REPO_URL: process.cwd()
|
||||
TYPESCRIPT_STARTER_REPO_URL: repoURL
|
||||
}
|
||||
}
|
||||
);
|
||||
t.regex(stdout, new RegExp(`Created ${testDirectories.two} 🎉`));
|
||||
// TODO: validate contents of testDirectories.two
|
||||
t.regex(stdout, new RegExp(`Created ${TestDirectories.two} 🎉`));
|
||||
const map = await hashAllTheThings(TestDirectories.two);
|
||||
t.deepEqual(map, {
|
||||
'test-two/LICENSE': 'd814c164ff6999405ccc7bf14dcdb50a',
|
||||
'test-two/README.md': '90745077106bf0554dd02bc967e7e80a',
|
||||
'test-two/bin/typescript-starter': 'a4ad3923f37f50df986b43b1adb9f6b3',
|
||||
'test-two/package.json': 'e0c7654aa5edcf1ee7a998df3f0f672f',
|
||||
'test-two/src/index.ts': 'fbc67c2cbf3a7d37e4e02583bf06eec9',
|
||||
'test-two/src/lib/async.spec.ts': '1e83b84de3f3b068244885219acb42bd',
|
||||
'test-two/src/lib/async.ts': '9012c267bb25fa98ad2561929de3d4e2',
|
||||
'test-two/src/lib/hash.spec.ts': '87bfca3c0116fd86a353750fcf585ecf',
|
||||
'test-two/src/lib/hash.ts': 'a4c552897f25da5963f410e375264bd1',
|
||||
'test-two/src/lib/number.spec.ts': '40ebb014eb7871d1f810c618aba1d589',
|
||||
'test-two/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
|
||||
'test-two/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
|
||||
'test-two/tsconfig.json': '43817952d399db9e44977b3703edd7cf',
|
||||
'test-two/tsconfig.module.json': 'e452fd6ff2580347077ae3fff2443e34',
|
||||
'test-two/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
|
||||
});
|
||||
});
|
||||
|
||||
const down = '\x1B\x5B\x42';
|
||||
@@ -112,14 +192,12 @@ async function testInteractive(
|
||||
t: ExecutionContext<{}>,
|
||||
projectName: string,
|
||||
entry: ReadonlyArray<string | ReadonlyArray<string>>
|
||||
): Promise<void> {
|
||||
): Promise<execa.ExecaReturns> {
|
||||
const lastCheck = entry[3] !== undefined;
|
||||
t.plan(lastCheck ? 6 : 5);
|
||||
|
||||
const proc = execa(`../bin/typescript-starter`, {
|
||||
cwd: join(process.cwd(), 'build'),
|
||||
const proc = execa(`../bin/typescript-starter`, ['--noinstall'], {
|
||||
cwd: buildDir,
|
||||
env: {
|
||||
TYPESCRIPT_STARTER_REPO_URL: process.cwd()
|
||||
TYPESCRIPT_STARTER_REPO_URL: repoURL
|
||||
}
|
||||
});
|
||||
|
||||
@@ -169,21 +247,110 @@ async function testInteractive(
|
||||
await ms(200);
|
||||
checkBuffer(new RegExp(`${entry[3][1]}`));
|
||||
}
|
||||
return proc;
|
||||
}
|
||||
|
||||
test('integration test 3: interactive mode, javascript library', async t => {
|
||||
await testInteractive(t, `${testDirectories.three}`, [
|
||||
test(`${
|
||||
TestDirectories.three
|
||||
}: interactive mode: javascript library`, async t => {
|
||||
t.plan(7);
|
||||
const proc = await testInteractive(t, `${TestDirectories.three}`, [
|
||||
[`${down}${up}${down}`, `Javascript library`],
|
||||
`integration test 3 description${enter}`,
|
||||
[`${down}${up}${down}${enter}`, `yarn`],
|
||||
[`${down}${down}${down}${enter}`, `Both Node.js and DOM`]
|
||||
]);
|
||||
await proc;
|
||||
const map = await hashAllTheThings(TestDirectories.three);
|
||||
t.deepEqual(map, {
|
||||
'test-three/LICENSE': 'd814c164ff6999405ccc7bf14dcdb50a',
|
||||
'test-three/README.md': 'cd140f7a5ea693fd265807374efab219',
|
||||
'test-three/bin/typescript-starter': 'a4ad3923f37f50df986b43b1adb9f6b3',
|
||||
'test-three/package.json': 'b86d8c4e9827a2c72597a36ea5e1a2d6',
|
||||
'test-three/src/index.ts': '5991bedc40ac87a01d880c6db16fe349',
|
||||
'test-three/src/lib/number.spec.ts': '40ebb014eb7871d1f810c618aba1d589',
|
||||
'test-three/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
|
||||
'test-three/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
|
||||
'test-three/tsconfig.json': 'f36dc6407fc898f41a23cb620b2f4884',
|
||||
'test-three/tsconfig.module.json': 'e452fd6ff2580347077ae3fff2443e34',
|
||||
'test-three/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
|
||||
});
|
||||
});
|
||||
|
||||
test('integration test 4: interactive mode, node.js application', async t => {
|
||||
await testInteractive(t, `${testDirectories.four}`, [
|
||||
test(`${
|
||||
TestDirectories.four
|
||||
}: interactive mode: node.js application`, async t => {
|
||||
t.plan(6);
|
||||
const proc = await testInteractive(t, `${TestDirectories.four}`, [
|
||||
[`${down}${up}`, `Node.js application`],
|
||||
`integration test 4 description${enter}`,
|
||||
[`${down}${up}${enter}`, `npm`]
|
||||
]);
|
||||
await proc;
|
||||
const map = await hashAllTheThings(TestDirectories.four);
|
||||
t.deepEqual(map, {
|
||||
'test-four/LICENSE': 'd814c164ff6999405ccc7bf14dcdb50a',
|
||||
'test-four/README.md': 'c321a7d2ad331e74ce394c819181a96e',
|
||||
'test-four/bin/typescript-starter': 'a4ad3923f37f50df986b43b1adb9f6b3',
|
||||
'test-four/package.json': '01393ce262160df70dc2610cd8ff0a81',
|
||||
'test-four/src/index.ts': '5991bedc40ac87a01d880c6db16fe349',
|
||||
'test-four/src/lib/number.spec.ts': '40ebb014eb7871d1f810c618aba1d589',
|
||||
'test-four/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
|
||||
'test-four/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
|
||||
'test-four/tsconfig.json': 'f36dc6407fc898f41a23cb620b2f4884',
|
||||
'test-four/tsconfig.module.json': 'e452fd6ff2580347077ae3fff2443e34',
|
||||
'test-four/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
|
||||
});
|
||||
});
|
||||
|
||||
test(`${
|
||||
TestDirectories.five
|
||||
}: Bare API: pretend to npm install, should never commit`, async t => {
|
||||
t.plan(2);
|
||||
const tasks: Tasks = {
|
||||
cloneRepo: cloneRepo(execa, true),
|
||||
initialCommit: async () => {
|
||||
t.fail();
|
||||
},
|
||||
install: async () => {
|
||||
t.pass();
|
||||
}
|
||||
};
|
||||
const options = {
|
||||
description: 'this is an example description',
|
||||
domDefinitions: false,
|
||||
email: Placeholders.email,
|
||||
fullName: Placeholders.name,
|
||||
githubUsername: 'REDACTED',
|
||||
install: true,
|
||||
nodeDefinitions: false,
|
||||
projectName: TestDirectories.five,
|
||||
repoURL,
|
||||
runner: Runner.Npm,
|
||||
workingDirectory: buildDir
|
||||
};
|
||||
|
||||
const log = console.log;
|
||||
// tslint:disable-next-line:no-object-mutation
|
||||
console.log = () => {
|
||||
// mock console.log to silence it
|
||||
return;
|
||||
};
|
||||
await typescriptStarter(options, tasks);
|
||||
// tslint:disable-next-line:no-object-mutation
|
||||
console.log = log; // and put it back
|
||||
const map = await hashAllTheThings(TestDirectories.five);
|
||||
t.deepEqual(map, {
|
||||
'test-five/LICENSE': '1dfe8c78c6af40fc14ea3b40133f1fa5',
|
||||
'test-five/README.md': '07783e7d4d30b9d57a907854700f1e59',
|
||||
'test-five/bin/typescript-starter': 'a4ad3923f37f50df986b43b1adb9f6b3',
|
||||
'test-five/package.json': '3d7a95598a98ba956e47ccfde8590689',
|
||||
'test-five/src/index.ts': '5991bedc40ac87a01d880c6db16fe349',
|
||||
'test-five/src/lib/number.spec.ts': '40ebb014eb7871d1f810c618aba1d589',
|
||||
'test-five/src/lib/number.ts': '43756f90e6ac0b1c4ee6c81d8ab969c7',
|
||||
'test-five/src/types/example.d.ts': '4221812f6f0434eec77ccb1fba1e3759',
|
||||
'test-five/tsconfig.json': 'f36dc6407fc898f41a23cb620b2f4884',
|
||||
'test-five/tsconfig.module.json': 'e452fd6ff2580347077ae3fff2443e34',
|
||||
'test-five/tslint.json': '7ac167ffbcb724a6c270e8dc4e747067'
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,16 +4,16 @@ import { ExecaStatic } from 'execa';
|
||||
import meow from 'meow';
|
||||
import nock from 'nock';
|
||||
import { checkArgs } from '../args';
|
||||
import { getIntro, Runner } from '../primitives';
|
||||
import {
|
||||
cloneRepo,
|
||||
getGithubUsername,
|
||||
getRepoUrl,
|
||||
getUserInfo,
|
||||
initialCommit,
|
||||
install,
|
||||
Placeholders
|
||||
} from '../tasks';
|
||||
import { completeSpinner } from '../typescript-starter';
|
||||
import { getIntro, Runner } from '../utils';
|
||||
|
||||
test('errors if outdated', async t => {
|
||||
nock.disableNetConnect();
|
||||
@@ -32,25 +32,29 @@ test('errors if outdated', async t => {
|
||||
t.regex(error.message, /is outdated/);
|
||||
});
|
||||
|
||||
test(`doesn't error if not outdated`, async t => {
|
||||
const currentVersion = meow('').pkg.version;
|
||||
t.truthy(typeof currentVersion === 'string');
|
||||
const passUpdateNotifier = (version: string) => {
|
||||
nock.disableNetConnect();
|
||||
nock('https://registry.npmjs.org:443')
|
||||
.get('/typescript-starter')
|
||||
.reply(200, {
|
||||
'dist-tags': { latest: currentVersion },
|
||||
'dist-tags': { latest: version },
|
||||
name: 'typescript-starter',
|
||||
versions: {
|
||||
[currentVersion]: {
|
||||
version: currentVersion
|
||||
[version]: {
|
||||
version
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
test("doesn't error if not outdated", async t => {
|
||||
const currentVersion = meow('').pkg.version;
|
||||
t.truthy(typeof currentVersion === 'string');
|
||||
passUpdateNotifier(currentVersion);
|
||||
await t.notThrows(checkArgs);
|
||||
});
|
||||
|
||||
test(`errors if update-notifier fails`, async t => {
|
||||
test('errors if update-notifier fails', async t => {
|
||||
nock.disableNetConnect();
|
||||
nock('https://registry.npmjs.org:443')
|
||||
.get('/typescript-starter')
|
||||
@@ -59,6 +63,38 @@ test(`errors if update-notifier fails`, async t => {
|
||||
t.regex(error.message, /doesn\'t exist/);
|
||||
});
|
||||
|
||||
test('checkArgs returns the right options', async t => {
|
||||
passUpdateNotifier('1.0.0');
|
||||
// tslint:disable-next-line:no-object-mutation
|
||||
process.argv = [
|
||||
'path/to/node',
|
||||
'path/to/typescript-starter',
|
||||
'example-project',
|
||||
`-description "example description"`,
|
||||
'--yarn',
|
||||
'--node',
|
||||
'--dom',
|
||||
'--noinstall'
|
||||
];
|
||||
const opts = await checkArgs();
|
||||
t.deepEqual(opts, {
|
||||
description: '',
|
||||
domDefinitions: true,
|
||||
install: false,
|
||||
nodeDefinitions: true,
|
||||
projectName: 'example-project',
|
||||
runner: Runner.Yarn
|
||||
});
|
||||
});
|
||||
|
||||
test('checkArgs always returns { install } (so --noinstall works in interactive mode)', async t => {
|
||||
passUpdateNotifier('1.0.0');
|
||||
// tslint:disable-next-line:no-object-mutation
|
||||
process.argv = ['path/to/node', 'path/to/typescript-starter'];
|
||||
const opts = await checkArgs();
|
||||
t.deepEqual(opts, { install: true });
|
||||
});
|
||||
|
||||
test('ascii art shows if stdout has 85+ columns', async t => {
|
||||
const jumbo = getIntro(100);
|
||||
const snippet = `| __| | | | '_ \\ / _ \\/ __|/ __| '__| | '_ \\|`;
|
||||
@@ -74,23 +110,23 @@ const mockErr = (code?: string | number) =>
|
||||
}) as any) as ExecaStatic;
|
||||
|
||||
test('cloneRepo: errors when Git is not installed on PATH', async t => {
|
||||
const error = await t.throws(cloneRepo(mockErr('ENOENT'))('fail'));
|
||||
const error = await t.throws(cloneRepo(mockErr('ENOENT'))('r', 'd', 'p'));
|
||||
t.regex(error.message, /Git is not installed on your PATH/);
|
||||
});
|
||||
|
||||
test('cloneRepo: throws when clone fails', async t => {
|
||||
const error = await t.throws(cloneRepo(mockErr(128))('fail'));
|
||||
const error = await t.throws(cloneRepo(mockErr(128))('r', 'd', 'p'));
|
||||
t.regex(error.message, /Git clone failed./);
|
||||
});
|
||||
|
||||
test('cloneRepo: throws when rev-parse fails', async t => {
|
||||
// tslint:disable-next-line:prefer-const no-let
|
||||
let calls = 0;
|
||||
const mock = () => {
|
||||
const mock = ((async () => {
|
||||
calls++;
|
||||
return calls === 1 ? {} : (mockErr(128) as any)();
|
||||
};
|
||||
const error = await t.throws(cloneRepo((mock as any) as ExecaStatic)('fail'));
|
||||
}) as any) as ExecaStatic;
|
||||
const error = await t.throws(cloneRepo(mock)('r', 'd', 'p'));
|
||||
t.regex(error.message, /Git rev-parse failed./);
|
||||
});
|
||||
|
||||
@@ -102,6 +138,14 @@ test('getGithubUsername: returns found users', async t => {
|
||||
t.is(username, 'bitjson');
|
||||
});
|
||||
|
||||
test("getGithubUsername: returns placeholder if user doesn't have Git user.email set", async t => {
|
||||
const mockFetcher = async () => t.fail();
|
||||
const username: string = await getGithubUsername(mockFetcher)(
|
||||
Placeholders.email
|
||||
);
|
||||
t.is(username, Placeholders.username);
|
||||
});
|
||||
|
||||
test('getGithubUsername: returns placeholder if not found', async t => {
|
||||
const mockFetcher = async () => {
|
||||
throw new Error();
|
||||
@@ -134,60 +178,30 @@ test('getUserInfo: returns results properly', async t => {
|
||||
});
|
||||
|
||||
test('initialCommit: throws generated errors', async t => {
|
||||
const error = await t.throws(
|
||||
initialCommit(mockErr(1))('deadbeef', 'fail', 'name', 'bitjson@github.com')
|
||||
);
|
||||
const error = await t.throws(initialCommit(mockErr(1))('deadbeef', 'fail'));
|
||||
t.is(error.code, 1);
|
||||
});
|
||||
|
||||
test('initialCommit: attempts to commit', async t => {
|
||||
// tslint:disable-next-line:no-let
|
||||
test('initialCommit: spawns 3 times', async t => {
|
||||
t.plan(4);
|
||||
const mock = ((async () => {
|
||||
t.pass();
|
||||
}) as any) as ExecaStatic;
|
||||
t.true(
|
||||
await initialCommit(mock)('commit', 'dir', 'name', 'valid@example.com')
|
||||
);
|
||||
});
|
||||
|
||||
test("initialCommit: don't attempt to commit if user.name/email is not set", async t => {
|
||||
// tslint:disable-next-line:no-let
|
||||
let calls = 0;
|
||||
const errorIf3 = ((async () => {
|
||||
calls++;
|
||||
calls === 1 ? t.pass() : calls === 2 ? t.pass() : t.fail();
|
||||
}) as any) as ExecaStatic;
|
||||
t.false(
|
||||
await initialCommit(errorIf3)(
|
||||
'deadbeef',
|
||||
'fail',
|
||||
Placeholders.name,
|
||||
Placeholders.email
|
||||
)
|
||||
);
|
||||
await t.notThrows(initialCommit(mock)('commit', 'dir'));
|
||||
});
|
||||
|
||||
test('install: uses the correct runner', async t => {
|
||||
const mock = ((async (runner: Runner) => {
|
||||
runner === Runner.Yarn ? t.pass() : t.fail();
|
||||
}) as any) as ExecaStatic;
|
||||
await install(mock)(true, Runner.Yarn, 'pass');
|
||||
await install(mock)(Runner.Yarn, 'pass');
|
||||
});
|
||||
|
||||
test('install: throws pretty error on failure', async t => {
|
||||
const error = await t.throws(install(mockErr())(true, Runner.Npm, 'fail'));
|
||||
const error = await t.throws(install(mockErr())(Runner.Npm, 'fail'));
|
||||
t.is(error.message, "Installation failed. You'll need to install manually.");
|
||||
});
|
||||
|
||||
test('completeSpinner: resolves spinners properly', async t => {
|
||||
t.plan(2);
|
||||
const never = () => {
|
||||
t.fail();
|
||||
};
|
||||
const check = (confirm?: string) => (result?: string) => {
|
||||
confirm ? t.is(confirm, result) : t.pass();
|
||||
};
|
||||
completeSpinner({ succeed: check(), fail: never }, true);
|
||||
completeSpinner({ succeed: never, fail: check('message') }, false, 'message');
|
||||
test("getRepoUrl: returns GitHub repo when TYPESCRIPT_STARTER_REPO_URL isn't set", async t => {
|
||||
t.is(getRepoUrl(), 'https://github.com/bitjson/typescript-starter.git');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user