Skip to content

Commit 1f3ddbe

Browse files
Finish MVP implementation of unit tests expanding feature
1 parent f8206a6 commit 1f3ddbe

File tree

4 files changed

+98
-61
lines changed

4 files changed

+98
-61
lines changed

src/bin/run.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ do
6767
elif [[ "${args[$i]}" == "--expand-unit-tests" ]]
6868
then
6969
echo "${green}${bold}Expanding unit tests...${reset}"
70-
PYTHAGORA_CONFIG="$@" node "./node_modules/${pythagora_dir}/src/scripts/unitExpand.js"
70+
PYTHAGORA_CONFIG="$@" node "${pythagora_dir}/src/scripts/unitExpand.js"
7171
exit 0
7272

7373
elif [[ "${args[$i]}" == "--export-setup" ]]

src/helpers/unitTests.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ let functionList = {},
3333
ignoreFilesEndingWith = [".test.js", ".test.ts", ".test.tsx"],
3434
processExtensions = ['.js', '.ts', '.tsx'],
3535
ignoreErrors = ['BABEL_PARSER_SYNTAX_ERROR'],
36-
force
36+
force,
37+
isFileToIgnore = (fileName) => {
38+
return ignoreFilesEndingWith.some(ending => fileName.endsWith(ending))
39+
}
3740
;
3841

3942
function resolveFilePath(filePath, extension) {
@@ -269,7 +272,7 @@ async function traverseDirectory(file, onlyCollectFunctionData, prefix = '', fun
269272
const stat = fs.statSync(absolutePath);
270273
const isLast = filesToProcess.length === 0;
271274

272-
if (ignoreFilesEndingWith.some(ending => file.endsWith(ending))) return;
275+
if (!stat.isDirectory() && isFileToIgnore(file)) return;
273276

274277
if (stat.isDirectory()) {
275278
if (ignoreFolders.includes(path.basename(absolutePath)) || path.basename(absolutePath).charAt(0) === '.') return;
@@ -288,7 +291,7 @@ async function traverseDirectory(file, onlyCollectFunctionData, prefix = '', fun
288291
return !ignoreFolders.includes(baseName) && !baseName.startsWith('.');
289292
} else {
290293
const ext = path.extname(f);
291-
return processExtensions.includes(ext) && !ignoreFilesEndingWith.some(ending => f.endsWith(ending));
294+
return processExtensions.includes(ext) && !isFileToIgnore(f);
292295
}
293296
})
294297
.map(f => path.join(absolutePath, f));
@@ -324,12 +327,15 @@ function updateFolderTree(prefix, isLast, absolutePath) {
324327
}
325328
}
326329

327-
async function getFunctionsForExport(dirPath) {
330+
async function getFunctionsForExport(dirPath, ignoreFilesRewrite) {
331+
if (ignoreFilesRewrite) {
332+
isFileToIgnore = ignoreFilesRewrite;
333+
}
328334
rootPath = dirPath;
329335
await traverseDirectory(rootPath, true);
330336
processedFiles = [];
331337
await traverseDirectory(rootPath, true);
332-
return functionList;
338+
return {functionList, folderStructureTree};
333339
}
334340

335341
async function generateTestsForDirectory(args, processingFunction = 'getUnitTests') {
@@ -363,6 +369,7 @@ async function generateTestsForDirectory(args, processingFunction = 'getUnitTest
363369
console.log(`Tests are saved in the following directories:${testsGenerated.reduce((acc, item) => acc + '\n' + blue + item, '')}`);
364370
console.log(`${bold+green}${testsGenerated.length} unit tests generated!${reset}`);
365371
}
372+
366373
process.exit(0);
367374
}
368375

src/helpers/unitTestsExpand.js

Lines changed: 81 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,38 @@ const fs = require('fs');
22
const path = require('path');
33
const _ = require('lodash');
44
const { expandUnitTests, checkForAPIKey} = require('./api');
5-
// const {PYTHAGORA_UNIT_DIR} = require("../const/common");
6-
// const generator = require("@babel/generator").default;
7-
const {delay, checkDirectoryExists} = require("../utils/common");
5+
const {PYTHAGORA_UNIT_DIR} = require("../const/common");
6+
const {checkDirectoryExists} = require("../utils/common");
87
const {
98
getAstFromFilePath,
109
getRelatedTestImports,
1110
getSourceCodeFromAst,
1211
getModuleTypeFromFilePath
1312
} = require("../utils/code");
1413
const { getFunctionsForExport } = require("./unitTests")
15-
const {getRelativePath, getFolderTreeItem, getTestFolderPath, checkPathType, isPathInside} = require("../utils/files");
14+
const {getRelativePath, getTestFolderPath, checkPathType} = require("../utils/files");
1615
const {initScreenForUnitTests} = require("./cmdGUI");
17-
const { forEach } = require('lodash');
18-
//const {green, red, blue, bold, reset} = require('../utils/cmdPrint').colors;
16+
const {green, red, blue, bold, reset} = require('../utils/cmdPrint').colors;
1917

2018
let functionList = {},
21-
leftPanel,
22-
rightPanel,
2319
screen,
2420
scrollableContent,
2521
spinner,
2622
rootPath = '',
2723
queriedPath = '',
2824
folderStructureTree = [],
2925
testsGenerated = [],
26+
skippedFiles = [],
3027
errors = [],
3128
ignoreFolders = ['node_modules', 'pythagora_tests'],
29+
filesEndingWith = [".js", ".ts", ".tsx"],
3230
processExtensions = ['.js', '.ts'],
3331
ignoreErrors = ['BABEL_PARSER_SYNTAX_ERROR'],
3432
force
3533
;
3634

37-
//the same like in uniTests.js - TODO: reuse it
3835
async function saveTests(filePath, fileName, testData) {
3936
let dir = filePath.substring(0, filePath.lastIndexOf('/'));
40-
//dir = getTestFolderPath(filePath, rootPath);
4137

4238
if (!await checkDirectoryExists(dir)) {
4339
fs.mkdirSync(dir, { recursive: true });
@@ -52,18 +48,17 @@ function reformatDataForPythagoraAPI(filePath, testCode, relatedCode, syntaxType
5248
const importedFiles = [];
5349

5450
_.forEach(relatedCode, (f) => {
55-
const fileFolderPath = f.filePath.substring(0, f.filePath.lastIndexOf('/'))
51+
const fileFolderPath = f.filePath.substring(0, f.filePath.lastIndexOf('/'));
5652
const pathRelativeToTest = getRelativePath(f.filePath, getTestFolderPath(fileFolderPath, rootPath));
5753
f.pathRelativeToTest = pathRelativeToTest;
58-
//f.fileName = f.fileName.substring(f.fileName.lastIndexOf('/') + 1);
5954

6055
if (!importedFiles.find(i => i.filePath == f.filePath)) {
6156
importedFiles.push({
6257
fileName: f.fileName.substring(f.fileName.lastIndexOf('/') + 1),
6358
filePath: f.filePath,
6459
pathRelativeToTest: f.pathRelativeToTest,
6560
syntaxType: f.syntaxType
66-
})
61+
});
6762
}
6863
})
6964

@@ -77,47 +72,62 @@ function reformatDataForPythagoraAPI(filePath, testCode, relatedCode, syntaxType
7772
importedFiles,
7873
isES6Syntax: syntaxType === 'ES6',
7974
pathRelativeToTest
80-
}
75+
};
8176
}
8277

8378
async function createAdditionalTests(filePath, prefix) {
84-
//TODO-1: get test source code and source code of related functions (import, require)
85-
const ast = await getAstFromFilePath(filePath);
86-
let syntaxType = await getModuleTypeFromFilePath(ast);
87-
88-
//const testCode = getSourceCodeFromAst(ast);
89-
90-
const importRegex = /^(.*import.*|.*require.*)$/gm;
91-
let testCode = getSourceCodeFromAst(ast);
92-
testCode = testCode.replace(importRegex, '');
93-
94-
95-
const relatedTestCode = getRelatedTestImports(ast, filePath, functionList)
96-
97-
// Now we have test source code and related test functions source code (imported or required)
98-
// TODO-1: reformat data
99-
100-
const testPath = getTestFolderPath(filePath, rootPath)
101-
const formattedData = reformatDataForPythagoraAPI(filePath, testCode, relatedTestCode, syntaxType)
102-
103-
// TODO-2: send date to API for processing
104-
// TODO-3: check only for *.test.js files pattern
105-
// TODO-4: save processing results data
79+
try {
80+
const ast = await getAstFromFilePath(filePath);
81+
let syntaxType = await getModuleTypeFromFilePath(ast);
82+
83+
const importRegex = /^(.*import.*|.*require.*)$/gm;
84+
let testCode = getSourceCodeFromAst(ast);
85+
testCode = testCode.replace(importRegex, '');
86+
87+
const relatedTestCode = getRelatedTestImports(ast, filePath, functionList);
88+
const testPath = path.join(
89+
path.resolve(PYTHAGORA_UNIT_DIR),
90+
filePath.replace(rootPath, '')
91+
);
92+
93+
const formattedData = reformatDataForPythagoraAPI(filePath, testCode, relatedTestCode, syntaxType)
94+
const fileIndex = folderStructureTree.findIndex(item => item.absolutePath === filePath);
95+
spinner.start(folderStructureTree, fileIndex);
96+
97+
if (fs.existsSync(testPath) && !force) {
98+
skippedFiles.push(testPath);
99+
await spinner.stop();
100+
folderStructureTree[fileIndex].line = `${green}${folderStructureTree[fileIndex].line}${reset}`;
101+
return;
102+
}
106103

107-
console.log('formattedData: ', formattedData)
108-
let { tests, error } = await expandUnitTests(formattedData);
109-
if (tests) {
110-
await saveTests(testPath, formattedData.testFileName, tests);
104+
let { tests, error } = await expandUnitTests(formattedData, (content) => {
105+
scrollableContent.setContent(content);
106+
scrollableContent.setScrollPerc(100);
107+
screen.render();
108+
});
109+
110+
if (tests) {
111+
await saveTests(testPath, formattedData.testFileName, tests);
112+
testsGenerated.push(testPath);
113+
await spinner.stop();
114+
folderStructureTree[fileIndex].line = `${green}${folderStructureTree[fileIndex].line}${reset}`;
115+
} else if (error) {
116+
errors.push({
117+
file: filePath,
118+
error: { stack: error.stack, message: error.message }
119+
});
120+
await spinner.stop();
121+
folderStructureTree[fileIndex].line = `${red}${folderStructureTree[fileIndex].line}${reset}`;
122+
}
123+
} catch (e) {
124+
if (!ignoreErrors.includes(e.code)) errors.push(e);
111125
}
112-
113-
// TODO-5: display processing progress in a proper way
114126
}
115127

116128
function checkForTestFilePath(filePath) {
117-
const pattern = /test\.(js|ts)$/;
129+
const pattern = /test\.(js|ts|tsx)$/;
118130
return pattern.test(filePath);
119-
// if (pattern.test(filePath)) return true
120-
// else
121131
}
122132

123133
async function traverseDirectoryTests(directory, prefix = '') {
@@ -132,32 +142,49 @@ async function traverseDirectoryTests(directory, prefix = '') {
132142
for (const file of files) {
133143
const absolutePath = path.join(directory, file);
134144
const stat = fs.statSync(absolutePath);
135-
//const isLast = files.indexOf(file) === files.length - 1;
136145
if (stat.isDirectory()) {
137146
if (ignoreFolders.includes(path.basename(absolutePath)) || path.basename(absolutePath).charAt(0) === '.') continue;
138-
139-
//const newPrefix = isLast ? `${prefix} ` : `${prefix}| `;
140147
await traverseDirectoryTests(absolutePath, prefix);
141148
} else {
142149
if (!processExtensions.includes(path.extname(absolutePath)) || !checkForTestFilePath(file)) continue;
143-
//const newPrefix = isLast ? `| ${prefix} ` : `| ${prefix}| `;
144-
//await createAdditionalTests(absolutePath, newPrefix);
145150
await createAdditionalTests(absolutePath, prefix);
146151
}
147152
}
148153
}
149154

150155
async function expandTestsForDirectory(args) {
151-
let pathToProcess = args.expand_path,
152-
force = args.force;
156+
let pathToProcess = args.expand_path;
157+
force = args.force;
153158

154159
checkForAPIKey();
155160
queriedPath = path.resolve(pathToProcess);
156161
rootPath = process.cwd();
157162
({ screen, spinner, scrollableContent } = initScreenForUnitTests());
158-
functionList = await getFunctionsForExport(rootPath)
159163

160-
await traverseDirectoryTests(queriedPath, false)
164+
const exportData = await getFunctionsForExport(pathToProcess || rootPath, (fileName) => {
165+
return !filesEndingWith.some(ending => fileName.endsWith(ending));
166+
});
167+
functionList = exportData.functionList;
168+
folderStructureTree = exportData.folderStructureTree;
169+
folderStructureTree = folderStructureTree.filter((i) => checkForTestFilePath(i.absolutePath) || i.isDirectory);
170+
171+
await traverseDirectoryTests(queriedPath, false);
172+
173+
screen.destroy();
174+
process.stdout.write('\x1B[2J\x1B[0f');
175+
if (errors.length) {
176+
let errLogPath = `${path.resolve(PYTHAGORA_UNIT_DIR, 'errorLogs.log')}`;
177+
fs.writeFileSync(errLogPath, JSON.stringify(errors, null, 2));
178+
console.error('There were errors encountered while trying to expand unit tests.\n');
179+
console.error(`You can find logs here: ${errLogPath}`);
180+
}
181+
if (skippedFiles.length) console.log(`${bold}Generation of ${skippedFiles.length} test suites were skipped because tests already exist. If you want to override them add "--force" flag to command${reset}`);
182+
if (testsGenerated.length === 0) {
183+
console.log(`${bold+red}No tests generated!${reset}`);
184+
} else {
185+
console.log(`Tests are saved in the following directories:${testsGenerated.reduce((acc, item) => acc + '\n' + blue + item, '')}`);
186+
console.log(`${bold+green}${testsGenerated.length} unit tests generated!${reset}`);
187+
}
161188

162189
process.exit(0);
163190
}

src/utils/files.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const path = require("path");
22
const {PYTHAGORA_UNIT_DIR} = require("../const/common");
33
const fs = require("fs").promises;
4+
const fsSync = require("fs");
45

56

67
async function checkPathType(path) {
@@ -18,9 +19,11 @@ function getRelativePath(filePath, referenceFolderPath) {
1819

1920

2021
function getFolderTreeItem(prefix, isLast, name, absolutePath) {
22+
const stat = fsSync.statSync(absolutePath);
2123
return {
2224
line: `${prefix}${isLast ? '└───' : '├───'}${name}`,
23-
absolutePath
25+
absolutePath,
26+
isDirectory: stat.isDirectory()
2427
};
2528
}
2629

0 commit comments

Comments
 (0)