Browse Source

Merge branch 'breaking-rewrite'

master
Muthu Kumar 6 years ago
parent
commit
069d963190
  1. 8
      package.json
  2. 98
      sample/sample.test.js
  3. 34
      sample/sample2.test.js
  4. 102
      sample/sample7.test.js
  5. 303
      shrinkwrap.yaml
  6. 103
      src/gunner.js
  7. 4
      src/lib/assertPromise.js
  8. 41
      src/lib/assertionsLibrary.js
  9. 49
      src/lib/buildTestQueue.js
  10. 30
      src/lib/caller.js
  11. 32
      src/lib/expect.js
  12. 150
      src/lib/runTests.js
  13. 21
      src/lib/snipStack.js
  14. 124
      src/lib/testrunner.js
  15. 2
      src/runner/index.js
  16. 10
      src/strategy/index.js
  17. 28
      src/util/index.js
  18. 1
      src/util/requireDeep.js
  19. 2
      src/util/symbols.js

8
package.json

@ -1,6 +1,6 @@
{ {
"name": "@klenty/gunner", "name": "@klenty/gunner",
"version": "0.6.7", "version": "0.8.6",
"description": "Zero magic, fast test-runner and assertion framework. No magic globals.", "description": "Zero magic, fast test-runner and assertion framework. No magic globals.",
"main": "index.js", "main": "index.js",
"repository": { "repository": {
@ -23,9 +23,9 @@
"dependencies": { "dependencies": {
"@codefeathers/iseq": "^1.2.1", "@codefeathers/iseq": "^1.2.1",
"@codefeathers/promise.object": "^0.9.5", "@codefeathers/promise.object": "^0.9.5",
"bluebird": "^3.5.1",
"chalk": "^2.4.1",
"eslint": "^5.2.0",
"json-stringify-safe": "^5.0.1" "json-stringify-safe": "^5.0.1"
},
"devDependencies": {
"eslint": "^5.2.0"
} }
} }

98
sample/sample.test.js

@ -1,98 +0,0 @@
/**
* This file contains random tests
* used during development
*/
const Gunner = require('../index.js');
const gunner = new Gunner({ name: 'sample tests' });
const a = 1;
// gunner.before(Gunner.Start, () => console.log('Started tests!'));
// gunner.before(Gunner.End, () => console.log('Ended tests!'));
// let runCount = 1;
// gunner.before('*', () => console.log(`Running test ${runCount++}`));
gunner.test('should automatically pass', expect => expect().done());
gunner.test(`should be equal`, expect => expect(1).equal(1));
gunner.test(`objects are deep equal`, expect => expect({ a: 1 }).deepEqual({ a: 1 }));
gunner.test('expression should be true', expect => expect(a === 1).isTrue());
gunner.test('should be a Promise (resolved)', expect =>
expect(Promise.resolve()).isPromise());
gunner.test('should be a Promise (rejected)', expect =>
expect(Promise.reject()).isPromise());
gunner.test('wait and resolve', () => {
return new Promise(r => {
setTimeout(
() => r('ok'),
500
);
});
});
gunner.test('should resolve to 5', expect =>
expect(Promise.resolve(5)).resolvesTo(5));
// gunner.before(
// 'file must have hello as content',
// () => console.log('>> starting test! file must have hello as content'),
// );
// gunner.after(
// 'file must have hello as content',
// () => console.log('>> finished test! file must have hello as content'),
// );
gunner.test('file must have hello as content', async expect => {
const { readFile } = require('fs').promises;
const file = await readFile(__dirname + '/hello.txt', { encoding: 'utf8' });
return [
expect(file).equal('hello'),
expect(file.length).equal(5),
];
});
gunner.test('(should fail) Should automatically fail', expect =>
expect().fail());
gunner.test('(should fail) Value is not a Promise', expect =>
expect(5).isPromise());
gunner.test('(should fail) Error is not a Promise', expect =>
expect(flamethrower()).isPromise());
gunner.test(`(should fail) objects aren't deeply equal`, expect => expect({a : 1}).deepEqual({ a: 2 }));
gunner.test('(should fail) promise must reject', expect =>
expect(Promise.reject(new Error('Promise Rejected'))).equal('no rejection'));
gunner.test('(should fail) multiple expect', expect => {
const a = { };
a.b = 1;
a.c = 2;
return [
expect(a).hasProp('b'),
expect(a).hasPair('c', 3),
];
});
const flamethrower = () => {
throw new Error('This burns!');
};
gunner.test('(should fail) should catch error', expect => {
return expect(flamethrower()).equal(5);
});
gunner.test('(should fail) should not resolve to 5', expect =>
expect(Promise.resolve()).resolvesTo(5));
const trace = process.argv.slice(2).indexOf('--trace') !== -1;
const log = process.argv.slice(2).indexOf('--log') !== -1;
gunner.run({ trace, log });

34
sample/sample2.test.js

@ -3,23 +3,27 @@
* used during development * used during development
*/ */
const Gunner = require('../index.js'); const { Gunner, expect, expectMany } = require('..');
const gunner = new Gunner({ name: 'state tests' }); const gunner = new Gunner({ name: 'state tests' });
gunner.before(Gunner.Start, () => 'hello'); gunner.before(Gunner.Start, () => 'world', 'hello');
gunner.before(Gunner.Start, () => 'below'); gunner.before(Gunner.Start, () => 'earth', 'below');
gunner.before(Gunner.Start, () => 'shallow'); gunner.before(Gunner.Start, () => 'waters', 'shallow');
gunner.before('*', () => 'stars'); gunner.before('*', () => 'stars', 'stars');
gunner.before('Test 1', () => 'nope'); gunner.before('Test 1', () => 'nope', 'test1');
gunner.test('Test 1', (expect, state) => gunner.test('Test 1', state =>
[ expectMany([
expect(state['@start']).deepEquals([ 'hello', 'below', 'shallow' ]), expect(state['@start']).deepEquals({
expect(state['@every']).deepEquals([ 'stars' ]), hello: 'world',
expect(state['@this']).deepEquals([ 'nope' ]), below: 'earth',
]); shallow: 'waters'
}),
expect(state['@every']).deepEquals({ stars: 'stars' }),
expect(state['@this']).deepEquals({ test1: 'nope' }),
]));
gunner.test('(should fail) Test 2', (expect, state) => gunner.test('(should fail) Test 2', state =>
expect(state['@start']).deepEquals([ 'hellno' ])); expect(state['@start']).deepEquals({ 'hellna': true }));
gunner.run({ log: true }); gunner.run({ log: true }).then(console.log);

102
sample/sample7.test.js

@ -0,0 +1,102 @@
/**
* This file contains random tests
* used during development
*/
const Gunner = require('../index.js');
const expect = Gunner.expect;
const expectMany = Gunner.expectMany;
const gunner = new Gunner({ name: 'sample tests' });
const a = 1;
gunner.before(Gunner.Start, () => console.log('Started tests!'));
gunner.before(Gunner.End, () => console.log('Ended tests!'));
let runCount = 1;
gunner.before('*', () => console.log(`Running test ${runCount++}`));
gunner.test('should automatically pass', () => expect().done());
gunner.test(`should be equal`, () => expect(1).equal(1));
gunner.test(`objects are deep equal`, () => expect({ a: 1 }).deepEqual({ a: 1 }));
gunner.test('expression should be true', () => expect(a === 1).isTrue());
gunner.test('should be a Promise (resolved)', () =>
expect(Promise.resolve()).isPromise());
gunner.test('should be a Promise (rejected)', () =>
expect(Promise.reject()).isPromise());
gunner.test('wait and resolve', () => {
return new Promise(r => {
setTimeout(
() => r('ok'),
50
);
});
});
gunner.test('should resolve to 5', () =>
expect(Promise.resolve(5)).resolvesTo(5));
gunner.before(
'file must have hello as content',
() => console.log('>> starting test! file must have hello as content'),
'helloContentBefore',
);
gunner.after(
'file must have hello as content',
() => console.log('>> finished test! file must have hello as content'),
'helloContentAfter',
);
gunner.test('file must have hello as content', async () => {
const { readFile } = require('fs').promises;
const file = await readFile(__dirname + '/hello.txt', { encoding: 'utf8' });
return [
expect(file).equal('hello'),
expect(file.length).equal(5),
];
});
gunner.test('(should fail) Should automatically fail', () =>
expect().fail());
gunner.test('(should fail) Value is not a Promise', () =>
expect(5).isPromise());
gunner.test('(should fail) Error is not a Promise', () =>
expect(flamethrower()).isPromise());
gunner.test(`(should fail) objects aren't deeply equal`, () => expect({a : 1}).deepEqual({ a: 2 }));
gunner.test('(should fail) promise must reject', () =>
expect(Promise.reject(new Error('Promise Rejected'))).equal('no rejection'));
gunner.test('(should fail) multiple expect', () => {
const a = { };
a.b = 1;
a.c = 2;
return expectMany([
expect(a).hasProp('b'),
expect(a).hasPair('c', 3),
]);
});
const flamethrower = () => {
throw new Error('This burns!');
};
gunner.test('(should fail) should catch error', () => {
return expect(flamethrower, []).equal(5);
});
gunner.test('(should fail) should not resolve to 5', () =>
expect(Promise.resolve()).resolvesTo(5));
const trace = process.argv.slice(2).indexOf('--trace') !== -1;
const log = process.argv.slice(2).indexOf('--log') !== -1;
gunner.run({ trace, log }).then(console.log);

303
shrinkwrap.yaml

File diff suppressed because it is too large

103
src/gunner.js

@ -1,126 +1,91 @@
'use strict'; 'use strict';
const { EOL } = require('os'); const { arrayOrPush } = require('./util');
const chalk = require('chalk'); const caller = require('./lib/caller');
const Promise = require('bluebird'); const testrunner = require('./lib/testrunner');
Promise.object = require('@codefeathers/promise.object'); const { expect, expectMany } = require('./lib/expect');
const _runTests = require('./lib/runTests');
const _expect = require('./lib/expect');
const logger = require('./lib/logger');
const symbols = require('./util/symbols'); const symbols = require('./util/symbols');
class Gunner { class Gunner {
constructor (options = {}) { constructor (name) {
this.__hooks__ = { this.name = name;
before: { this.__suite__ = {
tests: [],
beforeHooks: {
[symbols.Start]: [], [symbols.Start]: [],
[symbols.End]: [], [symbols.End]: [],
'*': [], '*': [],
}, },
after: { afterHooks: {
[symbols.Start]: [], [symbols.Start]: [],
[symbols.End]: [], [symbols.End]: [],
'*': [], '*': [],
}, }
}; };
this.__state__ = [];
this.__tests__ = [];
this.name = options.name;
} }
test (description, test) { test (description, test) {
const existing = ( const existing = (
this.__tests__ this.__suite__.tests
.find(x => x.description === description) .find(x => x.description === description)
); );
if (existing) if (existing)
throw new Error(`Test '${description}' already exists!`); throw new Error(`Test '${description}' already exists!`);
this.__tests__.push({ const unit = {
description, description,
test: state => { type: 'test',
try { run: state => caller(test, state),
return test(_expect, state); };
} catch (e) { this.__suite__.tests.push(unit);
// If errors are thrown, reject them
return Promise.reject(e);
}
},
});
return this; return this;
} }
before (description, run) { before (description, run, label) {
const hook = { const unit = {
description, description,
run, label,
type: 'hook',
run: state => caller(run, state),
}; };
arrayOrPush(this.__suite__.beforeHooks, description, unit);
this.__hooks__.before[description]
? this.__hooks__.before[description].push(hook)
: this.__hooks__.before[description] = [ hook ];
return this; return this;
} }
after (description, run) { after (description, run, label) {
const hook = { const unit = {
description, description,
run, label,
type: 'hook',
run: state => caller(run, state),
}; };
arrayOrPush(this.__suite__.afterHooks, description, unit);
this.__hooks__.after[description]
? this.__hooks__.after[description].push(hook)
: this.__hooks__.after[description] = [ hook ];
return this; return this;
} }
run (options = {}) { run (options = {}) {
return _runTests(this, options) return testrunner(this, options)
.then(results => { .then(results => {
const success = results.filter(r => r.result === 'pass'); const success = results.filter(r => r.status === 'ok');
const successPercent = Math.floor( const successPercent = Math.floor(
success.length/results.length * 100 success.length/results.length * 100
); );
const beforeAfterLine =
successPercent === 100
? chalk`{green ------------------------------------}`
: chalk`{red ------------------------------------}`;
const log = logger.create(options);
log(
EOL,
beforeAfterLine,
EOL, EOL,
chalk`{green ${success.length}}`,
`tests passed of ${results.length}`,
`[${successPercent}% success]`,
EOL, EOL,
beforeAfterLine
);
if((successPercent !== 100) && typeof process !== 'undefined') if((successPercent !== 100) && typeof process !== 'undefined')
process.exitCode = 1; process.exitCode = 1;
return results; return results;
})
.then(results => {
if (options.exit && typeof process !== 'undefined')
process.exit();
return results;
}); });
} }
} }
module.exports = Gunner; module.exports = Gunner;
module.exports.expect = _expect; module.exports.Gunner = Gunner;
module.exports.expect = expect;
module.exports.expectMany = expectMany;
module.exports.Start = symbols.Start; module.exports.Start = symbols.Start;
module.exports.End = symbols.End; module.exports.End = symbols.End;

4
src/lib/assertPromise.js

@ -1,17 +1,19 @@
const Promise = require('bluebird');
const { isPromise } = require('../util'); const { isPromise } = require('../util');
const createRejectionStatement = (statement, ...args) => const createRejectionStatement = (statement, ...args) =>
Promise.reject(statement ? statement(...args) : ''); Promise.reject(statement ? statement(...args) : '');
const _assertPromise = (bool, statementTuple) => { const _assertPromise = (bool, statementTuple) => {
const [ statement, ...args ] = statementTuple; const [ statement, ...args ] = statementTuple;
if(isPromise(bool)) if(isPromise(bool))
return bool.catch(() => return bool.catch(() =>
createRejectionStatement(statement, ...args)); createRejectionStatement(statement, ...args));
return bool return bool
? Promise.resolve() ? Promise.resolve()
: createRejectionStatement(statement, ...args); : createRejectionStatement(statement, ...args);
}; };
module.exports = _assertPromise; module.exports = _assertPromise;

41
src/lib/assertionsLibrary.js

@ -2,13 +2,15 @@ const isEq = require('@codefeathers/iseq');
const U = require('../util'); const U = require('../util');
const _ = U.taggedStringify; const _ = U.taggedStringify;
module.exports.done = [ module.exports.done =
[
() => true, () => true,
() => null, () => null,
]; ];
module.exports.fail = [ module.exports.fail =
[
() => false, () => false,
() => null, (_, rejection) => rejection,
]; ];
module.exports.exists = module.exports.exists =
[ [
@ -74,11 +76,42 @@ module.exports.resolvesTo =
: Promise.reject(`${val} was not a Promise`), : Promise.reject(`${val} was not a Promise`),
(val, thing) => _`'${val}' does not resolve to '${thing}'`, (val, thing) => _`'${val}' does not resolve to '${thing}'`,
]; ];
module.exports.isType =
[
(val, type) => (type === 'nil'
&& (val === 'null' || val === 'undefined'))
|| (typeof val === type)
|| (Array.isArray(val) && type === "array")
&& (val === null && type !== 'object'),
(val, type) => _`'${val}' is not of type '${type}'`,
];
module.exports.greaterThan =
[
(val, compare) => val > compare,
(val, compare) => _`'${val}' is not greater than ${compare}`,
];
module.exports.lessThan =
[
(val, compare) => val < compare,
(val, compare) => _`'${val}' is not less than ${compare}`
];
module.exports.gte =
[
(val, compare) => val >= compare,
(val, compare) => _`'${val}' is less than ${compare}`
];
module.exports.lte =
[
(val, compare) => val <= compare,
(val, compare) => _`'${val}' is greater than ${compare}`,
];
/* Convenience methods */ /* Convenience aliases */
module.exports.success = module.exports.done; module.exports.success = module.exports.done;
module.exports.succeed = module.exports.done; module.exports.succeed = module.exports.done;
module.exports.failure = module.exports.fail; module.exports.failure = module.exports.fail;
module.exports.equal = module.exports.equals; module.exports.equal = module.exports.equals;
module.exports.deepEqual = module.exports.deepEquals; module.exports.deepEqual = module.exports.deepEquals;
module.exports.match = module.exports.deepEquals; module.exports.match = module.exports.deepEquals;
module.exports.greaterThanOrEqualTo = module.exports.gte;
module.exports.lessThanOrEqualTo = module.exports.lte;

49
src/lib/buildTestQueue.js

@ -0,0 +1,49 @@
// Only imported for JSDoc
/* eslint-disable-next-line */
const Gunner = require('../gunner');
const symbols = require('../util/symbols');
const wrap = type => unit => ({ type, unit });
/**
* runs the test suite
* @param {Gunner} instance
*/
const buildTestTree = instance => {
const testQueue = [];
Array.prototype.push.apply(testQueue,
instance.__suite__.beforeHooks[symbols.Start].map(wrap('@start')));
testQueue.push.apply(
instance.__suite__.afterHooks[symbols.Start].map(wrap('@start')));
instance.__suite__.tests.forEach(test => (
Array.prototype.push.apply(testQueue,
instance.__suite__.beforeHooks['*']
.map(wrap('@every'))),
Array.prototype.push.apply(testQueue,
(instance.__suite__.beforeHooks[test.description] || [])
.map(wrap('@this'))),
testQueue.push(wrap('@test')(test)),
Array.prototype.push.apply(testQueue,
(instance.__suite__.afterHooks[test.description] || [])
.map(wrap('@afterTest'))),
Array.prototype.push.apply(testQueue,
(instance.__suite__.afterHooks['*']).map(wrap('@afterEvery')))
));
testQueue.push.apply(
instance.__suite__.beforeHooks[symbols.End]
.map(wrap('@end')));
testQueue.push.apply(
instance.__suite__.afterHooks[symbols.End]
.map(wrap(symbols.End)));
return testQueue;
};
module.exports = buildTestTree;

30
src/lib/caller.js

@ -0,0 +1,30 @@
const { isPromise } = require('../util');
const caller = (test, state) => {
let value, error, errored;
try {
value = test(state);
} catch (e) {
errored = true;
error = e;
}
const promise = isPromise(value);
if (promise) {
return value
.then(res => ({ status: 'ok', resolve: res, promise: true }))
.catch(rej => ({ status: 'notOk', rejection: rej, promise: true }));
} else {
return Promise.resolve({
status: errored ? 'notOk' : 'ok',
...(!errored && { value }),
...(errored && { error }),
promise: false,
});
}
};
module.exports = caller;

32
src/lib/expect.js

@ -1,12 +1,13 @@
const Promise = require('bluebird'); 'use strict';
const { liftPromise } = require('../util');
const _assertPromise = require('./assertPromise'); const { liftPromise, lowerCaseFirstLetter } = require('../util');
const assertPromise = require('./assertPromise');
const expectPromise = (pred, statement, options = {}) => const expectPromise = (pred, statement, options = {}) =>
toTest => toTest =>
(...testValues) => (...testValues) =>
liftPromise( liftPromise(
resolvedValue => _assertPromise( resolvedValue => assertPromise(
pred(toTest, ...testValues), pred(toTest, ...testValues),
[ statement, resolvedValue, ...testValues ], [ statement, resolvedValue, ...testValues ],
), ),
@ -14,7 +15,7 @@ const expectPromise = (pred, statement, options = {}) =>
) )
.catch(rejectedValue => .catch(rejectedValue =>
options.shouldCatch options.shouldCatch
? _assertPromise( ? assertPromise(
pred(toTest, ...testValues), pred(toTest, ...testValues),
[ statement, rejectedValue, ...testValues ], [ statement, rejectedValue, ...testValues ],
) )
@ -24,6 +25,7 @@ const expectPromise = (pred, statement, options = {}) =>
const library = require('./assertionsLibrary'); const library = require('./assertionsLibrary');
const expects = Object.keys(library).reduce((acc, e) => { const expects = Object.keys(library).reduce((acc, e) => {
const [ pred, statement, options ] = library[e]; const [ pred, statement, options ] = library[e];
acc[e] = expectPromise( acc[e] = expectPromise(
@ -36,11 +38,25 @@ const expects = Object.keys(library).reduce((acc, e) => {
}, {}); }, {});
const expect = thing => const negateP = prom =>
prom.then(Promise.reject, Promise.resolve);
const expect = (thing, args) =>
new Proxy({}, { new Proxy({}, {
get: function (obj, prop) { get: function (obj, prop) {
return expects[prop](thing); const toCheck = args ? thing(...args) : thing;
if (prop.slice(0, 3) === 'not')
return (...check) =>
negateP(
expects[
lowerCaseFirstLetter(prop.slice(3))
](toCheck)(...check)
);
return (...check) => expects[prop](toCheck)(...check);
}, },
}); });
module.exports = expect; const expectMany = Promise.all.bind(Promise);
module.exports.expect = expect;
module.exports.expectMany = expectMany;

150
src/lib/runTests.js

@ -1,150 +0,0 @@
'use strict';
const Promise = require('bluebird');
Promise.object = require('@codefeathers/promise.object');
const chalk = require('chalk');
const logger = require('./logger');
const { isPromise, taggedStringify: _ } = require('../util');
const constants = require('../util/symbols');
const snipStack = e => {
if (e.stack)
e.stack = e.stack
.split('\n')
.reduceRight(
(acc, x) =>
/* eslint-disable-next-line */
acc.done
? acc.cur
: x.match(/at Object\.test.*\/src\/gunner\.js/)
? { cur: x, done: true }
: { cur: [x, acc.cur].join('\n') },
{ cur: '' })
.cur.trim();
return e;
};
const unitReducer =
(units = [], stateMark) =>
(state = {}) =>
units.reduce(
(accumulator, unit) =>
accumulator
.then(thisState =>
Promise.resolve(
unit.run({ ...state, [stateMark]: thisState })
)
.then(newState =>
[ ...thisState, newState ])),
Promise.resolve(state[stateMark] || []),
);
const runTests = (instance, options) => {
const log = logger.create(options);
const beforeAll = () =>
unitReducer(
[
...(instance.__hooks__.before[constants.Start] || []),
...(instance.__hooks__.after[constants.Start] || []),
],
'@start',
)();
const beforeEvery = state =>
unitReducer(
instance.__hooks__.before['*'],
'@every',
)(state);
const runner = state => Promise.mapSeries(instance.__tests__, each => {
const beforeThis =
unitReducer(
instance.__hooks__.before[each.description],
'@this'
);
const afterThis =
unitReducer(
instance.__hooks__.after[each.description],
'@afterThis'
);
return Promise.object({ ...state, '@every': beforeEvery(state) })
.then(state => Promise.object({ ...state, '@this': beforeThis(state) }))
.then(state => {
const pred = each.test(state);
/* There are 4 different cases at play:
1. A plain expect() is returned.
2. An array of [ expect() ] is returned
3. A plain expect() is wrapped in a promise
4. An array of [ expect() ] is wrapped in a promise.
Here we normalise all of them into something we can process */
if (!isPromise(pred) && !(pred && isPromise(pred[0])))
throw new Error(`Malformed test '${each.description}'`);
const toTest = Array.isArray(pred)
? Promise.all(pred)
: pred.then(x => Array.isArray(x) ? Promise.all(x) : x);
return ([
state,
toTest
.then(() => {
log(
`${chalk`{green ✅}`} :: `,
`${each.description}`
);
return {
description: each.description,
result: constants.pass
};
})
.catch(e => {
const error = (e && e.stack) ? snipStack(e) : e;
const trace = (options.trace && error)
? `\n Traceback:\n ` + _`${error}`
: '';
log(
`${chalk`{red ❌}`} :: `,
`${each.description}`,
`${trace}`
);
return {
description: each.description,
result: constants.fail,
error,
};
}),
]);
})
.then(([state, result]) => afterThis(state).then(() => result));
});
const afterAll =
unitReducer(
[
...(instance.__hooks__.before[constants.End] || []),
...(instance.__hooks__.after[constants.End] || []),
],
'@after-all',
);
return Promise.object({ '@start': beforeAll() })
.then(state => Promise.object({ ...state, '@results': runner(state)}))
.then(state => Promise.object({ ...state, '@end': afterAll(state) }))
.then(state => state['@results']);
};
module.exports = runTests;

21
src/lib/snipStack.js

@ -0,0 +1,21 @@
const snipStack = e => {
if (e.stack)
e.stack = e.stack
.split('\n')
.reduceRight(
(acc, x) =>
/* eslint-disable-next-line */
acc.done
? acc.cur
: x.match(/at Object\.test.*\/src\/gunner\.js/)
? { cur: x, done: true }
: { cur: [x, acc.cur].join('\n') },
{ cur: '' })
.cur.trim();
return e;
};
module.exports = snipStack;

124
src/lib/testrunner.js

@ -0,0 +1,124 @@
// Only imported for JSDoc
/* eslint-disable-next-line */
const Gunner = require('../gunner');
Promise.object = require('@codefeathers/promise.object');
const { last, pipe, pick, assignToObject } = require('../util');
const buildTestQueue = require('./buildTestQueue');
const findSkip = (skip, unit) => {
const startFailed = skip.findIndex(x =>
x.type === '@start');
const everyFailed = skip.findIndex(x =>
x.type === '@every');
const beforeFailed = skip.findIndex(x =>
x.description === unit.description);
return (startFailed !== -1
&& 'A start hook failed\n'
+ skip[startFailed].error)
|| (everyFailed !== -1
&& 'An every hook failed\n'
+ skip[everyFailed].error)
|| (beforeFailed !== -1
&& 'A before test hook failed\n'
+ skip[beforeFailed].error);
};
const reduceQueue =
queue => queue.reduce(
(acc, item) =>
Promise.resolve(acc)
.then(acc => {
return Promise.all([last(acc.results), Promise.object(acc.state)])
.then(([, state]) => {
const toSkip = findSkip(acc.skip, item.unit);
return [toSkip, state];
})
.then(([toSkip, state]) => {
return toSkip
? { status: 'skip', description: toSkip }
: item.unit.run(state);
})
.then(result => {
const { status } = result;
const identifier = (item.unit.label)
|| (queue
.filter(i => i.type === item.type)
.filter(i => (i.unit.description
=== item.unit.description))
.length);
if (item.type === '@test') {
const resultObject = {
status,
description: item.unit.description,
...((status === 'notOk' || status === 'skip')
&& {reason : result.error
|| result.rejection
|| result.description})
};
acc.results.push(resultObject);
} else {
const stateAddition =
/* eslint-disable-next-line */
status === 'ok'
? result.promise ? result.resolve : result.value
: null;
if (stateAddition)
assignToObject(
acc.state, item.type
)(identifier, stateAddition);
}
if (status === 'notOk') {
acc.skip.push({
type: item.type,
description: item.unit.description,
error: result.promise
? result.rejection
: result.error,
});
}
return acc;
});
}),
{ results: [], state: {}, skip: [] },
);
/**
* runs the test suite
* @param {Gunner} instance
* @param {object} options
*/
const testrunner = (instance) => {
return pipe(
buildTestQueue,
reduceQueue,
pick('results'),
)(instance);
};
module.exports = testrunner;

2
src/runner/index.js

@ -1,5 +1,3 @@
const Promise = require('bluebird');
const { flatten } = require('../util'); const { flatten } = require('../util');
const logger = require('../lib/logger'); const logger = require('../lib/logger');

10
src/strategy/index.js

@ -1,5 +1,3 @@
const Promise = require('bluebird');
const requireDeep = require('../util/requireDeep'); const requireDeep = require('../util/requireDeep');
const Runner = require('../runner'); const Runner = require('../runner');
@ -40,9 +38,11 @@ class Strategy {
* @param {string|Array<string>=} options.pattern * @param {string|Array<string>=} options.pattern
*/ */
fetchSpecs (options) { fetchSpecs (options) {
this.__await__.push(Promise.map(requireDeep(options), each => { this.__await__.push(
this.__gunnerInstances = this.compiler(this)(each); Promise.all(
})); requireDeep(options).map(
each => this.__gunnerInstances = this.compiler(this)(each)
)));
return this; return this;
} }

28
src/util/index.js

@ -35,11 +35,17 @@ module.exports = {
path => path =>
path.reduce((result, segment) => result && result[segment], obj), path.reduce((result, segment) => result && result[segment], obj),
/* Picks a key from an object */
pick : key => obj => obj[key],
/* Pipe a value or promise through any number of unary functions */ /* Pipe a value or promise through any number of unary functions */
pipe : (...fns) => pipe : (...fns) =>
arg => fns.reduce((acc, fn) => arg => fns.reduce((acc, fn) =>
liftPromise(fn, acc), arg), liftPromise(fn, acc), arg),
/* Reduces an array */
reduce : (fn, def) => arr => arr.reduce(fn, def),
/* Flattens an array of arrays to an array */ /* Flattens an array of arrays to an array */
flatten : arrData => [].concat.apply([], arrData), flatten : arrData => [].concat.apply([], arrData),
@ -92,4 +98,26 @@ module.exports = {
/* Fetches last element from list */ /* Fetches last element from list */
last : arr => arr[arr.length - 1], last : arr => arr[arr.length - 1],
/* Uppercases first letter of word */
upperCaseFirstLetter : word =>
word[0].toUpperCase()
+ word.slice(1),
/* Lowercases first letter of word */
lowerCaseFirstLetter : word =>
word[0].toLowerCase()
+ word.slice(1),
/* Creates an array or pushes to an existing one */
arrayOrPush : (obj, key, item) =>
Array.isArray(obj[key])
? obj[key].push(item)
: obj[key] = [item],
/* Assigns to key or creates a new object */
assignToObject : (obj, path) => (key, value) =>
isObject(obj[path])
? obj[path][key] = value
: obj[path] = { [key]: value },
}; };

1
src/util/requireDeep.js

@ -1,4 +1,3 @@
const Promise = require('bluebird');
const fs = require(`fs`).promises; const fs = require(`fs`).promises;
const { const {
map, map,

2
src/util/symbols.js

@ -3,6 +3,8 @@ module.exports = {
Start : Symbol('Start'), Start : Symbol('Start'),
End : Symbol('End'), End : Symbol('End'),
expect: Symbol('expect'),
pass: 'pass', pass: 'pass',
fail: 'fail', fail: 'fail',

Loading…
Cancel
Save