Skip to content

Commit 9e2c9a5

Browse files
feat: add tests, allow scaffolding from URLs (#38)
* tests: add tests for open command * migrate to cli-testing-tool name * feat: allow scaffolding projects from url templates
1 parent 90450c6 commit 9e2c9a5

9 files changed

+178
-9
lines changed

.eslintrc.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
module.exports = {
22
env: {
33
es6: true,
4-
node: true
4+
node: true,
5+
jest: true
56
},
67
extends: ['google', 'prettier'],
78
plugins: ['prettier'],

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,5 @@ typings/
6363

6464
# OSX specific files
6565
.DS_Store
66+
67+
test-app

lib/commands/create.js

+32-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const gitIgnoreParser = require('gitignore-parser');
55
// internal modules
66
const fs = require('fs');
77
const path = require('path');
8+
const spawn = require('child_process').spawn;
89

910
// helper functions
1011
const color = require('../colors.js');
@@ -14,7 +15,9 @@ const {
1415
onCancel,
1516
selectProject,
1617
copyFolderSync,
17-
getSettings
18+
getSettings,
19+
isURL,
20+
logger
1821
} = require('../helper.js');
1922

2023
// pm create [projectName]
@@ -68,6 +71,34 @@ async function createProject(projectName) {
6871
const newProjectDirectoryName = projectName.toLowerCase().replace(/ /g, '-');
6972
const newProjectDirectory = path.join(process.cwd(), newProjectDirectoryName);
7073

74+
if (isURL(selectedTemplate.path)) {
75+
const childProcess = spawn(
76+
'git',
77+
['clone', selectedTemplate.path, newProjectDirectory],
78+
{
79+
stdio: 'inherit'
80+
}
81+
);
82+
83+
childProcess.on('exit', (code) => {
84+
if (code === 0) {
85+
console.log('');
86+
logger.success(
87+
// eslint-disable-next-line max-len
88+
`${newProjectDirectoryName} is successfully scaffolded from ${selectedTemplate.path}`
89+
);
90+
} else {
91+
console.log('');
92+
logger.error(
93+
// eslint-disable-next-line max-len
94+
`Could not scaffold project. Git clone exitted with 1. Check error above`
95+
);
96+
}
97+
});
98+
99+
return;
100+
}
101+
71102
let gitignoreContent = '.git/\n';
72103
const gitignorePath = path.join(selectedTemplate.path, '.gitignore');
73104

lib/helper.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ const DEFAULT_SETTINGS = {
2020
};
2121

2222
// Create settings.json if does not exist or just require it if it does exist
23-
const SETTINGS_PATH = path.join(os.homedir(), '.projectman', 'settings.json');
23+
const SETTINGS_DIR =
24+
process.env.NODE_ENV === 'test'
25+
? path.join(__dirname, '..', 'tests', 'dotprojectman')
26+
: path.join(os.homedir(), '.projectman');
27+
28+
const SETTINGS_PATH = path.join(SETTINGS_DIR, 'settings.json');
2429

2530
/**
2631
* Returns settings if already exists, else creates default settings and returns it
@@ -33,9 +38,8 @@ const getSettings = () => {
3338
} catch (err) {
3439
if (err.code === 'MODULE_NOT_FOUND') {
3540
// Create if doesn't exist
36-
const settingsDir = path.join(os.homedir(), '.projectman');
37-
if (!fs.existsSync(settingsDir)) {
38-
fs.mkdirSync(settingsDir);
41+
if (!fs.existsSync(SETTINGS_DIR)) {
42+
fs.mkdirSync(SETTINGS_DIR);
3943
}
4044
fs.writeFileSync(
4145
SETTINGS_PATH,
@@ -272,6 +276,7 @@ function copyFolderSync(from, to, gitignore, ignoreEmptyDirs = true, basePath) {
272276

273277
module.exports = {
274278
settings,
279+
logger,
275280
getSettings,
276281
SETTINGS_PATH,
277282
throwCreateIssueError,

npm-shrinkwrap.json

+47-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "projectman",
3-
"version": "2.0.0",
3+
"version": "2.1.0",
44
"description": "Hate opening folders? Select and open your projects in your favourite editor straight from your command line without 'CD'ing into the deeply nested folders.",
55
"main": "bin/index.js",
66
"bin": {
@@ -38,6 +38,7 @@
3838
"prompts": "^2.3.0"
3939
},
4040
"devDependencies": {
41+
"cli-testing-tool": "^0.2.0",
4142
"eslint": "^7.0.0",
4243
"eslint-config-google": "^0.14.0",
4344
"eslint-config-prettier": "^6.11.0",

tests/helper.spec.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
const { isURL } = require('../lib/helper');
2+
const cleanUp = require('./utils/cleanup');
23

34
describe('helper', () => {
5+
afterEach(cleanUp);
46
test('#isURL()', () => {
57
// Links
68
expect(isURL('https://www.google.com')).toBe(true);

tests/open.spec.js

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const path = require('path');
2+
const { createCommandInterface, parseOutput } = require('cli-testing-tool');
3+
const cleanUp = require('./utils/cleanup');
4+
const { STRING_ESC } = require('cli-testing-tool/lib/cli-ansi-parser');
5+
6+
const NO_PROJECT_FOUND_ERROR =
7+
'[BOLD_START][RED_START]>>>[COLOR_END][BOLD_END] No projects to open :(';
8+
const CD_TO_ADD_PROJECT_MSG =
9+
// eslint-disable-next-line max-len
10+
'[BOLD_START][BLUE_START]>>>[COLOR_END][BOLD_END] cd /till/project/directory/ and run [YELLOW_START]pm add[COLOR_END] to add projects and get started';
11+
12+
async function addProject(dir) {
13+
const addCommandInterface = createCommandInterface(
14+
`node ${__dirname}/../bin/index.js add`,
15+
{
16+
cwd: dir
17+
}
18+
);
19+
await addCommandInterface.keys.enter(); // selects default name
20+
return addCommandInterface.getOutput();
21+
}
22+
23+
describe('projectman open', () => {
24+
afterEach(() => {
25+
cleanUp();
26+
});
27+
28+
test('should log no error found and pm add instructions', async () => {
29+
const commandInterface = createCommandInterface(
30+
'node ../bin/index.js open',
31+
{
32+
cwd: __dirname
33+
}
34+
);
35+
36+
const terminal = await commandInterface.getOutput();
37+
expect(terminal.tokenizedOutput).toBe(
38+
NO_PROJECT_FOUND_ERROR + '\n' + CD_TO_ADD_PROJECT_MSG
39+
);
40+
});
41+
42+
// eslint-disable-next-line max-len
43+
test('should show dropdown with tests and utils and with arrow on utils', async () => {
44+
// add tests and utils
45+
await addProject(__dirname);
46+
await addProject(path.join(__dirname, 'utils'));
47+
48+
const openCommandInterface = createCommandInterface(
49+
'node ../bin/index.js open',
50+
{
51+
cwd: __dirname
52+
}
53+
);
54+
await openCommandInterface.keys.arrowDown(); // move down to select utils
55+
const { rawOutput } = await openCommandInterface.getOutput();
56+
/**
57+
* Why slice from last clear line?
58+
*
59+
* Even though we see one output in terminal, sometimes libraries can create multiple outputs
60+
* slicing from the last clear line just makes sure we only get final output.
61+
*/
62+
const outputAfterLastLineClear = rawOutput.slice(
63+
rawOutput.lastIndexOf(`${STRING_ESC}2K`)
64+
);
65+
const parsedOutputAfterLastLineClear = parseOutput(
66+
outputAfterLastLineClear
67+
);
68+
69+
expect(parsedOutputAfterLastLineClear.stringOutput).toBe(
70+
`? Select project to open:› \ntests \nutils`
71+
);
72+
});
73+
});

tests/utils/cleanup.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
const dotProjectMan = path.join(__dirname, '..', 'dotprojectman');
5+
const cleanUp = () => {
6+
fs.rmSync(dotProjectMan, { recursive: true });
7+
};
8+
9+
module.exports = cleanUp;

0 commit comments

Comments
 (0)