Merge branch 'master' into uri-encoded-scopes-fix

This commit is contained in:
kyle
2017-11-14 15:18:41 -08:00
committed by GitHub
62 changed files with 1086 additions and 434 deletions

View File

@@ -1,6 +1,9 @@
<!--- <!---
Thanks for filing an issue 😄 ! Before you submit, please read the following: Thanks for filing an issue 😄 ! Before you submit, please read the following:
If you're here to report a security issue, please STOP writing an issue and contact us
at security@swagger.io instead!
Search open/closed issues before submitting since someone might have asked the same thing before! Search open/closed issues before submitting since someone might have asked the same thing before!
Issues on GitHub are only related to problems of Swagger-UI itself. We'll try to offer support Issues on GitHub are only related to problems of Swagger-UI itself. We'll try to offer support

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@ node_modules
.idea .idea
.deps_check .deps_check
.DS_Store .DS_Store
.nyc_output
npm-debug.log* npm-debug.log*
.eslintcache .eslintcache
package-lock.json package-lock.json

View File

@@ -1,12 +1,16 @@
language: node_js language: node_js
node_js: node_js:
- '6.9' - '6.9'
cache:
directories:
- node_modules
services: services:
- docker - docker
branches: branches:
only: only:
- master - master
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/ - /^v\d+\.\d+(\.\d+)?(-\S*)?$/
install: "npm i && npm update"
before_deploy: before_deploy:
- npm run build - npm run build
env: env:

View File

@@ -4,7 +4,7 @@ We love contributions from our community of users! This document explains our gu
#### Environment setup #### Environment setup
0. Install Node.js (4 or newer) and npm (3 or newer). 0. Install Node.js (6 or newer) and npm (3 or newer).
1. Make a fork of Swagger-UI on GitHub, then clone your fork to your machine. 1. Make a fork of Swagger-UI on GitHub, then clone your fork to your machine.
2. Run `npm install` in your Swagger-UI directory. 2. Run `npm install` in your Swagger-UI directory.
3. Run `npm run dev`. `localhost:3200` should open automatically. 3. Run `npm run dev`. `localhost:3200` should open automatically.

View File

@@ -22,7 +22,7 @@ The OpenAPI Specification has undergone 5 revisions since initial creation in 20
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | ----- ------------------ | ------------ | -------------------------- | -----
3.4.1 | 2017-10-20 | 2.0, 3.0 | [tag v3.4.1](https://github.com/swagger-api/swagger-ui/tree/v3.4.1) 3.4.4 | 2017-11-03 | 2.0, 3.0 | [tag v3.4.4](https://github.com/swagger-api/swagger-ui/tree/v3.4.4)
3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21) 3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) 2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10)
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) 2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/swagger-ui.css vendored

File diff suppressed because one or more lines are too long

4
dist/swagger-ui.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -20,8 +20,8 @@ Some distinct identifiers to Swagger-UI 3.X:
If you've determined this is the version you have, to find the exact version: If you've determined this is the version you have, to find the exact version:
- Open your browser's web console (changes between browsers) - Open your browser's web console (changes between browsers)
- Type `versions` in the console and execute the call. - Type `JSON.stringify(versions)` in the console and execute the call.
- You might need to expand the result, until you get a string similar to `swaggerUi : Object { version: "3.1.6", gitRevision: "g786cd47", gitDirty: true, … }`. - The result should look similar to `swaggerUi : Object { version: "3.1.6", gitRevision: "g786cd47", gitDirty: true, … }`.
- The version taken from that example would be `3.1.6`. - The version taken from that example would be `3.1.6`.
Note: This functionality was added in 3.0.8. If you're unable to execute it, you're likely to use an older version, and in that case the first step would be to upgrade. Note: This functionality was added in 3.0.8. If you're unable to execute it, you're likely to use an older version, and in that case the first step would be to upgrade.

View File

@@ -1,6 +1,6 @@
{ {
"name": "swagger-ui", "name": "swagger-ui",
"version": "3.4.1", "version": "3.4.4",
"main": "dist/swagger-ui.js", "main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git", "repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [ "contributors": [
@@ -33,12 +33,14 @@
"test-in-node": "npm run lint-errors && npm run just-test-in-node", "test-in-node": "npm run lint-errors && npm run just-test-in-node",
"just-test": "karma start --config karma.conf.js", "just-test": "karma start --config karma.conf.js",
"just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package test/xss", "just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package test/xss",
"just-check-coverage": "nyc npm run just-test-in-node",
"test-e2e": "sleep 3 && nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json", "test-e2e": "sleep 3 && nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json",
"e2e-initial-render": "nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json --group initial-render", "e2e-initial-render": "nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json --group initial-render",
"mock-api": "json-server --watch test/e2e/db.json --port 3204", "mock-api": "json-server --watch test/e2e/db.json --port 3204",
"e2e": "npm-run-all --parallel -r hot-server mock-api test-e2e" "e2e": "npm-run-all --parallel -r hot-server mock-api test-e2e"
}, },
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^2.0.2",
"base64-js": "^1.2.0", "base64-js": "^1.2.0",
"brace": "0.7.0", "brace": "0.7.0",
"classnames": "^2.2.5", "classnames": "^2.2.5",
@@ -55,17 +57,17 @@
"memoizee": "0.4.1", "memoizee": "0.4.1",
"promise-worker": "^1.1.1", "promise-worker": "^1.1.1",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^15.4.0", "react": "^15.6.2",
"react-addons-perf": "^15.4.0", "react-addons-perf": "^15.4.0",
"react-addons-shallow-compare": "0.14.8", "react-addons-shallow-compare": "0.14.8",
"react-addons-test-utils": "^15.4.0", "react-addons-test-utils": "^15.6.2",
"react-collapse": "2.3.1", "react-collapse": "^4.0.3",
"react-dom": "^15.4.0", "react-dom": "^15.6.2",
"react-height": "^2.0.0", "react-height": "^2.0.0",
"react-hot-loader": "1.3.1", "react-hot-loader": "1.3.1",
"react-immutable-proptypes": "2.1.0", "react-immutable-proptypes": "2.1.0",
"react-markdown": "^2.5.0", "react-markdown": "^2.5.0",
"react-motion": "0.4.4", "react-motion": "^0.5.2",
"react-object-inspector": "0.2.1", "react-object-inspector": "0.2.1",
"react-redux": "^4.x.x", "react-redux": "^4.x.x",
"react-split-pane": "0.1.57", "react-split-pane": "0.1.57",
@@ -78,11 +80,12 @@
"scroll-to-element": "^2.0.0", "scroll-to-element": "^2.0.0",
"serialize-error": "2.0.0", "serialize-error": "2.0.0",
"shallowequal": "0.2.2", "shallowequal": "0.2.2",
"swagger-client": "^3.3.1", "swagger-client": "^3.3.3",
"url-parse": "^1.1.8", "url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1", "whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1", "worker-loader": "^0.7.1",
"xml": "1.0.1", "xml": "1.0.1",
"xml-but-prettier": "^1.0.1",
"yaml-js": "0.2.0" "yaml-js": "0.2.0"
}, },
"devDependencies": { "devDependencies": {
@@ -125,6 +128,7 @@
"node-sass": "^4.5.0", "node-sass": "^4.5.0",
"npm-run-all": "4.0.2", "npm-run-all": "4.0.2",
"null-loader": "0.1.1", "null-loader": "0.1.1",
"nyc": "^11.3.0",
"open": "0.0.5", "open": "0.0.5",
"postcss-loader": "2.0.6", "postcss-loader": "2.0.6",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
@@ -150,5 +154,11 @@
], ],
"optionalDependencies": { "optionalDependencies": {
"webpack-dev-server": "2.5.0" "webpack-dev-server": "2.5.0"
},
"nyc": {
"all": true,
"include": [
"**/src/core/plugins/**.js"
]
} }
} }

View File

@@ -24,6 +24,7 @@ export default class ArrayModel extends Component {
const Markdown = getComponent("Markdown") const Markdown = getComponent("Markdown")
const ModelCollapse = getComponent("ModelCollapse") const ModelCollapse = getComponent("ModelCollapse")
const Model = getComponent("Model") const Model = getComponent("Model")
const Property = getComponent("Property")
const titleEl = title && const titleEl = title &&
<span className="model-title"> <span className="model-title">
@@ -39,9 +40,7 @@ export default class ArrayModel extends Component {
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]"> <ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]">
[ [
{ {
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }> properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <Property key={`${key}-${v}`} propKey={ key } propVal={ v } propStyle={ propStyle } />) : null
<br />{ key }: { String(v) }</span>)
: null
} }
{ {
!description ? null : !description ? null :

View File

@@ -1,25 +1,23 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
export default class AuthorizeOperationBtn extends React.Component { export default class AuthorizeOperationBtn extends React.Component {
static propTypes = {
isAuthorized: PropTypes.bool.isRequired,
onClick: PropTypes.func
}
onClick =(e) => { onClick =(e) => {
e.stopPropagation() e.stopPropagation()
let { onClick } = this.props
let { security, authActions, authSelectors } = this.props if(onClick) {
let definitions = authSelectors.getDefinitionsByNames(security) onClick()
}
authActions.showDefinitions(definitions)
} }
render() { render() {
let { security, authSelectors } = this.props let { isAuthorized } = this.props
let isAuthorized = authSelectors.isAuthorized(security)
if(isAuthorized === null) {
return null
}
return ( return (
<button className={isAuthorized ? "authorization__btn locked" : "authorization__btn unlocked"} onClick={ this.onClick }> <button className={isAuthorized ? "authorization__btn locked" : "authorization__btn unlocked"} onClick={ this.onClick }>
@@ -30,10 +28,4 @@ export default class AuthorizeOperationBtn extends React.Component {
) )
} }
static propTypes = {
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
security: ImPropTypes.iterable.isRequired
}
} }

View File

@@ -37,7 +37,7 @@ export default class ContentType extends React.Component {
return ( return (
<div className={ "content-type-wrapper " + ( className || "" ) }> <div className={ "content-type-wrapper " + ( className || "" ) }>
<select className="content-type" value={value} onChange={this.onChangeWrapper} > <select className="content-type" value={value || ""} onChange={this.onChangeWrapper} >
{ contentTypes.map( (val) => { { contentTypes.map( (val) => {
return <option key={ val } value={ val }>{ val }</option> return <option key={ val } value={ val }>{ val }</option>
}).toArray()} }).toArray()}

View File

@@ -1,6 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import Collapse from "react-collapse" import { Collapse } from "react-collapse"
import { presets } from "react-motion" import { presets } from "react-motion"
import ObjectInspector from "react-object-inspector" import ObjectInspector from "react-object-inspector"
import Perf from "react-addons-perf" import Perf from "react-addons-perf"

View File

@@ -1,7 +1,7 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { List } from "immutable" import { List } from "immutable"
import Collapse from "react-collapse" import { Collapse } from "react-collapse"
export default class Errors extends React.Component { export default class Errors extends React.Component {
@@ -113,7 +113,7 @@ const SpecErrorItem = ( { error, jumpToLine } ) => {
} }
function toTitleCase(str) { function toTitleCase(str) {
return str return (str || "")
.split(" ") .split(" ")
.map(substr => substr[0].toUpperCase() + substr.slice(1)) .map(substr => substr[0].toUpperCase() + substr.slice(1))
.join(" ") .join(" ")

View File

@@ -2,6 +2,7 @@ import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { fromJS } from "immutable" import { fromJS } from "immutable"
import ImPropTypes from "react-immutable-proptypes" import ImPropTypes from "react-immutable-proptypes"
import { sanitizeUrl } from "core/utils"
class Path extends React.Component { class Path extends React.Component {
@@ -35,9 +36,9 @@ class Contact extends React.Component {
return ( return (
<div> <div>
{ url && <div><a href={ url } target="_blank">{ name } - Website</a></div> } { url && <div><a href={ sanitizeUrl(url) } target="_blank">{ name } - Website</a></div> }
{ email && { email &&
<a href={`mailto:${email}`}> <a href={sanitizeUrl(`mailto:${email}`)}>
{ url ? `Send email to ${name}` : `Contact ${name}`} { url ? `Send email to ${name}` : `Contact ${name}`}
</a> </a>
} }
@@ -59,7 +60,7 @@ class License extends React.Component {
return ( return (
<div> <div>
{ {
url ? <a target="_blank" href={ url }>{ name }</a> url ? <a target="_blank" href={ sanitizeUrl(url) }>{ name }</a>
: <span>{ name }</span> : <span>{ name }</span>
} }
</div> </div>
@@ -97,7 +98,7 @@ export default class Info extends React.Component {
{ version && <VersionStamp version={version}></VersionStamp> } { version && <VersionStamp version={version}></VersionStamp> }
</h2> </h2>
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null } { host || basePath ? <Path host={ host } basePath={ basePath } /> : null }
{ url && <a target="_blank" href={ url }><span className="url"> { url } </span></a> } { url && <a target="_blank" href={ sanitizeUrl(url) }><span className="url"> { url } </span></a> }
</hgroup> </hgroup>
<div className="description"> <div className="description">
@@ -106,14 +107,14 @@ export default class Info extends React.Component {
{ {
termsOfService && <div> termsOfService && <div>
<a target="_blank" href={ termsOfService }>Terms of service</a> <a target="_blank" href={ sanitizeUrl(termsOfService) }>Terms of service</a>
</div> </div>
} }
{ contact && contact.size ? <Contact data={ contact } /> : null } { contact && contact.size ? <Contact data={ contact } /> : null }
{ license && license.size ? <License license={ license } /> : null } { license && license.size ? <License license={ license } /> : null }
{ externalDocsUrl ? { externalDocsUrl ?
<a target="_blank" href={externalDocsUrl}>{externalDocsDescription || externalDocsUrl}</a> <a target="_blank" href={sanitizeUrl(externalDocsUrl)}>{externalDocsDescription || externalDocsUrl}</a>
: null } : null }
</div> </div>

View File

@@ -1,6 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import OriCollapse from "react-collapse" import { Collapse as OriCollapse } from "react-collapse"
function xclass(...args) { function xclass(...args) {
return args.filter(a => !!a).join(" ").trim() return args.filter(a => !!a).join(" ").trim()

View File

@@ -1,7 +1,7 @@
import React, { Component } from "react" import React, { PureComponent } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
export default class Model extends Component { export default class Model extends PureComponent {
static propTypes = { static propTypes = {
schema: PropTypes.object.isRequired, schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,

View File

@@ -21,6 +21,10 @@ export default class ObjectModel extends Component {
let { specSelectors } = otherProps let { specSelectors } = otherProps
let { isOAS3 } = specSelectors let { isOAS3 } = specSelectors
if(!schema) {
return null
}
let description = schema.get("description") let description = schema.get("description")
let properties = schema.get("properties") let properties = schema.get("properties")
let additionalProperties = schema.get("additionalProperties") let additionalProperties = schema.get("additionalProperties")

View File

@@ -1,5 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { sanitizeUrl } from "core/utils"
export default class OnlineValidatorBadge extends React.Component { export default class OnlineValidatorBadge extends React.Component {
static propTypes = { static propTypes = {
@@ -32,6 +33,8 @@ export default class OnlineValidatorBadge extends React.Component {
let { getConfigs } = this.props let { getConfigs } = this.props
let { spec } = getConfigs() let { spec } = getConfigs()
let sanitizedValidatorUrl = sanitizeUrl(this.state.validatorUrl)
if ( typeof spec === "object" && Object.keys(spec).length) return null if ( typeof spec === "object" && Object.keys(spec).length) return null
if (!this.state.url || !this.state.validatorUrl || this.state.url.indexOf("localhost") >= 0 if (!this.state.url || !this.state.validatorUrl || this.state.url.indexOf("localhost") >= 0
@@ -40,8 +43,8 @@ export default class OnlineValidatorBadge extends React.Component {
} }
return (<span style={{ float: "right"}}> return (<span style={{ float: "right"}}>
<a target="_blank" href={`${ this.state.validatorUrl }/debug?url=${ this.state.url }`}> <a target="_blank" href={`${ sanitizedValidatorUrl }/debug?url=${ this.state.url }`}>
<ValidatorImage src={`${ this.state.validatorUrl }?url=${ this.state.url }`} alt="Online validator badge"/> <ValidatorImage src={`${ sanitizedValidatorUrl }?url=${ this.state.url }`} alt="Online validator badge"/>
</a> </a>
</span>) </span>)
} }

View File

@@ -2,6 +2,7 @@ import React, { PureComponent } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { getList } from "core/utils" import { getList } from "core/utils"
import * as CustomPropTypes from "core/proptypes" import * as CustomPropTypes from "core/proptypes"
import { sanitizeUrl } from "core/utils"
//import "less/opblock" //import "less/opblock"
@@ -11,8 +12,10 @@ export default class Operation extends PureComponent {
method: PropTypes.string.isRequired, method: PropTypes.string.isRequired,
operation: PropTypes.object.isRequired, operation: PropTypes.object.isRequired,
showSummary: PropTypes.bool, showSummary: PropTypes.bool,
isShown: PropTypes.bool.isRequired,
isShownKey: CustomPropTypes.arrayOrString.isRequired, tagKey: PropTypes.string,
operationKey: PropTypes.string,
jumpToKey: CustomPropTypes.arrayOrString.isRequired, jumpToKey: CustomPropTypes.arrayOrString.isRequired,
allowTryItOut: PropTypes.bool, allowTryItOut: PropTypes.bool,
@@ -51,38 +54,16 @@ export default class Operation extends PureComponent {
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const defaultContentType = "application/json"
let { specActions, path, method, operation } = nextProps
let producesValue = operation.get("produces_value")
let produces = operation.get("produces")
let consumes = operation.get("consumes")
let consumesValue = operation.get("consumes_value")
if(nextProps.response !== this.props.response) { if(nextProps.response !== this.props.response) {
this.setState({ executeInProgress: false }) this.setState({ executeInProgress: false })
} }
if (producesValue === undefined) {
producesValue = produces && produces.size ? produces.first() : defaultContentType
specActions.changeProducesValue([path, method], producesValue)
}
if (consumesValue === undefined) {
consumesValue = consumes && consumes.size ? consumes.first() : defaultContentType
specActions.changeConsumesValue([path, method], consumesValue)
}
} }
toggleShown =() => { toggleShown =() => {
let { layoutActions, isShownKey } = this.props let { layoutActions, tagKey, operationKey, isShown } = this.props
layoutActions.show(isShownKey, !this.isShown()) const isShownKey = ["operations", tagKey, operationKey]
}
isShown =() => { layoutActions.show(isShownKey, !isShown)
let { layoutSelectors, isShownKey, getConfigs } = this.props
let { docExpansion } = getConfigs()
return layoutSelectors.isShown(isShownKey, docExpansion === "full" ) // Here is where we set the default
} }
onTryoutClick =() => { onTryoutClick =() => {
@@ -101,7 +82,9 @@ export default class Operation extends PureComponent {
render() { render() {
let { let {
isShownKey, operationKey,
tagKey,
isShown,
jumpToKey, jumpToKey,
path, path,
method, method,
@@ -155,18 +138,17 @@ export default class Operation extends PureComponent {
} }
let { tryItOutEnabled } = this.state let { tryItOutEnabled } = this.state
let shown = this.isShown()
let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method ) let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )
return ( return (
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey.join("-")} > <div className={deprecated ? "opblock opblock-deprecated" : isShown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={`operations-${tagKey}-${operationKey}`} >
<div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} > <div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} >
<span className="opblock-summary-method">{method.toUpperCase()}</span> <span className="opblock-summary-method">{method.toUpperCase()}</span>
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } > <span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
<a <a
className="nostyle" className="nostyle"
onClick={isDeepLinkingEnabled ? (e) => e.preventDefault() : null} onClick={isDeepLinkingEnabled ? (e) => e.preventDefault() : null}
href={isDeepLinkingEnabled ? `#/${isShownKey[1]}/${isShownKey[2]}` : null}> href={isDeepLinkingEnabled ? `#/${tagKey}/${operationKey}` : null}>
<span>{path}</span> <span>{path}</span>
</a> </a>
<JumpToPath path={jumpToKey} /> <JumpToPath path={jumpToKey} />
@@ -182,13 +164,17 @@ export default class Operation extends PureComponent {
{ {
(!security || !security.count()) ? null : (!security || !security.count()) ? null :
<AuthorizeOperationBtn authActions={ authActions } <AuthorizeOperationBtn
security={ security } isAuthorized={ authSelectors.isAuthorized(security) }
authSelectors={ authSelectors }/> onClick={() => {
const applicableDefinitions = authSelectors.definitionsForRequirements(security)
authActions.showDefinitions(applicableDefinitions)
}}
/>
} }
</div> </div>
<Collapse isOpened={shown}> <Collapse isOpened={isShown}>
<div className="opblock-body"> <div className="opblock-body">
{ deprecated && <h4 className="opblock-title_normal"> Warning: Deprecated</h4>} { deprecated && <h4 className="opblock-title_normal"> Warning: Deprecated</h4>}
{ description && { description &&
@@ -206,7 +192,7 @@ export default class Operation extends PureComponent {
<span className="opblock-external-docs__description"> <span className="opblock-external-docs__description">
<Markdown source={ externalDocs.get("description") } /> <Markdown source={ externalDocs.get("description") } />
</span> </span>
<a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a> <a className="opblock-external-docs__link" href={ sanitizeUrl(externalDocs.get("url")) }>{ externalDocs.get("url") }</a>
</div> </div>
</div> : null </div> : null
} }

View File

@@ -1,7 +1,7 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { helpers } from "swagger-client" import { helpers } from "swagger-client"
import { createDeepLinkPath } from "core/utils" import { createDeepLinkPath, sanitizeUrl } from "core/utils"
const { opId } = helpers const { opId } = helpers
export default class Operations extends React.Component { export default class Operations extends React.Component {
@@ -101,7 +101,7 @@ export default class Operations extends React.Component {
{ tagExternalDocsUrl ? ": " : null } { tagExternalDocsUrl ? ": " : null }
{ tagExternalDocsUrl ? { tagExternalDocsUrl ?
<a <a
href={tagExternalDocsUrl} href={sanitizeUrl(tagExternalDocsUrl)}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
target={"_blank"} target={"_blank"}
>{tagExternalDocsUrl}</a> : null >{tagExternalDocsUrl}</a> : null
@@ -127,7 +127,8 @@ export default class Operations extends React.Component {
const operationId = const operationId =
op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), path, method) || op.get("id") op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), path, method) || op.get("id")
const isShownKey = ["operations", createDeepLinkPath(tag), createDeepLinkPath(operationId)] const tagKey = createDeepLinkPath(tag)
const operationKey = createDeepLinkPath(operationId)
const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method")) const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method"))
const response = specSelectors.responseFor(op.get("path"), op.get("method")) const response = specSelectors.responseFor(op.get("path"), op.get("method"))
@@ -135,11 +136,12 @@ export default class Operations extends React.Component {
return <Operation return <Operation
{...op.toObject()} {...op.toObject()}
tagKey={tagKey}
isShownKey={isShownKey} operationKey={operationKey}
isShown={layoutSelectors.isShown(["operations", tagKey, operationKey], docExpansion === "full")}
jumpToKey={jumpToKey} jumpToKey={jumpToKey}
showSummary={showSummary} showSummary={showSummary}
key={isShownKey} key={tagKey + operationKey}
response={ response } response={ response }
request={ request } request={ request }
allowTryItOut={allowTryItOut} allowTryItOut={allowTryItOut}

View File

@@ -28,6 +28,7 @@ export default class Primitive extends Component {
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 ) let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 )
const Markdown = getComponent("Markdown") const Markdown = getComponent("Markdown")
const EnumModel = getComponent("EnumModel") const EnumModel = getComponent("EnumModel")
const Property = getComponent("Property")
return <span className="model"> return <span className="model">
<span className="prop"> <span className="prop">
@@ -35,9 +36,7 @@ export default class Primitive extends Component {
<span className="prop-type">{ type }</span> <span className="prop-type">{ type }</span>
{ format && <span className="prop-format">(${format})</span>} { format && <span className="prop-format">(${format})</span>}
{ {
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }> properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <Property key={`${key}-${v}`} propKey={ key } propVal={ v } propStyle={ propStyle } />) : null
<br />{ key }: { String(v) }</span>)
: null
} }
{ {
!description ? null : !description ? null :

View File

@@ -0,0 +1,16 @@
import React from "react"
import PropTypes from "prop-types"
export const Property = ({ propKey, propVal, propStyle }) => {
return (
<span style={ propStyle }>
<br />{ propKey }: { String(propVal) }</span>
)
}
Property.propTypes = {
propKey: PropTypes.string,
propVal: PropTypes.any,
propStyle: PropTypes.object
}
export default Property

View File

@@ -1,6 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { formatXml } from "core/utils" import formatXml from "xml-but-prettier"
import lowerCase from "lodash/lowerCase" import lowerCase from "lodash/lowerCase"
export default class ResponseBody extends React.Component { export default class ResponseBody extends React.Component {
@@ -31,7 +31,10 @@ export default class ResponseBody extends React.Component {
// XML // XML
} else if (/xml/i.test(contentType)) { } else if (/xml/i.test(contentType)) {
body = formatXml(content) body = formatXml(content, {
textNodesOnSameLine: true,
indentor: " "
})
bodyEl = <HighlightCode value={ body } /> bodyEl = <HighlightCode value={ body } />
// HTML or Plain Text // HTML or Plain Text

View File

@@ -58,13 +58,12 @@ module.exports = function SwaggerUI(opts) {
plugins: [ plugins: [
], ],
// Initial state
initialState: { },
// Inline Plugin // Inline Plugin
fn: { }, fn: { },
components: { }, components: { },
state: { },
// Override some core configs... at your own risk
store: { },
} }
let queryConfig = parseSearch() let queryConfig = parseSearch()
@@ -74,12 +73,12 @@ module.exports = function SwaggerUI(opts) {
const constructorConfig = deepExtend({}, defaults, opts, queryConfig) const constructorConfig = deepExtend({}, defaults, opts, queryConfig)
const storeConfigs = deepExtend({}, constructorConfig.store, { const storeConfigs = {
system: { system: {
configs: constructorConfig.configs configs: constructorConfig.configs
}, },
plugins: constructorConfig.presets, plugins: constructorConfig.presets,
state: { state: deepExtend({
layout: { layout: {
layout: constructorConfig.layout, layout: constructorConfig.layout,
filter: constructorConfig.filter filter: constructorConfig.filter
@@ -88,8 +87,8 @@ module.exports = function SwaggerUI(opts) {
spec: "", spec: "",
url: constructorConfig.url url: constructorConfig.url
} }
}, constructorConfig.initialState)
} }
})
let inlinePlugin = ()=> { let inlinePlugin = ()=> {
return { return {

View File

@@ -58,6 +58,7 @@ export class JsonSchema_string extends Component {
if ( enumValue ) { if ( enumValue ) {
const Select = getComponent("Select") const Select = getComponent("Select")
return (<Select className={ errors.length ? "invalid" : ""} return (<Select className={ errors.length ? "invalid" : ""}
title={ errors.length ? errors : ""}
allowedValues={ enumValue } allowedValues={ enumValue }
value={ value } value={ value }
allowEmptyValue={ !required } allowEmptyValue={ !required }
@@ -67,10 +68,20 @@ export class JsonSchema_string extends Component {
const isDisabled = schema["in"] === "formData" && !("FormData" in window) const isDisabled = schema["in"] === "formData" && !("FormData" in window)
const Input = getComponent("Input") const Input = getComponent("Input")
if (schema["type"] === "file") { if (schema["type"] === "file") {
return <Input type="file" className={ errors.length ? "invalid" : ""} onChange={ this.onChange } disabled={isDisabled}/> return (<Input type="file"
className={ errors.length ? "invalid" : ""}
title={ errors.length ? errors : ""}
onChange={ this.onChange }
disabled={isDisabled}/>)
} }
else { else {
return <Input type={ schema.format === "password" ? "password" : "text" } className={ errors.length ? "invalid" : ""} value={value} placeholder={description} onChange={ this.onChange } disabled={isDisabled}/> return (<Input type={ schema.format === "password" ? "password" : "text" }
className={ errors.length ? "invalid" : ""}
title={ errors.length ? errors : ""}
value={value}
placeholder={description}
onChange={ this.onChange }
disabled={isDisabled}/>)
} }
} }
} }
@@ -134,6 +145,7 @@ export class JsonSchema_array extends PureComponent {
if ( enumValue ) { if ( enumValue ) {
const Select = getComponent("Select") const Select = getComponent("Select")
return (<Select className={ errors.length ? "invalid" : ""} return (<Select className={ errors.length ? "invalid" : ""}
title={ errors.length ? errors : ""}
multiple={ true } multiple={ true }
value={ value } value={ value }
allowedValues={ enumValue } allowedValues={ enumValue }
@@ -175,6 +187,7 @@ export class JsonSchema_boolean extends Component {
const Select = getComponent("Select") const Select = getComponent("Select")
return (<Select className={ errors.length ? "invalid" : ""} return (<Select className={ errors.length ? "invalid" : ""}
title={ errors.length ? errors : ""}
value={ String(value) } value={ String(value) }
allowedValues={ fromJS(["true", "false"]) } allowedValues={ fromJS(["true", "false"]) }
allowEmptyValue={ true } allowEmptyValue={ true }

View File

@@ -11,7 +11,7 @@ export const shownDefinitions = createSelector(
export const definitionsToAuthorize = createSelector( export const definitionsToAuthorize = createSelector(
state, state,
() => ( { specSelectors } ) => { () => ( { specSelectors } ) => {
let definitions = specSelectors.securityDefinitions() let definitions = specSelectors.securityDefinitions() || Map({})
let list = List() let list = List()
//todo refactor //todo refactor
@@ -28,6 +28,7 @@ export const definitionsToAuthorize = createSelector(
export const getDefinitionsByNames = ( state, securities ) => ( { specSelectors } ) => { export const getDefinitionsByNames = ( state, securities ) => ( { specSelectors } ) => {
console.warn("WARNING: getDefinitionsByNames is deprecated and will be removed in the next major version.")
let securityDefinitions = specSelectors.securityDefinitions() let securityDefinitions = specSelectors.securityDefinitions()
let result = List() let result = List()
@@ -58,6 +59,13 @@ export const getDefinitionsByNames = ( state, securities ) => ( { specSelectors
return result return result
} }
export const definitionsForRequirements = (state, securities = List()) => ({ authSelectors }) => {
const allDefinitions = authSelectors.definitionsToAuthorize() || List()
return allDefinitions.filter((def) => {
return securities.some(sec => sec.get(def.keySeq().first()))
})
}
export const authorized = createSelector( export const authorized = createSelector(
state, state,
auth => auth.get("authorized") || Map() auth => auth.get("authorized") || Map()

View File

@@ -3,6 +3,7 @@ import serializeError from "serialize-error"
export const NEW_THROWN_ERR = "err_new_thrown_err" export const NEW_THROWN_ERR = "err_new_thrown_err"
export const NEW_THROWN_ERR_BATCH = "err_new_thrown_err_batch" export const NEW_THROWN_ERR_BATCH = "err_new_thrown_err_batch"
export const NEW_SPEC_ERR = "err_new_spec_err" export const NEW_SPEC_ERR = "err_new_spec_err"
export const NEW_SPEC_ERR_BATCH = "err_new_spec_err_batch"
export const NEW_AUTH_ERR = "err_new_auth_err" export const NEW_AUTH_ERR = "err_new_auth_err"
export const CLEAR = "err_clear" export const CLEAR = "err_clear"
@@ -27,6 +28,13 @@ export function newSpecErr(err) {
} }
} }
export function newSpecErrBatch(errArray) {
return {
type: NEW_SPEC_ERR_BATCH,
payload: errArray
}
}
export function newAuthErr(err) { export function newAuthErr(err) {
return { return {
type: NEW_AUTH_ERR, type: NEW_AUTH_ERR,

View File

@@ -2,6 +2,7 @@ import {
NEW_THROWN_ERR, NEW_THROWN_ERR,
NEW_THROWN_ERR_BATCH, NEW_THROWN_ERR_BATCH,
NEW_SPEC_ERR, NEW_SPEC_ERR,
NEW_SPEC_ERR_BATCH,
NEW_AUTH_ERR, NEW_AUTH_ERR,
CLEAR CLEAR
} from "./actions" } from "./actions"
@@ -45,6 +46,15 @@ export default function(system) {
.update("errors", errors => transformErrors(errors, system.getSystem())) .update("errors", errors => transformErrors(errors, system.getSystem()))
}, },
[NEW_SPEC_ERR_BATCH]: (state, { payload }) => {
payload = payload.map(err => {
return fromJS(Object.assign(DEFAULT_ERROR_STRUCTURE, err, { type: "spec" }))
})
return state
.update("errors", errors => (errors || List()).concat( fromJS( payload )) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},
[NEW_AUTH_ERR]: (state, { payload }) => { [NEW_AUTH_ERR]: (state, { payload }) => {
let error = fromJS(Object.assign({}, payload)) let error = fromJS(Object.assign({}, payload))

View File

@@ -108,9 +108,6 @@ export default class HttpAuth extends React.Component {
<Row> <Row>
<Markdown source={ schema.get("description") } /> <Markdown source={ schema.get("description") } />
</Row> </Row>
<Row>
<p>In: <code>{ schema.get("in") }</code></p>
</Row>
<Row> <Row>
<label>Value:</label> <label>Value:</label>
{ {

View File

@@ -50,7 +50,7 @@ RequestBody.propTypes = {
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired, getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
contentType: PropTypes.string.isRequired, contentType: PropTypes.string,
isExecute: PropTypes.bool.isRequired, isExecute: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired
} }

View File

@@ -48,6 +48,10 @@ export const definitions = onlyOAS3(createSelector(
spec => spec.getIn(["components", "schemas"]) || Map() spec => spec.getIn(["components", "schemas"]) || Map()
)) ))
export const hasHost = onlyOAS3((state) => {
return spec(state).hasIn(["servers", 0])
})
export const securityDefinitions = onlyOAS3(createSelector( export const securityDefinitions = onlyOAS3(createSelector(
spec, spec,
spec => spec.getIn(["components", "securitySchemes"]) || null spec => spec.getIn(["components", "securitySchemes"]) || null

View File

@@ -22,6 +22,7 @@ class Parameters extends Component {
specActions: PropTypes.object.isRequired, specActions: PropTypes.object.isRequired,
operation: PropTypes.object.isRequired, operation: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired, oas3Actions: PropTypes.object.isRequired,
oas3Selectors: PropTypes.object.isRequired, oas3Selectors: PropTypes.object.isRequired,
@@ -86,6 +87,7 @@ class Parameters extends Component {
fn, fn,
getComponent, getComponent,
getConfigs,
specSelectors, specSelectors,
oas3Actions, oas3Actions,
oas3Selectors, oas3Selectors,
@@ -137,6 +139,7 @@ class Parameters extends Component {
eachMap(parameters, (parameter) => ( eachMap(parameters, (parameter) => (
<ParameterRow fn={ fn } <ParameterRow fn={ fn }
getComponent={ getComponent } getComponent={ getComponent }
getConfigs={ getConfigs }
param={ parameter } param={ parameter }
key={ parameter.get( "name" ) } key={ parameter.get( "name" ) }
onChange={ this.onChange } onChange={ this.onChange }

View File

@@ -10,6 +10,7 @@ import auth from "core/plugins/auth"
import util from "core/plugins/util" import util from "core/plugins/util"
import SplitPaneModePlugin from "core/plugins/split-pane-mode" import SplitPaneModePlugin from "core/plugins/split-pane-mode"
import downloadUrlPlugin from "core/plugins/download-url" import downloadUrlPlugin from "core/plugins/download-url"
import configsPlugin from "plugins/configs"
import deepLinkingPlugin from "core/plugins/deep-linking" import deepLinkingPlugin from "core/plugins/deep-linking"
import App from "core/components/app" import App from "core/components/app"
@@ -52,6 +53,7 @@ import EnumModel from "core/components/enum-model"
import ObjectModel from "core/components/object-model" import ObjectModel from "core/components/object-model"
import ArrayModel from "core/components/array-model" import ArrayModel from "core/components/array-model"
import PrimitiveModel from "core/components/primitive-model" import PrimitiveModel from "core/components/primitive-model"
import Property from "core/components/property"
import TryItOutButton from "core/components/try-it-out-button" import TryItOutButton from "core/components/try-it-out-button"
import VersionStamp from "core/components/version-stamp" import VersionStamp from "core/components/version-stamp"
@@ -106,6 +108,7 @@ export default function() {
ObjectModel, ObjectModel,
ArrayModel, ArrayModel,
PrimitiveModel, PrimitiveModel,
Property,
TryItOutButton, TryItOutButton,
Markdown, Markdown,
BaseLayout, BaseLayout,
@@ -122,6 +125,7 @@ export default function() {
} }
return [ return [
configsPlugin,
util, util,
logs, logs,
view, view,

View File

@@ -1,5 +1,5 @@
import Im from "immutable" import Im from "immutable"
import { sanitizeUrl as braintreeSanitizeUrl } from "@braintree/sanitize-url"
import camelCase from "lodash/camelCase" import camelCase from "lodash/camelCase"
import upperFirst from "lodash/upperFirst" import upperFirst from "lodash/upperFirst"
import _memoize from "lodash/memoize" import _memoize from "lodash/memoize"
@@ -155,83 +155,6 @@ export function getList(iterable, keys) {
return Im.List.isList(val) ? val : Im.List() return Im.List.isList(val) ? val : Im.List()
} }
// Adapted from http://stackoverflow.com/a/2893259/454004
// Note: directly ported from CoffeeScript
export function formatXml (xml) {
var contexp, fn, formatted, indent, l, lastType, len, lines, ln, reg, transitions, wsexp
reg = /(>)(<)(\/*)/g
wsexp = /[ ]*(.*)[ ]+\n/g
contexp = /(<.+>)(.+\n)/g
xml = xml.replace(/\r\n/g, "\n").replace(reg, "$1\n$2$3").replace(wsexp, "$1\n").replace(contexp, "$1\n$2")
formatted = ""
lines = xml.split("\n")
indent = 0
lastType = "other"
transitions = {
"single->single": 0,
"single->closing": -1,
"single->opening": 0,
"single->other": 0,
"closing->single": 0,
"closing->closing": -1,
"closing->opening": 0,
"closing->other": 0,
"opening->single": 1,
"opening->closing": 0,
"opening->opening": 1,
"opening->other": 1,
"other->single": 0,
"other->closing": -1,
"other->opening": 0,
"other->other": 0
}
fn = function(ln) {
var fromTo, key, padding, type, types, value
types = {
single: Boolean(ln.match(/<.+\/>/)),
closing: Boolean(ln.match(/<\/.+>/)),
opening: Boolean(ln.match(/<[^!?].*>/))
}
type = ((function() {
var results
results = []
for (key in types) {
value = types[key]
if (value) {
results.push(key)
}
}
return results
})())[0]
type = type === void 0 ? "other" : type
fromTo = lastType + "->" + type
lastType = type
padding = ""
indent += transitions[fromTo]
padding = ((function() {
/* eslint-disable no-unused-vars */
var m, ref1, results, j
results = []
for (j = m = 0, ref1 = indent; 0 <= ref1 ? m < ref1 : m > ref1; j = 0 <= ref1 ? ++m : --m) {
results.push(" ")
}
/* eslint-enable no-unused-vars */
return results
})()).join("")
if (fromTo === "opening->closing") {
formatted = formatted.substr(0, formatted.length - 1) + ln + "\n"
} else {
formatted += padding + ln + "\n"
}
}
for (l = 0, len = lines.length; l < len; l++) {
ln = lines[l]
fn(ln)
}
return formatted
}
/** /**
* Adapted from http://github.com/asvd/microlight * Adapted from http://github.com/asvd/microlight
* @copyright 2016 asvd <heliosframework@gmail.com> * @copyright 2016 asvd <heliosframework@gmail.com>
@@ -536,6 +459,13 @@ export const validateMinLength = (val, min) => {
} }
} }
export const validatePattern = (val, rxPattern) => {
var patt = new RegExp(rxPattern)
if (!patt.test(val)) {
return "Value must follow pattern " + rxPattern
}
}
// validation of parameters before execute // validation of parameters before execute
export const validateParam = (param, isXml, isOAS3 = false) => { export const validateParam = (param, isXml, isOAS3 = false) => {
let errors = [] let errors = []
@@ -549,6 +479,8 @@ export const validateParam = (param, isXml, isOAS3 = false) => {
let format = paramDetails.get("format") let format = paramDetails.get("format")
let maxLength = paramDetails.get("maxLength") let maxLength = paramDetails.get("maxLength")
let minLength = paramDetails.get("minLength") let minLength = paramDetails.get("minLength")
let pattern = paramDetails.get("pattern")
/* /*
If the parameter is required OR the parameter has a value (meaning optional, but filled in) If the parameter is required OR the parameter has a value (meaning optional, but filled in)
@@ -556,14 +488,24 @@ export const validateParam = (param, isXml, isOAS3 = false) => {
Only bother validating the parameter if the type was specified. Only bother validating the parameter if the type was specified.
*/ */
if ( type && (required || value) ) { if ( type && (required || value) ) {
// These checks should evaluate to true if the parameter's value is valid // These checks should evaluate to true if there is a parameter
let stringCheck = type === "string" && value && !validateString(value) let stringCheck = type === "string" && value
let arrayCheck = type === "array" && Array.isArray(value) && value.length let arrayCheck = type === "array" && Array.isArray(value) && value.length
let listCheck = type === "array" && Im.List.isList(value) && value.count() let listCheck = type === "array" && Im.List.isList(value) && value.count()
let fileCheck = type === "file" && value instanceof win.File let fileCheck = type === "file" && value instanceof win.File
let booleanCheck = type === "boolean" && !validateBoolean(value) let booleanCheck = type === "boolean" && (value || value === false)
let numberCheck = type === "number" && !validateNumber(value) // validateNumber returns undefined if the value is a number let numberCheck = type === "number" && (value || value === 0)
let integerCheck = type === "integer" && !validateInteger(value) // validateInteger returns undefined if the value is an integer let integerCheck = type === "integer" && (value || value === 0)
if ( required && !(stringCheck || arrayCheck || listCheck || fileCheck || booleanCheck || numberCheck || integerCheck) ) {
errors.push("Required field is not provided")
return errors
}
if (pattern) {
let err = validatePattern(value, pattern)
if (err) errors.push(err)
}
if (maxLength || maxLength === 0) { if (maxLength || maxLength === 0) {
let err = validateMaxLength(value, maxLength) let err = validateMaxLength(value, maxLength)
@@ -575,11 +517,6 @@ export const validateParam = (param, isXml, isOAS3 = false) => {
if (err) errors.push(err) if (err) errors.push(err)
} }
if ( required && !(stringCheck || arrayCheck || listCheck || fileCheck || booleanCheck || numberCheck || integerCheck) ) {
errors.push("Required field is not provided")
return errors
}
if (maximum || maximum === 0) { if (maximum || maximum === 0) {
let err = validateMaximum(value, maximum) let err = validateMaximum(value, maximum)
if (err) errors.push(err) if (err) errors.push(err)
@@ -722,6 +659,14 @@ export const shallowEqualKeys = (a,b, keys) => {
}) })
} }
export function sanitizeUrl(url) {
if(typeof url !== "string" || url === "") {
return ""
}
return braintreeSanitizeUrl(url)
}
export function getAcceptControllingResponse(responses) { export function getAcceptControllingResponse(responses) {
if(!Im.OrderedMap.isOrderedMap(responses)) { if(!Im.OrderedMap.isOrderedMap(responses)) {
// wrong type! // wrong type!

View File

@@ -0,0 +1,20 @@
export const UPDATE_CONFIGS = "configs_update"
export const TOGGLE_CONFIGS = "configs_toggle"
// Update the configs, with a merge ( not deep )
export function update(configName, configValue) {
return {
type: UPDATE_CONFIGS,
payload: {
[configName]: configValue
},
}
}
// Toggle's the config, by name
export function toggle(configName) {
return {
type: TOGGLE_CONFIGS,
payload: configName,
}
}

View File

@@ -1,5 +1,8 @@
import YAML from "js-yaml" import YAML from "js-yaml"
import yamlConfig from "../../../swagger-config.yaml" import yamlConfig from "../../../swagger-config.yaml"
import * as actions from "./actions"
import * as selectors from "./selectors"
import reducers from "./reducers"
const parseYamlConfig = (yaml, system) => { const parseYamlConfig = (yaml, system) => {
try { try {
@@ -13,11 +16,8 @@ const parseYamlConfig = (yaml, system) => {
} }
export default function configPlugin (toolbox) { const specActions = {
let { fn } = toolbox downloadConfig: (url) => ({fn}) => {
const actions = {
downloadConfig: (url) => () => {
let {fetch} = fn let {fetch} = fn
return fetch(url) return fetch(url)
}, },
@@ -39,18 +39,28 @@ export default function configPlugin (toolbox) {
} }
} }
} }
}
} const specSelectors = {
const selectors = {
getLocalConfig: () => { getLocalConfig: () => {
return parseYamlConfig(yamlConfig) return parseYamlConfig(yamlConfig)
} }
} }
export default function configsPlugin() {
return { return {
statePlugins: { statePlugins: {
spec: { actions, selectors } spec: {
actions: specActions,
selectors: specSelectors,
},
configs: {
reducers,
actions,
selectors,
}
} }
} }
} }

View File

@@ -0,0 +1,20 @@
import { fromJS } from "immutable"
import {
UPDATE_CONFIGS,
TOGGLE_CONFIGS,
} from "./actions"
export default {
[UPDATE_CONFIGS]: (state, action) => {
return state.merge(fromJS(action.payload))
},
[TOGGLE_CONFIGS]: (state, action) => {
const configName = action.payload
const oriVal = state.get(configName)
return state.set(configName, !oriVal)
},
}

View File

@@ -0,0 +1,4 @@
// Just get the config value ( it can possibly be an immutable object)
export const get = (state, path) => {
return state.getIn(Array.isArray(path) ? path : [path])
}

View File

@@ -1,4 +1,4 @@
import React from "react" import React, { cloneElement } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
//import "./topbar.less" //import "./topbar.less"
@@ -134,7 +134,7 @@ export default class Topbar extends React.Component {
<span>swagger</span> <span>swagger</span>
</Link> </Link>
<form className="download-url-wrapper" onSubmit={formOnSubmit}> <form className="download-url-wrapper" onSubmit={formOnSubmit}>
{control} {control.map((el, i) => cloneElement(el, { key: i }))}
</form> </form>
</div> </div>
</div> </div>

View File

@@ -25,7 +25,7 @@
margin: 0 0 10px 0; margin: 0 0 10px 0;
padding: 10px 20px; padding: 10px 20px;
border-bottom: 1px solid #ebebeb; border-bottom: 1px solid $auth-container-border-color;
&:last-of-type &:last-of-type
{ {

View File

@@ -7,10 +7,10 @@
transition: all .3s; transition: all .3s;
border: 2px solid #888; border: 2px solid $btn-border-color;
border-radius: 4px; border-radius: 4px;
background: transparent; background: transparent;
box-shadow: 0 1px 2px rgba(#000,.1); box-shadow: 0 1px 2px rgba($btn-box-shadow-color,.1);
@include text_headline(); @include text_headline();
@@ -29,14 +29,14 @@
&:hover &:hover
{ {
box-shadow: 0 0 5px rgba(#000,.3); box-shadow: 0 0 5px rgba($btn-box-shadow-color,.3);
} }
&.cancel &.cancel
{ {
border-color: #ff6060; border-color: $btn-cancel-border-color;
background-color: $btn-cancel-background-color;
@include text_headline(#ff6060); @include text_headline($btn-cancel-font-color);
} }
&.authorize &.authorize
@@ -45,9 +45,9 @@
display: inline; display: inline;
color: $_color-post; color: $btn-authorize-font-color;
border-color: $_color-post; border-color: $btn-authorize-border-color;
background-color: $btn-authorize-background-color;
span span
{ {
@@ -58,16 +58,17 @@
svg svg
{ {
fill: $_color-post; fill: $btn-authorize-svg-fill-color;
} }
} }
&.execute &.execute
{ {
animation: swagger-ui-pulse 2s infinite; animation: swagger-ui-pulse 2s infinite;
will-change: transform;
color: #fff; background-color: $btn-execute-background-color;
border-color: #4990e2; color: $btn-execute-font-color;
border-color: $btn-execute-border-color;
} }
} }
@@ -76,21 +77,19 @@
{ {
0% 0%
{ {
color: #fff; color: $btn-execute-font-color;
background: #4990e2; background: $btn-execute-background-color-alt;
box-shadow: 0 0 0 0 rgba(#4990e2, .8); box-shadow: 0 0 0 0 rgba($btn-execute-background-color-alt, .8);
} }
70% 70%
{ {
//color: #4990e2; box-shadow: 0 0 0 5px rgba($btn-execute-background-color-alt, 0);
//background: transparent;
box-shadow: 0 0 0 5px rgba(#4990e2, 0);
} }
100% 100%
{ {
color: #fff; color: $btn-execute-font-color;
background: #4990e2; background: $btn-execute-background-color-alt;
box-shadow: 0 0 0 0 rgba(#4990e2, 0); box-shadow: 0 0 0 0 rgba($btn-execute-background-color-alt, 0);
} }
} }
@@ -155,7 +154,7 @@
{ {
svg svg
{ {
fill: #444; fill: $expand-methods-svg-fill-color-hover;
} }
} }
@@ -163,7 +162,7 @@
{ {
transition: all .3s; transition: all .3s;
fill: #777; fill: $expand-methods-svg-fill-color;
} }
} }

View File

@@ -27,7 +27,7 @@
small small
{ {
color: #666; color: $errors-wrapper-errors-small-font-color;
} }
} }

View File

@@ -5,11 +5,11 @@ select
padding: 5px 40px 5px 10px; padding: 5px 40px 5px 10px;
border: 2px solid #41444e; border: 2px solid $form-select-border-color;
border-radius: 4px; border-radius: 4px;
background: #f7f7f7 url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+ICAgIDxwYXRoIGQ9Ik0xMy40MTggNy44NTljLjI3MS0uMjY4LjcwOS0uMjY4Ljk3OCAwIC4yNy4yNjguMjcyLjcwMSAwIC45NjlsLTMuOTA4IDMuODNjLS4yNy4yNjgtLjcwNy4yNjgtLjk3OSAwbC0zLjkwOC0zLjgzYy0uMjctLjI2Ny0uMjctLjcwMSAwLS45NjkuMjcxLS4yNjguNzA5LS4yNjguOTc4IDBMMTAgMTFsMy40MTgtMy4xNDF6Ii8+PC9zdmc+) right 10px center no-repeat; background: $form-select-background-color url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+ICAgIDxwYXRoIGQ9Ik0xMy40MTggNy44NTljLjI3MS0uMjY4LjcwOS0uMjY4Ljk3OCAwIC4yNy4yNjguMjcyLjcwMSAwIC45NjlsLTMuOTA4IDMuODNjLS4yNy4yNjgtLjcwNy4yNjgtLjk3OSAwbC0zLjkwOC0zLjgzYy0uMjctLjI2Ny0uMjctLjcwMSAwLS45NjkuMjcxLS4yNjguNzA5LS4yNjguOTc4IDBMMTAgMTFsMy40MTgtMy4xNDF6Ii8+PC9zdmc+) right 10px center no-repeat;
background-size: 20px; background-size: 20px;
box-shadow: 0 1px 2px 0 rgba(0,0,0,.25); box-shadow: 0 1px 2px 0 rgba($form-select-box-shadow-color, .25);
@include text_headline(); @include text_headline();
appearance: none; appearance: none;
@@ -19,7 +19,7 @@ select
margin: 5px 0; margin: 5px 0;
padding: 5px; padding: 5px;
background: #f7f7f7; background: $form-select-background-color;
} }
&.invalid { &.invalid {
@@ -57,9 +57,9 @@ input[type=file]
margin: 5px 0; margin: 5px 0;
padding: 8px 10px; padding: 8px 10px;
border: 1px solid #d9d9d9; border: 1px solid $form-input-border-color;
border-radius: 4px; border-radius: 4px;
background: #fff; background: $form-input-background-color;
@media (max-width: 768px) { @media (max-width: 768px) {
max-width: 175px; max-width: 175px;
} }
@@ -110,13 +110,13 @@ textarea
border: none; border: none;
border-radius: 4px; border-radius: 4px;
outline: none; outline: none;
background: rgba(#fff,.8); background: rgba($form-textarea-background-color,.8);
@include text_code(); @include text_code();
&:focus &:focus
{ {
border: 2px solid $_color-get; border: 2px solid $form-textarea-focus-border-color;
} }
&.curl &.curl
@@ -130,9 +130,9 @@ textarea
resize: none; resize: none;
border-radius: 4px; border-radius: 4px;
background: #41444e; background: $form-textarea-curl-background-color;
@include text_code(#fff); @include text_code($form-textarea-curl-font-color);
} }
} }
@@ -143,7 +143,7 @@ textarea
transition: opacity .5s; transition: opacity .5s;
color: #333; color: $form-checkbox-label-font-color;
label label
{ {
@@ -179,8 +179,8 @@ textarea
cursor: pointer; cursor: pointer;
border-radius: 1px; border-radius: 1px;
background: #e8e8e8; background: $form-checkbox-background-color;
box-shadow: 0 0 0 2px #e8e8e8; box-shadow: 0 0 0 2px $form-checkbox-box-shadow-color;
flex: none; flex: none;
@@ -192,7 +192,7 @@ textarea
&:checked + label > .item &:checked + label > .item
{ {
background: #e8e8e8 url(data:image/svg+xml,%0A%3Csvg%20width%3D%2210px%22%20height%3D%228px%22%20viewBox%3D%223%207%2010%208%22%20version%3D%221.1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%3E%0A%20%20%20%20%3C%21--%20Generator%3A%20Sketch%2042%20%2836781%29%20-%20http%3A//www.bohemiancoding.com/sketch%20--%3E%0A%20%20%20%20%3Cdesc%3ECreated%20with%20Sketch.%3C/desc%3E%0A%20%20%20%20%3Cdefs%3E%3C/defs%3E%0A%20%20%20%20%3Cpolygon%20id%3D%22Rectangle-34%22%20stroke%3D%22none%22%20fill%3D%22%2341474E%22%20fill-rule%3D%22evenodd%22%20points%3D%226.33333333%2015%203%2011.6666667%204.33333333%2010.3333333%206.33333333%2012.3333333%2011.6666667%207%2013%208.33333333%22%3E%3C/polygon%3E%0A%3C/svg%3E) center center no-repeat; background: $form-checkbox-background-color url(data:image/svg+xml,%0A%3Csvg%20width%3D%2210px%22%20height%3D%228px%22%20viewBox%3D%223%207%2010%208%22%20version%3D%221.1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%3E%0A%20%20%20%20%3C%21--%20Generator%3A%20Sketch%2042%20%2836781%29%20-%20http%3A//www.bohemiancoding.com/sketch%20--%3E%0A%20%20%20%20%3Cdesc%3ECreated%20with%20Sketch.%3C/desc%3E%0A%20%20%20%20%3Cdefs%3E%3C/defs%3E%0A%20%20%20%20%3Cpolygon%20id%3D%22Rectangle-34%22%20stroke%3D%22none%22%20fill%3D%22%2341474E%22%20fill-rule%3D%22evenodd%22%20points%3D%226.33333333%2015%203%2011.6666667%204.33333333%2010.3333333%206.33333333%2012.3333333%2011.6666667%207%2013%208.33333333%22%3E%3C/polygon%3E%0A%3C/svg%3E) center center no-repeat;
} }
} }
} }

View File

@@ -30,9 +30,9 @@
padding: 3px 5px; padding: 3px 5px;
border-radius: 4px; border-radius: 4px;
background: rgba(#000,.05); background: rgba($info-code-background-color,.05);
@include text_code(#9012fe); @include text_code($info-code-font-color);
} }
a a
@@ -41,11 +41,11 @@
transition: all .4s; transition: all .4s;
@include text_body(#4990e2); @include text_body($info-link-font-color);
&:hover &:hover
{ {
color: darken(#4990e2, 15%); color: darken($info-link-font-color-hover, 15%);
} }
} }
> div > div
@@ -86,13 +86,13 @@
vertical-align: super; vertical-align: super;
border-radius: 57px; border-radius: 57px;
background: #7d8492; background: $info-title-small-background-color;
pre pre
{ {
margin: 0; margin: 0;
@include text_headline(#fff); @include text_headline($info-title-small-pre-font-color);
} }
} }
} }

View File

@@ -34,11 +34,11 @@
cursor: pointer; cursor: pointer;
transition: all .2s; transition: all .2s;
border-bottom: 1px solid rgba(#3b4151, .3); border-bottom: 1px solid rgba($opblock-tag-border-bottom-color, .3);
&:hover &:hover
{ {
background: rgba(#000,.02); background: rgba($opblock-tag-background-color-hover,.02);
} }
} }
@@ -127,9 +127,9 @@
{ {
margin: 0 0 15px 0; margin: 0 0 15px 0;
border: 1px solid #000; border: 1px solid $opblock-border-color;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 0 3px rgba(#000,.19); box-shadow: 0 0 3px rgba($opblock-box-shadow-color,.19);
.tab-header .tab-header
{ {
@@ -168,7 +168,7 @@
content: ''; content: '';
transform: translateX(-50%); transform: translateX(-50%);
background: #888; background: $opblock-tab-header-tab-item-active-h4-span-after-background-color;
} }
} }
} }
@@ -181,7 +181,7 @@
{ {
.opblock-summary .opblock-summary
{ {
border-bottom: 1px solid #000; border-bottom: 1px solid $opblock-isopen-summary-border-bottom-color;
} }
} }
@@ -194,8 +194,8 @@
min-height: 50px; min-height: 50px;
background: rgba(#fff,.8); background: rgba($opblock-isopen-section-header-background-color,.8);
box-shadow: 0 1px 2px rgba(#000,.1); box-shadow: 0 1px 2px rgba($opblock-isopen-section-header-box-shadow-color,.1);
label label
{ {
@@ -239,10 +239,10 @@
text-align: center; text-align: center;
border-radius: 3px; border-radius: 3px;
background: #000; background: $opblock-summary-method-background-color;
text-shadow: 0 1px 0 rgba(#000,.1); text-shadow: 0 1px 0 rgba($opblock-summary-method-text-shadow-color,.1);
@include text_headline(#fff); @include text_headline($opblock-summary-method-font-color);
} }
.opblock-summary-path, .opblock-summary-path,
@@ -377,7 +377,7 @@
margin: 20px 0; margin: 20px 0;
padding: 10px 10px; padding: 10px 10px;
border: 2px solid #d8dde7; border: 2px solid $operational-filter-input-border-color;
} }
} }
@@ -420,7 +420,7 @@
content: ''; content: '';
background: rgba(#000,.2); background: rgba($tab-list-item-first-background-color,.2);
} }
} }
@@ -525,7 +525,7 @@
{ {
font-size: 11px; font-size: 11px;
@include text_code(#999); @include text_code($response-col-status-undocumented-font-color);
} }
} }
@@ -541,7 +541,7 @@
{ {
font-size: 11px; font-size: 11px;
@include text_code(#999); @include text_code($response-col-links-font-color);
} }
} }
@@ -558,9 +558,9 @@
padding: 10px; padding: 10px;
border-radius: 4px; border-radius: 4px;
background: #41444e; background: $response-col-description-inner-markdown-background-color;
@include text_code(#fff); @include text_code($response-col-description-inner-markdown-font-color);
p p
{ {
@@ -569,10 +569,10 @@
a a
{ {
@include text_code(#89bf04); @include text_code($response-col-description-inner-markdown-link-font-color);
text-decoration: underline; text-decoration: underline;
&:hover { &:hover {
color: #81b10c; color: $response-col-description-inner-markdown-link-font-color-hover;
} }
} }
} }
@@ -593,13 +593,13 @@
hyphens: auto; hyphens: auto;
border-radius: 4px; border-radius: 4px;
background: #41444e; background: $opblock-body-background-color;
overflow-wrap: break-word; overflow-wrap: break-word;
@include text_code(#fff); @include text_code($opblock-body-font-color);
span span
{ {
color: #fff !important; color: $opblock-body-font-color !important;
} }
.headerline .headerline
@@ -613,8 +613,8 @@
margin: 0 0 20px 0; margin: 0 0 20px 0;
padding: 30px 0; padding: 30px 0;
background: #fff; background: $scheme-container-background-color;
box-shadow: 0 1px 2px 0 rgba(0,0,0,.15); box-shadow: 0 1px 2px 0 rgba($scheme-container-box-shadow-color,.15);
.schemes .schemes
{ {
@@ -648,14 +648,14 @@
margin: 0 0 20px 0; margin: 0 0 20px 0;
padding: 30px 0; padding: 30px 0;
background: #fff; background: $server-container-background-color;
box-shadow: 0 1px 2px 0 rgba(0,0,0,.15); box-shadow: 0 1px 2px 0 rgba($server-container-box-shadow-color,.15);
.computed-url { .computed-url {
margin: 2em 0; margin: 2em 0;
code { code {
color: grey; color: $server-container-computed-url-code-font-color;
display: inline-block; display: inline-block;
padding: 4px; padding: 4px;
font-size: 16px; font-size: 16px;
@@ -755,8 +755,8 @@
animation: rotation 1s infinite linear, opacity .5s; animation: rotation 1s infinite linear, opacity .5s;
opacity: 1; opacity: 1;
border: 2px solid rgba(#555, .1); border: 2px solid rgba($loading-container-before-border-color, .1);
border-top-color: rgba(#000, .6); border-top-color: rgba($loading-container-before-border-top-color, .6);
border-radius: 100%; border-radius: 100%;
backface-visibility: hidden; backface-visibility: hidden;
@@ -777,11 +777,11 @@
&.controls-accept-header { &.controls-accept-header {
select { select {
border-color: green; border-color: $response-content-type-controls-accept-header-select-border-color;
} }
small { small {
color: green; color: $response-content-type-controls-accept-header-small-font-color;
font-size: .7em; font-size: .7em;
} }
} }

View File

@@ -15,7 +15,7 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
background: rgba(#000,.8); background: rgba($dialog-ux-backdrop-background-color,.8);
} }
.modal-ux .modal-ux
@@ -31,10 +31,10 @@
transform: translate(-50%,-50%); transform: translate(-50%,-50%);
border: 1px solid #ebebeb; border: 1px solid $dialog-ux-modal-border-color;
border-radius: 4px; border-radius: 4px;
background: #fff; background: $dialog-ux-modal-background-color;
box-shadow: 0 10px 30px 0 rgba(0,0,0,.20); box-shadow: 0 10px 30px 0 rgba($dialog-ux-modal-box-shadow-color,.20);
} }
.modal-ux-content .modal-ux-content
@@ -50,7 +50,7 @@
margin: 0 0 5px 0; margin: 0 0 5px 0;
color: #41444e; color: $dialog-ux-modal-content-font-color;
@include text_body(); @include text_body();
} }
@@ -72,7 +72,7 @@
padding: 12px 0; padding: 12px 0;
border-bottom: 1px solid #ebebeb; border-bottom: 1px solid $dialog-ux-modal-header-border-bottom-color;
align-items: center; align-items: center;

View File

@@ -3,14 +3,16 @@
font-size: 12px; font-size: 12px;
font-weight: 300; font-weight: 300;
@include text_code();
.deprecated .deprecated
{ {
span, td { span,
color: #aaa !important; td
{
color: $model-deprecated-font-color !important;
} }
} }
@include text_code();
&-toggle &-toggle
{ {
font-size: 10px; font-size: 10px;
@@ -82,12 +84,13 @@
white-space: nowrap; white-space: nowrap;
color: #ebebeb; color: $model-hint-font-color;
border-radius: 4px; border-radius: 4px;
background: rgba(#000,.7); background: rgba($model-hint-background-color,.7);
} }
p { p
{
margin: 0 0 1em 0; margin: 0 0 1em 0;
} }
} }
@@ -97,7 +100,7 @@ section.models
{ {
margin: 30px 0; margin: 30px 0;
border: 1px solid rgba(#3b4151, .3); border: 1px solid rgba($section-models-border-color, .3);
border-radius: 4px; border-radius: 4px;
&.is-open &.is-open
@@ -106,7 +109,8 @@ section.models
h4 h4
{ {
margin: 0 0 5px 0; margin: 0 0 5px 0;
border-bottom: 1px solid rgba(#3b4151, .3);
border-bottom: 1px solid rgba($section-models-isopen-h4-border-bottom-color, .3);
} }
} }
h4 h4
@@ -114,6 +118,7 @@ section.models
font-size: 16px; font-size: 16px;
display: flex; display: flex;
align-items: center;
margin: 0; margin: 0;
padding: 10px 20px 10px 10px; padding: 10px 20px 10px 10px;
@@ -121,8 +126,7 @@ section.models
cursor: pointer; cursor: pointer;
transition: all .2s; transition: all .2s;
@include text_headline(#777); @include text_headline($section-models-h4-font-color);
align-items: center;
svg svg
{ {
@@ -136,7 +140,7 @@ section.models
&:hover &:hover
{ {
background: rgba(#000,.02); background: rgba($section-models-h4-background-color-hover,.02);
} }
} }
@@ -146,7 +150,7 @@ section.models
margin: 0 0 10px 0; margin: 0 0 10px 0;
@include text_headline(#777); @include text_headline($section-models-h5-font-color);
} }
.model-jump-to-path .model-jump-to-path
@@ -162,11 +166,11 @@ section.models
transition: all .5s; transition: all .5s;
border-radius: 4px; border-radius: 4px;
background: rgba(#000,.05); background: rgba($section-models-model-container-background-color,.05);
&:hover &:hover
{ {
background: rgba(#000,.07); background: rgba($section-models-model-container-background-color,.07);
} }
&:first-of-type &:first-of-type
@@ -192,7 +196,7 @@ section.models
padding: 10px; padding: 10px;
border-radius: 4px; border-radius: 4px;
background: rgba(#000,.1); background: rgba($section-models-model-box-background-color,.1);
.model-jump-to-path .model-jump-to-path
{ {
@@ -211,14 +215,16 @@ section.models
{ {
font-size: 16px; font-size: 16px;
@include text_headline(#555); @include text_headline($section-models-model-title-font-color);
} }
.model-deprecated-warning .model-deprecated-warning
{ {
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
margin-right: 1em; margin-right: 1em;
@include text_headline($_color-delete); @include text_headline($_color-delete);
} }
@@ -237,13 +243,13 @@ span
.prop-name .prop-name
{ {
display: inline-block; display: inline-block;
margin-right: 1em; margin-right: 1em;
width: 8em;
} }
.prop-type .prop-type
{ {
color: #55a; color: $prop-type-font-color;
} }
.prop-enum .prop-enum
@@ -252,5 +258,5 @@ span
} }
.prop-format .prop-format
{ {
color: #999; color: $prop-format-font-color;
} }

View File

@@ -74,7 +74,7 @@ table
text-align: left; text-align: left;
border-bottom: 1px solid rgba(#3b4151, .2); border-bottom: 1px solid rgba($table-thead-td-border-bottom-color, .2);
@include text_body(); @include text_body();
} }
@@ -126,7 +126,7 @@ table
content: 'required'; content: 'required';
color: rgba(#f00, .6); color: rgba($table-parameter-name-required-font-color, .6);
} }
} }
} }
@@ -136,7 +136,7 @@ table
font-size: 12px; font-size: 12px;
font-style: italic; font-style: italic;
@include text_code(#888); @include text_code($table-parameter-in-font-color);
} }
.parameter__deprecated .parameter__deprecated
@@ -144,7 +144,7 @@ table
font-size: 12px; font-size: 12px;
font-style: italic; font-style: italic;
@include text_code(#f00); @include text_code($table-parameter-deprecated-font-color);
} }

View File

@@ -2,7 +2,7 @@
{ {
padding: 8px 0; padding: 8px 0;
background-color: #89bf04; background-color: $topbar-background-color;
.topbar-wrapper .topbar-wrapper
{ {
display: flex; display: flex;
@@ -21,7 +21,7 @@
text-decoration: none; text-decoration: none;
@include text_headline(#fff); @include text_headline($topbar-link-font-color);
span span
{ {
@@ -41,7 +41,7 @@
width: 100%; width: 100%;
margin: 0; margin: 0;
border: 2px solid #547f00; border: 2px solid $topbar-download-url-wrapper-element-border-color;
border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px;
outline: none; outline: none;
} }
@@ -71,7 +71,7 @@
width: 100%; width: 100%;
border: 2px solid #547f00; border: 2px solid $topbar-download-url-wrapper-element-border-color;
outline: none; outline: none;
box-shadow: none; box-shadow: none;
} }
@@ -87,9 +87,9 @@
border: none; border: none;
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
background: #547f00; background: $topbar-download-url-button-background-color;
@include text_headline(#fff); @include text_headline($topbar-download-url-button-font-color);
} }
} }
} }

View File

@@ -1,11 +1,11 @@
@mixin text_body($color: #3b4151) @mixin text_body($color: $text-body-default-font-color)
{ {
font-family: 'Open Sans', sans-serif; font-family: 'Open Sans', sans-serif;
color: $color; color: $color;
} }
@mixin text_code($color: #3b4151) @mixin text_code($color: $text-code-default-font-color)
{ {
font-family: 'Source Code Pro', monospace; font-family: 'Source Code Pro', monospace;
font-weight: 600; font-weight: 600;
@@ -13,7 +13,7 @@
color: $color; color: $color;
} }
@mixin text_headline($color: #3b4151) @mixin text_headline($color: $text-headline-default-font-color)
{ {
font-family: 'Titillium Web', sans-serif; font-family: 'Titillium Web', sans-serif;

View File

@@ -0,0 +1,218 @@
$gray-base: #000 !default;
$white: #fff !default;
$gray-50: #ebebeb !default;
$gray-100: #d8dde7 !default;
$gray-200: lighten($gray-base, 62.75%) !default; // #aaa
$gray-300: lighten($gray-base, 56.5%) !default; // #999
$gray-400: lighten($gray-base, 50%) !default; // #888
$gray-500: lighten($gray-base, 43.75%) !default; // #777
$gray-600: lighten($gray-base, 37.5%) !default; // #666
$gray-650: lighten($gray-base, 33.3%) !default; // ##555555
$gray-700: lighten($gray-base, 31.25%) !default; // #555
$gray-800: lighten($gray-base, 25%) !default; // #444
$gray-900: lighten($gray-base, 18.75%) !default; // #333
$gray-custom-1: #41444e !default;
$gray-custom-2: #3b4151 !default;
$color-primary: #89bf04 !default;
$color-secondary: #9012fe !default;
$color-info: #4990e2 !default;
$color-warning: #ff6060 !default;
$color-danger: #f00 !default;
$_color-post: #49cc90 !default;
$_color-get: #61affe !default;
$_color-put: #fca130 !default;
$_color-delete: #f93e3e !default;
$_color-head: #9012fe !default;
$_color-patch: #50e3c2 !default;
$_color-disabled: #ebebeb !default;
$_color-options: #0d5aa7 !default;
$color-green: #008000 !default;
$color-primary-hover: #81b10c !default;
// Authorize
$auth-container-border-color: $gray-50 !default;
// Buttons
$btn-background-color: transparent !default;
$btn-border-color: $gray-400 !default;
$btn-font-color: inherit !default;
$btn-box-shadow-color: $gray-base !default;
$btn-authorize-background-color: transparent !default;
$btn-authorize-border-color: $_color-post !default;
$btn-authorize-font-color: $_color-post !default;
$btn-authorize-svg-fill-color: $_color-post !default;
$btn-cancel-background-color: transparent !default;
$btn-cancel-border-color: $color-warning !default;
$btn-cancel-font-color: $color-warning !default;
$btn-execute-background-color: transparent !default;
$btn-execute-border-color: $color-info !default;
$btn-execute-font-color: $white !default;
$btn-execute-background-color-alt: $color-info !default;
$expand-methods-svg-fill-color: $gray-500 !default;
$expand-methods-svg-fill-color-hover: $gray-800 !default;
// Errors
$errors-wrapper-background-color: $_color-delete !default;
$errors-wrapper-border-color: $_color-delete !default;
$errors-wrapper-errors-small-font-color: $gray-600 !default;
// Form
$form-select-background-color: #f7f7f7 !default;
$form-select-border-color: $gray-custom-1 !default;
$form-select-box-shadow-color: $gray-base !default;
$form-input-border-color: #d9d9d9 !default;
$form-input-background-color: $white !default;
$form-textarea-background-color: $white !default;
$form-textarea-focus-border-color: $_color-get !default;
$form-textarea-curl-background-color: $gray-custom-1 !default;
$form-textarea-curl-font-color: $white !default;
$form-checkbox-label-font-color: $gray-900 !default;
$form-checkbox-background-color: #e8e8e8 !default;
$form-checkbox-box-shadow-color: #e8e8e8 !default;
// Information
$info-code-background-color: $gray-base !default;
$info-code-font-color: $_color-head !default;
$info-link-font-color: $color-info !default;
$info-link-font-color-hover: $info-link-font-color !default;
$info-title-small-background-color: #7d8492 !default;
$info-title-small-pre-font-color: $white !default;
// Layout
$opblock-border-color: $gray-base !default;
$opblock-box-shadow-color: $gray-base !default;
$opblock-tag-border-bottom-color: $gray-custom-2 !default;
$opblock-tag-background-color-hover: $gray-base !default;
$opblock-tab-header-tab-item-active-h4-span-after-background-color: $gray-400 !default;
$opblock-isopen-summary-border-bottom-color: $gray-base !default;
$opblock-isopen-section-header-background-color: $white !default;
$opblock-isopen-section-header-box-shadow-color: $gray-base !default;
$opblock-summary-method-background-color: $gray-base !default;
$opblock-summary-method-font-color: $white !default;
$opblock-summary-method-text-shadow-color: $gray-base !default;
$operational-filter-input-border-color: #d8dde7 !default;
$tab-list-item-first-background-color: $gray-base !default;
$response-col-status-undocumented-font-color: $gray-300 !default;
$response-col-links-font-color: $gray-300 !default;
$response-col-description-inner-markdown-font-color: $white !default;
$response-col-description-inner-markdown-background-color: $gray-custom-1 !default;
$response-col-description-inner-markdown-link-font-color: $color-primary !default;
$response-col-description-inner-markdown-link-font-color-hover: $color-primary-hover !default;
$opblock-body-background-color: $gray-custom-1 !default;
$opblock-body-font-color: $white !default;
$scheme-container-background-color: $white !default;
$scheme-container-box-shadow-color: $gray-base !default;
$server-container-background-color: $white !default;
$server-container-box-shadow-color: $gray-base !default;
$server-container-computed-url-code-font-color: $gray-400 !default;
$loading-container-before-border-color: $gray-650 !default;
$loading-container-before-border-top-color: $gray-base !default;
$response-content-type-controls-accept-header-select-border-color: $color-green !default;
$response-content-type-controls-accept-header-small-font-color: $color-green !default;
// Modal
$dialog-ux-backdrop-background-color: $gray-base !default;
$dialog-ux-modal-background-color: $white !default;
$dialog-ux-modal-border-color: $gray-50 !default;
$dialog-ux-modal-box-shadow-color: $gray-base !default;
$dialog-ux-modal-content-font-color: $gray-custom-1 !default;
$dialog-ux-modal-header-border-bottom-color: $gray-50 !default;
// Models
$model-deprecated-font-color: $gray-200 !default;
$model-hint-font-color: $gray-50 !default;
$model-hint-background-color: $gray-base !default;
$section-models-border-color: $gray-custom-2 !default;
$section-models-isopen-h4-border-bottom-color: $section-models-border-color !default;
$section-models-h4-font-color: $gray-500 !default;
$section-models-h4-background-color-hover: $gray-base !default;
$section-models-h5-font-color: $gray-500 !default;
$section-models-model-container-background-color: $gray-base !default;
$section-models-model-box-background-color: $gray-base !default;
$section-models-model-title-font-color: $gray-700 !default;
$prop-type-font-color: #55a !default;
$prop-format-font-color: $gray-300 !default;
// Tables
$table-thead-td-border-bottom-color: $gray-custom-2 !default;
$table-parameter-name-required-font-color: $color-danger !default;
$table-parameter-in-font-color: $gray-400 !default;
$table-parameter-deprecated-font-color: $color-danger !default;
// Topbar
$topbar-background-color: $color-primary !default;
$topbar-link-font-color: $white !default;
$topbar-download-url-wrapper-element-border-color: #547f00 !default;
$topbar-download-url-button-background-color: #547f00 !default;
$topbar-download-url-button-font-color: $white !default;
// Type
$text-body-default-font-color: $gray-custom-2 !default;
$text-code-default-font-color: $gray-custom-2 !default;
$text-headline-default-font-color: $gray-custom-2 !default;

View File

@@ -0,0 +1,63 @@
import React from "react"
import expect from "expect"
import { shallow } from "enzyme"
import { fromJS } from "immutable"
import ObjectModel from "components/object-model"
import ModelExample from "components/model-example"
import Immutable from "immutable"
import Model from "components/model"
import ModelCollapse from "components/model-collapse"
import { inferSchema } from "corePlugins/samples/fn"
describe("<ObjectModel />", function() {
const dummyComponent = () => null
const components = {
"JumpToPath" : dummyComponent,
"Markdown" : dummyComponent,
"Model" : Model,
"ModelCollapse" : ModelCollapse
}
const props = {
getComponent: c => components[c],
isRef : false,
schema: Immutable.fromJS(
{
"properties": {
// Note reverse order: c, b, a
c: {
type: "integer",
name: "c"
},
b: {
type: "boolean",
name: "b"
},
a: {
type: "string",
name: "a"
}
}
}
),
specSelectors: {
isOAS3(){
return false
}
},
className: "for-test"
}
it("renders a collapsible header", function(){
const wrapper = shallow(<ObjectModel {...props}/>)
const renderedModelCollapse = wrapper.find(ModelCollapse)
expect(renderedModelCollapse.length).toEqual(1)
})
it("renders the object properties in order", function() {
const wrapper = shallow(<ObjectModel {...props}/>)
const renderedModel = wrapper.find(Model)
expect(renderedModel.length).toEqual(3)
expect(renderedModel.get(0).props.schema.get("name")).toEqual("c")
expect(renderedModel.get(1).props.schema.get("name")).toEqual("b")
expect(renderedModel.get(2).props.schema.get("name")).toEqual("a")
})
})

View File

@@ -0,0 +1,133 @@
/* eslint-env mocha */
import expect from "expect"
import { fromJS } from "immutable"
import { definitionsToAuthorize, definitionsForRequirements } from "corePlugins/auth/selectors"
describe("auth plugin - selectors", () => {
describe("definitionsToAuthorize", () => {
it("should return securityDefinitions as a List", () => {
const securityDefinitions = {
"petstore_auth": {
"type": "oauth2",
"authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
"flow": "implicit",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "header"
}
}
const system = {
specSelectors: {
securityDefinitions() {
return fromJS(securityDefinitions)
}
}
}
const res = definitionsToAuthorize({})(system)
expect(res.toJS()).toEqual([
{
"petstore_auth": securityDefinitions["petstore_auth"]
},
{
"api_key": securityDefinitions["api_key"]
},
])
})
it("should fail gracefully with bad data", () => {
const securityDefinitions = null
const system = {
specSelectors: {
securityDefinitions() {
return fromJS(securityDefinitions)
}
}
}
const res = definitionsToAuthorize({})(system)
expect(res.toJS()).toEqual([])
})
})
describe("definitionsForRequirements", () => {
it("should return applicable securityDefinitions as a List", () => {
const securityDefinitions = {
"petstore_auth": {
"type": "oauth2",
"authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
"flow": "implicit",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "header"
}
}
const system = {
authSelectors: {
definitionsToAuthorize() {
return fromJS([
{
"petstore_auth": securityDefinitions["petstore_auth"]
},
{
"api_key": securityDefinitions["api_key"]
},
])
}
}
}
const securities = fromJS([
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
])
const res = definitionsForRequirements({}, securities)(system)
expect(res.toJS()).toEqual([
{
"petstore_auth": securityDefinitions["petstore_auth"]
}
])
})
it("should fail gracefully with bad data", () => {
const securityDefinitions = null
const system = {
authSelectors: {
definitionsToAuthorize() {
return null
}
}
}
const securities = null
const res = definitionsForRequirements({}, securities)(system)
expect(res.toJS()).toEqual([])
})
})
})

View File

@@ -3,6 +3,7 @@ import expect from "expect"
import { fromJS, OrderedMap } from "immutable" import { fromJS, OrderedMap } from "immutable"
import { import {
mapToList, mapToList,
validatePattern,
validateMinLength, validateMinLength,
validateMaxLength, validateMaxLength,
validateDateTime, validateDateTime,
@@ -16,7 +17,8 @@ import {
fromJSOrdered, fromJSOrdered,
getAcceptControllingResponse, getAcceptControllingResponse,
createDeepLinkPath, createDeepLinkPath,
escapeDeepLinkPath escapeDeepLinkPath,
sanitizeUrl
} from "core/utils" } from "core/utils"
import win from "core/window" import win from "core/window"
@@ -273,6 +275,28 @@ describe("utils", function() {
}) })
}) })
describe("validatePattern", function() {
let rxPattern = "^(red|blue)"
let errorMessage = "Value must follow pattern " + rxPattern
it("doesn't return for a match", function() {
expect(validatePattern("red", rxPattern)).toBeFalsy()
expect(validatePattern("blue", rxPattern)).toBeFalsy()
})
it("returns a message for invalid pattern", function() {
expect(validatePattern("pink", rxPattern)).toEqual(errorMessage)
expect(validatePattern("123", rxPattern)).toEqual(errorMessage)
})
it("fails gracefully when an invalid regex value is passed", function() {
expect(() => validatePattern("aValue", "---")).toNotThrow()
expect(() => validatePattern("aValue", 1234)).toNotThrow()
expect(() => validatePattern("aValue", null)).toNotThrow()
expect(() => validatePattern("aValue", [])).toNotThrow()
})
})
describe("validateParam", function() { describe("validateParam", function() {
let param = null let param = null
let result = null let result = null
@@ -524,7 +548,7 @@ describe("utils", function() {
type: "boolean", type: "boolean",
value: "test string" value: "test string"
} }
assertValidateParam(param, ["Required field is not provided"]) assertValidateParam(param, ["Value must be a boolean"])
// valid boolean value // valid boolean value
param = { param = {
@@ -584,7 +608,7 @@ describe("utils", function() {
type: "number", type: "number",
value: "test" value: "test"
} }
assertValidateParam(param, ["Required field is not provided"]) assertValidateParam(param, ["Value must be a number"])
// invalid number, undefined value // invalid number, undefined value
param = { param = {
@@ -666,7 +690,7 @@ describe("utils", function() {
type: "integer", type: "integer",
value: "test" value: "test"
} }
assertValidateParam(param, ["Required field is not provided"]) assertValidateParam(param, ["Value must be an integer"])
// invalid integer, undefined value // invalid integer, undefined value
param = { param = {
@@ -676,6 +700,14 @@ describe("utils", function() {
} }
assertValidateParam(param, ["Required field is not provided"]) assertValidateParam(param, ["Required field is not provided"])
// valid integer, but 0 is falsy in JS
param = {
required: true,
type: "integer",
value: 0
}
assertValidateParam(param, [])
// valid integer // valid integer
param = { param = {
required: true, required: true,
@@ -885,4 +917,44 @@ describe("utils", function() {
expect(result).toEqual("hello\\#world") expect(result).toEqual("hello\\#world")
}) })
}) })
describe("sanitizeUrl", function() {
it("should sanitize a `javascript:` url", function() {
const res = sanitizeUrl("javascript:alert('bam!')")
expect(res).toEqual("about:blank")
})
it("should sanitize a `data:` url", function() {
const res = sanitizeUrl(`data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGV
sbG8iKTs8L3NjcmlwdD4=`)
expect(res).toEqual("about:blank")
})
it("should not modify a `http:` url", function() {
const res = sanitizeUrl(`http://swagger.io/`)
expect(res).toEqual("http://swagger.io/")
})
it("should not modify a `https:` url", function() {
const res = sanitizeUrl(`https://swagger.io/`)
expect(res).toEqual("https://swagger.io/")
})
it("should gracefully handle empty strings", function() {
expect(sanitizeUrl("")).toEqual("")
})
it("should gracefully handle non-string values", function() {
expect(sanitizeUrl(123)).toEqual("")
expect(sanitizeUrl(null)).toEqual("")
expect(sanitizeUrl(undefined)).toEqual("")
expect(sanitizeUrl([])).toEqual("")
expect(sanitizeUrl({})).toEqual("")
})
})
}) })

View File

@@ -93,6 +93,7 @@ module.exports = {
petAPIWrapperBar: { petAPIWrapperBar: {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) .opblock-tag" selector: ".swagger-ui .opblock-tag-section:nth-child(1) .opblock-tag"
}, },
/** /**
* Post pet/ api * Post pet/ api
*/ */
@@ -141,6 +142,7 @@ module.exports = {
petOperationPostStatus: { petOperationPostStatus: {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-addPet pre.microlight span:nth-child(70)" selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-addPet pre.microlight span:nth-child(70)"
}, },
/** /**
* Put pet/ api * Put pet/ api
*/ */
@@ -189,8 +191,9 @@ module.exports = {
petOperationPutStatus: { petOperationPutStatus: {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-updatePet pre.microlight span:nth-child(70)" selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-updatePet pre.microlight span:nth-child(70)"
}, },
/** /**
* Get pet/ * Get /pet/findByTags
*/ */
petOperationGetByTagContainer: { petOperationGetByTagContainer: {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-findPetsByTags" selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-findPetsByTags"
@@ -238,6 +241,34 @@ module.exports = {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-findPetsByTags pre.microlight span:nth-child(70)" selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-findPetsByTags pre.microlight span:nth-child(70)"
}, },
/**
* Get /pet/{petId}
*/
petOperationGetByIdContainer: {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-getPetById"
},
petOperationGetByIdTitle: {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-getPetById .opblock-summary-get span.opblock-summary-path span"
},
petOperationGetByIdCollpase: {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-getPetById .opblock-summary-get"
},
petOperationGetByIdCollapseContainer: {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-getPetById .ReactCollapse--collapse"
},
petOperationGetByIdTryBtn: {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-getPetById button.try-out__btn"
},
petOperationGetByIdExecuteBtn: {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-getPetById button.execute"
},
petOperationGetByIdParameter: {
selector: ".swagger-ui .opblock-tag-section:nth-child(3) div#operations-pet-getPetById div.parameters-col_description input"
},
petOperationGetByIdResultsBox: {
selector: ".swagger-ui .opblock-tag-section:nth-child(1) div#operations-pet-getPetById pre.microlight"
},
/** /**
* Delete pet/ * Delete pet/
*/ */
@@ -497,9 +528,5 @@ module.exports = {
}, },
} }
} }
} }
} }

View File

@@ -84,6 +84,7 @@ describe("render pet api container", function () {
client.end() client.end()
}) })
it("Testing put /pet api Mock data", function (client) { it("Testing put /pet api Mock data", function (client) {
apiWrapper.waitForElementVisible("@petOperationPutContainer", 5000) apiWrapper.waitForElementVisible("@petOperationPutContainer", 5000)
.click("@petOperationPutCollpase") .click("@petOperationPutCollpase")
@@ -138,6 +139,38 @@ describe("render pet api container", function () {
client.end() client.end()
}) })
it("render get by ID /pet/{petId} api container", function (client) {
apiWrapper.waitForElementVisible("@petOperationGetByIdContainer", 5000)
.assert.containsText("@petOperationGetByIdTitle", "/pet/{petId}")
.click("@petOperationGetByIdCollpase")
.waitForElementVisible("@petOperationGetByIdCollapseContainer", 3000)
.click("@petOperationGetByIdTryBtn")
.waitForElementVisible("@petOperationGetByTagExecuteBtn", 1000)
.click("@petOperationGetByTagTryBtn")
.assert.cssClassNotPresent("@petOperationGetByTagTryBtn", "cancel")
client.end()
})
it("render get by ID /pet/{petId} api Mock data", function (client) {
apiWrapper.waitForElementVisible("@petOperationGetByIdContainer", 5000)
.assert.containsText("@petOperationGetByIdTitle", "/pet/{petId}")
.click("@petOperationGetByIdCollpase")
.waitForElementVisible("@petOperationGetByIdCollapseContainer", 3000)
.click("@petOperationGetByIdTryBtn")
.waitForElementVisible("@petOperationGetByTagExecuteBtn", 1000)
.setValue("@petOperationGetByIdParameter", "abc")
.click("@petOperationGetByIdExecuteBtn")
.waitForElementVisible("@petOperationGetByIdResultsBox")
.assert.containsText("@petOperationGetByIdParameter", "abc")
.assert.cssClassPresent("@petOperationGetByIdParameter", "invalid")
.assert.attributeEquals("@petOperationGetByIdParameter", "title", "Value must be an integer")
.click("@petOperationGetByTagTryBtn")
.assert.cssClassNotPresent("@petOperationGetByTagTryBtn", "cancel")
client.end()
})
it("render delete /pet api container", function (client) { it("render delete /pet api container", function (client) {
apiWrapper.waitForElementVisible("@petOperationDeleteContainer") apiWrapper.waitForElementVisible("@petOperationDeleteContainer")
.assert.containsText("@petOperationDeleteTitle", "/pet/{petId}") .assert.containsText("@petOperationDeleteTitle", "/pet/{petId}")
@@ -150,6 +183,7 @@ describe("render pet api container", function () {
client.end() client.end()
}) })
it("Testing delete /pet api Mock data", function (client) { it("Testing delete /pet api Mock data", function (client) {
apiWrapper.waitForElementVisible("@petOperationDeleteContainer", 3000) apiWrapper.waitForElementVisible("@petOperationDeleteContainer", 3000)
.click("@petOperationDeleteCollpase") .click("@petOperationDeleteCollpase")