diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 00000000..5afd82db
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1 @@
+__tests__/build/
diff --git a/tests/__tests__/src/tests/pendingTestTests.js b/tests/__tests__/src/tests/pendingTestTests.js
index e7c128a9..1dafed1c 100644
--- a/tests/__tests__/src/tests/pendingTestTests.js
+++ b/tests/__tests__/src/tests/pendingTestTests.js
@@ -103,6 +103,84 @@ function pendingTestTests({ it: _it, describe: _describe }) {
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;
diff --git a/tests/lib/TestRun.js b/tests/lib/TestRun.js
index c927949d..82bd4484 100644
--- a/tests/lib/TestRun.js
+++ b/tests/lib/TestRun.js
@@ -286,8 +286,7 @@ class TestRun {
suiteId: this.testSuite.id,
status: RunStatus.ERR,
time: Date.now() - this.runStartTime,
- message: `Test suite failed: ${error.message}`,
- stackTrace: error.stack,
+ message: `Test suite failed: ${error.message}`
});
});
}
@@ -306,7 +305,7 @@ class TestRun {
}
async _safelyRunFunction(func, timeOutDuration, description) {
- const syncResultOrPromise = tryCatcher(func);
+ const syncResultOrPromise = captureThrownErrors(func);
if (syncResultOrPromise.error) {
// Synchronous Error
@@ -314,49 +313,59 @@ class TestRun {
}
// Asynchronous Error
- return promiseToCallBack(syncResultOrPromise.value, timeOutDuration, description);
+ return capturePromiseErrors(syncResultOrPromise.result, timeOutDuration, description);
}
}
/**
- * Try catch to object
- * @returns {{}}
+ * Call a function and capture any errors that are immediately thrown.
+ * @returns {Object} Object containing result of executing the function, or the error
+ * message that was captured
* @private
*/
-function tryCatcher(func) {
+function captureThrownErrors(func) {
const result = {};
try {
- result.value = func();
- } catch (e) {
- result.error = e;
+ result.result = func();
+ } catch (error) {
+ result.error = error;
}
return result;
}
/**
- * Make a promise callback-able to trap errors
- * @param promise
+ * Wraps a promise so that if it's rejected or an error is thrown while it's being
+ * 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
*/
-function promiseToCallBack(promise, timeoutDuration, description) {
+function capturePromiseErrors(target, timeoutDuration, description) {
let returnValue = null;
try {
- returnValue = Promise.resolve(promise)
+ returnValue = Promise.resolve(target)
.then(() => {
return null;
}, (error) => {
return Promise.resolve(error);
})
- .timeout(timeoutDuration, `${description} took longer than ${timeoutDuration}ms. This can be extended with the timeout option.`)
.catch((error) => {
return Promise.resolve(error);
- });
+ })
+ .timeout(timeoutDuration,
+ `${description} took longer than ${timeoutDuration}ms. This can be extended with the timeout option.`
+ );
} catch (error) {
returnValue = Promise.resolve(error);
}
diff --git a/tests/lib/TestSuite.js b/tests/lib/TestSuite.js
index 3924bb8b..aaa6735c 100644
--- a/tests/lib/TestSuite.js
+++ b/tests/lib/TestSuite.js
@@ -111,19 +111,19 @@ class TestSuite {
*/
async run(testIds = undefined) {
const testsToRun = (() => {
- if (testIds) {
- return testIds.map((id) => {
- const test = this.testDefinitions.tests[id];
+ return (testIds || Object.keys(this.testDefinitions.tests)).reduce((memo, id) => {
+ const test = this.testDefinitions.tests[id];
- if (!test) {
- throw new RangeError(`ReactNativeFirebaseTests.TestRunError: Test with id ${id} not found in test suite ${this.name}`);
- }
+ if (!test) {
+ 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);
diff --git a/tests/src/actions/TestActions.js b/tests/src/actions/TestActions.js
index a2ed5fe3..71487b0a 100644
--- a/tests/src/actions/TestActions.js
+++ b/tests/src/actions/TestActions.js
@@ -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 {
type: TEST_SET_STATUS,
testId,
status,
message,
+ stackTrace,
time,
};
}
diff --git a/tests/src/reducers/testsReducers.js b/tests/src/reducers/testsReducers.js
index 204e600f..cfd6fb07 100644
--- a/tests/src/reducers/testsReducers.js
+++ b/tests/src/reducers/testsReducers.js
@@ -12,6 +12,7 @@ function testsReducers(state = initState.tests, action: Object): State {
flattened[`${action.testId}.status`] = action.status;
flattened[`${action.testId}.message`] = action.message;
flattened[`${action.testId}.time`] = action.time;
+ flattened[`${action.testId}.stackTrace`] = action.stackTrace;
return unflatten(flattened);
}
diff --git a/tests/src/screens/Test.js b/tests/src/screens/Test.js
index e716ece1..d69cb412 100644
--- a/tests/src/screens/Test.js
+++ b/tests/src/screens/Test.js
@@ -51,35 +51,28 @@ class Test extends React.Component {
setParams({ test });
}
- renderError() {
- const { test: { message } } = this.props;
-
- if (message) {
- return (
-
- Test Error
-
- {message}
-
-
- );
- }
-
- return null;
- }
-
-
render() {
- const { test: { func, status, time } } = this.props;
+ const { test: { stackTrace, description, func, status, time }, testContextName } = this.props;
return (
{Test.renderBanner({ status, time })}
-
- {this.renderError()}
- Test Code Preview
-
-
+
+
+ {testContextName}
+ {description}
+
+
+ Test Error
+
+ {stackTrace || 'None.'}
+
+
+
+ Test Code Preview
+
+
+
{beautify(removeLastLine(removeFirstLine(func.toString())), { indent_size: 4, break_chained_methods: true })}
@@ -93,10 +86,13 @@ Test.propTypes = {
test: PropTypes.shape({
status: PropTypes.string,
time: PropTypes.number,
- message: PropTypes.string,
func: PropTypes.function,
+ stackTrace: PropTypes.function,
+ description: PropTypes.string,
}).isRequired,
+ testContextName: PropTypes.string,
+
navigation: PropTypes.shape({
setParams: PropTypes.func.isRequired,
}).isRequired,
@@ -107,27 +103,32 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: '#ffffff',
},
- content: {},
- code: {
- backgroundColor: '#3F373A',
- color: '#c3c3c3',
- padding: 5,
- fontSize: 12,
+ sectionContainer: {
+ minHeight: 100,
},
- codeHeader: {
- fontWeight: '600',
- fontSize: 18,
- backgroundColor: '#000',
- color: '#fff',
+ heading: {
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];
+ let testContext = testContexts[test.testContextId];
+ while(testContext.parentContextId && testContexts[testContext.parentContextId].parentContextId) {
+ testContext = testContexts[testContext.parentContextId];
+ }
return {
test,
+ testContextName: testContext.name,
};
}