Browse Source

add test, codecov and fix bugs

pull/5/head
Nicolas Beaussart 6 years ago
parent
commit
0ad7e167a8
No known key found for this signature in database GPG Key ID: 51D5A407BFCE64A9
  1. 3
      .circleci/config.yml
  2. 1
      .gitignore
  3. 4
      .prettierrc
  4. 6
      babel.config.js
  5. 5
      jest.config.js
  6. 12
      package.json
  7. 31
      src/commands/add/prettier.ts
  8. 19
      src/commands/add/tailwind.ts
  9. 81
      src/commands/wall/index.ts
  10. 68
      src/tests/__snapshots__/wall.spec.ts.snap
  11. 68
      src/tests/add/__snapshots__/prettier.spec.ts.snap
  12. 50
      src/tests/add/__snapshots__/tailwind.spec.ts.snap
  13. 153
      src/tests/add/prettier.spec.ts
  14. 267
      src/tests/add/tailwind.spec.ts
  15. 96
      src/tests/wall.spec.ts
  16. 85
      src/utls/__snapshots__/base-add-command.spec.ts.snap
  17. 113
      src/utls/__snapshots__/base-command.spec.ts.snap
  18. 353
      src/utls/base-add-command.spec.ts
  19. 48
      src/utls/base-add-command.ts
  20. 255
      src/utls/base-command.spec.ts
  21. 58
      src/utls/base-command.ts
  22. 2935
      yarn.lock

3
.circleci/config.yml

@ -23,6 +23,9 @@ jobs:
- run:
name: Testing
command: yarn test
- run:
name: Sending to codecov
command: yarn codecov
node-12:
<<: *test
docker:

1
.gitignore

@ -8,3 +8,4 @@
node_modules
.idea/
.nbxrc
coverage

4
.prettierrc

@ -1,7 +1,7 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 120,
"printWidth": 100,
"quoteProps": "consistent",
"trailingComma": "all"
}
}

6
babel.config.js

@ -0,0 +1,6 @@
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
};

5
jest.config.js

@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverage: true,
};

12
package.json

@ -24,13 +24,19 @@
"tslib": "1.10.0"
},
"devDependencies": {
"@babel/core": "7.8.4",
"@babel/preset-env": "7.8.4",
"@babel/preset-typescript": "7.8.3",
"@oclif/dev-cli": "1.22.2",
"@semantic-release/changelog": "5.0.0",
"@semantic-release/exec": "5.0.0",
"@semantic-release/git": "9.0.0",
"@types/jest": "25.1.2",
"@types/node": "10.17.14",
"@types/nodegit": "0.26.1",
"@types/prompts": "2.0.3",
"babel-jest": "25.1.0",
"codecov": "3.6.5",
"eslint": "5.13.0",
"eslint-config-oclif": "3.1.0",
"eslint-config-oclif-typescript": "0.1.0",
@ -38,10 +44,13 @@
"eslint-plugin-prettier": "3.1.2",
"globby": "10.0.2",
"husky": "4.2.1",
"jest": "25.1.0",
"prettier": "1.19.1",
"pretty-quick": "2.0.1",
"semantic-release": "17.0.2",
"semantic-release-gitmoji": "1.3.2",
"strip-ansi": "6.0.0",
"ts-jest": "25.2.0",
"ts-node": "8.6.2",
"typescript": "3.7.5"
},
@ -80,7 +89,8 @@
"postpack": "rm -f oclif.manifest.json",
"posttest": "eslint . --ext .ts --config .eslintrc",
"prepack": "rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme",
"test": "echo NO TESTS",
"test": "jest",
"test:watch": "jest --watch",
"update-readme": "oclif-dev readme",
"format:write": "prettier --write \"**/*.{js,vue,json,ts,tsx,md,yml,html}\"",
"format:check": "prettier --list-different \"**/*.{js,vue,json,ts,tsx,md,yml,html}\"",

31
src/commands/add/prettier.ts

@ -74,7 +74,10 @@ export default class Prettier extends BaseAddCommand {
if (shouldCommit) {
await add({ filepath: 'package.json', dir: '.' });
await commit({ dir: '.', message: ':wrench: add script and husky to package.json' });
await commit({
dir: '.',
message: ':wrench: add script and husky to package.json',
});
}
});
@ -82,16 +85,21 @@ export default class Prettier extends BaseAddCommand {
const prettierRcFile = {
semi: true,
singleQuote: true,
printWidth: 120,
printWidth: 80,
quoteProps: 'consistent',
trailingComma: 'all',
};
await filesystem.write(filesystem.path('.', '.prettierrc'), prettierRcFile, { jsonIndent: 2 });
await filesystem.write(filesystem.path('.', '.prettierrc'), prettierRcFile, {
jsonIndent: 2,
});
if (shouldCommit) {
await add({ filepath: '.prettierrc', dir: '.' });
await commit({ dir: '.', message: ':wrench: add prettierrc config file' });
await commit({
dir: '.',
message: ':wrench: add prettierrc config file',
});
}
});
@ -100,7 +108,10 @@ export default class Prettier extends BaseAddCommand {
if (shouldCommit) {
await this.gitAddUnstaged();
await commit({ dir: '.', message: ':art: apply prettier style to project' });
await commit({
dir: '.',
message: ':art: apply prettier style to project',
});
}
});
@ -153,7 +164,10 @@ export default class Prettier extends BaseAddCommand {
if (shouldCommit) {
await add({ filepath: eslintFileName, dir: '.' });
await commit({ dir: '.', message: ':wrench: update eslint to use prettier' });
await commit({
dir: '.',
message: ':wrench: update eslint to use prettier',
});
}
});
}
@ -197,7 +211,10 @@ export default class Prettier extends BaseAddCommand {
if (shouldCommit) {
await add({ filepath: 'tslint.json', dir: '.' });
await commit({ dir: '.', message: ':wrench: update tslint to use prettier' });
await commit({
dir: '.',
message: ':wrench: update tslint to use prettier',
});
}
});
}

19
src/commands/add/tailwind.ts

@ -54,7 +54,10 @@ export default class Tailwind extends BaseAddCommand {
if (shouldCommit) {
await add({ filepath: 'tailwind.config.js', dir: '.' });
await commit({ dir: '.', message: ':wrench: add tailwind config file' });
await commit({
dir: '.',
message: ':wrench: add tailwind config file',
});
}
});
await this.runWithSpinner('Generating postcss', async () => {
@ -109,8 +112,8 @@ module.exports = {
...packageJsonWithDeps.scripts,
'start': 'npm-run-all -p start:css start:js',
'build': 'npm-run-all build:css build:js',
'start:js': 'react-scripts start',
'build:js': 'react-scripts build',
'start:js': packageJsonWithDeps.scripts.start,
'build:js': packageJsonWithDeps.scripts.build,
'start:css': 'postcss src/css/tailwind.src.css -o src/tailwind.css -w',
'build:css': 'postcss src/css/tailwind.src.css -o src/tailwind.css --env production',
},
@ -120,7 +123,10 @@ module.exports = {
if (shouldCommit) {
await add({ filepath: 'package.json', dir: '.' });
await commit({ dir: '.', message: ':wrench: add script for tailwind to package.json' });
await commit({
dir: '.',
message: ':wrench: add script for tailwind to package.json',
});
}
});
@ -129,7 +135,10 @@ module.exports = {
if (shouldCommit) {
await add({ filepath: '.gitignore', dir: '.' });
await commit({ dir: '.', message: ':see_no_evil: add generated tailwind to .gitignore' });
await commit({
dir: '.',
message: ':see_no_evil: add generated tailwind to .gitignore',
});
}
});
}

81
src/commands/wall/index.ts

@ -21,7 +21,9 @@ export default class Wall extends BaseCommand {
`,
];
static args = [{ name: 'terms', description: 'The search terms for the wallpaper', required: true }];
static args = [
{ name: 'terms', description: 'The search terms for the wallpaper', required: true },
];
static flags = {
...BaseCommand.flags,
@ -38,9 +40,6 @@ export default class Wall extends BaseCommand {
const { args, argv, flags } = this.parse(Wall);
const { general, anime, people, output, force, random, sketchy } = flags;
const { terms } = args;
const {
print: { spin },
} = this.tools;
this.vprint(args, 'args');
this.vprint(argv, 'argv');
@ -50,55 +49,59 @@ export default class Wall extends BaseCommand {
this.error('You must use at least one category flag');
}
const spinFetchList = spin('Searching wallpapers');
const categories = `${booleanToNumber(general)}${booleanToNumber(anime)}${booleanToNumber(people)}`;
const {
data: { data },
} = await axios.get<{ data: WallhavenItem[] }>('https://wallhaven.cc/api/v1/search', {
params: {
q: `${terms}`,
sorting: random ? 'random' : 'relevance',
categories,
purity: `1${booleanToNumber(sketchy)}0`,
atleast: '1920x1080',
ratios: '16x9',
},
const resultFromSearch = await this.runWithSpinner('Searching wallpapers', async () => {
const categories = `${booleanToNumber(general)}${booleanToNumber(anime)}${booleanToNumber(
people,
)}`;
const {
data: { data },
} = await axios.get<{ data: WallhavenItem[] }>('https://wallhaven.cc/api/v1/search', {
params: {
q: `${terms}`,
sorting: random ? 'random' : 'relevance',
categories,
purity: `1${booleanToNumber(sketchy)}0`,
atleast: '1920x1080',
ratios: '16x9',
},
});
return data;
});
spinFetchList.succeed();
if (!data || data.length === 0) {
if (!resultFromSearch || resultFromSearch.length === 0) {
this.error('No image found, try another search term');
}
const firstImage = data[0];
const firstImage = resultFromSearch[0];
this.vprint(firstImage, 'Image raw data');
const filename = output ? output : `wallhaven-${firstImage.id}.jpg`;
const spinner = spin('Downloading wallpaper');
if (filesystem.exists(filename)) {
if (force) {
spinner.warn('Overrinding ' + filename);
} else {
spinner.fail('The file ' + filename + ' already exist');
this.error('The flag force was not set, aborting');
await this.runWithSpinner('Downloading wallpaper', async spinner => {
if (filesystem.exists(filename)) {
if (force) {
spinner.warn(`Overriding ${filename}`);
} else {
spinner.fail(`The file ${filename} already exist`);
this.error('The flag force was not set, aborting');
}
}
}
const writer = filesystem.createWriteStream(filename, {});
const writer = filesystem.createWriteStream(filename, {});
const response = await axios.get(firstImage.path, {
method: 'GET',
responseType: 'stream',
});
const response = await axios.get(firstImage.path, {
method: 'GET',
responseType: 'stream',
});
response.data.pipe(writer);
response.data.pipe(writer);
await new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
await new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
spinner.succeed(`Successfully wrote ${filename}.`);
});
spinner.succeed();
}
}

68
src/tests/__snapshots__/wall.spec.ts.snap

@ -0,0 +1,68 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`wall command should download wallpaper and console output 1`] = `
Array [
"Searching wallpapers",
"The step was done without any error.",
"Downloading wallpaper",
"Successfully wrote wallhaven-thing.jpg.",
]
`;
exports[`wall command should download wallpaper and console output to specific file 1`] = `
Array [
"Searching wallpapers",
"The step was done without any error.",
"Downloading wallpaper",
"Successfully wrote wall.jpg.",
]
`;
exports[`wall command should print a warning when output exists 1`] = `
Array [
"Searching wallpapers",
"The step was done without any error.",
"Downloading wallpaper",
"The file wall.jpg already exist",
"The flag force was not set, aborting",
]
`;
exports[`wall command should print a warning when output exists with the force flag 1`] = `
Array [
"Searching wallpapers",
"The step was done without any error.",
"Downloading wallpaper",
"Overriding wall.jpg",
"Successfully wrote wall.jpg.",
]
`;
exports[`wall command should print help correctly 1`] = `
Array [
"download a wallpaper from wallhaven using search
",
"USAGE
$ nbx wall TERMS
ARGUMENTS
TERMS The search terms for the wallpaper
OPTIONS
-a, --anime Enable anime category
-f, --force Override the file if found
-g, --general Enable general category
-h, --help show CLI help
-o, --output=output Output for the wallpaper
-p, --people Enable people category
-r, --random Pick one randomly
-s, --sketchy Enables sketchy search
-v, --verbose Verbose output
--[no-]spinner Enable spinner in cli output, true by default
EXAMPLE
$ nbx wall -r \\"cat\\" -o \\"wall.jpg\\" -fg
",
"",
]
`;

68
src/tests/add/__snapshots__/prettier.spec.ts.snap

@ -0,0 +1,68 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`prettier should print help correctly 1`] = `
Array [
"add prettier to project and format it
",
"USAGE
$ nbx add:prettier
OPTIONS
-h, --help show CLI help
-v, --verbose Verbose output
--[no-]spinner Enable spinner in cli output, true by default",
"",
]
`;
exports[`prettier should work with commits 1`] = `
Array [
"Adding prettier as a dev dependency",
"The step was done without any error.",
"Adding husky as a dev dependency",
"The step was done without any error.",
"Adding pretty-quick as a dev dependency",
"The step was done without any error.",
"Adding package.json scripts",
"The step was done without any error.",
"Adding .prettierrc config file",
"The step was done without any error.",
"Updating code to match prettier style",
"The step was done without any error.",
]
`;
exports[`prettier should work with commits 2`] = `
"<html>
<body>
<a>testokiii</a>
</body>
</html>
"
`;
exports[`prettier should work without commits 1`] = `
Array [
"Adding prettier as a dev dependency",
"The step was done without any error.",
"Adding husky as a dev dependency",
"The step was done without any error.",
"Adding pretty-quick as a dev dependency",
"The step was done without any error.",
"Adding package.json scripts",
"The step was done without any error.",
"Adding .prettierrc config file",
"The step was done without any error.",
"Updating code to match prettier style",
"The step was done without any error.",
]
`;
exports[`prettier should work without commits 2`] = `
"<html>
<body>
<a>testokiii</a>
</body>
</html>
"
`;

50
src/tests/add/__snapshots__/tailwind.spec.ts.snap

@ -0,0 +1,50 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`tailwind should print help correctly 1`] = `
Array [
"add tailwindcss to a project
",
"USAGE
$ nbx add:tailwind
OPTIONS
-h, --help show CLI help
-v, --verbose Verbose output
--[no-]spinner Enable spinner in cli output, true by default",
"",
]
`;
exports[`tailwind should work on a react application with commits 1`] = `
"const purgecss = require('@fullhuman/postcss-purgecss')({
content: ['./src/**/*.jsx', './src/**/*.js', './src/index.js', './public/index.html'],
css: ['./src/tailwind.css'],
// Include any special characters you're using in this regular expression
defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || [],
});
module.exports = {
plugins: [
require('tailwindcss')('./tailwind.config.js'),
require('autoprefixer'),
...(process.env.NODE_ENV === 'production' ? [purgecss] : []),
],
};
"
`;
exports[`tailwind should work on a react application without commits 1`] = `
"const purgecss = require('@fullhuman/postcss-purgecss')({
content: ['./src/**/*.jsx', './src/**/*.js', './src/index.js', './public/index.html'],
css: ['./src/tailwind.css'],
// Include any special characters you're using in this regular expression
defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || [],
});
module.exports = {
plugins: [
require('tailwindcss')('./tailwind.config.js'),
require('autoprefixer'),
...(process.env.NODE_ENV === 'production' ? [purgecss] : []),
],
};
"
`;

153
src/tests/add/prettier.spec.ts

@ -0,0 +1,153 @@
import { filesystem, system } from 'gluegun';
const stripANSI = require('strip-ansi');
import * as prompts from 'prompts';
import * as latestVersion from 'latest-version';
import Prettier from '../../commands/add/prettier';
jest.setTimeout(30000);
/* eslint-disable no-console,max-nested-callbacks,@typescript-eslint/ban-ts-ignore */
let consoleLogOutput: string[];
let execPath: string;
const originalCwd = process.cwd();
const originalLog = console.log;
beforeEach(() => {
consoleLogOutput = [];
console.log = (x: any) => consoleLogOutput.push(stripANSI(x));
const tmpDirName = `${new Date().getTime()}-${Math.random() * 100}-add-nbx-test`;
execPath = filesystem.path('/', 'tmp', tmpDirName);
filesystem.dir(execPath);
process.chdir(execPath);
});
afterEach(() => {
console.log = originalLog;
process.chdir(originalCwd);
jest.restoreAllMocks();
filesystem.remove(execPath);
});
const initWithConfigAndCommit = async () => {
await system.run('git init');
filesystem.write('.nbxrc', { git: { user: 'aaa', email: 'bbb' } });
filesystem.write('package.json', {});
filesystem.write('test.html', '<html><body><a>testokiii</a></body></html>');
await system.run('touch yarn.lock');
await system.run('echo node_modules > .gitignore');
await system.run('git add * .nbxrc .gitignore');
await system.run('git commit -m "init state"');
};
describe('prettier', () => {
it('should print help correctly', async () => {
try {
await Prettier.run(['-h']);
} catch {}
expect(consoleLogOutput).toMatchSnapshot();
});
it('should work without commits', async () => {
await initWithConfigAndCommit();
prompts.inject(['**/*.{js,vue,json,ts,tsx,md,yml,html}', false]);
const [latestHusky, latestPrettier, latestPrettyQuick] = await Promise.all([
latestVersion('husky'),
latestVersion('prettier'),
latestVersion('pretty-quick'),
]);
await Prettier.run(['--no-spinner']);
const packageJson = filesystem.read('package.json', 'json');
const htmlFile = filesystem.read('test.html', 'utf8');
expect(consoleLogOutput).toMatchSnapshot();
expect(packageJson).toStrictEqual({
devDependencies: {
'husky': latestHusky,
'prettier': latestPrettier,
'pretty-quick': latestPrettyQuick,
},
husky: {
hooks: {
'pre-commit': 'pretty-quick --staged',
},
},
scripts: {
'format:check': 'prettier --list-different "**/*.{js,vue,json,ts,tsx,md,yml,html}"',
'format:write': 'prettier --write "**/*.{js,vue,json,ts,tsx,md,yml,html}"',
},
});
expect(htmlFile).toMatchSnapshot();
const after = await system.run('git log --name-status --format="%s"');
const afterSlitted = after
.split('\n')
.map(val => val.trim())
.map(val => stripANSI(val))
.filter(val => val !== '');
expect(afterSlitted).toHaveLength(6);
});
it('should work with commits', async () => {
await initWithConfigAndCommit();
prompts.inject(['**/*.{js,vue,json,ts,tsx,md,yml,html}', true]);
const [latestHusky, latestPrettier, latestPrettyQuick] = await Promise.all([
latestVersion('husky'),
latestVersion('prettier'),
latestVersion('pretty-quick'),
]);
await Prettier.run(['--no-spinner']);
const packageJson = filesystem.read('package.json', 'json');
const htmlFile = filesystem.read('test.html', 'utf8');
expect(consoleLogOutput).toMatchSnapshot();
expect(packageJson).toStrictEqual({
devDependencies: {
'husky': latestHusky,
'prettier': latestPrettier,
'pretty-quick': latestPrettyQuick,
},
husky: {
hooks: {
'pre-commit': 'pretty-quick --staged',
},
},
scripts: {
'format:check': 'prettier --list-different "**/*.{js,vue,json,ts,tsx,md,yml,html}"',
'format:write': 'prettier --write "**/*.{js,vue,json,ts,tsx,md,yml,html}"',
},
});
expect(htmlFile).toMatchSnapshot();
const after = await system.run('git log --name-status --format="%s"');
const afterSlitted = after
.split('\n')
.map(val => val.trim())
.map(val => stripANSI(val))
.filter(val => val !== '');
expect(afterSlitted).toStrictEqual([
':art: apply prettier style to project',
'M\tpackage.json',
'M\ttest.html',
':wrench: add prettierrc config file',
'A\t.prettierrc',
':wrench: add script and husky to package.json',
'M\tpackage.json',
`:heavy_plus_sign: add pretty-quick@${latestPrettyQuick} as a dev dependency`,
'M\tpackage.json',
'M\tyarn.lock',
`:heavy_plus_sign: add husky@${latestHusky} as a dev dependency`,
'M\tpackage.json',
'M\tyarn.lock',
`:heavy_plus_sign: add prettier@${latestPrettier} as a dev dependency`,
'M\tpackage.json',
'M\tyarn.lock',
'init state',
'A\t.gitignore',
'A\t.nbxrc',
'A\tpackage.json',
'A\ttest.html',
'A\tyarn.lock',
]);
});
});

267
src/tests/add/tailwind.spec.ts

@ -0,0 +1,267 @@
import { filesystem, system } from 'gluegun';
const stripANSI = require('strip-ansi');
import * as prompts from 'prompts';
import * as latestVersion from 'latest-version';
import Tailwind from '../../commands/add/tailwind';
jest.setTimeout(30000);
/* eslint-disable no-console,max-nested-callbacks,@typescript-eslint/ban-ts-ignore */
let consoleLogOutput: string[];
let execPath: string;
const originalCwd = process.cwd();
const originalLog = console.log;
beforeEach(() => {
consoleLogOutput = [];
console.log = (x: any) => consoleLogOutput.push(stripANSI(x));
const tmpDirName = `${new Date().getTime()}-${Math.random() * 100}-add-nbx-test`;
execPath = filesystem.path('/', 'tmp', tmpDirName);
filesystem.dir(execPath);
process.chdir(execPath);
});
afterEach(() => {
console.log = originalLog;
process.chdir(originalCwd);
jest.restoreAllMocks();
filesystem.remove(execPath);
});
const initWithConfigAndCommit = async (packageJson = {}) => {
await system.run('git init');
filesystem.write('.nbxrc', { git: { user: 'aaa', email: 'bbb' } });
filesystem.write('package.json', packageJson);
filesystem.write('test.html', '<html><body><a>testokiii</a></body></html>');
await system.run('touch yarn.lock');
await system.run('echo node_modules > .gitignore');
await system.run('git add * .nbxrc .gitignore');
await system.run('git commit -m "init state"');
};
describe('tailwind', () => {
it('should print help correctly', async () => {
try {
await Tailwind.run(['-h']);
} catch {}
expect(consoleLogOutput).toMatchSnapshot();
});
it('should error if no react is found', async () => {
await initWithConfigAndCommit();
try {
await Tailwind.run(['--no-spinner']);
// eslint-disable-next-line unicorn/catch-error-name
} catch (e) {
expect(e).toEqual(new Error('This script support only for now create react apps.'));
}
const after = await system.run('git log --name-status --format="%s"');
const afterSlitted = after
.split('\n')
.map(val => val.trim())
.map(val => stripANSI(val))
.filter(val => val !== '');
expect(afterSlitted).toHaveLength(6);
});
it('should work on a react application without commits', async () => {
await initWithConfigAndCommit({
name: 'sample',
version: '0.1.0',
private: true,
dependencies: {
'react-scripts': '3.3.1',
},
scripts: {
start: 'react-scripts start',
build: 'react-scripts build',
test: 'react-scripts test',
eject: 'react-scripts eject',
},
eslintConfig: {
extends: 'react-app',
},
browserslist: {
production: ['>0.2%', 'not dead', 'not op_mini all'],
development: ['last 1 chrome version', 'last 1 firefox version', 'last 1 safari version'],
},
});
prompts.inject([false]);
const [tailwind, purgecss, autoprefixer, npmRunAll, postcss] = await Promise.all([
latestVersion('tailwindcss'),
latestVersion('@fullhuman/postcss-purgecss'),
latestVersion('autoprefixer'),
latestVersion('npm-run-all'),
latestVersion('postcss-cli'),
]);
await Tailwind.run(['--no-spinner']);
const status = await system.run('git status --short');
const packageJson = filesystem.read('package.json', 'json');
const after = await system.run('git log --name-status --format="%s"');
const afterSlitted = after
.split('\n')
.map(val => val.trim())
.map(val => stripANSI(val))
.filter(val => val !== '');
expect(afterSlitted).toHaveLength(6);
expect(status.split('\n')).toStrictEqual([
' M .gitignore',
' M package.json',
' M yarn.lock',
'?? postcss.config.js',
'?? src/',
'?? tailwind.config.js',
'',
]);
expect(filesystem.read('postcss.config.js', 'utf8')).toMatchSnapshot();
expect(packageJson).toStrictEqual({
name: 'sample',
version: '0.1.0',
private: true,
dependencies: {
'react-scripts': '3.3.1',
'tailwindcss': tailwind,
},
devDependencies: {
'@fullhuman/postcss-purgecss': purgecss,
'autoprefixer': autoprefixer,
'npm-run-all': npmRunAll,
'postcss-cli': postcss,
},
scripts: {
'start': 'npm-run-all -p start:css start:js',
'start:css': 'postcss src/css/tailwind.src.css -o src/tailwind.css -w',
'start:js': 'react-scripts start',
'build': 'npm-run-all build:css build:js',
'build:css': 'postcss src/css/tailwind.src.css -o src/tailwind.css --env production',
'build:js': 'react-scripts build',
'test': 'react-scripts test',
'eject': 'react-scripts eject',
},
eslintConfig: {
extends: 'react-app',
},
browserslist: {
production: ['>0.2%', 'not dead', 'not op_mini all'],
development: ['last 1 chrome version', 'last 1 firefox version', 'last 1 safari version'],
},
});
});
it('should work on a react application with commits', async () => {
await initWithConfigAndCommit({
name: 'sample',
version: '0.1.0',
private: true,
dependencies: {
'react-scripts': '3.3.1',
},
scripts: {
start: 'react-scripts start',
build: 'react-scripts build',
test: 'react-scripts test',
eject: 'react-scripts eject',
},
eslintConfig: {
extends: 'react-app',
},
browserslist: {
production: ['>0.2%', 'not dead', 'not op_mini all'],
development: ['last 1 chrome version', 'last 1 firefox version', 'last 1 safari version'],
},
});
prompts.inject([true]);
const [tailwind, purgecss, autoprefixer, npmRunAll, postcss] = await Promise.all([
latestVersion('tailwindcss'),
latestVersion('@fullhuman/postcss-purgecss'),
latestVersion('autoprefixer'),
latestVersion('npm-run-all'),
latestVersion('postcss-cli'),
]);
await Tailwind.run(['--no-spinner']);
const packageJson = filesystem.read('package.json', 'json');
const after = await system.run('git log --name-status --format="%s"');
const afterSlitted = after
.split('\n')
.map(val => val.trim())
.map(val => stripANSI(val))
.filter(val => val !== '');
expect(afterSlitted).toStrictEqual([
':see_no_evil: add generated tailwind to .gitignore',
'M\t.gitignore',
':wrench: add script for tailwind to package.json',
'M\tpackage.json',
':wrench: add tailwind css file',
'A\tsrc/css/tailwind.src.css',
':wrench: add postcss config file',
'A\tpostcss.config.js',
':wrench: add tailwind config file',
'A\ttailwind.config.js',
`:heavy_plus_sign: add tailwindcss@${tailwind} as a dependency`,
'M\tpackage.json',
'M\tyarn.lock',
`:heavy_plus_sign: add postcss-cli@${postcss} as a dev dependency`,
'M\tpackage.json',
'M\tyarn.lock',
`:heavy_plus_sign: add npm-run-all@${npmRunAll} as a dev dependency`,
'M\tpackage.json',
'M\tyarn.lock',
`:heavy_plus_sign: add autoprefixer@${autoprefixer} as a dev dependency`,
'M\tpackage.json',
'M\tyarn.lock',
`:heavy_plus_sign: add @fullhuman/postcss-purgecss@${purgecss} as a dev dependency`,
'M\tpackage.json',
'M\tyarn.lock',
'init state',
'A\t.gitignore',
'A\t.nbxrc',
'A\tpackage.json',
'A\ttest.html',
'A\tyarn.lock',
]);
expect(filesystem.read('postcss.config.js', 'utf8')).toMatchSnapshot();
expect(packageJson).toStrictEqual({
name: 'sample',
version: '0.1.0',
private: true,
dependencies: {
'react-scripts': '3.3.1',
'tailwindcss': tailwind,
},
devDependencies: {
'@fullhuman/postcss-purgecss': purgecss,
'autoprefixer': autoprefixer,
'npm-run-all': npmRunAll,
'postcss-cli': postcss,
},
scripts: {
'start': 'npm-run-all -p start:css start:js',
'start:css': 'postcss src/css/tailwind.src.css -o src/tailwind.css -w',
'start:js': 'react-scripts start',
'build': 'npm-run-all build:css build:js',
'build:css': 'postcss src/css/tailwind.src.css -o src/tailwind.css --env production',
'build:js': 'react-scripts build',
'test': 'react-scripts test',
'eject': 'react-scripts eject',
},
eslintConfig: {
extends: 'react-app',
},
browserslist: {
production: ['>0.2%', 'not dead', 'not op_mini all'],
development: ['last 1 chrome version', 'last 1 firefox version', 'last 1 safari version'],
},
});
});
});

96
src/tests/wall.spec.ts

@ -0,0 +1,96 @@
import { filesystem } from 'gluegun';
import Wall from '../commands/wall';
const stripANSI = require('strip-ansi');
/* eslint-disable no-console,max-nested-callbacks,@typescript-eslint/ban-ts-ignore */
let consoleLogOutput: string[];
let execPath: string;
const originalCwd = process.cwd();
const originalLog = console.log;
beforeEach(() => {
consoleLogOutput = [];
console.log = (x: any) => consoleLogOutput.push(stripANSI(x));
const tmpDirName = `${new Date().getTime()}-${Math.random() * 100}-add-nbx-test`;
execPath = filesystem.path('/', 'tmp', tmpDirName);
filesystem.dir(execPath);
process.chdir(execPath);
});
afterEach(() => {
console.log = originalLog;
process.chdir(originalCwd);
jest.restoreAllMocks();
filesystem.remove(execPath);
});
describe('wall command', () => {
it('should print help correctly', async () => {
try {
await Wall.run(['-h']);
} catch {}
expect(consoleLogOutput).toMatchSnapshot();
});
it('should error when no flag is used', async () => {
try {
await Wall.run(['cats']);
// eslint-disable-next-line unicorn/catch-error-name
} catch (e) {
expect(e).toEqual(new Error('You must use at least one category flag'));
}
});
it('should download wallpaper and console output', async () => {
try {
await Wall.run(['cats', '-g', '--no-spinner']);
// eslint-disable-next-line unicorn/catch-error-name
} catch {}
const lastEdited = consoleLogOutput.map((item, idx, arr) => {
if (idx === arr.length - 1) {
return item.replace(/wallhaven-.*\.jpg/g, 'wallhaven-thing.jpg');
}
return item;
});
expect(lastEdited).toMatchSnapshot();
const files = filesystem.list('.');
expect(files && files[0]).toMatch(/wallhaven-.*/);
});
it('should download wallpaper and console output to specific file', async () => {
try {
await Wall.run(['cats', '-owall.jpg', '-g', '--no-spinner']);
// eslint-disable-next-line unicorn/catch-error-name
} catch {}
expect(consoleLogOutput).toMatchSnapshot();
const files = filesystem.list('.');
expect(files && files[0]).toBe('wall.jpg');
});
it('should print a warning when output exists', async () => {
expect.assertions(4);
filesystem.write('wall.jpg', { name: 'hello' });
try {
await Wall.run(['cats', '-owall.jpg', '-g', '--no-spinner']);
// eslint-disable-next-line unicorn/catch-error-name
} catch (e) {
expect(e).toEqual(new Error('The flag force was not set, aborting'));
}
expect(consoleLogOutput).toMatchSnapshot();
const files = filesystem.list('.');
expect(files && files[0]).toBe('wall.jpg');
expect(filesystem.read('wall.jpg', 'json')).toStrictEqual({ name: 'hello' });
});
it('should print a warning when output exists with the force flag', async () => {
expect.assertions(2);
filesystem.write('wall.jpg', {});
try {
await Wall.run(['cats', '-owall.jpg', '-f', '-g', '--no-spinner']);
// eslint-disable-next-line unicorn/catch-error-name
} catch {}
expect(consoleLogOutput).toMatchSnapshot();
const files = filesystem.list('.');
expect(files && files[0]).toBe('wall.jpg');
});
});

85
src/utls/__snapshots__/base-add-command.spec.ts.snap

@ -0,0 +1,85 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BaseAddCommand InitGit() should error when git.mail is missing 1`] = `[Error: Missing config key git.email for git commits]`;
exports[`BaseAddCommand InitGit() should error when git.user is missing 1`] = `[Error: Missing config key git.user for git commits]`;
exports[`BaseAddCommand InitGit() should error when no config for git is found 1`] = `[Error: Config not found. Tried to look for a .nbxrc, .nbxrc.json, .nbxrc.yaml, .nbxrc.yml, .nbxrc.js]`;
exports[`BaseAddCommand InitGit() should error when no git in repository 1`] = `[Error: Could not set git config. Maybe the command was not run in a git repository.]`;
exports[`BaseAddCommand InitGit() should error when the config is not full 1`] = `[Error: Missing config key git.user for git commits]`;
exports[`BaseAddCommand InitGit() should error when the git repository is not clean 1`] = `[Error: There is unsaved changed in the git repository, aborting]`;
exports[`BaseAddCommand addDependency should add the dependancy with git 1`] = `
Array [
"Adding chalk as a dependency",
"The step was done without any error.",
]
`;
exports[`BaseAddCommand addDependency should add the dependancy without git 1`] = `
Array [
"Adding chalk as a dependency",
"The step was done without any error.",
]
`;
exports[`BaseAddCommand addDependency should add the dependancy without git 2`] = `
" M package.json
M yarn.lock
?? node_modules/
"
`;
exports[`BaseAddCommand addDevDependency should add the dependancy with git 1`] = `
Array [
"Adding chalk as a dev dependency",
"The step was done without any error.",
]
`;
exports[`BaseAddCommand addDevDependency should add the dependancy without git 1`] = `
Array [
"Adding chalk as a dev dependency",
"The step was done without any error.",
]
`;
exports[`BaseAddCommand addDevDependency should add the dependancy without git 2`] = `
" M package.json
M yarn.lock
?? node_modules/
"
`;
exports[`BaseAddCommand gitAddUnstaged should add uncommited changes 1`] = `
"A .nbxrc
A test.11.md
A test.12.md
A test.13.md
A test.14.md
A test.15.md
A test.21.md
A test.22.md
A test.23.md
A test.24.md
A test.25.md
A test.31.md
A test.32.md
A test.33.md
A test.34.md
A test.35.md
A test.41.md
A test.42.md
A test.43.md
A test.44.md
A test.45.md
A test.51.md
A test.52.md
A test.53.md
A test.54.md
A test.55.md
"
`;

113
src/utls/__snapshots__/base-command.spec.ts.snap

@ -0,0 +1,113 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BaseCommand commands getConfig should throw an error when no config is found 1`] = `[Error: Config not found. Tried to look for a .nbxrc, .nbxrc.json, .nbxrc.yaml, .nbxrc.yml, .nbxrc.js]`;
exports[`BaseCommand commands runWithSpinner should fail on error 1`] = `
Array [
"Should be good",
"FAILED !!!",
]
`;
exports[`BaseCommand commands runWithSpinner should not double success 1`] = `
Array [
"Should be good",
"Yay, done.",
]
`;
exports[`BaseCommand commands runWithSpinner should return the function value 1`] = `
Array [
"Should be good",
"Yay, done.",
]
`;
exports[`BaseCommand commands runWithSpinner should success automatically 1`] = `
Array [
"Should be good",
"The step was done without any error.",
]
`;
exports[`BaseCommand flags help should be set to h 1`] = `
Array [
"USAGE
$ nbx [FIRSTARG] [SECONDARG]
OPTIONS
-h, --help show CLI help
-v, --verbose Verbose output
--[no-]spinner Enable spinner in cli output, true by default",
"",
]
`;
exports[`BaseCommand flags help should be set to help 1`] = `
Array [
"USAGE
$ nbx [FIRSTARG] [SECONDARG]
OPTIONS
-h, --help show CLI help
-v, --verbose Verbose output
--[no-]spinner Enable spinner in cli output, true by default",
"",
]
`;
exports[`BaseCommand flags verbose should print args when run 1`] = `
Array [
"vvv -----[ args ]----- vvv",
Object {
"firstArg": "firstArg",
"secondArg": "secondArg",
},
"^^^ -----[ args ]----- ^^^",
"vvv -----[ argv ]----- vvv",
Array [
"firstArg",
"secondArg",
],
"^^^ -----[ argv ]----- ^^^",
"vvv -----[ flags ]----- vvv",
Object {
"spinner": true,
"verbose": true,
},
"^^^ -----[ flags ]----- ^^^",
"vvv -----[ SAMPLE ]----- vvv",
Object {
"code": "aaaa",
},
"^^^ -----[ SAMPLE ]----- ^^^",
]
`;
exports[`MyNoSpinner should display the start message 1`] = `
Array [
"hello from the spinner",
]
`;
exports[`MyNoSpinner should display the start message and fail message 1`] = `
Array [
"hello from the spinner",
"FAIL",
]
`;
exports[`MyNoSpinner should display the start message and success message 1`] = `
Array [
"hello from the spinner",
"SUCCESS",
]
`;
exports[`MyNoSpinner should display the start message and success message 2`] = `
Array [
"hello from the spinner",
"WARNING",
"SUCCESS",
]
`;

353
src/utls/base-add-command.spec.ts

@ -0,0 +1,353 @@
import { BaseAddCommand } from './base-add-command';
import { filesystem, system } from 'gluegun';
import * as latestVersion from 'latest-version';
const stripANSI = require('strip-ansi');
import { flags } from '@oclif/command';
/* eslint-disable no-console,max-nested-callbacks,@typescript-eslint/ban-ts-ignore */
let consoleLogOutput: string[];
let execPath: string;
const originalCwd = process.cwd();
const originalLog = console.log;
beforeEach(() => {
consoleLogOutput = [];
console.log = (x: any) => consoleLogOutput.push(stripANSI(x));
const tmpDirName = `${new Date().getTime()}-add-nbx-test`;
execPath = filesystem.path('/', 'tmp', tmpDirName);
filesystem.dir(execPath);
process.chdir(execPath);
});
afterEach(() => {
console.log = originalLog;
process.chdir(originalCwd);
jest.restoreAllMocks();
filesystem.remove(execPath);
});
class RunCommand extends BaseAddCommand {
static flags = {
...BaseAddCommand.flags,
opt: flags.boolean({ default: false }),
};
static args = [{ name: 'command', required: true }, { name: 'arg' }];
async run() {
const {
args: { command, arg },
flags: { opt },
} = this.parse(RunCommand);
if (opt) {
// @ts-ignore
return this[command](arg, opt);
}
// @ts-ignore
return this[command](arg, false);
}
}
const initWithConfigAndCommit = async () => {
await system.run('git init');
filesystem.write('.nbxrc', { git: { user: 'aaa', email: 'bbb' } });
filesystem.write('package.json', {});
await system.run('touch yarn.lock');
await system.run('git add * .nbxrc');
await system.run('git commit -m "init state"');
};
describe('BaseAddCommand', () => {
describe('InitGit()', () => {
it('should error when no config for git is found', async () => {
expect.assertions(1);
try {
await RunCommand.run(['initGit']);
// eslint-disable-next-line unicorn/catch-error-name
} catch (e) {
expect(e).toMatchSnapshot();
}
});
it('should error when the config is not full', async () => {
expect.assertions(1);
try {
filesystem.write('.nbxrc', { toto: 'aaa ' });
await RunCommand.run(['initGit']);
// eslint-disable-next-line unicorn/catch-error-name
} catch (e) {
expect(e).toMatchSnapshot();
}
});
it('should error when git.user is missing', async () => {
expect.assertions(1);
try {
filesystem.write('.nbxrc', { git: { email: 'aaa' } });
await RunCommand.run(['initGit']);
// eslint-disable-next-line unicorn/catch-error-name
} catch (e) {
expect(e).toMatchSnapshot();
}
});
it('should error when git.mail is missing', async () => {
expect.assertions(1);
try {
filesystem.write('.nbxrc', { git: { user: 'aaa' } });
await RunCommand.run(['initGit']);
// eslint-disable-next-line unicorn/catch-error-name
} catch (e) {
expect(e).toMatchSnapshot();
}
});
it('should error when no git in repository', async () => {
expect.assertions(1);
try {
filesystem.write('.nbxrc', { git: { user: 'aaa', email: 'bbb' } });
await RunCommand.run(['initGit']);
// eslint-disable-next-line unicorn/catch-error-name
} catch (e) {
expect(e).toMatchSnapshot();
}
});
const actions = [
{
name: 'added uncomited file',
actions: ['touch test.md'],
},
{
name: 'added file, not commited',
actions: ['touch test.md', 'git add test.md', 'git add .nbxrc'],
},
{
name: 'edited commited files',
actions: [
'touch test.md',
'git add test.md',
'git add .nbxrc',
'git commit -m "test commit"',
'echo hello > test.md',
],
},
{
name: 'deleted commited file',
actions: [
'touch test.md',
'git add test.md',
'git add .nbxrc',
'git commit -m "test commit"',
'rm -f test.md',
],
},
];
for (const action of actions) {
it(`should erro when ${action.name}`, async () => {
filesystem.write('.nbxrc', { git: { user: 'aaa', email: 'bbb' } });
await system.run('git init');
for (const step of action.actions) {
// eslint-disable-next-line no-await-in-loop
await system.run(step);
}
expect.assertions(1);
try {
await RunCommand.run(['initGit']);
// eslint-disable-next-line unicorn/catch-error-name
} catch (e) {
expect(e).toEqual(new Error('There is unsaved changed in the git repository, aborting'));
}
});
}
it('should error when the git repository is not clean', async () => {
await system.run('git init');
await system.run('touch test.md');
expect.assertions(1);
try {
filesystem.write('.nbxrc', { git: { user: 'aaa', email: 'bbb' } });
await RunCommand.run(['initGit']);
// eslint-disable-next-line unicorn/catch-error-name
} catch (e) {
expect(e).toMatchSnapshot();
}
});
it('should work on a clean git repository', async () => {
filesystem.write('.nbxrc', { git: { user: 'aaa', email: 'bbb' } });
await system.run('git init');
await system.run('git add .nbxrc');
await system.run('pwd');
await system.run('git commit -m "initial commit"');
await system.run('git status');
await RunCommand.run(['initGit']);
const gitConfig = filesystem.read('.git/config');
expect(gitConfig).toBeDefined();
const gitConfigLines = gitConfig?.split('\n')?.map(value => value.trim());
expect(gitConfigLines).toContainEqual('email = bbb');
expect(gitConfigLines).toContainEqual('name = aaa');
});
});
describe('hasDirPackageJson', () => {
it('should return false if no packageJson is in the path', async () => {
const result = await RunCommand.run(['hasDirPackageJson']);
expect(result).toBeFalsy();
});
it('should return true if no packageJson is in the path', async () => {
filesystem.write('package.json', {});
const result = await RunCommand.run(['hasDirPackageJson']);
expect(result).toBeTruthy();
});
});
describe('hasDependencyInPackageJson', () => {
it('should return false if no packageJson is in the path', async () => {
const result = await RunCommand.run(['hasDependencyInPackageJson', 'testDep']);
expect(result).toBeFalsy();
});
it('should return false if the dependencies is not in package json', async () => {
filesystem.write('package.json', { dependencies: {} });
const result = await RunCommand.run(['hasDependencyInPackageJson', 'testDep']);
expect(result).toBeFalsy();
});
it('should return false if there is no dependencies in package json', async () => {
filesystem.write('package.json', {});
const result = await RunCommand.run(['hasDependencyInPackageJson', 'testDep']);
expect(result).toBeFalsy();
});
it('should return true if no packageJson is in the path', async () => {
filesystem.write('package.json', { dependencies: { testDep: 'v1.0.0' } });
const result = await RunCommand.run(['hasDependencyInPackageJson', 'testDep']);
expect(result).toBeTruthy();
});
});
describe('hasDevDependencyInPackageJson', () => {
it('should return false if no packageJson is in the path', async () => {
const result = await RunCommand.run(['hasDevDependencyInPackageJson', 'testDep']);
expect(result).toBeFalsy();
});
it('should return false if the dependencies is not in package json', async () => {
filesystem.write('package.json', { devDependencies: {} });
const result = await RunCommand.run(['hasDevDependencyInPackageJson', 'testDep']);
expect(result).toBeFalsy();
});
it('should return false if there is no dependencies in package json', async () => {
filesystem.write('package.json', {});
const result = await RunCommand.run(['hasDevDependencyInPackageJson', 'testDep']);
expect(result).toBeFalsy();
});
it('should return true if no packageJson is in the path', async () => {
filesystem.write('package.json', { devDependencies: { testDep: 'v1.0.0' } });
const result = await RunCommand.run(['hasDevDependencyInPackageJson', 'testDep']);
expect(result).toBeTruthy();
});
});
describe('gitAddUnstaged', () => {
it('should do nothing if nothing has to be done', async () => {
await system.run('git init');
filesystem.write('.nbxrc', { git: { user: 'aaa', email: 'bbb' } });
await system.run('touch test.md');
await system.run('git add * .nbxrc');
await system.run('git commit -m "test"');
const before = await system.run('git status -s');
await RunCommand.run(['gitAddUnstaged']);
const after = await system.run('git status -s');
expect(before).toBe(after);
});
it('should add uncommited changes', async () => {
await system.run('git init');
filesystem.write('.nbxrc', { git: { user: 'aaa', email: 'bbb' } });
await system.run('touch test.{1,2,3,4,5}{1,2,3,4,5}.md');
await RunCommand.run(['gitAddUnstaged']);
const after = await system.run('git status -s');
expect(after).toMatchSnapshot();
});
});
describe('addDevDependency', () => {
it('should add the dependancy with git', async () => {
await initWithConfigAndCommit();
const chalkLatest = await latestVersion('chalk');
await RunCommand.run(['initGit']);
await RunCommand.run(['addDevDependency', 'chalk', '--opt', '--no-spinner']);
const after = await system.run('git log --name-status --format="%s" -1');
const afterSlitted = after
.split('\n')
.map(val => val.trim())
.map(val => stripANSI(val))
.filter(val => val !== '');
const packageJsonFinal = filesystem.read('package.json', 'json');
expect(consoleLogOutput).toMatchSnapshot();
expect(packageJsonFinal).toStrictEqual({ devDependencies: { chalk: chalkLatest } });
expect(afterSlitted[0]).toBe(
`:heavy_plus_sign: add chalk@${chalkLatest} as a dev dependency`,
);
expect(afterSlitted[1]).toMatch(/package\.json$/);
expect(afterSlitted[2]).toMatch(/yarn\.lock$/);
});
it('should add the dependancy without git', async () => {
await initWithConfigAndCommit();
const chalkLatest = await latestVersion('chalk');
await RunCommand.run(['initGit']);
await RunCommand.run(['addDevDependency', 'chalk', '--no-spinner']);
const after = await system.run('git status --short');
const packageJsonFinal = filesystem.read('package.json', 'json');
expect(consoleLogOutput).toMatchSnapshot();
expect(packageJsonFinal).toStrictEqual({ devDependencies: { chalk: chalkLatest } });
expect(after).toMatchSnapshot();
});
});
describe('addDependency', () => {
it('should add the dependancy with git', async () => {
await initWithConfigAndCommit();
const chalkLatest = await latestVersion('chalk');
await RunCommand.run(['initGit']);
await RunCommand.run(['addDependency', 'chalk', '--opt', '--no-spinner']);
const after = await system.run('git log --name-status --format="%s" -1');
const afterSlitted = after
.split('\n')
.map(val => val.trim())
.map(val => stripANSI(val))
.filter(val => val !== '');
const packageJsonFinal = filesystem.read('package.json', 'json');
expect(consoleLogOutput).toMatchSnapshot();
expect(packageJsonFinal).toStrictEqual({ dependencies: { chalk: chalkLatest } });
expect(afterSlitted[0]).toBe(`:heavy_plus_sign: add chalk@${chalkLatest} as a dependency`);
expect(afterSlitted[1]).toMatch(/package\.json$/);
expect(afterSlitted[2]).toMatch(/yarn\.lock$/);
});
it('should add the dependancy without git', async () => {
await initWithConfigAndCommit();
const chalkLatest = await latestVersion('chalk');
await RunCommand.run(['initGit']);
await RunCommand.run(['addDependency', 'chalk', '--no-spinner']);
const after = await system.run('git status --short');
const packageJsonFinal = filesystem.read('package.json', 'json');
expect(consoleLogOutput).toMatchSnapshot();
expect(packageJsonFinal).toStrictEqual({ dependencies: { chalk: chalkLatest } });
expect(after).toMatchSnapshot();
});
});
});

48
src/utls/base-add-command.ts

@ -16,18 +16,22 @@ export abstract class BaseAddCommand extends BaseCommand {
if (!config?.git?.email) {
this.error('Missing config key git.email for git commits');
}
await gitConfig({
dir: '.',
path: 'user.name',
value: config?.git?.user,
});
await gitConfig({
dir: '.',
path: 'user.email',
value: config?.git?.email,
});
try {
await gitConfig({
dir: '.',
path: 'user.name',
value: config?.git?.user,
});
await gitConfig({
dir: '.',
path: 'user.email',
value: config?.git?.email,
});
} catch {
this.error('Could not set git config. Maybe the command was not run in a git repository.');
}
const changes = (await statusMatrix({ dir: '.', pattern: '**' })).filter(
([_, head, workdir, stage]) => !(head === 1 && workdir === 1 && stage === 1),
([_, head, workdir]) => head !== workdir,
);
if (changes.length > 0) {
this.error('There is unsaved changed in the git repository, aborting');
@ -39,18 +43,24 @@ export abstract class BaseAddCommand extends BaseCommand {
}
hasDevDependencyInPackageJson(name: string): boolean {
if (!this.hasDirPackageJson()) {
return false;
}
const packageJson = filesystem.read('package.json', 'json');
return Boolean(packageJson.devDependencies[name]);
return Boolean(packageJson.devDependencies?.[name]);
}
hasDependencyInPackageJson(name: string): boolean {
if (!this.hasDirPackageJson()) {
return false;
}
const packageJson = filesystem.read('package.json', 'json');
return Boolean(packageJson.dependencies[name]);
return Boolean(packageJson.dependencies?.[name]);
}
async gitAddUnstaged() {
const commitsPromice = (await statusMatrix({ dir: '.', pattern: '**' }))
.filter(([_, head, workdir, stage]) => !(head === 1 && workdir === 1 && stage === 1))
.filter(([_, head, workdir]) => head !== workdir)
.map(arr => arr[0])
.map(filepath => add({ filepath, dir: '.' }));
@ -64,7 +74,10 @@ export abstract class BaseAddCommand extends BaseCommand {
if (shouldCommit) {
await add({ filepath: 'package.json', dir: '.' });
await add({ filepath: 'yarn.lock', dir: '.' });
await commit({ dir: '.', message: `:heavy_plus_sign: add ${name}@${versionToInstall}` });
await commit({
dir: '.',
message: `:heavy_plus_sign: add ${name}@${versionToInstall} as a dev dependency`,
});
}
});
}
@ -76,7 +89,10 @@ export abstract class BaseAddCommand extends BaseCommand {
if (shouldCommit) {
await add({ filepath: 'package.json', dir: '.' });
await add({ filepath: 'yarn.lock', dir: '.' });
await commit({ dir: '.', message: `:heavy_plus_sign: add ${name}@${versionToInstall}` });
await commit({
dir: '.',
message: `:heavy_plus_sign: add ${name}@${versionToInstall} as a dependency`,
});
}
});
}

255
src/utls/base-command.spec.ts

@ -0,0 +1,255 @@
import { BaseCommand, MyNoSpinner } from './base-command';
import { filesystem } from 'gluegun';
const stripANSI = require('strip-ansi');
/* eslint-disable no-console,max-nested-callbacks */
let consoleLogOutput: string[];
const originalLog = console.log;
const originalCwd = process.cwd();
beforeEach(() => {
consoleLogOutput = [];
console.log = (x: any) => consoleLogOutput.push(stripANSI(x));
});
afterEach(() => {
console.log = originalLog;
process.chdir(originalCwd);
jest.restoreAllMocks();
});
describe('MyNoSpinner', () => {
it('should start with a spinning state', () => {
const spinner = new MyNoSpinner('AA');
expect(spinner.isSpinning).toBeTruthy();
});
it('should display the start message', () => {
const message = 'hello from the spinner';
// eslint-disable-next-line no-new
new MyNoSpinner(message);
expect(consoleLogOutput).toMatchSnapshot();
});
it('should display the start message and success message', () => {
const message = 'hello from the spinner';
const spinner = new MyNoSpinner(message);
spinner.succeed('SUCCESS');
expect(spinner.isSpinning).toBeFalsy();
expect(consoleLogOutput).toMatchSnapshot();
});
it('should display the start message and fail message', () => {
const message = 'hello from the spinner';
const spinner = new MyNoSpinner(message);
spinner.fail('FAIL');
expect(spinner.isSpinning).toBeFalsy();
expect(consoleLogOutput).toMatchSnapshot();
});
it('should display the start message and success message', () => {
const message = 'hello from the spinner';
const spinner = new MyNoSpinner(message);
spinner.warn('WARNING');
expect(spinner.isSpinning).toBeTruthy();
spinner.succeed('SUCCESS');
expect(spinner.isSpinning).toBeFalsy();
expect(consoleLogOutput).toMatchSnapshot();
});
});
class SampleCommand extends BaseCommand {
static flags = {
...BaseCommand.flags,
};
static args = [{ name: 'firstArg' }, { name: 'secondArg' }];
async run() {
this.vprint({ code: 'aaaa' }, 'SAMPLE');
return 'hello';
}
}
class ConfigCommand extends BaseCommand {
static flags = {
...BaseCommand.flags,
};
async run() {
return this.getConfig();
}
}
describe('BaseCommand', () => {
describe('flags', () => {
describe('verbose', () => {
it('should print args when run', async () => {
await SampleCommand.run(['-v', 'firstArg', 'secondArg']);
expect(consoleLogOutput).toMatchSnapshot();
});
it('should not print args when run', async () => {
await SampleCommand.run(['firstArg', 'secondArg']);
expect(consoleLogOutput).toHaveLength(0);
});
});
describe('spinner', () => {
it('should be enable by default', async () => {
await SampleCommand.run(['-v']);
expect(consoleLogOutput).toContainEqual({ verbose: true, spinner: true });
});
it('can be disabled', async () => {
await SampleCommand.run(['-v', '--no-spinner']);
expect(consoleLogOutput).toContainEqual({ verbose: true, spinner: false });
});
});
describe('help', () => {
it('should be set to h', async () => {
try {
await SampleCommand.run(['-h']);
} catch {}
expect(consoleLogOutput).toMatchSnapshot();
});
it('should be set to help', async () => {
try {
await SampleCommand.run(['--help']);
} catch {}
expect(consoleLogOutput).toMatchSnapshot();
});
});
});
describe('commands', () => {
describe('getConfig', () => {
it('should throw an error when no config is found', async () => {
expect.assertions(1);
try {
process.chdir('/');
await ConfigCommand.run([]);
// eslint-disable-next-line unicorn/catch-error-name
} catch (e) {
expect(e).toMatchSnapshot();
}
});
for (const jsonConfigFileName of ['.nbxrc', '.nbxrc.json']) {
it(`should find configuration in the same folder with the name ${jsonConfigFileName}`, async () => {
expect.assertions(1);
const config = { git: { user: 'AAAA', email: 'BBBB' } };
const tmpDirName = `${new Date().getTime()}-base-nbx-test`;
const execPath = filesystem.path('/', 'tmp', tmpDirName);
filesystem.dir(execPath);
const path = filesystem.path(execPath, jsonConfigFileName);
filesystem.write(path, config);
process.chdir(execPath);
try {
const value = await ConfigCommand.run([]);
expect(value).toStrictEqual({ config, path });
} finally {
filesystem.remove(path);
filesystem.remove(execPath);
}
});
}
});
describe('runWithSpinner', () => {
it('should success automatically', async () => {
class SpinnerSuccessCommand extends BaseCommand {
static flags = {
...BaseCommand.flags,
};
async run() {
await this.runWithSpinner('Should be good', async () => {
return '';
});
}
}
await SpinnerSuccessCommand.run(['--no-spinner']);
expect(consoleLogOutput).toMatchSnapshot();
});
it('should not double success', async () => {
class SpinnerSuccessCommand extends BaseCommand {
static flags = {
...BaseCommand.flags,
};
async run() {
await this.runWithSpinner('Should be good', async spinner => {
spinner.succeed('Yay, done.');
return '';
});
}
}
await SpinnerSuccessCommand.run(['--no-spinner']);
expect(consoleLogOutput).toMatchSnapshot();
});
it('should fail on error', async () => {
class SpinnerSuccessCommand extends BaseCommand {
static flags = {
...BaseCommand.flags,
};
async run() {
try {
await this.runWithSpinner('Should be good', async () => {
throw new Error('FAILED !!!');
});
} finally {
// eslint-disable-next-line no-unsafe-finally
return 'done';
}
}
}
try {
await SpinnerSuccessCommand.run(['--no-spinner']);
} finally {
expect(consoleLogOutput).toMatchSnapshot();
}
});
it('should run default spinner without flag', async () => {
class SpinnerSuccessCommand extends BaseCommand {
static flags = {
...BaseCommand.flags,
};
async run() {
await this.runWithSpinner('Should be good', async () => {
return '';
});
}
}
try {
await SpinnerSuccessCommand.run([]);
} finally {
expect(consoleLogOutput).toHaveLength(0);
}
});
it('should return the function value', async () => {
class SpinnerSuccessCommand extends BaseCommand {
static flags = {
...BaseCommand.flags,
};
async run() {
const ret = await this.runWithSpinner('Should be good', async spinner => {
spinner.succeed('Yay, done.');
return 'DONE';
});
expect(ret).toEqual('DONE');
}
}
await SpinnerSuccessCommand.run(['--no-spinner']);
expect(consoleLogOutput).toMatchSnapshot();
});
});
});
});

58
src/utls/base-command.ts

@ -4,6 +4,7 @@ import { print } from 'gluegun';
export interface BaseCommandFlags {
verbose: boolean;
spinner: boolean;
}
export interface NbxConfig {
@ -13,9 +14,48 @@ export interface NbxConfig {
};
}
export interface SpinnerInterface {
warn: (message: string) => void;
fail: (message: string) => void;
succeed: (message?: string) => void;
isSpinning: boolean;
}
export class MyNoSpinner implements SpinnerInterface {
get isSpinning(): boolean {
return this._isSpinning;
}
private _isSpinning = false;
constructor(message: string) {
this._isSpinning = true;
print.info(message);
}
public fail(message: string): void {
this._isSpinning = false;
print.error(message);
}
public succeed(message?: string): void {
this._isSpinning = false;
print.success(message ?? 'The step was done without any error.');
}
public warn(message: string): void {
print.warning(message);
}
}
export abstract class BaseCommand extends Command {
static flags = {
verbose: flags.boolean({ char: 'v', description: 'Verbose output' }),
spinner: flags.boolean({
description: 'Enable spinner in cli output, true by default',
allowNo: true,
default: true,
}),
help: flags.help({ char: 'h' }),
};
@ -38,13 +78,21 @@ export abstract class BaseCommand extends Command {
}
vprint(value: any, title?: string) {
if (this.flags && this.flags.verbose) {
if (this.flags?.verbose) {
this.tools.print.debug(value, title);
}
}
async runWithSpinner(name: string, func: (spinner: any) => Promise<any>): Promise<any> {
const spinner = this.tools.print.spin(name);
async runWithSpinner<T>(
name: string,
func: (spinner: SpinnerInterface) => Promise<T>,
): Promise<T> {
let spinner: SpinnerInterface;
if (this.flags?.spinner) {
spinner = this.tools.print.spin(name);
} else {
spinner = new MyNoSpinner(name);
}
try {
const maybeRet = await func(spinner);
if (spinner.isSpinning) {
@ -63,7 +111,9 @@ export abstract class BaseCommand extends Command {
const explorer = cosmic('nbx');
const data = await explorer.search();
if (!data || data.isEmpty || !data.config) {
this.error('Config not found. Tried to look for a .nbxrc, .nbxrc.json, .nbxrc.yaml, .nbxrc.yml, .nbxrc.js');
this.error(
'Config not found. Tried to look for a .nbxrc, .nbxrc.json, .nbxrc.yaml, .nbxrc.yml, .nbxrc.js',
);
}
return {
config: data?.config,

2935
yarn.lock
File diff suppressed because it is too large
View File

Loading…
Cancel
Save