Merge pull request #90 from greena13/dev_focused_pending_tests
[Test Suite] Test Suite Improvements
This commit is contained in:
commit
71a9bf4857
|
@ -0,0 +1 @@
|
||||||
|
__tests__/build/
|
|
@ -103,6 +103,84 @@ function pendingTestTests({ it: _it, describe: _describe }) {
|
||||||
otherTest.should.be.called();
|
otherTest.should.be.called();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_describe('when an outer context is focused', () => {
|
||||||
|
_it('a pending test will still not run', async () => {
|
||||||
|
const pendingTest = sinon.spy();
|
||||||
|
const otherTest = sinon.spy();
|
||||||
|
const unfocusedTest = sinon.spy();
|
||||||
|
|
||||||
|
const testSuite = new TestSuite('', '', {});
|
||||||
|
|
||||||
|
testSuite.addTests(({ fdescribe, it, xit }) => {
|
||||||
|
fdescribe('', () => {
|
||||||
|
xit('', pendingTest);
|
||||||
|
|
||||||
|
it('', otherTest);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('', unfocusedTest);
|
||||||
|
});
|
||||||
|
|
||||||
|
testSuite.setStore({
|
||||||
|
getState: () => { return {}; },
|
||||||
|
});
|
||||||
|
|
||||||
|
const testIdsToRun = Object.keys(testSuite.testDefinitions.focusedTestIds).reduce((memo, testId) => {
|
||||||
|
if (!testSuite.testDefinitions.pendingTestIds[testId]) {
|
||||||
|
memo.push(testId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return memo;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
await testSuite.run(testIdsToRun);
|
||||||
|
|
||||||
|
pendingTest.should.not.be.called();
|
||||||
|
otherTest.should.be.called();
|
||||||
|
unfocusedTest.should.not.be.called();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_describe('when an outer context is focused', () => {
|
||||||
|
_it('a pending context will still not run', async () => {
|
||||||
|
const pendingTest = sinon.spy();
|
||||||
|
const otherTest = sinon.spy();
|
||||||
|
const unfocusedTest = sinon.spy();
|
||||||
|
|
||||||
|
const testSuite = new TestSuite('', '', {});
|
||||||
|
|
||||||
|
testSuite.addTests(({ fdescribe, it, xdescribe }) => {
|
||||||
|
fdescribe('', () => {
|
||||||
|
xdescribe('', () => {
|
||||||
|
it('', pendingTest);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('', otherTest);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('', unfocusedTest);
|
||||||
|
});
|
||||||
|
|
||||||
|
testSuite.setStore({
|
||||||
|
getState: () => { return {}; },
|
||||||
|
});
|
||||||
|
|
||||||
|
const testIdsToRun = Object.keys(testSuite.testDefinitions.focusedTestIds).reduce((memo, testId) => {
|
||||||
|
if (!testSuite.testDefinitions.pendingTestIds[testId]) {
|
||||||
|
memo.push(testId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return memo;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
await testSuite.run(testIdsToRun);
|
||||||
|
|
||||||
|
pendingTest.should.not.be.called();
|
||||||
|
otherTest.should.be.called();
|
||||||
|
unfocusedTest.should.not.be.called();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default pendingTestTests;
|
export default pendingTestTests;
|
||||||
|
|
|
@ -286,8 +286,7 @@ class TestRun {
|
||||||
suiteId: this.testSuite.id,
|
suiteId: this.testSuite.id,
|
||||||
status: RunStatus.ERR,
|
status: RunStatus.ERR,
|
||||||
time: Date.now() - this.runStartTime,
|
time: Date.now() - this.runStartTime,
|
||||||
message: `Test suite failed: ${error.message}`,
|
message: `Test suite failed: ${error.message}`
|
||||||
stackTrace: error.stack,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -306,7 +305,7 @@ class TestRun {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _safelyRunFunction(func, timeOutDuration, description) {
|
async _safelyRunFunction(func, timeOutDuration, description) {
|
||||||
const syncResultOrPromise = tryCatcher(func);
|
const syncResultOrPromise = captureThrownErrors(func);
|
||||||
|
|
||||||
if (syncResultOrPromise.error) {
|
if (syncResultOrPromise.error) {
|
||||||
// Synchronous Error
|
// Synchronous Error
|
||||||
|
@ -314,49 +313,59 @@ class TestRun {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asynchronous Error
|
// Asynchronous Error
|
||||||
return promiseToCallBack(syncResultOrPromise.value, timeOutDuration, description);
|
return capturePromiseErrors(syncResultOrPromise.result, timeOutDuration, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try catch to object
|
* Call a function and capture any errors that are immediately thrown.
|
||||||
* @returns {{}}
|
* @returns {Object} Object containing result of executing the function, or the error
|
||||||
|
* message that was captured
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function tryCatcher(func) {
|
function captureThrownErrors(func) {
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result.value = func();
|
result.result = func();
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
result.error = e;
|
result.error = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a promise callback-able to trap errors
|
* Wraps a promise so that if it's rejected or an error is thrown while it's being
|
||||||
* @param promise
|
* evaluated, it's captured and thrown no further
|
||||||
|
* @param {*} target - Target to wrap. If a thenable object, it's wrapped so if it's
|
||||||
|
* rejected or an error is thrown, it will be captured. If a non-thenable object,
|
||||||
|
* wrapped in resolved promise and returned.
|
||||||
|
* @param {Number} timeoutDuration - Number of milliseconds the promise is allowed
|
||||||
|
* to pend before it's considered timed out
|
||||||
|
* @param {String} description - Description of the context the promises is defined
|
||||||
|
* in, used for reporting where a timeout occurred in the resulting error message.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function promiseToCallBack(promise, timeoutDuration, description) {
|
function capturePromiseErrors(target, timeoutDuration, description) {
|
||||||
let returnValue = null;
|
let returnValue = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
returnValue = Promise.resolve(promise)
|
returnValue = Promise.resolve(target)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return null;
|
return null;
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
return Promise.resolve(error);
|
return Promise.resolve(error);
|
||||||
})
|
})
|
||||||
.timeout(timeoutDuration, `${description} took longer than ${timeoutDuration}ms. This can be extended with the timeout option.`)
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
return Promise.resolve(error);
|
return Promise.resolve(error);
|
||||||
});
|
})
|
||||||
|
.timeout(timeoutDuration,
|
||||||
|
`${description} took longer than ${timeoutDuration}ms. This can be extended with the timeout option.`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
returnValue = Promise.resolve(error);
|
returnValue = Promise.resolve(error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,19 +111,19 @@ class TestSuite {
|
||||||
*/
|
*/
|
||||||
async run(testIds = undefined) {
|
async run(testIds = undefined) {
|
||||||
const testsToRun = (() => {
|
const testsToRun = (() => {
|
||||||
if (testIds) {
|
return (testIds || Object.keys(this.testDefinitions.tests)).reduce((memo, id) => {
|
||||||
return testIds.map((id) => {
|
const test = this.testDefinitions.tests[id];
|
||||||
const test = this.testDefinitions.tests[id];
|
|
||||||
|
|
||||||
if (!test) {
|
if (!test) {
|
||||||
throw new RangeError(`ReactNativeFirebaseTests.TestRunError: Test with id ${id} not found in test suite ${this.name}`);
|
throw new RangeError(`ReactNativeFirebaseTests.TestRunError: Test with id ${id} not found in test suite ${this.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return test;
|
if (!this.testDefinitions.pendingTestIds[id]) {
|
||||||
});
|
memo.push(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(this.testDefinitions.tests);
|
return memo;
|
||||||
|
}, []);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const testRun = new TestRun(this, testsToRun.reverse(), this.testDefinitions);
|
const testRun = new TestRun(this, testsToRun.reverse(), this.testDefinitions);
|
||||||
|
|
|
@ -14,13 +14,14 @@ export function setSuiteStatus({ suiteId, status, time, message, progress }) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setTestStatus({ testId, status, time = 0, message = null }) {
|
export function setTestStatus({ testId, status, stackTrace, time = 0, message = null }) {
|
||||||
return {
|
return {
|
||||||
type: TEST_SET_STATUS,
|
type: TEST_SET_STATUS,
|
||||||
testId,
|
testId,
|
||||||
|
|
||||||
status,
|
status,
|
||||||
message,
|
message,
|
||||||
|
stackTrace,
|
||||||
time,
|
time,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ function testsReducers(state = initState.tests, action: Object): State {
|
||||||
flattened[`${action.testId}.status`] = action.status;
|
flattened[`${action.testId}.status`] = action.status;
|
||||||
flattened[`${action.testId}.message`] = action.message;
|
flattened[`${action.testId}.message`] = action.message;
|
||||||
flattened[`${action.testId}.time`] = action.time;
|
flattened[`${action.testId}.time`] = action.time;
|
||||||
|
flattened[`${action.testId}.stackTrace`] = action.stackTrace;
|
||||||
|
|
||||||
return unflatten(flattened);
|
return unflatten(flattened);
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,35 +51,28 @@ class Test extends React.Component {
|
||||||
setParams({ test });
|
setParams({ test });
|
||||||
}
|
}
|
||||||
|
|
||||||
renderError() {
|
|
||||||
const { test: { message } } = this.props;
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
return (
|
|
||||||
<ScrollView>
|
|
||||||
<Text style={styles.codeHeader}>Test Error</Text>
|
|
||||||
<Text style={styles.code}>
|
|
||||||
<Text>{message}</Text>
|
|
||||||
</Text>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { test: { func, status, time } } = this.props;
|
const { test: { stackTrace, description, func, status, time }, testContextName } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
{Test.renderBanner({ status, time })}
|
{Test.renderBanner({ status, time })}
|
||||||
<View style={styles.content}>
|
<View >
|
||||||
{this.renderError()}
|
<ScrollView style={styles.sectionContainer}>
|
||||||
<Text style={styles.codeHeader}>Test Code Preview</Text>
|
<Text style={styles.heading}>{testContextName}</Text>
|
||||||
<ScrollView>
|
<Text style={styles.description}>{description}</Text>
|
||||||
<Text style={styles.code}>
|
</ScrollView>
|
||||||
|
<ScrollView style={styles.sectionContainer}>
|
||||||
|
<Text style={styles.heading}>Test Error</Text>
|
||||||
|
<Text style={styles.description}>
|
||||||
|
<Text>{stackTrace || 'None.'}</Text>
|
||||||
|
</Text>
|
||||||
|
</ScrollView>
|
||||||
|
<Text style={styles.heading}>
|
||||||
|
Test Code Preview
|
||||||
|
</Text>
|
||||||
|
<ScrollView style={styles.sectionContainer}>
|
||||||
|
<Text style={styles.description}>
|
||||||
{beautify(removeLastLine(removeFirstLine(func.toString())), { indent_size: 4, break_chained_methods: true })}
|
{beautify(removeLastLine(removeFirstLine(func.toString())), { indent_size: 4, break_chained_methods: true })}
|
||||||
</Text>
|
</Text>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -93,10 +86,13 @@ Test.propTypes = {
|
||||||
test: PropTypes.shape({
|
test: PropTypes.shape({
|
||||||
status: PropTypes.string,
|
status: PropTypes.string,
|
||||||
time: PropTypes.number,
|
time: PropTypes.number,
|
||||||
message: PropTypes.string,
|
|
||||||
func: PropTypes.function,
|
func: PropTypes.function,
|
||||||
|
stackTrace: PropTypes.function,
|
||||||
|
description: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
||||||
|
testContextName: PropTypes.string,
|
||||||
|
|
||||||
navigation: PropTypes.shape({
|
navigation: PropTypes.shape({
|
||||||
setParams: PropTypes.func.isRequired,
|
setParams: PropTypes.func.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
@ -107,27 +103,32 @@ const styles = StyleSheet.create({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
},
|
},
|
||||||
content: {},
|
sectionContainer: {
|
||||||
code: {
|
minHeight: 100,
|
||||||
backgroundColor: '#3F373A',
|
|
||||||
color: '#c3c3c3',
|
|
||||||
padding: 5,
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
},
|
||||||
codeHeader: {
|
heading: {
|
||||||
fontWeight: '600',
|
|
||||||
fontSize: 18,
|
|
||||||
backgroundColor: '#000',
|
|
||||||
color: '#fff',
|
|
||||||
padding: 5,
|
padding: 5,
|
||||||
|
backgroundColor: '#0288d1',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
padding: 5,
|
||||||
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function select({ tests }, { navigation: { state: { params: { testId } } } }) {
|
function select({ tests, testContexts }, { navigation: { state: { params: { testId } } } }) {
|
||||||
const test = tests[testId];
|
const test = tests[testId];
|
||||||
|
let testContext = testContexts[test.testContextId];
|
||||||
|
|
||||||
|
while(testContext.parentContextId && testContexts[testContext.parentContextId].parentContextId) {
|
||||||
|
testContext = testContexts[testContext.parentContextId];
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
test,
|
test,
|
||||||
|
testContextName: testContext.name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue