Unit test improvements (#2337)

* Start working on insomnia-testing package to generate Mocha code

* Moved some things around

* Integration tests and ability to run multiple files

* Fix integration tests

* Rename runSuite to runMochaTests

* Add types for test results

* Fix lint errors

* Play with Chia assertions

* insomnia and chai globals, plus a bunch of improvements

* Stub chai and axios Flow types

* Ability to reference requests by ID

* Fix chai flow-typed location

* Address PR comments (small tweaks)

* Basic UI going

* Lots and lots and lots

* Pretty-much done with the unit test UI

* Minor CSS tweak

* Activity bar triple toggle and more

* Minor tweak

* Unit Test and Suite deletion

* Bump Designer version

* Fix eslint stuff

* Fix insomnia-testing tests

* Fix tests

* lib@2.2.9

* Remove tests tab from response pane

* Hook up Insomnia networking

* Fix linting

* Bump version for alpha

* Remove extra ActivityToggleSwitch

* Remove unused import

* Add test:pre-release script that excludes CLI tests

* Less repetition

* Clean some things up

* Tweaks after self-cr

* Undo request ID tag

* Swap out switch for new activity toggle component

* Extra check

* Remove dead code

* Delete dead test

* Oops, revert example code

* PR feedback

* Unit test UI tweaks

* More PR comment addresses

* Change test name
This commit is contained in:
Gregory Schier authored and GitHub committed 2020-07-01 15:57:42 -07:00
1 parent 3883dc6feb
commit eae0d8d433
7 files changed
+154 -89

No files matched your search

@@ -101,7 +101,6 @@ class Editable extends PureComponent {
title: singleClick ? 'Click to edit' : 'Double click to edit',
onClick: this._handleSingleClickEditStart,
onDoubleClick: this._handleEditStart,
blankValue,
...extra,
};
@@ -30,12 +30,14 @@ type Props = {|
type State = {|
testsRunning: Array<UnitTest> | null,
resultsError: string | null,
|};
@autobind
class WrapperUnitTest extends React.PureComponent<Props, State> {
state = {
testsRunning: null,
resultsError: null,
};
// Defining it here instead of in render() so it won't act as a changed prop
@@ -130,7 +132,7 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
showPrompt({
title: 'New Test',
defaultValue: 'Returns 200',
submitName: 'Create Test',
submitName: 'New Test',
label: 'Test Name',
selectText: true,
onComplete: async name => {
@@ -165,7 +167,18 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
}
async _handleDeleteTest(unitTest: UnitTest): Promise<void> {
await models.unitTest.remove(unitTest);
showAlert({
title: `Delete ${unitTest.name}`,
message: (
<span>
Really delete <strong>{unitTest.name}</strong>?
</span>
),
addCancel: true,
onConfirm: async () => {
await models.unitTest.remove(unitTest);
},
});
}
async _handleSetActiveRequest(
@@ -202,10 +215,15 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
await models.unitTest.update(unitTest, { name });
}
async _handleChangeActiveSuiteName(name: string): Promise<void> {
const { activeUnitTestSuite } = this.props.wrapperProps;
await models.unitTestSuite.update(activeUnitTestSuite, { name });
}
async _runTests(unitTests: Array<UnitTest>): Promise<void> {
const { requests, activeWorkspace, activeEnvironment } = this.props.wrapperProps;
this.setState({ testsRunning: unitTests });
this.setState({ testsRunning: unitTests, resultsError: null });
const tests = [];
for (const t of unitTests) {
@@ -217,10 +235,18 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
}
const src = await generate([{ name: 'My Suite', suites: [], tests }]);
const results = await runTests(src, {
requests,
sendRequest: getSendRequestCallback(activeEnvironment ? activeEnvironment._id : null),
});
const sendRequest = getSendRequestCallback(activeEnvironment ? activeEnvironment._id : null);
let results;
try {
results = await runTests(src, { requests, sendRequest });
} catch (err) {
// Set the state after a timeout so the user still sees the loading state
setTimeout(() => {
this.setState({ resultsError: err.message, testsRunning: null });
}, 400);
return;
}
await models.unitTestResult.create({ results, parentId: activeWorkspace._id });
@@ -251,18 +277,37 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
renderResults(): React.Node {
const { activeUnitTestResult } = this.props.wrapperProps;
const { testsRunning } = this.state;
const { testsRunning, resultsError } = this.state;
if (resultsError) {
return (
<div className="unit-tests__results">
<div className="unit-tests__top-header">
<h2>Run Failed</h2>
</div>
<div className="danger pad">{resultsError}</div>
</div>
);
}
if (testsRunning) {
return (
<div className="unit-tests__results">
<h2>Running {testsRunning.length} Tests...</h2>
<div className="unit-tests__top-header">
<h2>Running {testsRunning.length} Tests...</h2>
</div>
</div>
);
}
if (!activeUnitTestResult) {
return null;
return (
<div className="unit-tests__results">
<div className="unit-tests__top-header">
<h2>No Results</h2>
</div>
</div>
);
}
const { stats, tests } = activeUnitTestResult.results;
@@ -271,20 +316,21 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
<div className="unit-tests__results">
{activeUnitTestResult && (
<div key={activeUnitTestResult._id}>
{stats.failures ? (
<h2 className="warning">
Tests Failed {stats.failures}/{stats.tests}
</h2>
) : (
<h2 className="success">
Tests Passed {stats.passes}/{stats.tests}
</h2>
)}
<div className="unit-tests__top-header">
{stats.failures ? (
<h2 className="warning">
Tests Failed {stats.failures}/{stats.tests}
</h2>
) : (
<h2 className="success">
Tests Passed {stats.passes}/{stats.tests}
</h2>
)}
</div>
<ul>
{tests.map((t, i) => (
<li key={i}>
<SvgIcon icon={t.err.message ? 'error' : 'success'} /> {t.fullTitle} ({t.duration}{' '}
ms)
<SvgIcon icon={t.err.message ? 'error' : 'success'} /> {t.title} ({t.duration} ms)
{t.err.message && (
<>
<br />
@@ -369,11 +415,12 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
renderPageBody(): React.Node {
const { activeUnitTests, activeUnitTestSuite } = this.props.wrapperProps;
const { testsRunning } = this.state;
if (!activeUnitTestSuite) {
return (
<div className="unit-tests layout-body--sidebar theme--pane">
<div className="unit-tests__tests theme--pane__body">No test suite selected</div>
<div className="unit-tests__tests theme--pane__body pad">No test suite selected</div>
</div>
);
}
@@ -381,12 +428,27 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
return (
<div className="unit-tests layout-body--sidebar theme--pane">
<div className="unit-tests__tests theme--pane__body">
{activeUnitTests.map(this.renderUnitTest)}
<div className="pad">
<Button variant="contained" bg="surprise" onClick={this._handleCreateTest}>
Create Test
<div className="unit-tests__top-header">
<h2>
<Editable
singleClick
onSubmit={this._handleChangeActiveSuiteName}
value={activeUnitTestSuite.name}
/>
</h2>
<Button variant="outlined" onClick={this._handleCreateTest}>
New Test
</Button>
<Button
variant="contained"
bg="surprise"
onClick={this._handleRunTests}
size="default"
disabled={testsRunning}>
{testsRunning ? 'Running... ' : 'Run Tests'}
</Button>
</div>
{activeUnitTests.map(this.renderUnitTest)}
</div>
{this.renderResults()}
</div>
@@ -395,6 +457,7 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
renderPageSidebar(): React.Node {
const { activeUnitTestSuites, activeUnitTestSuite } = this.props.wrapperProps;
const { testsRunning } = this.state;
const activeId = activeUnitTestSuite ? activeUnitTestSuite._id : 'n/a';
return (
@@ -418,6 +481,12 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
<i className="fa fa-caret-down" />
</button>
)}>
<DropdownItem
stayOpenAfterClick
onClick={this._handleRunTests}
disabled={testsRunning}>
{testsRunning ? 'Running... ' : 'Run Tests'}
</DropdownItem>
<DropdownItem onClick={this._handleDeleteUnitTestSuite.bind(this, s)}>
Delete Suite
</DropdownItem>
@@ -433,7 +502,6 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
render() {
const { handleActivityChange, gitSyncDropdown } = this.props;
const { activeWorkspace, activity, settings, activeApiSpec } = this.props.wrapperProps;
const { testsRunning } = this.state;
return (
<PageLayout
wrapperProps={this.props.wrapperProps}
@@ -460,18 +528,7 @@ class WrapperUnitTest extends React.PureComponent<Props, State> {
workspace={activeWorkspace}
/>
}
gridRight={
<>
<Button
variant="contained"
onClick={this._handleRunTests}
size="default"
disabled={testsRunning}>
{testsRunning ? 'Running... ' : 'Run Tests'}
</Button>
{gitSyncDropdown}
</>
}
gridRight={gitSyncDropdown}
/>
)}
/>
@@ -13,39 +13,12 @@
grid-template-columns: 1fr 0;
}
&__header {
grid-area: header;
background: var(--color-bg);
color: var(--color-font);
display: flex;
flex-direction: row;
align-items: center;
border-bottom: 1px solid var(--hl-md);
&__buttons {
margin-left: auto;
& > button {
align-self: stretch;
height: 100%;
background: none;
border: none !important;
}
}
h1 {
padding: 0 var(--padding-md);
margin: 0;
}
}
&__tests {
grid-area: body-left;
background: var(--color-bg);
color: var(--color-font);
display: flex;
flex-direction: column;
overflow-y: auto;
box-sizing: border-box;
&__block {
height: auto;
@@ -96,13 +69,14 @@
grid-area: body-right;
background: var(--color-bg);
color: var(--color-font);
display: grid;
grid-template-columns: 100%;
grid-template-rows: 1fr auto;
overflow-y: auto;
li {
display: block;
padding: var(--padding-xs) 0;
ul {
padding-top: var(--padding-md);
li {
display: block;
padding: var(--padding-xs) var(--padding-md);
}
}
}
@@ -150,4 +124,22 @@
}
}
}
&__top-header {
padding: 0 var(--padding-md);
height: var(--line-height-lg);
padding-right: 0;
border-bottom: 1px solid var(--hl-md);
display: flex;
align-items: center;
& > * {
margin-right: var(--padding-md);
}
h2 {
margin: 0 auto 0 0;
font-size: var(--font-size-xl);
}
}
}
@@ -19,6 +19,7 @@ html {
--padding-xl: calc(var(--font-size) * 5);
/* Wrapper */
--line-height-lg: calc(var(--font-size) * 4.5);
--line-height-md: calc(var(--font-size) * 3.6);
--line-height-sm: calc(var(--font-size) * 2.88);
--line-height-xs: calc(var(--font-size) * 2.448);
@@ -227,6 +227,7 @@
--padding-xl: calc(var(--font-size) * 5);
/* Wrapper */
--line-height-lg: calc(var(--font-size) * 4.5);
--line-height-md: calc(var(--font-size) * 3.6);
--line-height-sm: calc(var(--font-size) * 2.88);
--line-height-xs: calc(var(--font-size) * 2.448);
@@ -45,4 +45,15 @@ describe('run', () => {
expect(sendRequest).toHaveBeenCalledWith('req_123');
expect(stats.passes).toBe(1);
});
it('throws on invalid JavaScript', async () => {
let err;
try {
await runTests('this is invalid');
} catch (e) {
err = e;
}
expect(err).not.toBeNull();
});
});
+19 -15
View File
@@ -58,7 +58,7 @@ async function runInternal<T>(
reporter: Reporter | Function,
extractResult: (runner: Object) => T,
): Promise<T> {
return new Promise(resolve => {
return new Promise((resolve, reject) => {
const { bail } = options;
// Add global `insomnia` helper.
// This is the only way to add new globals to the Mocha environment as far
@@ -78,22 +78,26 @@ async function runInternal<T>(
mocha.addFile(writeTempFile(src));
}
const runner = mocha.run(() => {
resolve(extractResult(runner));
try {
const runner = mocha.run(() => {
resolve(extractResult(runner));
// Remove global since we don't need it anymore
delete global.insomnia;
delete global.chai;
// Remove global since we don't need it anymore
delete global.insomnia;
delete global.chai;
// Clean up temp files
for (const f of mocha.files) {
fs.unlink(f, err => {
if (err) {
console.log('Failed to clean up test file', f, err);
}
});
}
});
// Clean up temp files
for (const f of mocha.files) {
fs.unlink(f, err => {
if (err) {
console.log('Failed to clean up test file', f, err);
}
});
}
});
} catch (err) {
reject(err);
}
});
}