mirror of
https://github.com/Kong/insomnia.git
synced 2026-10-23 15:19:04 -04:00
POC for Unit Test UI (#2315)
* 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 * More PR comment addresses
This commit is contained in:
1 parent
48348e04c1
commit
3883dc6feb
30 files changed
+1086
-88
No files matched your search
@@ -14,6 +14,9 @@ import * as _requestVersion from './request-version';
|
||||
import * as _response from './response';
|
||||
import * as _settings from './settings';
|
||||
import * as _stats from './stats';
|
||||
import * as _unitTest from './unit-test';
|
||||
import * as _unitTestResult from './unit-test-result';
|
||||
import * as _unitTestSuite from './unit-test-suite';
|
||||
import * as _workspace from './workspace';
|
||||
import * as _workspaceMeta from './workspace-meta';
|
||||
import { generateId } from '../common/misc';
|
||||
@@ -42,6 +45,9 @@ export const requestVersion = _requestVersion;
|
||||
export const response = _response;
|
||||
export const settings = _settings;
|
||||
export const stats = _stats;
|
||||
export const unitTest = _unitTest;
|
||||
export const unitTestSuite = _unitTestSuite;
|
||||
export const unitTestResult = _unitTestResult;
|
||||
export const workspace = _workspace;
|
||||
export const workspaceMeta = _workspaceMeta;
|
||||
|
||||
@@ -64,6 +70,9 @@ export function all() {
|
||||
oAuth2Token,
|
||||
clientCertificate,
|
||||
pluginData,
|
||||
unitTestSuite,
|
||||
unitTestResult,
|
||||
unitTest,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ type BaseSettings = {
|
||||
|
||||
// Feature flags
|
||||
enableSyncBeta: boolean,
|
||||
enableUnitTestingBeta: boolean,
|
||||
};
|
||||
|
||||
export type Settings = BaseModel & BaseSettings;
|
||||
@@ -112,6 +113,7 @@ export function init(): BaseSettings {
|
||||
|
||||
// Feature flags
|
||||
enableSyncBeta: false,
|
||||
enableUnitTestingBeta: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// @flow
|
||||
import * as db from '../common/database';
|
||||
import type { BaseModel } from './index';
|
||||
|
||||
export const name = 'Unit Test Result';
|
||||
export const type = 'UnitTestResult';
|
||||
export const prefix = 'utr';
|
||||
export const canDuplicate = false;
|
||||
export const canSync = false;
|
||||
|
||||
type BaseUnitTestResult = {
|
||||
results: Object,
|
||||
};
|
||||
|
||||
export type UnitTestResult = BaseModel & BaseUnitTestResult;
|
||||
|
||||
export function init() {
|
||||
return {
|
||||
results: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function migrate(doc: UnitTestResult): UnitTestResult {
|
||||
return doc;
|
||||
}
|
||||
|
||||
export function create(patch: $Shape<UnitTestResult> = {}) {
|
||||
if (!patch.parentId) {
|
||||
throw new Error('New UnitTestResult missing `parentId` ' + JSON.stringify(patch));
|
||||
}
|
||||
|
||||
return db.docCreate(type, patch);
|
||||
}
|
||||
|
||||
export function update(unitTest: UnitTestResult, patch: $Shape<UnitTestResult>) {
|
||||
return db.docUpdate(unitTest, patch);
|
||||
}
|
||||
|
||||
export function getByParentId(parentId: string) {
|
||||
return db.getWhere(type, { parentId });
|
||||
}
|
||||
|
||||
export function all(): Promise<Array<UnitTestResult>> {
|
||||
return db.all(type);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// @flow
|
||||
import * as db from '../common/database';
|
||||
import type { BaseModel } from './index';
|
||||
|
||||
export const name = 'Unit Test Suite';
|
||||
export const type = 'UnitTestSuite';
|
||||
export const prefix = 'uts';
|
||||
export const canDuplicate = true;
|
||||
export const canSync = true;
|
||||
|
||||
type BaseUnitTestSuite = {
|
||||
name: string,
|
||||
};
|
||||
|
||||
export type UnitTestSuite = BaseModel & BaseUnitTestSuite;
|
||||
|
||||
export function init() {
|
||||
return {
|
||||
name: 'My Test',
|
||||
};
|
||||
}
|
||||
|
||||
export function migrate(doc: UnitTestSuite): UnitTestSuite {
|
||||
return doc;
|
||||
}
|
||||
|
||||
export function create(patch: $Shape<UnitTestSuite> = {}) {
|
||||
if (!patch.parentId) {
|
||||
throw new Error('New UnitTestSuite missing `parentId` ' + JSON.stringify(patch));
|
||||
}
|
||||
|
||||
return db.docCreate(type, patch);
|
||||
}
|
||||
|
||||
export function update(
|
||||
unitTestSuite: UnitTestSuite,
|
||||
patch: $Shape<UnitTestSuite> = {},
|
||||
): Promise<UnitTestSuite> {
|
||||
return db.docUpdate(unitTestSuite, patch);
|
||||
}
|
||||
|
||||
export function remove(unitTestSuite: UnitTestSuite): Promise<void> {
|
||||
return db.remove(unitTestSuite);
|
||||
}
|
||||
|
||||
export function getByParentId(parentId: string): Promise<UnitTestSuite | null> {
|
||||
return db.getWhere(type, { parentId });
|
||||
}
|
||||
|
||||
export function all(): Promise<Array<UnitTestSuite>> {
|
||||
return db.all(type);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// @flow
|
||||
import * as db from '../common/database';
|
||||
import type { BaseModel } from './index';
|
||||
|
||||
export const name = 'Unit Test';
|
||||
export const type = 'UnitTest';
|
||||
export const prefix = 'ut';
|
||||
export const canDuplicate = true;
|
||||
export const canSync = true;
|
||||
|
||||
type BaseUnitTest = {
|
||||
name: string,
|
||||
code: string,
|
||||
requestId: string | null,
|
||||
};
|
||||
|
||||
export type UnitTest = BaseModel & BaseUnitTest;
|
||||
|
||||
export function init() {
|
||||
return {
|
||||
requestId: null,
|
||||
name: 'My Test',
|
||||
code: '',
|
||||
};
|
||||
}
|
||||
|
||||
export function migrate(doc: UnitTest): UnitTest {
|
||||
return doc;
|
||||
}
|
||||
|
||||
export function create(patch: $Shape<UnitTest> = {}): Promise<UnitTest> {
|
||||
if (!patch.parentId) {
|
||||
throw new Error('New UnitTest missing `parentId` ' + JSON.stringify(patch));
|
||||
}
|
||||
|
||||
return db.docCreate(type, patch);
|
||||
}
|
||||
|
||||
export function remove(unitTest: UnitTest): Promise<void> {
|
||||
return db.remove(unitTest);
|
||||
}
|
||||
|
||||
export function update(unitTest: UnitTest, patch: $Shape<UnitTest> = {}): Promise<UnitTest> {
|
||||
return db.docUpdate(unitTest, patch);
|
||||
}
|
||||
|
||||
export function getByParentId(parentId: string): Promise<UnitTest | null> {
|
||||
return db.getWhere(type, { parentId });
|
||||
}
|
||||
|
||||
export function all(): Promise<Array<UnitTest>> {
|
||||
return db.all(type);
|
||||
}
|
||||
@@ -14,41 +14,43 @@ export const canDuplicate = false;
|
||||
export const canSync = false;
|
||||
|
||||
type BaseWorkspaceMeta = {
|
||||
activeRequestId: string | null,
|
||||
activeEnvironmentId: string | null,
|
||||
activeActivity: string | null,
|
||||
sidebarFilter: string,
|
||||
sidebarHidden: boolean,
|
||||
previewHidden: boolean,
|
||||
sidebarWidth: number,
|
||||
paneWidth: number,
|
||||
paneHeight: number,
|
||||
hasSeen: boolean,
|
||||
gitRepositoryId: string | null,
|
||||
cachedGitRepositoryBranch: string | null,
|
||||
activeEnvironmentId: string | null,
|
||||
activeRequestId: string | null,
|
||||
activeUnitTestSuiteId: string | null,
|
||||
cachedGitLastAuthor: string | null,
|
||||
cachedGitLastCommitTime: number | null,
|
||||
cachedGitRepositoryBranch: string | null,
|
||||
gitRepositoryId: string | null,
|
||||
hasSeen: boolean,
|
||||
paneHeight: number,
|
||||
paneWidth: number,
|
||||
previewHidden: boolean,
|
||||
sidebarFilter: string,
|
||||
sidebarHidden: boolean,
|
||||
sidebarWidth: number,
|
||||
};
|
||||
|
||||
export type WorkspaceMeta = BaseWorkspaceMeta & BaseModel;
|
||||
|
||||
export function init(): BaseWorkspaceMeta {
|
||||
return {
|
||||
parentId: null,
|
||||
activeRequestId: null,
|
||||
activeEnvironmentId: null,
|
||||
activeActivity: null,
|
||||
sidebarFilter: '',
|
||||
sidebarHidden: false,
|
||||
previewHidden: false,
|
||||
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
|
||||
paneWidth: DEFAULT_PANE_WIDTH,
|
||||
paneHeight: DEFAULT_PANE_HEIGHT,
|
||||
hasSeen: true,
|
||||
gitRepositoryId: null,
|
||||
cachedGitRepositoryBranch: null,
|
||||
activeEnvironmentId: null,
|
||||
activeRequestId: null,
|
||||
activeUnitTestSuiteId: null,
|
||||
cachedGitLastAuthor: null,
|
||||
cachedGitLastCommitTime: null,
|
||||
cachedGitRepositoryBranch: null,
|
||||
gitRepositoryId: null,
|
||||
hasSeen: true,
|
||||
paneHeight: DEFAULT_PANE_HEIGHT,
|
||||
paneWidth: DEFAULT_PANE_WIDTH,
|
||||
parentId: null,
|
||||
previewHidden: false,
|
||||
sidebarFilter: '',
|
||||
sidebarHidden: false,
|
||||
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ const WHITE_LIST = {
|
||||
[models.request.type]: true,
|
||||
[models.requestGroup.type]: true,
|
||||
[models.environment.type]: true,
|
||||
[models.unitTest.type]: true,
|
||||
[models.unitTestSuite.type]: true,
|
||||
|
||||
// These can be overridden in sync config
|
||||
[models.cookieJar.type]: true,
|
||||
|
||||
@@ -39,6 +39,8 @@ describe.each(['win32', 'posix'])('NeDBPlugin using path.%s', type => {
|
||||
models.environment.type,
|
||||
models.request.type,
|
||||
models.requestGroup.type,
|
||||
models.unitTest.type,
|
||||
models.unitTestSuite.type,
|
||||
models.workspace.type,
|
||||
]);
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ export default class NeDBPlugin {
|
||||
models.requestGroup.type,
|
||||
models.request.type,
|
||||
models.apiSpec.type,
|
||||
models.unitTestSuite.type,
|
||||
models.unitTest.type,
|
||||
];
|
||||
} else if (type !== null && id === null) {
|
||||
const workspace = await db.get(models.workspace.type, this._workspaceId);
|
||||
|
||||
@@ -54,6 +54,7 @@ import './modes/nunjucks';
|
||||
import './modes/curl';
|
||||
import './modes/openapi';
|
||||
import './lint/openapi';
|
||||
import './lint/javascript-async-lint';
|
||||
import './extensions/autocomplete';
|
||||
import './extensions/clickable';
|
||||
import './extensions/nunjucks-tags';
|
||||
@@ -62,6 +63,6 @@ import './extensions/nunjucks-tags';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import '../../css/editor/index.less';
|
||||
|
||||
// Make jsonlint available to the jsonlint plugin
|
||||
// Make jsonlint available to the jsonlint addon
|
||||
import { parser as jsonlint } from 'jsonlint';
|
||||
global.jsonlint = jsonlint;
|
||||
@@ -0,0 +1,72 @@
|
||||
// This linter is a fork of Codemirror's built-in javascript linter that enables top-level
|
||||
// await usage. It does this by wrapping the text in an async function, then offsetting any
|
||||
// lint errors to account for the wrapper.
|
||||
//
|
||||
// The major modifications are marked in comments starting with "CHANGED ---"
|
||||
|
||||
import CodeMirror from 'codemirror';
|
||||
import { JSHINT } from 'jshint';
|
||||
|
||||
CodeMirror.registerHelper('lint', 'javascript', validator);
|
||||
|
||||
function validator(text, options) {
|
||||
if (!options.indent) {
|
||||
// JSHint error.character actually is a column index, this fixes underlining on lines using tabs for indentation
|
||||
options.indent = 1;
|
||||
} // JSHint default value is 4
|
||||
|
||||
// CHANGED ----------------------------------------------------------- //
|
||||
// Wrap text with async function to "enable" top-level await
|
||||
const textWithWrapper = `async function asyncWrapper() {\n${text}\n}`;
|
||||
|
||||
JSHINT(textWithWrapper, options, options.globals);
|
||||
const errors = JSHINT.data().errors;
|
||||
const result = [];
|
||||
|
||||
if (errors) {
|
||||
parseErrors(errors, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseErrors(errors, output) {
|
||||
for (let i = 0; i < errors.length; i++) {
|
||||
const error = errors[i];
|
||||
if (error) {
|
||||
if (error.line <= 0) {
|
||||
if (window.console) {
|
||||
window.console.warn(
|
||||
'Cannot display JSHint error (invalid line ' + error.line + ')',
|
||||
error,
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const start = error.character - 1;
|
||||
let end = start + 1;
|
||||
if (error.evidence) {
|
||||
const index = error.evidence.substring(start).search(/.\b/);
|
||||
if (index > -1) {
|
||||
end += index;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to format expected by validation service
|
||||
const hint = {
|
||||
message: error.reason,
|
||||
severity: error.code ? (error.code.startsWith('W') ? 'warning' : 'error') : 'error',
|
||||
from: CodeMirror.Pos(error.line - 1, start),
|
||||
to: CodeMirror.Pos(error.line - 1, end),
|
||||
};
|
||||
|
||||
// CHANGED ----------------------------------------------------------- //
|
||||
// Offset an extra line to account for async wrapper function
|
||||
hint.from.line -= 1;
|
||||
hint.to.line -= 1;
|
||||
|
||||
output.push(hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,6 @@ class PageLayout extends React.PureComponent<Props, State> {
|
||||
enableSyncBeta={settings.enableSyncBeta}
|
||||
environmentHighlightColorStyle={settings.environmentHighlightColorStyle}
|
||||
handleInitializeEntities={handleInitializeEntities}
|
||||
handleNavigateHome={this._handleSetHomeActivity}
|
||||
handleSetActiveEnvironment={handleSetActiveEnvironment}
|
||||
handleSetActiveWorkspace={handleSetActiveWorkspace}
|
||||
hidden={sidebarHidden || false}
|
||||
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
} from '../../models/request';
|
||||
import type { Workspace } from '../../models/workspace';
|
||||
import type { OAuth2Token } from '../../models/o-auth-2-token';
|
||||
|
||||
import autobind from 'autobind-decorator';
|
||||
import { deconstructQueryStringToParams, extractQueryStringFromUrl } from 'insomnia-url';
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -31,6 +31,7 @@ import type { RequestVersion } from '../../models/request-version';
|
||||
import { showError } from '../components/modals/index';
|
||||
import { json as jsonPrettify } from 'insomnia-prettify';
|
||||
import type { Environment } from '../../models/environment';
|
||||
import type { UnitTestResult } from '../../models/unit-test-result';
|
||||
|
||||
type Props = {
|
||||
// Functions
|
||||
@@ -61,6 +62,7 @@ type Props = {
|
||||
request: ?Request,
|
||||
response: ?Response,
|
||||
environment: ?Environment,
|
||||
unitTestResult: ?UnitTestResult,
|
||||
};
|
||||
|
||||
@autobind
|
||||
|
||||
@@ -4,11 +4,13 @@ import * as fontScanner from 'font-manager';
|
||||
import * as electron from 'electron';
|
||||
import autobind from 'autobind-decorator';
|
||||
import HelpTooltip from '../help-tooltip';
|
||||
import type { HttpVersion } from '../../../common/constants';
|
||||
import {
|
||||
EDITOR_KEY_MAP_DEFAULT,
|
||||
EDITOR_KEY_MAP_EMACS,
|
||||
EDITOR_KEY_MAP_SUBLIME,
|
||||
EDITOR_KEY_MAP_VIM,
|
||||
getAppId,
|
||||
HttpVersions,
|
||||
isLinux,
|
||||
isMac,
|
||||
@@ -21,7 +23,7 @@ import { setFont } from '../../../plugins/misc';
|
||||
import * as session from '../../../account/session';
|
||||
import Tooltip from '../tooltip';
|
||||
import CheckForUpdatesButton from '../check-for-updates-button';
|
||||
import type { HttpVersion } from '../../../common/constants';
|
||||
import { APP_ID_DESIGNER } from '../../../../config';
|
||||
|
||||
// Font family regex to match certain monospace fonts that don't get
|
||||
// recognized as monospace
|
||||
@@ -479,13 +481,6 @@ class General extends React.PureComponent<Props, State> {
|
||||
|
||||
<br />
|
||||
|
||||
{session.isLoggedIn() && (
|
||||
<React.Fragment>
|
||||
<hr />
|
||||
{this.renderBooleanSetting('Enable version control beta', 'enableSyncBeta', '', true)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<hr className="pad-top" />
|
||||
<h2>Data Sharing</h2>
|
||||
<div className="form-control form-control--thin">
|
||||
@@ -508,6 +503,25 @@ class General extends React.PureComponent<Props, State> {
|
||||
as request data, names, etc.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{session.isLoggedIn() && (
|
||||
<React.Fragment>
|
||||
<hr />
|
||||
{this.renderBooleanSetting('Enable version control beta', 'enableSyncBeta', '', true)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{getAppId() === APP_ID_DESIGNER && (
|
||||
<>
|
||||
<hr />
|
||||
{this.renderBooleanSetting(
|
||||
'Enable unit testing beta',
|
||||
'enableUnitTestingBeta',
|
||||
'',
|
||||
false,
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ type Props = {|
|
||||
children: React.Node,
|
||||
enableSyncBeta: boolean,
|
||||
environmentHighlightColorStyle: string,
|
||||
handleNavigateHome: () => any,
|
||||
handleSetActiveEnvironment: Function,
|
||||
handleSetActiveWorkspace: Function,
|
||||
hidden: boolean,
|
||||
|
||||
@@ -120,6 +120,8 @@ class WrapperDebug extends React.PureComponent<Props> {
|
||||
activeRequest,
|
||||
activeRequestResponses,
|
||||
activeResponse,
|
||||
activeUnitTest,
|
||||
activeUnitTestResult,
|
||||
activeWorkspace,
|
||||
environments,
|
||||
enableSyncBeta,
|
||||
@@ -265,6 +267,7 @@ class WrapperDebug extends React.PureComponent<Props> {
|
||||
nunjucksPowerUserMode={settings.nunjucksPowerUserMode}
|
||||
oAuth2Token={oAuth2Token}
|
||||
request={activeRequest}
|
||||
unitTest={activeUnitTest}
|
||||
settings={settings}
|
||||
updateRequestAuthentication={handleUpdateRequestAuthentication}
|
||||
updateRequestBody={handleUpdateRequestBody}
|
||||
@@ -320,6 +323,7 @@ class WrapperDebug extends React.PureComponent<Props> {
|
||||
requestVersions={requestVersions}
|
||||
response={activeResponse}
|
||||
responses={activeRequestResponses}
|
||||
unitTestResult={activeUnitTestResult}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -19,9 +19,8 @@ import generateConfigIcon from '../images/icn-gear.svg';
|
||||
import * as models from '../../models/index';
|
||||
import { parseApiSpec } from '../../common/api-specs';
|
||||
import { getConfigGenerators } from '../../plugins';
|
||||
import AlertModal from './modals/alert-modal';
|
||||
import { ACTIVITY_HOME } from '../../common/constants';
|
||||
import type { GlobalActivity } from '../../common/constants';
|
||||
import { ACTIVITY_HOME } from '../../common/constants';
|
||||
import ActivityToggle from './activity-toggle';
|
||||
|
||||
const spectral = new Spectral();
|
||||
@@ -73,32 +72,6 @@ class WrapperDesign extends React.PureComponent<Props, State> {
|
||||
showModal(GenerateConfigModal, { apiSpec: activeApiSpec });
|
||||
}
|
||||
|
||||
async _handleDebugSpec(errors, e): Pomise<void> {
|
||||
e.preventDefault();
|
||||
if (errors) {
|
||||
showModal(AlertModal, {
|
||||
title: 'Error Generating Configuration',
|
||||
message:
|
||||
'Some requests may not be available due to errors found in the specification. We recommend fixing errors before proceeding. 🤗',
|
||||
okLabel: 'Proceed',
|
||||
addCancel: true,
|
||||
onConfirm: async () => {
|
||||
const {
|
||||
handleSetDebugActivity,
|
||||
wrapperProps: { activeApiSpec },
|
||||
} = this.props;
|
||||
await handleSetDebugActivity(activeApiSpec);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const {
|
||||
handleSetDebugActivity,
|
||||
wrapperProps: { activeApiSpec },
|
||||
} = this.props;
|
||||
await handleSetDebugActivity(activeApiSpec);
|
||||
}
|
||||
}
|
||||
|
||||
async _handleTogglePreview() {
|
||||
await this.setState(
|
||||
prevState => ({ previewHidden: !prevState.previewHidden }),
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import autobind from 'autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import PageLayout from './page-layout';
|
||||
import { Breadcrumb, Button, Dropdown, DropdownItem, Header, SvgIcon } from 'insomnia-components';
|
||||
import ErrorBoundary from './error-boundary';
|
||||
import CodeEditor from './codemirror/code-editor';
|
||||
import designerLogo from '../images/insomnia-designer-logo.svg';
|
||||
import type { WrapperProps } from './wrapper';
|
||||
import * as models from '../../models';
|
||||
import type { UnitTest } from '../../models/unit-test';
|
||||
import { generate, runTests } from 'insomnia-testing';
|
||||
import { showAlert, showModal, showPrompt } from './modals';
|
||||
import Editable from './base/editable';
|
||||
import type { SidebarChildObjects } from './sidebar/sidebar-children';
|
||||
import SelectModal from './modals/select-modal';
|
||||
import type { UnitTestSuite } from '../../models/unit-test-suite';
|
||||
import ActivityToggle from './activity-toggle';
|
||||
import { getSendRequestCallback } from '../../common/send-request';
|
||||
import type { GlobalActivity } from '../../common/constants';
|
||||
import { ACTIVITY_HOME } from '../../common/constants';
|
||||
|
||||
type Props = {|
|
||||
children: SidebarChildObjects,
|
||||
gitSyncDropdown: React.Node,
|
||||
handleActivityChange: (workspaceId: string, activity: GlobalActivity) => Promise<void>,
|
||||
wrapperProps: WrapperProps,
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
testsRunning: Array<UnitTest> | null,
|
||||
|};
|
||||
|
||||
@autobind
|
||||
class WrapperUnitTest extends React.PureComponent<Props, State> {
|
||||
state = {
|
||||
testsRunning: null,
|
||||
};
|
||||
|
||||
// Defining it here instead of in render() so it won't act as a changed prop
|
||||
// when being passed to <CodeEditor> again
|
||||
static lintOptions = {
|
||||
globals: {
|
||||
// https://jshint.com/docs/options/
|
||||
insomnia: true,
|
||||
expect: true,
|
||||
chai: true,
|
||||
debugger: true,
|
||||
},
|
||||
asi: true, // Don't require semicolons
|
||||
undef: true, // Prevent undefined usages
|
||||
node: true, // Enable NodeJS globals
|
||||
esversion: 8, // ES8 syntax (async/await, etc)
|
||||
};
|
||||
|
||||
generateSendReqSnippet(existingCode: string, requestId: string): string {
|
||||
let variableName = 'response';
|
||||
|
||||
for (let i = 1; i < 100; i++) {
|
||||
variableName = `response${i}`;
|
||||
|
||||
// Try next one if code already contains this variable
|
||||
if (existingCode.includes(`const ${variableName} =`)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Found variable that doesn't exist in code yet
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
`const ${variableName} = await insomnia.send(${requestId});\n` +
|
||||
`expect(${variableName}.status).to.equal(200);`
|
||||
);
|
||||
}
|
||||
|
||||
autocompleteSnippets(unitTest: UnitTest): Array<{ name: string, value: () => Promise<string> }> {
|
||||
return [
|
||||
{
|
||||
name: 'Send Current Request',
|
||||
displayValue: '',
|
||||
value: this.generateSendReqSnippet(unitTest.code, ''),
|
||||
},
|
||||
{
|
||||
name: 'Send Request By ID',
|
||||
displayValue: '',
|
||||
value: async () => {
|
||||
return new Promise(resolve => {
|
||||
showModal(SelectModal, {
|
||||
title: 'Select Request',
|
||||
message: 'Select a request to fill',
|
||||
value: '__NULL__',
|
||||
options: [
|
||||
{ name: '-- Select Request --', value: '__NULL__' },
|
||||
...this.buildSelectableRequests().map(({ name, request }) => ({
|
||||
name: name,
|
||||
displayValue: '',
|
||||
value: this.generateSendReqSnippet(unitTest.code, `'${request._id}'`),
|
||||
})),
|
||||
],
|
||||
onDone: v => resolve(v),
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async _handleCreateTestSuite(): Promise<void> {
|
||||
const { activeWorkspace } = this.props.wrapperProps;
|
||||
showPrompt({
|
||||
title: 'New Test Suite',
|
||||
defaultValue: 'New Suite',
|
||||
submitName: 'Create Suite',
|
||||
label: 'Test Suite Name',
|
||||
selectText: true,
|
||||
onComplete: async name => {
|
||||
const unitTestSuite = await models.unitTestSuite.create({
|
||||
parentId: activeWorkspace._id,
|
||||
name,
|
||||
});
|
||||
await this._handleSetActiveUnitTestSuite(unitTestSuite);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async _handleCreateTest(): Promise<void> {
|
||||
const { activeUnitTestSuite } = this.props.wrapperProps;
|
||||
showPrompt({
|
||||
title: 'New Test',
|
||||
defaultValue: 'Returns 200',
|
||||
submitName: 'Create Test',
|
||||
label: 'Test Name',
|
||||
selectText: true,
|
||||
onComplete: async name => {
|
||||
await models.unitTest.create({
|
||||
parentId: activeUnitTestSuite._id,
|
||||
code: this.generateSendReqSnippet('', ''),
|
||||
name,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async _handleUnitTestCodeChange(unitTest: UnitTest, v: string): Promise<void> {
|
||||
await models.unitTest.update(unitTest, { code: v });
|
||||
}
|
||||
|
||||
async _handleBreadcrumb(): void {
|
||||
const {
|
||||
handleActivityChange,
|
||||
wrapperProps: { activeWorkspace },
|
||||
} = this.props;
|
||||
await handleActivityChange(activeWorkspace._id, ACTIVITY_HOME);
|
||||
}
|
||||
|
||||
async _handleRunTests(): Promise<void> {
|
||||
const { activeUnitTests } = this.props.wrapperProps;
|
||||
await this._runTests(activeUnitTests);
|
||||
}
|
||||
|
||||
async _handleRunTest(unitTest: UnitTest): Promise<void> {
|
||||
await this._runTests([unitTest]);
|
||||
}
|
||||
|
||||
async _handleDeleteTest(unitTest: UnitTest): Promise<void> {
|
||||
await models.unitTest.remove(unitTest);
|
||||
}
|
||||
|
||||
async _handleSetActiveRequest(
|
||||
unitTest: UnitTest,
|
||||
e: SyntheticEvent<HTMLSelectElement>,
|
||||
): Promise<void> {
|
||||
const requestId = e.currentTarget.value === '__NULL__' ? null : e.currentTarget.value;
|
||||
await models.unitTest.update(unitTest, { requestId });
|
||||
}
|
||||
|
||||
async _handleDeleteUnitTestSuite(unitTestSuite: UnitTestSuite): Promise<void> {
|
||||
showAlert({
|
||||
title: `Delete ${unitTestSuite.name}`,
|
||||
message: (
|
||||
<span>
|
||||
Really delete <strong>{unitTestSuite.name}</strong>?
|
||||
</span>
|
||||
),
|
||||
addCancel: true,
|
||||
onConfirm: async () => {
|
||||
await models.unitTestSuite.remove(unitTestSuite);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async _handleSetActiveUnitTestSuite(unitTestSuite: UnitTestSuite): Promise<void> {
|
||||
const { activeWorkspace } = this.props.wrapperProps;
|
||||
await models.workspaceMeta.updateByParentId(activeWorkspace._id, {
|
||||
activeUnitTestSuiteId: unitTestSuite._id,
|
||||
});
|
||||
}
|
||||
|
||||
async _handleChangeTestName(unitTest: UnitTest, name: string): Promise<void> {
|
||||
await models.unitTest.update(unitTest, { name });
|
||||
}
|
||||
|
||||
async _runTests(unitTests: Array<UnitTest>): Promise<void> {
|
||||
const { requests, activeWorkspace, activeEnvironment } = this.props.wrapperProps;
|
||||
|
||||
this.setState({ testsRunning: unitTests });
|
||||
|
||||
const tests = [];
|
||||
for (const t of unitTests) {
|
||||
tests.push({
|
||||
name: t.name,
|
||||
code: t.code,
|
||||
defaultRequestId: t.requestId,
|
||||
});
|
||||
}
|
||||
|
||||
const src = await generate([{ name: 'My Suite', suites: [], tests }]);
|
||||
const results = await runTests(src, {
|
||||
requests,
|
||||
sendRequest: getSendRequestCallback(activeEnvironment ? activeEnvironment._id : null),
|
||||
});
|
||||
|
||||
await models.unitTestResult.create({ results, parentId: activeWorkspace._id });
|
||||
|
||||
this.setState({ testsRunning: null });
|
||||
}
|
||||
|
||||
buildSelectableRequests(): Array<{ name: string, request: Request }> {
|
||||
const { children } = this.props;
|
||||
const selectableRequests: Array<{ name: string, request: Request }> = [];
|
||||
|
||||
const next = (p, children) => {
|
||||
for (const c of children) {
|
||||
if (c.doc.type === models.request.type) {
|
||||
selectableRequests.push({
|
||||
name: `${p} [${c.doc.method}] ${c.doc.name}`,
|
||||
request: c.doc,
|
||||
});
|
||||
} else if (c.doc.type === models.requestGroup.type) {
|
||||
next(c.doc.name + ' / ', c.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
next('', children.all);
|
||||
|
||||
return selectableRequests;
|
||||
}
|
||||
|
||||
renderResults(): React.Node {
|
||||
const { activeUnitTestResult } = this.props.wrapperProps;
|
||||
const { testsRunning } = this.state;
|
||||
|
||||
if (testsRunning) {
|
||||
return (
|
||||
<div className="unit-tests__results">
|
||||
<h2>Running {testsRunning.length} Tests...</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!activeUnitTestResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { stats, tests } = activeUnitTestResult.results;
|
||||
|
||||
return (
|
||||
<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>
|
||||
)}
|
||||
<ul>
|
||||
{tests.map((t, i) => (
|
||||
<li key={i}>
|
||||
<SvgIcon icon={t.err.message ? 'error' : 'success'} /> {t.fullTitle} ({t.duration}{' '}
|
||||
ms)
|
||||
{t.err.message && (
|
||||
<>
|
||||
<br />
|
||||
<code className="text-danger">{t.err.message}</code>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderUnitTest(unitTest: UnitTest): React.Node {
|
||||
const { settings } = this.props.wrapperProps;
|
||||
const { testsRunning } = this.state;
|
||||
|
||||
const selectableRequests = this.buildSelectableRequests();
|
||||
|
||||
return (
|
||||
<div key={unitTest._id} className="unit-tests__tests__block">
|
||||
<div className="unit-tests__tests__block__header">
|
||||
<h2 className="pad-left-md">
|
||||
<Editable
|
||||
singleClick
|
||||
onSubmit={this._handleChangeTestName.bind(this, unitTest)}
|
||||
value={unitTest.name}
|
||||
/>
|
||||
</h2>
|
||||
<div className="form-control form-control--outlined">
|
||||
<select
|
||||
name="request"
|
||||
id="request"
|
||||
onChange={this._handleSetActiveRequest.bind(this, unitTest)}
|
||||
value={unitTest.requestId || '__NULL__'}>
|
||||
<option value="__NULL__">
|
||||
{selectableRequests.length ? '-- Select Request --' : '-- No Requests --'}
|
||||
</option>
|
||||
{selectableRequests.map(({ name, request }) => (
|
||||
<option key={request._id} value={request._id}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<Dropdown
|
||||
renderButton={() => (
|
||||
<Button variant="outlined">
|
||||
<SvgIcon icon="gear" />
|
||||
</Button>
|
||||
)}>
|
||||
<DropdownItem
|
||||
disabled={testsRunning && testsRunning.find(t => t._id === unitTest._id)}
|
||||
onClick={() => this._handleRunTest(unitTest)}>
|
||||
Run Test
|
||||
</DropdownItem>
|
||||
<DropdownItem onClick={() => this._handleDeleteTest(unitTest)}>Delete</DropdownItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<CodeEditor
|
||||
dynamicHeight
|
||||
manualPrettify
|
||||
fontSize={settings.editorFontSize}
|
||||
indentSize={settings.editorIndentSize}
|
||||
indentWithTabs={settings.editorIndentWithTabs}
|
||||
keyMap={settings.editorKeyMap}
|
||||
defaultValue={unitTest ? unitTest.code : ''}
|
||||
getAutocompleteSnippets={() => this.autocompleteSnippets(unitTest)}
|
||||
lintOptions={WrapperUnitTest.lintOptions}
|
||||
onChange={this._handleUnitTestCodeChange.bind(this, unitTest)}
|
||||
nunjucksPowerUserMode={settings.nunjucksPowerUserMode}
|
||||
isVariableUncovered={settings.isVariableUncovered}
|
||||
mode="javascript"
|
||||
lineWrapping={settings.editorLineWrapping}
|
||||
placeholder=""
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPageBody(): React.Node {
|
||||
const { activeUnitTests, activeUnitTestSuite } = this.props.wrapperProps;
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{this.renderResults()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPageSidebar(): React.Node {
|
||||
const { activeUnitTestSuites, activeUnitTestSuite } = this.props.wrapperProps;
|
||||
const activeId = activeUnitTestSuite ? activeUnitTestSuite._id : 'n/a';
|
||||
|
||||
return (
|
||||
<ErrorBoundary showAlert>
|
||||
<div className="unit-tests__sidebar">
|
||||
<div className="pad-sm">
|
||||
<Button variant="outlined" bg="surprise" onClick={this._handleCreateTestSuite}>
|
||||
New Test Suite
|
||||
</Button>
|
||||
</div>
|
||||
<ul>
|
||||
{activeUnitTestSuites.map(s => (
|
||||
<li key={s._id} className={classnames({ active: s._id === activeId })}>
|
||||
<button key={s._id} onClick={this._handleSetActiveUnitTestSuite.bind(this, s)}>
|
||||
{s.name}
|
||||
</button>
|
||||
<Dropdown
|
||||
right
|
||||
renderButton={() => (
|
||||
<button className="unit-tests__sidebar__action">
|
||||
<i className="fa fa-caret-down" />
|
||||
</button>
|
||||
)}>
|
||||
<DropdownItem onClick={this._handleDeleteUnitTestSuite.bind(this, s)}>
|
||||
Delete Suite
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { handleActivityChange, gitSyncDropdown } = this.props;
|
||||
const { activeWorkspace, activity, settings, activeApiSpec } = this.props.wrapperProps;
|
||||
const { testsRunning } = this.state;
|
||||
return (
|
||||
<PageLayout
|
||||
wrapperProps={this.props.wrapperProps}
|
||||
renderPageSidebar={this.renderPageSidebar}
|
||||
renderPageBody={this.renderPageBody}
|
||||
renderPageHeader={() => (
|
||||
<Header
|
||||
className="app-header"
|
||||
gridLeft={
|
||||
<React.Fragment>
|
||||
<img src={designerLogo} alt="Insomnia" width="32" height="32" />
|
||||
<Breadcrumb
|
||||
className="breadcrumb"
|
||||
crumbs={['Documents', activeApiSpec.fileName]}
|
||||
onClick={this._handleBreadcrumb}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
gridCenter={
|
||||
<ActivityToggle
|
||||
activity={activity}
|
||||
handleActivityChange={handleActivityChange}
|
||||
settings={settings}
|
||||
workspace={activeWorkspace}
|
||||
/>
|
||||
}
|
||||
gridRight={
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={this._handleRunTests}
|
||||
size="default"
|
||||
disabled={testsRunning}>
|
||||
{testsRunning ? 'Running... ' : 'Run Tests'}
|
||||
</Button>
|
||||
{gitSyncDropdown}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WrapperUnitTest;
|
||||
@@ -12,7 +12,6 @@ import type {
|
||||
RequestParameter,
|
||||
} from '../../models/request';
|
||||
import type { SidebarChildObjects } from './sidebar/sidebar-children';
|
||||
|
||||
import * as React from 'react';
|
||||
import autobind from 'autobind-decorator';
|
||||
import { registerModal, showModal } from './modals/index';
|
||||
@@ -71,21 +70,26 @@ import { trackPageView } from '../../common/analytics';
|
||||
import type { GitRepository } from '../../models/git-repository';
|
||||
import WrapperHome from './wrapper-home';
|
||||
import WrapperDesign from './wrapper-design';
|
||||
import WrapperUnitTest from './wrapper-unit-test';
|
||||
import WrapperOnboarding from './wrapper-onboarding';
|
||||
import WrapperDebug from './wrapper-debug';
|
||||
import { importRaw } from '../../common/import';
|
||||
import GitSyncDropdown from './dropdowns/git-sync-dropdown';
|
||||
import { DropdownButton } from './base/dropdown';
|
||||
import type { ForceToWorkspace } from '../redux/modules/helpers';
|
||||
import type { UnitTest } from '../../models/unit-test';
|
||||
import type { UnitTestResult } from '../../models/unit-test-result';
|
||||
import type { UnitTestSuite } from '../../models/unit-test-suite';
|
||||
import type { GlobalActivity } from '../../common/constants';
|
||||
import {
|
||||
ACTIVITY_DEBUG,
|
||||
ACTIVITY_HOME,
|
||||
ACTIVITY_INSOMNIA,
|
||||
ACTIVITY_SPEC,
|
||||
ACTIVITY_UNIT_TEST,
|
||||
getAppName,
|
||||
} from '../../common/constants';
|
||||
import { Spectral } from '@stoplight/spectral';
|
||||
import type { GlobalActivity } from '../../common/constants';
|
||||
|
||||
const spectral = new Spectral();
|
||||
|
||||
@@ -177,9 +181,12 @@ export type WrapperProps = {
|
||||
activeCookieJar: CookieJar,
|
||||
activeEnvironment: Environment | null,
|
||||
activeGitRepository: GitRepository | null,
|
||||
activeUnitTestResult: UnitTestResult | null,
|
||||
activeUnitTestSuites: Array<UnitTestSuite>,
|
||||
activeUnitTests: Array<UnitTest>,
|
||||
activeWorkspaceClientCertificates: Array<ClientCertificate>,
|
||||
isVariableUncovered: boolean,
|
||||
headerEditorKey: string,
|
||||
isVariableUncovered: boolean,
|
||||
vcs: VCS | null,
|
||||
gitVCS: GitVCS | null,
|
||||
gitRepositories: Array<GitRepository>,
|
||||
@@ -305,7 +312,8 @@ class Wrapper extends React.PureComponent<WrapperProps, State> {
|
||||
await models.workspaceMeta.updateByParentId(workspaceId, { activeActivity: nextActivity });
|
||||
}
|
||||
|
||||
if (activity !== ACTIVITY_SPEC) {
|
||||
const notEditingASpec = activity !== ACTIVITY_SPEC;
|
||||
if (notEditingASpec) {
|
||||
handleSetActiveActivity(nextActivity);
|
||||
return;
|
||||
}
|
||||
@@ -350,12 +358,6 @@ class Wrapper extends React.PureComponent<WrapperProps, State> {
|
||||
this.props.handleImportFileToWorkspace(this.props.activeWorkspace._id, forceToWorkspace);
|
||||
}
|
||||
|
||||
_handleUpdateSettingsUseBulkParametersEditor(
|
||||
useBulkParametersEditor: boolean,
|
||||
): Promise<Settings> {
|
||||
return sUpdate(this.props.settings, { useBulkParametersEditor });
|
||||
}
|
||||
|
||||
_handleImportUri(uri: string, forceToWorkspace?: ForceToWorkspace): void {
|
||||
this.props.handleImportUriToWorkspace(this.props.activeWorkspace._id, uri, forceToWorkspace);
|
||||
}
|
||||
@@ -783,6 +785,15 @@ class Wrapper extends React.PureComponent<WrapperProps, State> {
|
||||
/>
|
||||
)}
|
||||
|
||||
{activity === ACTIVITY_UNIT_TEST && (
|
||||
<WrapperUnitTest
|
||||
gitSyncDropdown={gitSyncDropdown}
|
||||
wrapperProps={this.props}
|
||||
handleActivityChange={this._handleWorkspaceActivityChange}
|
||||
children={sidebarChildren}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(activity === ACTIVITY_DEBUG || activity === ACTIVITY_INSOMNIA) && (
|
||||
<WrapperDebug
|
||||
forceRefreshKey={this.state.forceRefreshKey}
|
||||
|
||||
@@ -15,6 +15,8 @@ import CookiesModal from '../components/modals/cookies-modal';
|
||||
import RequestSwitcherModal from '../components/modals/request-switcher-modal';
|
||||
import SettingsModal, { TAB_INDEX_SHORTCUTS } from '../components/modals/settings-modal';
|
||||
import {
|
||||
ACTIVITY_HOME,
|
||||
ACTIVITY_INSOMNIA,
|
||||
COLLAPSE_SIDEBAR_REMS,
|
||||
DEFAULT_PANE_HEIGHT,
|
||||
DEFAULT_PANE_WIDTH,
|
||||
@@ -26,10 +28,8 @@ import {
|
||||
MIN_PANE_WIDTH,
|
||||
MIN_SIDEBAR_REMS,
|
||||
PREVIEW_MODE_SOURCE,
|
||||
getAppName,
|
||||
getAppId,
|
||||
ACTIVITY_INSOMNIA,
|
||||
ACTIVITY_HOME,
|
||||
getAppName,
|
||||
} from '../../common/constants';
|
||||
import * as globalActions from '../redux/modules/global';
|
||||
import * as entitiesActions from '../redux/modules/entities';
|
||||
@@ -43,6 +43,10 @@ import {
|
||||
selectActiveRequestMeta,
|
||||
selectActiveRequestResponses,
|
||||
selectActiveResponse,
|
||||
selectActiveUnitTestResult,
|
||||
selectActiveUnitTests,
|
||||
selectActiveUnitTestSuite,
|
||||
selectActiveUnitTestSuites,
|
||||
selectActiveWorkspace,
|
||||
selectActiveWorkspaceClientCertificates,
|
||||
selectActiveWorkspaceMeta,
|
||||
@@ -1363,15 +1367,15 @@ function mapStateToProps(state, props) {
|
||||
// Entities
|
||||
const entitiesLists = selectEntitiesLists(state, props);
|
||||
const {
|
||||
workspaces,
|
||||
workspaceMetas,
|
||||
apiSpecs,
|
||||
environments,
|
||||
requests,
|
||||
gitRepositories,
|
||||
requestGroups,
|
||||
requestMetas,
|
||||
requestVersions,
|
||||
apiSpecs,
|
||||
gitRepositories,
|
||||
requests,
|
||||
workspaceMetas,
|
||||
workspaces,
|
||||
} = entitiesLists;
|
||||
|
||||
const settings = entitiesLists.settings[0];
|
||||
@@ -1423,6 +1427,12 @@ function mapStateToProps(state, props) {
|
||||
// Api spec stuff
|
||||
const activeApiSpec = apiSpecs.find(s => s.parentId === activeWorkspace._id);
|
||||
|
||||
// Test stuff
|
||||
const activeUnitTests = selectActiveUnitTests(state, props);
|
||||
const activeUnitTestSuite = selectActiveUnitTestSuite(state, props);
|
||||
const activeUnitTestSuites = selectActiveUnitTestSuites(state, props);
|
||||
const activeUnitTestResult = selectActiveUnitTestResult(state, props);
|
||||
|
||||
return Object.assign({}, state, {
|
||||
activity: activeActivity,
|
||||
activeApiSpec,
|
||||
@@ -1432,6 +1442,10 @@ function mapStateToProps(state, props) {
|
||||
activeRequest,
|
||||
activeRequestResponses,
|
||||
activeResponse,
|
||||
activeUnitTestResult,
|
||||
activeUnitTestSuite,
|
||||
activeUnitTestSuites,
|
||||
activeUnitTests,
|
||||
activeWorkspace,
|
||||
activeWorkspaceClientCertificates,
|
||||
activeWorkspaceMeta,
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
.unit-tests {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
grid-template-areas: 'appheader appheader' 'body-left body-right';
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
|
||||
border-left: 1px solid var(--hl-md);
|
||||
|
||||
&.preview-hidden {
|
||||
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;
|
||||
|
||||
&__block {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
border-bottom: 1px dashed var(--hl-md);
|
||||
padding-bottom: var(--padding-md);
|
||||
|
||||
.editor {
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-lg);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
& > * {
|
||||
margin-top: var(--padding-md);
|
||||
margin-bottom: var(--padding-md);
|
||||
margin-right: var(--padding-md);
|
||||
}
|
||||
|
||||
& > *:first-child {
|
||||
margin-right: auto;
|
||||
margin-left: var(--padding-md);
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
max-width: 18rem;
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__results {
|
||||
padding-left: var(--padding-md);
|
||||
border-left: 1px solid var(--hl-md);
|
||||
grid-area: body-right;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-font);
|
||||
display: grid;
|
||||
grid-template-columns: 100%;
|
||||
grid-template-rows: 1fr auto;
|
||||
|
||||
li {
|
||||
display: block;
|
||||
padding: var(--padding-xs) 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
color: var(--hl);
|
||||
|
||||
&:first-child {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.unit-tests__sidebar__action {
|
||||
display: none;
|
||||
color: var(--hl);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-font);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .unit-tests__sidebar__action {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--hl-sm);
|
||||
color: var(--color-font);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--hl-xs);
|
||||
color: var(--color-font);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0 var(--padding-sm);
|
||||
height: var(--line-height-xs);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@
|
||||
@import 'components/shortcuts';
|
||||
@import 'components/sidebar';
|
||||
@import 'components/spec-editor';
|
||||
@import 'components/unit-tests';
|
||||
@import 'components/spec-editor-sidebar';
|
||||
@import 'components/tabs';
|
||||
@import 'components/tag';
|
||||
|
||||
@@ -115,5 +115,8 @@ export async function allDocs() {
|
||||
...(await models.oAuth2Token.all()),
|
||||
...(await models.clientCertificate.all()),
|
||||
...(await models.apiSpec.all()),
|
||||
...(await models.unitTestSuite.all()),
|
||||
...(await models.unitTest.all()),
|
||||
...(await models.unitTestResult.all()),
|
||||
];
|
||||
}
|
||||
@@ -324,6 +324,63 @@ export const selectActiveResponse = createSelector(
|
||||
},
|
||||
);
|
||||
|
||||
export const selectActiveUnitTestResult = createSelector(
|
||||
selectEntitiesLists,
|
||||
selectActiveWorkspace,
|
||||
(entities, activeWorkspace) => {
|
||||
let recentResult = null;
|
||||
for (const r of entities.unitTestResults) {
|
||||
if (r.parentId !== activeWorkspace._id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!recentResult) {
|
||||
recentResult = r;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r.created > recentResult.created) {
|
||||
recentResult = r;
|
||||
}
|
||||
}
|
||||
|
||||
return recentResult;
|
||||
},
|
||||
);
|
||||
|
||||
export const selectActiveUnitTestSuite = createSelector(
|
||||
selectEntitiesLists,
|
||||
selectActiveWorkspaceMeta,
|
||||
(entities, activeWorkspaceMeta) => {
|
||||
if (!activeWorkspaceMeta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const id = activeWorkspaceMeta.activeUnitTestSuiteId;
|
||||
return entities.unitTestSuites.find(s => s._id === id) || null;
|
||||
},
|
||||
);
|
||||
|
||||
export const selectActiveUnitTests = createSelector(
|
||||
selectEntitiesLists,
|
||||
selectActiveUnitTestSuite,
|
||||
(entities, activeUnitTestSuite) => {
|
||||
if (!activeUnitTestSuite) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return entities.unitTests.filter(s => s.parentId === activeUnitTestSuite._id);
|
||||
},
|
||||
);
|
||||
|
||||
export const selectActiveUnitTestSuites = createSelector(
|
||||
selectEntitiesLists,
|
||||
selectActiveWorkspace,
|
||||
(entities, activeWorkspace) => {
|
||||
return entities.unitTestSuites.filter(s => s.parentId === activeWorkspace._id);
|
||||
},
|
||||
);
|
||||
|
||||
export const selectSyncItems = createSelector(selectActiveWorkspaceEntities, workspaceEntities =>
|
||||
workspaceEntities.filter(models.canSync).map(doc => ({
|
||||
key: doc._id,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "2020.2.2",
|
||||
"version": "2020.3.0-alpha.2",
|
||||
"name": "insomnia-designer",
|
||||
"executableName": "insomnia-designer",
|
||||
"appId": "com.insomnia.designer",
|
||||
|
||||
@@ -98,11 +98,6 @@ const StyledButton: React.ComponentType<Props> = styled.button`
|
||||
&:disabled {
|
||||
opacity: 60%;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: inline-block !important;
|
||||
margin-left: 0.4em;
|
||||
}
|
||||
`;
|
||||
|
||||
const Button = ({ variant, bg, size, ...props }: Props) => (
|
||||
|
||||
@@ -66,6 +66,39 @@ describe('integration', () => {
|
||||
expect(stats2.passes).toBe(2);
|
||||
});
|
||||
|
||||
it('generates and runs more than once', async () => {
|
||||
const testSrc = await generate([
|
||||
{
|
||||
name: 'Example TestSuite',
|
||||
suites: [],
|
||||
tests: [
|
||||
{
|
||||
name: 'should return -1 when the value is not present',
|
||||
code: 'expect([1, 2, 3].indexOf(4)).to.equal(-1);\nexpect(true).to.be.true;',
|
||||
defaultRequestId: null,
|
||||
},
|
||||
{
|
||||
name: 'is an empty test',
|
||||
code: '',
|
||||
defaultRequestId: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const { stats, failures } = await runTests(testSrc);
|
||||
expect(failures).toEqual([]);
|
||||
expect(stats.tests).toBe(2);
|
||||
expect(stats.failures).toBe(0);
|
||||
expect(stats.passes).toBe(2);
|
||||
|
||||
const { stats: stats2, failures: failures2 } = await runTests(testSrc);
|
||||
expect(failures2).toEqual([]);
|
||||
expect(stats2.tests).toBe(2);
|
||||
expect(stats2.failures).toBe(0);
|
||||
expect(stats2.passes).toBe(2);
|
||||
});
|
||||
|
||||
it('sends an HTTP request', async () => {
|
||||
axios.__setResponse('GET', '200.insomnia.rest', {
|
||||
status: 200,
|
||||
|
||||
@@ -37,7 +37,7 @@ module.exports = {
|
||||
notice: '#c18401',
|
||||
warning: '#c18401',
|
||||
danger: '#e45649',
|
||||
surprise: '#a626a4',
|
||||
surprise: '#6030BF',
|
||||
info: '#0184bc',
|
||||
},
|
||||
foreground: {
|
||||
|
||||
@@ -86,6 +86,23 @@ describe('plugin', () => {
|
||||
expect(result).toBe('bar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('RequestExtension name', () => {
|
||||
it('should get name', async () => {
|
||||
const requests = [
|
||||
{
|
||||
_id: 'req_1',
|
||||
name: 'Foo',
|
||||
parameters: [{ name: 'foo', value: '{{ foo }}' }],
|
||||
url: 'https://insomnia.rest/foo/bar',
|
||||
},
|
||||
];
|
||||
const context = _getTestContext([{ _id: 'wrk_1' }], requests);
|
||||
const result = await tag.run(context, 'name');
|
||||
|
||||
expect(result).toBe('Foo');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function _getTestContext(workspaces, requests, jars) {
|
||||
|
||||
Reference in new issue
Block a user