Skip to content

Commit b867047

Browse files
WIP: expand unit tests feature
1 parent e6c6b3e commit b867047

File tree

5 files changed

+253
-2
lines changed

5 files changed

+253
-2
lines changed

src/bin/run.bash

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ do
9191
echo "${green}${bold}Generating unit tests...${reset}"
9292
PYTHAGORA_CONFIG="$@" node "./node_modules/${pythagora_dir}/src/scripts/unit.js"
9393
exit 0
94+
95+
elif [[ "${args[$i]}" == "--expand-unit-tests" ]]
96+
then
97+
echo "${green}${bold}Expanding unit tests...${reset}"
98+
PYTHAGORA_CONFIG="$@" node "./node_modules/${pythagora_dir}/src/scripts/unitExpand.js"
99+
exit 0
100+
94101
elif [[ "${args[$i]}" == "--export-setup" ]]
95102
then
96103
PYTHAGORA_CONFIG="$@" node "./node_modules/${pythagora_dir}/src/scripts/enterData.js"

src/helpers/api.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ async function getUnitTests(data, customLogFunction) {
9595
}
9696
}
9797

98+
async function expandUnitTests(data, customLogFunction) {
99+
let options = setOptions({path: '/api/expand-unit-tests'});
100+
let tests, error;
101+
try {
102+
tests = await makeRequest(JSON.stringify(data), options, customLogFunction);
103+
} catch (e) {
104+
error = e;
105+
} finally {
106+
return {tests, error};
107+
}
108+
}
109+
98110
async function getJestAuthFunction(loginMongoQueriesArray, loginRequestBody, loginEndpointPath) {
99111
jestAuthFileGenerationLog();
100112

@@ -148,5 +160,6 @@ module.exports = {
148160
getJestTestName,
149161
isEligibleForExport,
150162
getUnitTests,
163+
expandUnitTests,
151164
checkForAPIKey
152165
}

src/helpers/unitTestsExpand.js

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const _ = require('lodash');
4+
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");
8+
const {
9+
getAstFromFilePath,
10+
getRelatedTestImports,
11+
getSourceCodeFromAst,
12+
getModuleTypeFromFilePath
13+
} = require("../utils/code");
14+
const { getFunctionsForExport } = require("./unitTests")
15+
const {getRelativePath, getFolderTreeItem, getTestFolderPath, checkPathType, isPathInside} = require("../utils/files");
16+
const {initScreenForUnitTests} = require("./cmdGUI");
17+
const { forEach } = require('lodash');
18+
//const {green, red, blue, bold, reset} = require('../utils/cmdPrint').colors;
19+
20+
let functionList = {},
21+
leftPanel,
22+
rightPanel,
23+
screen,
24+
scrollableContent,
25+
spinner,
26+
rootPath = '',
27+
queriedPath = '',
28+
folderStructureTree = [],
29+
testsGenerated = [],
30+
errors = [],
31+
ignoreFolders = ['node_modules', 'pythagora_tests'],
32+
processExtensions = ['.js', '.ts'],
33+
ignoreErrors = ['BABEL_PARSER_SYNTAX_ERROR'],
34+
force
35+
;
36+
37+
//the same like in uniTests.js - TODO: reuse it
38+
async function saveTests(filePath, fileName, testData) {
39+
let dir = filePath.substring(0, filePath.lastIndexOf('/'));
40+
//dir = getTestFolderPath(filePath, rootPath);
41+
42+
if (!await checkDirectoryExists(dir)) {
43+
fs.mkdirSync(dir, { recursive: true });
44+
}
45+
46+
let testPath = path.join(dir, `/${fileName}`);
47+
fs.writeFileSync(testPath, testData);
48+
return testPath;
49+
}
50+
51+
function reformatDataForPythagoraAPI(filePath, testCode, relatedCode, syntaxType) {
52+
const importedFiles = [];
53+
54+
_.forEach(relatedCode, (f) => {
55+
const fileFolderPath = f.filePath.substring(0, f.filePath.lastIndexOf('/'))
56+
const pathRelativeToTest = getRelativePath(f.filePath, getTestFolderPath(fileFolderPath, rootPath));
57+
f.pathRelativeToTest = pathRelativeToTest;
58+
//f.fileName = f.fileName.substring(f.fileName.lastIndexOf('/') + 1);
59+
60+
if (!importedFiles.find(i => i.filePath == f.filePath)) {
61+
importedFiles.push({
62+
fileName: f.fileName.substring(f.fileName.lastIndexOf('/') + 1),
63+
filePath: f.filePath,
64+
pathRelativeToTest: f.pathRelativeToTest,
65+
syntaxType: f.syntaxType
66+
})
67+
}
68+
})
69+
70+
const testFilePath = getTestFolderPath(filePath, rootPath);
71+
const pathRelativeToTest = getRelativePath(filePath, testFilePath);
72+
73+
return {
74+
testFileName: filePath.substring(filePath.lastIndexOf('/') + 1),
75+
testCode,
76+
relatedCode,
77+
importedFiles,
78+
isES6Syntax: syntaxType === 'ES6',
79+
pathRelativeToTest
80+
}
81+
}
82+
83+
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
106+
107+
console.log('formattedData: ', formattedData)
108+
let { tests, error } = await expandUnitTests(formattedData);
109+
if (tests) {
110+
await saveTests(testPath, formattedData.testFileName, tests);
111+
}
112+
113+
// TODO-5: display processing progress in a proper way
114+
}
115+
116+
function checkForTestFilePath(filePath) {
117+
const pattern = /test\.(js|ts)$/;
118+
return pattern.test(filePath);
119+
// if (pattern.test(filePath)) return true
120+
// else
121+
}
122+
123+
async function traverseDirectoryTests(directory, prefix = '') {
124+
if (await checkPathType(directory) === 'file' && checkForTestFilePath(directory)) {
125+
const newPrefix = `| ${prefix}| `;
126+
return await createAdditionalTests(directory, newPrefix);
127+
} else if (await checkPathType(directory) === 'file' && !checkForTestFilePath(directory)) {
128+
throw new Error('Invalid test file path');
129+
}
130+
131+
const files = fs.readdirSync(directory);
132+
for (const file of files) {
133+
const absolutePath = path.join(directory, file);
134+
const stat = fs.statSync(absolutePath);
135+
//const isLast = files.indexOf(file) === files.length - 1;
136+
if (stat.isDirectory()) {
137+
if (ignoreFolders.includes(path.basename(absolutePath)) || path.basename(absolutePath).charAt(0) === '.') continue;
138+
139+
//const newPrefix = isLast ? `${prefix} ` : `${prefix}| `;
140+
await traverseDirectoryTests(absolutePath, prefix);
141+
} else {
142+
if (!processExtensions.includes(path.extname(absolutePath)) || !checkForTestFilePath(file)) continue;
143+
//const newPrefix = isLast ? `| ${prefix} ` : `| ${prefix}| `;
144+
//await createAdditionalTests(absolutePath, newPrefix);
145+
await createAdditionalTests(absolutePath, prefix);
146+
}
147+
}
148+
}
149+
150+
async function expandTestsForDirectory(args) {
151+
let pathToProcess = args.expand_path,
152+
force = args.force;
153+
154+
checkForAPIKey();
155+
queriedPath = path.resolve(pathToProcess);
156+
rootPath = process.cwd();
157+
({ screen, spinner, scrollableContent } = initScreenForUnitTests());
158+
functionList = await getFunctionsForExport(rootPath)
159+
160+
await traverseDirectoryTests(queriedPath, false)
161+
162+
process.exit(0);
163+
}
164+
165+
module.exports = {
166+
expandTestsForDirectory
167+
}

src/scripts/unitExpand.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const {expandTestsForDirectory} = require("../helpers/unitTestsExpand.js");
2+
let args = require('../utils/getArgs.js');
3+
const {setUpPythagoraDirs} = require("../helpers/starting.js");
4+
5+
if (!args.expand_path) args.expand_path = process.cwd();
6+
setUpPythagoraDirs();
7+
expandTestsForDirectory(args);

src/utils/code.js

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ async function getModuleTypeFromFilePath(ast) {
8585
return moduleType;
8686
}
8787

88-
8988
function collectTopRequires(node) {
9089
let requires = [];
9190
babelTraverse(node, {
@@ -335,6 +334,62 @@ function processAst(ast, cb) {
335334
});
336335
}
337336

337+
function getSourceCodeFromAst (ast) {
338+
return generator(ast).code;
339+
}
340+
341+
function collectTestRequires(node) {
342+
let requires = [];
343+
babelTraverse(node, {
344+
ImportDeclaration(path) {
345+
//path.node.specifiers[0].local.name
346+
if (path.node && path.node.specifiers && path.node.specifiers.length > 0) {
347+
const requireData = {
348+
code: generator(path.node).code,
349+
functionNames: []
350+
}
351+
352+
_.forEach(path.node.specifiers, (s) => {
353+
if (s.local && s.local.name) requireData.functionNames.push(s.local.name)
354+
})
355+
356+
requires.push(requireData);
357+
}
358+
}
359+
});
360+
return requires;
361+
}
362+
363+
function getRelatedTestImports(ast, filePath, functionList) {
364+
let relatedFunctions = [];
365+
let requiresFromFile = collectTestRequires(ast);
366+
367+
for (let fileImport in requiresFromFile) {
368+
let requiredPath = getPathFromRequireOrImport(requiresFromFile[fileImport].code);
369+
requiredPath = getFullPathFromRequireOrImport(requiredPath, filePath);
370+
371+
_.forEach(requiresFromFile[fileImport].functionNames, (funcName) => {
372+
let functionFromList = functionList[requiredPath + ':' + funcName];
373+
// let existingFile = relatedFunctions.find(f => f.filePath == functionFromList.filePath);
374+
375+
// if (existingFile) {
376+
// existingFile.code += `\n${functionFromList.code}`
377+
// } else if (functionFromList) {
378+
// relatedFunctions.push(_.extend(functionFromList, {
379+
// fileName: requiredPath
380+
// }));
381+
// }
382+
if (functionFromList) {
383+
relatedFunctions.push(_.extend(functionFromList, {
384+
fileName: requiredPath
385+
}));
386+
}
387+
})
388+
}
389+
390+
return relatedFunctions;
391+
}
392+
338393
module.exports = {
339394
replaceRequirePaths,
340395
getAstFromFilePath,
@@ -343,5 +398,7 @@ module.exports = {
343398
getRelatedFunctions,
344399
stripUnrelatedFunctions,
345400
processAst,
346-
getModuleTypeFromFilePath
401+
getModuleTypeFromFilePath,
402+
getSourceCodeFromAst,
403+
getRelatedTestImports
347404
}

0 commit comments

Comments
 (0)