Skip to content

Instantly share code, notes, and snippets.

@mightyhorst
Created August 9, 2020 12:33
Show Gist options
  • Select an option

  • Save mightyhorst/ab3fa6646a0c05908fca9e0080304a2a to your computer and use it in GitHub Desktop.

Select an option

Save mightyhorst/ab3fa6646a0c05908fca9e0080304a2a to your computer and use it in GitHub Desktop.
Jest Parser
/**
* @requires process
*/
const util = require('util');
const exec = util.promisify(require('child_process').exec);
/**
* @requires models
*/
const {
TestModel,
DescribeModel,
ShouldModel,
} = require('../models');
/**
* @requires services
*/
const uuid = require('./uuid.service');
/**
* @class JestResultsParserService
* @description run jest on the server and pass results into models
*
*/
class JestResultsParserService {
async runJest(optionalPath) {
try {
const { stdout, stderr } = await exec('jest --verbose' + (optionalPath ? optionalPath : ''));
return this.readLineByLine(stderr);
} catch (err) {
console.error(err);
};
}
readLineByLine(stdout) {
/**
* @var storage - temp variables for the last model or describe block
*/
var PASSING = [];
var lastPassingModel = false;
var lastPassingDescribeModel = false;
/**
* @step read each line
*/
const lines = stdout.toString().split('\n');
for (var line of lines) {
var passingTest = line.match(/(?<=PASS ).*/g),
passingShould = line.match(/(?<=βœ“ ).*/g);
/**
* @if PASS file path name
*/
if (passingTest) {
const testModel = new TestModel(true, passingTest[0], passingTest[0]);
PASSING.push(testModel);
lastPassingModel = testModel; //--store for the next line
lastPassingDescribeModel = false; //--reset
}
/**
* @if βœ“ should expect
*/
else if (passingShould) {
const shouldModel = new ShouldModel(true, passingShould[0]);
if (lastPassingDescribeModel) lastPassingDescribeModel.addShould(shouldModel);
}
/**
* @else describe block
*/
else {
const describeModel = new DescribeModel(line, lastPassingDescribeModel ? lastPassingDescribeModel.id : null);
if (lastPassingDescribeModel) lastPassingDescribeModel.addDescribe(describeModel);
else lastPassingModel.addDescribe(describeModel);
lastPassingDescribeModel = describeModel;
}
};
return {
passing: PASSING,
failing: '@todo'
}
};
}
/**
*
* @class BlockModel
*
* @param {number} startIndex - charachter start index in the content string
* @param {number} bracketIndent - how many brackets deep are we nested
* @param {boolean} isBracketClosed - has the bracket been closed yet
*
* @summary ranges
* @param {start:number, end:number} codeRange - start char index to end char index for code brackets
* @param {start:number, end:number} keyWordRange? - start char index to end char index for key word
*
* @option
* @param stringBetweenKeywordAndOpenbracket? - e.g. keyword [....] {
* @param contentsBetweenKeywordAndOpenbracket? - e.g. [....]
*
* @option
* @param {uuid} testModelId - add reference to describe model if its test model
* @param {uuid} describeModelId - add reference to describe model if its describe model
* @param {uuid} shouldModelId - add reference to describe model if its should model
*/
class BlockModel {
constructor(startIndex, bracketIndent) {
this.uuid = uuid();
/**
* @param bracketIndent - how many brackets deep are we nested
**/
this.bracketIndent = bracketIndent;
this.isBracketClosed = false;
/**
* @param {start:number, end:number} codeRange - start char index to end char index for code brackets
* @param {start:number, end:number} keyWordRange? - start char index to end char index for key word
*/
this.codeRange = {
start: startIndex,
end: null
};
this.keyWordRange = {
start: null,
end: null
};
/**
* @param stringBetweenKeywordAndOpenbracket - e.g. keyword [....] {
* @param contentsBetweenKeywordAndOpenbracket - e.g. [....]
*/
this.stringBetweenKeywordAndOpenbracket = null;
this.contentsBetweenKeywordAndOpenbracket = null;
/**
* @option
* @param {uuid} testModelId - add reference to describe model if its test model
* @param {uuid} describeModelId - add reference to describe model if its describe model
* @param {uuid} shouldModelId - add reference to describe model if its should model
*/
this.testModelId = null;
this.describeModelId = null;
this.shouldModelId = null;
}
}
const ParserEnum = {
JEST: 'JEST',
JAVASCRIPT: 'JAVASCRIPT'
}
const KeywordEnum = {
/**
* @see javascript
*/
IMPORT: "import",
CLASS: "class",
FOR: "for",
FOREACH: "forEach",
FUNCTION: "function",
NAMESPACE: "namespace",
/**
* @see test
*/
DESCRIBE: "describe",
IT: "it",
// EXPECT: "expect",
/**
* @see other - not matched but assumed if not matched
*/
UNKNOWN: 'UNKNOWN',
METHOD: 'METHOD',
}
class JestSpecParseService {
constructor(){
this.lastKeyWord = 'class';
this.resetModels();
}
resetModels(){
this.BlockModels = [];
this.TestModel;
this.DescribeModels = [];
this.ShouldModels = [];
}
parseJestFile(testFilePath, str) {
this.resetModels();
const isPassed=null;
this.TestModel = new TestModel(isPassed, testFilePath);
return this.parse(str, ParserEnum.JEST);
}
parseJavascriptFile(str) {
return this.parse(str, ParserEnum.JAVASCRIPT);
}
parse(str, checkForKeywordFn) {
var bracketIndent = 0;
for (var i = 0; i < str.length - 1; i++) {
var char = str[i];
var keyword;
switch(checkForKeywordFn){
case ParserEnum.JEST:
keyword = this.checkForJestKeyword(i, str);
case ParserEnum.JAVASCRIPT:
keyword = this.checkForJavascriptKeyword(i, str);
}
/**
* @desc open bracket
*/
if (char === '{') {
bracketIndent++;
const blockModel = new BlockModel(i, bracketIndent);
blockModel.keyWord = this.lastKeyWord;
blockModel.keyWordRange = this.lastKeyWordRange;
/**
* @desc match between the keyword e.g. "describe" and the start bracket e.g. "{"
*/
blockModel.contentsBetweenKeywordAndOpenbracket = str.substring(blockModel.keyWordRange.end, blockModel.codeRange.start);
const quotesRegex = /(?:\")(.*?)(?:\")|(?:')(.*?)(?:')/g;
blockModel.stringBetweenKeywordAndOpenbracket = blockModel.contentsBetweenKeywordAndOpenbracket.match(quotesRegex);
if(blockModel.stringBetweenKeywordAndOpenbracket) blockModel.stringBetweenKeywordAndOpenbracket = blockModel.stringBetweenKeywordAndOpenbracket.map(m=>m.replace(/\"/g, '')).reduce((_, m)=>m.replace(/\"/g, ''));
/**
* @desc @hack: since method has no keyword, if the last keyword was something different reset the default keyword to method
* @todo change lastKeyWord to method if Javascript
*/
// if (checkForKeywordFn===ParserEnum.JAVASCRIPT && this.lastKeyWord != 'method') {UNKNOWN
if (this.lastKeyWord !== KeywordEnum.UNKNOWN) {
this.lastKeyWord = KeywordEnum.UNKNOWN;
}
this.BlockModels.push(blockModel);
/**
* @desc find the parent
* β€’ if the bracket indent is > 1 then this is nested
* β€’ the last block processed should be the parent
**/
if(blockModel.bracketIndent > 1){
const lastBlockModel = this.BlockModels[this.BlockModels.length - 2];
blockModel.parentId = lastBlockModel.uuid;
}
}
/**
* @desc end bracket
*/
else if (char === '}') {
const blockModel = this.BlockModels.find(block => {
return block.bracketIndent === bracketIndent && block.isBracketClosed === false;
});
blockModel.codeRange.end = i;
blockModel.contents = str.substring(blockModel.codeRange.start, blockModel.codeRange.end + 1);
blockModel.isBracketClosed = true;
bracketIndent--;
}
}
return this.BlockModels;
}
checkForJestKeyword(i, str) {
const keywords = [
KeywordEnum.IMPORT,
KeywordEnum.DESCRIBE,
KeywordEnum.IT,
// KeywordEnum.EXPECT,
];
const nextAcceptableChar = {
'import': [' '],
'describe': ['('],
'it': ['('],
// 'expect': ['('],
};
return this.checkForKeyword(keywords, nextAcceptableChar, i, str);
}
checkForJavascriptKeyword(i, str) {
const keywords = [
KeywordEnum.IMPORT,
KeywordEnum.CLASS,
KeywordEnum.FOR,
KeywordEnum.FOREACH,
KeywordEnum.FUNCTION,
KeywordEnum.NAMESPACE,
];
const nextAcceptableChar = {
'class': [' '],
};
return this.checkForKeyword(keywords, nextAcceptableChar, i, str);
}
checkForKeyword(keywords, nextAcceptableChar, i, str) {
// console.log('========== check for keyword ===========', str[i]);
for (var k = 0; k < keywords.length; k++) {
var keyword = keywords[k];
/**
* @desc check that there are enough chars for the keyword check
*/
if (i >= keyword.length) {
/**
* @todo make sure the next acceptable character is a openeing bracket of a space epeneding on the keyword
**/
const theLastChars = str.substring((i - keyword.length), i);
const theNextChar = str.substring(i, i+1);
const isTheNextCharValid = nextAcceptableChar[keyword] ?
nextAcceptableChar[keyword].find(m => theNextChar === m) :
true;
/**
* @desc we have a hit, lets grab the key word and mark the start and end character index so we can chop up the string later
* @todo isTheNextCharValid in the if statement
*/
if (theLastChars === keyword ) {
// if (theLastChars === keyword && isTheNextCharValid) {
// console.log('βœ… keyword:', {keyword, i, keywordLength: keyword.length, endIndex: i + keyword.length, substr: str.substring(i - keyword.length, i)})
this.lastKeyWord = keyword;
this.lastKeyWordRange = {
start: i - keyword.length,
length: keyword.length,
end: i,
check: str.substring(i - keyword.length, i)
}
return keyword;
}
}
}
/**
* @desc no matches
*/
return false;
}
convertBlocksToJestModels(){
this.BlockModels.forEach(blockModel => {
this.convertBlockToJestModel(blockModel);
});
return;
/**
* @desc NOT USED - refactored to the convertBlockToJestModel method
* @deprecated NOT USED
*/
// this.DescribeModels.map(describeModel => {
// const blockModel = this.BlockModels.find(m=> m.parentId === describeModel.blockModelId);
// const parentBlockModel = this.BlockModels.find(m=> m.uuid === describeModel.blockModelId);
// console.log('πŸ₯πŸ₯πŸ₯πŸ₯ β€’convertBlocksToJestModels β€’DescribeModels', {
// describeModel,
// blockModelId: blockModel.uuid,
// parentBlockModelId: parentBlockModel.uuid,
// blockModel: {keyWord: blockModel.keyWord, uuid: blockModel.uuid, describeModelId: blockModel.describeModelId, shouldModelId: parentBlockModel.shouldModelId, stringBetweenKeywordAndOpenbracket: blockModel.stringBetweenKeywordAndOpenbracket},
// parentBlockModel: {keyWord: parentBlockModel.keyWord, uuid: parentBlockModel.uuid, describeModelId: parentBlockModel.describeModelId, stringBetweenKeywordAndOpenbracket: parentBlockModel.stringBetweenKeywordAndOpenbracket}
// });
// return describeModel;
// /**
// * @todo xxxx
// */
// /**
// * @desc Parent Block
// */
// if(parentBlockModel){
// switch(parentBlockModel.keyWord){
// /**
// * @desc Parent Block is a descibe model
// */
// case KeywordEnum.DESCRIBE:
// describeModel.parentId = parentBlockModel.describeModelId;
// const parentDescribeModel = this.DescribeModels.find(m=>m.id === parentBlockModel.describeModelId);
// if(parentDescribeModel){
// parentDescribeModel.addDescribe(describeModel);
// }
// else console.error('this should not fire', {parentBlockModel, parentDescribeModel});
// break;
// /**
// * @desc if the Parent Block is not a descibe model, it MUST be a test model
// */
// default:
// if(parentBlockModel.testModelId)
// describeModel.parentId = parentBlockModel.testModelId;
// describeModel.testModelId = this.TestModel.id;
// this.TestModel.addDescribe(describeModel);
// break;
// }
// }
// /**
// * @desc No Describe Parent - so use the TestModel
// */
// else{
// describeModel.testModelId = this.TestModel.id;
// this.TestModel.addDescribe(describeModel);
// }
// console.log('β€’ DescribeModels', {
// blockModelId: blockModel.uuid,
// parentBlockModel: parentBlockModel.uuid,
// 'describeModel.parentId': describeModel.parentId
// });
// })
/**
* @desc
*/
}
convertBlockToJestModel(blockModel){
console.log('\n\n\n🍎🍎🍎🍎🍎🍎');
/**
* @description find the parent ID
*/
let parentBlockModel, parentDescribeModel;
if(blockModel.parentId){
parentBlockModel = this.BlockModels.find(m => m.uuid === blockModel.parentId);
if(parentBlockModel) {
parentDescribeModel = this.DescribeModels.find(d => d.id === parentBlockModel.describeModelId);
}
console.log('🍎 β€’ convertBlockToJestModel', {'blockModel.parentId':blockModel.parentId, 'blockModel.uuid': blockModel.uuid})
console.log('🍎 β€’ convertBlockToJestModel 🍎parentModel', {parentBlockModel: {uuid: parentBlockModel.uuid, 'parentBlockModel.describeModelId': parentBlockModel.describeModelId, parentDescribeModel}})
}
console.log('🍎 β€’ convertBlockToJestModel', {[blockModel.keyWord]: blockModel.stringBetweenKeywordAndOpenbracket})
console.log('\n\n\n🍎🍎🍎🍎🍎🍎');
switch(blockModel.keyWord){
case KeywordEnum.DESCRIBE:
console.log('\n\n\nπŸ₯πŸ₯πŸ₯πŸ₯πŸ₯πŸ₯πŸ₯');
/**
* @description create a describe block
*/
const describeModel = new DescribeModel(blockModel.stringBetweenKeywordAndOpenbracket, parentDescribeModel ? parentDescribeModel.id : null);
this.DescribeModels.push(describeModel);
/**
* @description link the block model and describe model
*/
describeModel.blockModelId = blockModel.uuid;
blockModel.describeModelId = describeModel.id;
/**
* @desc Add the describeModel to its parent: either a describe or test model
*/
if(parentDescribeModel){
parentDescribeModel.addDescribe(describeModel);
console.log('πŸ₯ β€’ convertBlockToJestModel - adding to the DESCRIBE parent ', {describeModels: parentDescribeModel.describeModels})
}
else{
this.TestModel.addDescribe(describeModel);
console.log('πŸ₯ β€’ convertBlockToJestModel - adding to the TEST parent ', {describeModels: this.TestModel.describeModels})
}
// console.log('πŸ₯ β€’ convertBlockToJestModel', {'describeModel.parentId': describeModel.parentId, 'describeModel.id': describeModel.id, 'describeModel.blockModelId':describeModel.blockModelId, 'blockModel.describeModelId': blockModel.describeModelId});
console.log('πŸ₯πŸ₯πŸ₯πŸ₯πŸ₯πŸ₯πŸ₯\n\n\n');
break;
case KeywordEnum.IT:
console.log('\n\n\n🦊🦊🦊🦊🦊');
/**
* @description create a should model
*/
const isPassed = null;
const shouldModel = new ShouldModel(isPassed, blockModel.stringBetweenKeywordAndOpenbracket);
shouldModel.addCode(blockModel.contents);
this.ShouldModels.push(shouldModel);
console.log('🦊 β€’ KeywordEnum.IT', {shouldModel});
/**
* @description link the blcok model and the should model
*/
shouldModel.blockModelId = blockModel.uuid;
blockModel.shouldModelId = shouldModel.id;
/**
* @desc Add the describeModel to its parent: either a describe or test model
*/
if(parentDescribeModel){
parentDescribeModel.addShould(shouldModel);
console.log('🦊 β€’ KeywordEnum.IT - adding SHOULD to the DESCRIBE parent ', {shouldModels: parentDescribeModel.shouldModels})
}
console.log('🦊🦊🦊🦊🦊\n\n\n');
break;
}
}
}
/** πŸ‰πŸ‰πŸ‰πŸ‰πŸ‰πŸ‰πŸ‰πŸ‰πŸ‰πŸ‰
*
* @main
*
*/
const jestResultsParseService = new JestResultsParserService();
const jestFileParseService = new JestSpecParseService();
(async () => {
/**
* @step - parse the jest results
*/
const testResults = await jestResultsParseService.runJest();
console.log(testResults);
/**
* @step - parse the jest file
*/
var txtController = `
class Hello{
method1(){
this.doSomething();
for(var i = 0; i < something; i++){
//loop body
}
}
method2(){
this.doDifferent();
}
}
export default class TimeService {
static formatTime(timeInMs: number) : string {
let minutes = Math.floor(timeInMs / 60000);
let seconds = (timeInMs % 60000) / 1000;
let timeString = seconds + "s";
if (minutes !== 0)
{
timeString = minutes + "m" + timeString;
}
return timeString;
}
static formatTimeFull(timeInMs: number) : string {
let minutes = Math.floor(timeInMs / 60000);
let seconds = (timeInMs % 60000) / 1000;
let timeString = seconds + " secs";
if (minutes !== 0)
{
timeString = minutes + " mins. " + timeString;
}
return timeString;
}
}
`
const txtTest = `
import React from 'react';
import { checkProps } from '../../common/utils/test-helpers';
import Card from './card.component';
describe("CardComponent", () => {
describe("CardComponent - Checking prop types", () => {
it ("Should not throw any warnings", () => {
const expectedProps = {
name : "card name"
}
const propErrors = checkProps(Card, expectedProps);
expect(propErrors).toEqual({hello: 1234});
})
})
})
`
/**
*
* @desc parse jest file
*
*/
console.log(`
/** 🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏
*
* 🍏 @step parse jest file
*
*/
`);
const testFileName = '/path/to/testfile.test.tsx';
const blocks = jestFileParseService.parseJestFile(testFileName, txtTest);
jestFileParseService.convertBlocksToJestModels();
console.log('\n TEST MODELS', jestFileParseService.TestModel);
console.log('DESCRIBE MODELS', jestFileParseService.DescribeModels);
console.log('SHOULD MODELS', jestFileParseService.ShouldModels.map(s => s.toJson()));
})();
module.exports = {JestResultsParserService, JestSpecParseService}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment