Skip to content

Commit

Permalink
Support directory arguments in the CLI (#77)
Browse files Browse the repository at this point in the history
* Types: support cases where Duration might be null

* Enable handling of directories as arguments

* Pin cross-env to 5.1.1 because 5.1.2 is broken on node v4
kentcdodds/cross-env#160
  • Loading branch information
adamgruber committed Dec 21, 2017
1 parent 3705a90 commit e3ab35f
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 23 deletions.
51 changes: 45 additions & 6 deletions bin/src/cli-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const JsonFileRegex = /\.json{1}$/;
const mapJsonErrors = errors => errors.map(e => ` ${e.message}`).join('\n');
const ERRORS = {
NOT_FOUND: ' File not found.',
NOT_JSON: ' You must supply a valid JSON file.',
GENERIC: ' There was a problem loading mochawesome data.',
INVALID_JSON: errMsgs => mapJsonErrors(errMsgs),
IS_DIR: ' Directories are not supported (yet).'
Expand Down Expand Up @@ -42,8 +41,6 @@ function validateFile(file) {
err = ERRORS.NOT_FOUND;
} else if (e.code === 'EISDIR') {
err = ERRORS.IS_DIR;
} else if (!JsonFileRegex.test(file)) {
err = ERRORS.NOT_JSON;
} else if (JsonErrRegex.test(e.message)) {
err = ERRORS.INVALID_JSON([ e ]);
} else {
Expand Down Expand Up @@ -111,11 +108,17 @@ function handleResolved(resolvedValues) {
.join('\n\n'));
process.exitCode = 1;
}

if (!validFiles && !errors.length) {
logger.info(chalk.yellow('\nDid not find any JSON files to process.'));
process.exitCode = 1;
}

return resolvedValues;
}

/**
* Get the reportFilename option to be passed to report.create
* Get the reportFilename option to be passed to `report.create`
*
* Returns the `reportFilename` option if provided otherwise
* it returns the base filename stripped of path and extension
Expand All @@ -129,6 +132,42 @@ function getReportFilename({ filename }, { reportFilename }) {
return reportFilename || filename.split(path.sep).pop().replace(JsonFileRegex, '');
}

/**
* Process arguments, recursing through any directories,
* to find and validate JSON files
*
* @param {array} args Array of paths
* @param {array} files Array to populate
*
* @return {array} File objects to be processed
*/
function processArgs(args, files = []) {
return args.reduce((acc, arg) => {
let stats;
try {
stats = fs.statSync(arg);
} catch (err) {
// Do nothing
}

// If argument is a directory, process the files inside
if (stats && stats.isDirectory()) {
return processArgs(
fs.readdirSync(arg).map(file => path.join(arg, file)),
files
);
}

// If `statSync` failed, validating will handle the error
// If the argument is a file, check if its a JSON file before validating
if (!stats || JsonFileRegex.test(arg)) {
acc.push(validateFile(arg));
}

return acc;
}, files);
}

/**
* Main CLI Program
*
Expand All @@ -140,8 +179,8 @@ function marge(args) {
// Reset valid files count
validFiles = 0;

// Load and validate each file
const files = args._.map(validateFile);
// Get the array of JSON files to process
const files = processArgs(args._);

// When there are multiple valid files OR the timestamp option is set
// we must force `overwrite` to `false` to ensure all reports are created
Expand Down
7 changes: 4 additions & 3 deletions bin/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ const PercentClass = t.enums.of([ 'success', 'warning', 'danger' ], 'PercentClas
const TestState = t.enums.of([ 'passed', 'failed' ], 'TestState');
const TestSpeed = t.enums.of([ 'slow', 'medium', 'fast' ], 'TestSpeed');
const DateString = t.refinement(t.String, isISO8601, 'DateString');
const Duration = t.maybe(t.Integer);
const Uuid = t.refinement(t.String, isUUID, 'UUID');

const Test = t.struct({
title: t.String,
fullTitle: t.String,
timedOut: t.Boolean,
duration: t.Integer,
duration: Duration,
state: t.maybe(TestState),
speed: t.maybe(TestSpeed),
pass: t.Boolean,
Expand Down Expand Up @@ -43,7 +44,7 @@ Suite.define(t.struct({
failures: t.list(Uuid),
pending: t.list(Uuid),
skipped: t.list(Uuid),
duration: t.Integer,
duration: Duration,
rootEmpty: t.maybe(t.Boolean)
}));

Expand All @@ -56,7 +57,7 @@ const TestReport = t.struct({
failures: t.Integer,
start: DateString,
end: DateString,
duration: t.Integer,
duration: Duration,
testsRegistered: t.Integer,
passPercent: t.Number,
pendingPercent: t.Number,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
"chai-enzyme": "^1.0.0-beta.0",
"chart.js": "^2.3.0",
"classnames": "^2.2.5",
"cross-env": "^5.1.1",
"cross-env": "5.1.1",
"css-loader": "^0.28.0",
"css-modules-require-hook": "^4.0.5",
"enzyme": "^3.1.0",
Expand Down
115 changes: 115 additions & 0 deletions test/sample-data/sub/single.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
{
"stats": {
"suites": 1,
"tests": 1,
"passes": 0,
"pending": 0,
"failures": 1,
"start": "2017-06-05T12:27:21.360Z",
"end": "2017-06-05T12:27:21.376Z",
"duration": 16,
"testsRegistered": 1,
"passPercent": 0,
"pendingPercent": 0,
"other": 0,
"hasOther": false,
"skipped": 0,
"hasSkipped": false,
"passPercentClass": "danger",
"pendingPercentClass": "danger"
},
"suites": {
"title": "",
"suites": [
{
"title": "Main Suite",
"suites": [],
"tests": [
{
"title": "should be false",
"fullTitle": "Main Suite should be false",
"timedOut": false,
"duration": 4,
"state": "failed",
"pass": false,
"fail": true,
"pending": false,
"code": "// true.should.eql(bool);\nexp.should.eql({\n foo: true,\n bar: true,\n baz: 1\n});\nshouldAddContext && addContext(this, 'context');",
"err": {
"operator": "to equal",
"expected": "{\n \"bar\": true\n \"baz\": 1\n \"foo\": true\n}",
"details": "at bar, A has false and B has true",
"showDiff": true,
"actual": "{\n \"bar\": false\n \"baz\": 1\n \"foo\": true\n}",
"negate": false,
"assertion": {
"obj": {
"foo": true,
"bar": false,
"baz": 1
},
"anyOne": false,
"negate": false,
"params": {
"operator": "to equal",
"expected": {
"foo": true,
"bar": true,
"baz": 1
},
"details": "at bar, A has false and B has true",
"showDiff": true,
"actual": {
"foo": true,
"bar": false,
"baz": 1
},
"negate": false,
"assertion": "[Circular ~.suites.suites.0.tests.0.err.assertion]"
},
"light": false
},
"_message": "expected Object { foo: true, bar: false, baz: 1 } to equal Object { foo: true, bar: true, baz: 1 } (at bar, A has false and B has true)",
"generatedMessage": true,
"estack": "AssertionError: expected Object { foo: true, bar: false, baz: 1 } to equal Object { foo: true, bar: true, baz: 1 } (at bar, A has false and B has true)\n at Assertion.fail (node_modules/should/cjs/should.js:258:17)\n at Assertion.value (node_modules/should/cjs/should.js:335:19)\n at Context.<anonymous> (helpers.js:26:20)",
"diff": " {\n- \"bar\": false\n+ \"bar\": true\n \"baz\": 1\n \"foo\": true\n }\n"
},
"isRoot": false,
"uuid": "65ef9d51-2e7b-4155-9d46-af6ad7d0e2d4",
"parentUUID": "37685726-1c47-4e1f-a7d4-6c88ebf842f9",
"isHook": false,
"skipped": false
}
],
"pending": [],
"root": false,
"_timeout": 2000,
"file": "/cases/test.js",
"uuid": "37685726-1c47-4e1f-a7d4-6c88ebf842f9",
"beforeHooks": [],
"afterHooks": [],
"fullFile": "/Users/adamgruber/Sites/ma-test/cases/test.js",
"passes": [],
"failures": ["65ef9d51-2e7b-4155-9d46-af6ad7d0e2d4"],
"skipped": [],
"duration": 4,
"rootEmpty": false
}
],
"tests": [],
"pending": [],
"root": true,
"_timeout": 2000,
"uuid": "005cc237-6ddd-4da6-8b5c-e82c1ee5cfdc",
"beforeHooks": [],
"afterHooks": [],
"fullFile": "",
"file": "",
"passes": [],
"failures": [],
"skipped": [],
"duration": 0,
"rootEmpty": true
},
"copyrightYear": 2017
}
24 changes: 11 additions & 13 deletions test/spec/cli/cli-main.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,23 @@ describe('bin/cli', () => {
});
});

describe('when file is a directory', () => {
it('should not create a report', () => {
const args = getArgs([ 'test/sample-data/' ]);
return expect(cli(args)).to.become([ {
filename: 'test/sample-data/',
data: undefined,
err: ' Directories are not supported (yet).'
} ]);
describe('when arg is a directory', () => {
beforeEach(() => {
createStub.onCall(0).resolves('mochawesome-report/single.html');
});

it('should create a report', () => {
const args = getArgs([ 'test/sample-data/sub' ]);
return expect(cli(args)).to.become([
'mochawesome-report/single.html'
]);
});
});

describe('when file is not JSON', () => {
it('should not create a report', () => {
const args = getArgs([ 'README.md' ]);
return expect(cli(args)).to.become([ {
filename: 'README.md',
data: undefined,
err: ' You must supply a valid JSON file.'
} ]);
return expect(cli(args)).to.become([]);
});
});

Expand Down

0 comments on commit e3ab35f

Please sign in to comment.