Merge branch 'master' into support/editor-validation-refactor

This commit is contained in:
Kyle Shockey
2017-12-19 23:29:59 -06:00
26 changed files with 149 additions and 100 deletions

1
.agignore Normal file
View File

@@ -0,0 +1 @@
dist/

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.6.1 | 2017-12-01 | 2.0, 3.0 | [tag v3.6.1](https://github.com/swagger-api/swagger-ui/tree/v3.6.1) 3.7.0 | 2017-12-15 | 2.0, 3.0 | [tag v3.7.0](https://github.com/swagger-api/swagger-ui/tree/v3.7.0)
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

@@ -49,8 +49,9 @@ Parameter Name | Description
--- | --- --- | ---
`deepLinking` | `Boolean=false`. If set to `true`, enables deep linking for tags and operations. See the [Deep Linking documentation](/docs/usage/deep-linking.md) for more information. `deepLinking` | `Boolean=false`. If set to `true`, enables deep linking for tags and operations. See the [Deep Linking documentation](/docs/usage/deep-linking.md) for more information.
`displayOperationId` | `Boolean=false`. Controls the display of operationId in operations list. The default is `false`. `displayOperationId` | `Boolean=false`. Controls the display of operationId in operations list. The default is `false`.
`defaultModelExpandDepth` | `Number=1`. The default expansion depth for models. `defaultModelsExpandDepth` | `Number=1`. The default expansion depth for models (set to -1 completely hide the models).
`defaultModelRendering` | `String=["example"*, "model"]`. Controls how models are shown when the API is first rendered. (The user can always switch the rendering for a given model by clicking the 'Model' and 'Example Value' links.) `defaultModelExpandDepth` | `Number=1`. The default expansion depth for the model on the model-example section.
`defaultModelRendering` | `String=["example"*, "model"]`. Controls how the model is shown when the API is first rendered. (The user can always switch the rendering for a given model by clicking the 'Model' and 'Example Value' links.)
`displayRequestDuration` | `Boolean=false`. Controls the display of the request duration (in milliseconds) for Try-It-Out requests. `displayRequestDuration` | `Boolean=false`. Controls the display of the request duration (in milliseconds) for Try-It-Out requests.
`docExpansion` | `String=["list"*, "full", "none"]`. Controls the default expansion setting for the operations and tags. It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing). `docExpansion` | `String=["list"*, "full", "none"]`. Controls the default expansion setting for the operations and tags. It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing).
`filter` | `Boolean=false OR String`. If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that are shown. Can be Boolean to enable or disable, or a string, in which case filtering will be enabled using that string as the filter expression. Filtering is case sensitive matching the filter expression anywhere inside the tag. `filter` | `Boolean=false OR String`. If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that are shown. Can be Boolean to enable or disable, or a string, in which case filtering will be enabled using that string as the filter expression. Filtering is case sensitive matching the filter expression anywhere inside the tag.

View File

@@ -1,6 +1,6 @@
{ {
"name": "swagger-ui", "name": "swagger-ui",
"version": "3.6.1", "version": "3.7.0",
"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": [
@@ -81,7 +81,7 @@
"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.4.1", "swagger-client": "^3.4.2",
"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",

View File

@@ -0,0 +1,20 @@
import React from "react"
import PropTypes from "prop-types"
export const DeepLink = ({ enabled, path, text }) => {
return (
<a className="nostyle"
onClick={enabled ? (e) => e.preventDefault() : null}
href={enabled ? `#/${path}` : null}>
<span>{text}</span>
</a>
)
}
DeepLink.propTypes = {
enabled: PropTypes.bool,
isShown: PropTypes.bool,
path: PropTypes.string,
text: PropTypes.string
}
export default DeepLink

View File

@@ -13,14 +13,14 @@ export default class Models extends Component {
render(){ render(){
let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props
let definitions = specSelectors.definitions() let definitions = specSelectors.definitions()
let { docExpansion, defaultModelExpandDepth } = getConfigs() let { docExpansion, defaultModelsExpandDepth } = getConfigs()
let showModels = layoutSelectors.isShown("models", docExpansion === "full" || docExpansion === "list" ) if (!definitions.size || defaultModelsExpandDepth < 0) return null
let showModels = layoutSelectors.isShown("models", defaultModelsExpandDepth > 0 && docExpansion !== "none")
const specPathBase = specSelectors.isOAS3() ? ["components", "schemas"] : ["definitions"] const specPathBase = specSelectors.isOAS3() ? ["components", "schemas"] : ["definitions"]
const ModelWrapper = getComponent("ModelWrapper") const ModelWrapper = getComponent("ModelWrapper")
const Collapse = getComponent("Collapse") const Collapse = getComponent("Collapse")
if (!definitions.size) return null
return <section className={ showModels ? "models is-open" : "models"}> return <section className={ showModels ? "models is-open" : "models"}>
<h4 onClick={() => layoutActions.show("models", !showModels)}> <h4 onClick={() => layoutActions.show("models", !showModels)}>
@@ -35,7 +35,7 @@ export default class Models extends Component {
return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }> return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }>
<ModelWrapper name={ name } <ModelWrapper name={ name }
expandDepth={ defaultModelExpandDepth } expandDepth={ defaultModelsExpandDepth }
schema={ model } schema={ model }
specPath={[...specPathBase, name]} specPath={[...specPathBase, name]}
getComponent={ getComponent } getComponent={ getComponent }

View File

@@ -102,6 +102,7 @@ export default class Operation extends PureComponent {
const Schemes = getComponent( "schemes" ) const Schemes = getComponent( "schemes" )
const OperationServers = getComponent( "OperationServers" ) const OperationServers = getComponent( "OperationServers" )
const OperationExt = getComponent( "OperationExt" ) const OperationExt = getComponent( "OperationExt" )
const DeepLink = getComponent( "DeepLink" )
const { showExtensions } = getConfigs() const { showExtensions } = getConfigs()
@@ -120,12 +121,11 @@ export default class Operation extends PureComponent {
and pulled in with getComponent */} and pulled in with getComponent */}
<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 <DeepLink
className="nostyle" enabled={isDeepLinkingEnabled}
onClick={isDeepLinkingEnabled ? (e) => e.preventDefault() : null} isShown={isShown}
href={isDeepLinkingEnabled ? `#/${isShownKey.join("/")}` : null}> path={`${isShownKey.join("/")}`}
<span>{path}</span> text={path} />
</a>
<JumpToPath path={specPath} /> {/*TODO: use wrapComponents here, swagger-ui doesn't care about jumpToPath */} <JumpToPath path={specPath} /> {/*TODO: use wrapComponents here, swagger-ui doesn't care about jumpToPath */}
</span> </span>

View File

@@ -37,6 +37,7 @@ export default class Operations extends React.Component {
const OperationContainer = getComponent("OperationContainer", true) const OperationContainer = getComponent("OperationContainer", true)
const Collapse = getComponent("Collapse") const Collapse = getComponent("Collapse")
const Markdown = getComponent("Markdown") const Markdown = getComponent("Markdown")
const DeepLink = getComponent("DeepLink")
let { let {
docExpansion, docExpansion,
@@ -79,12 +80,11 @@ export default class Operations extends React.Component {
onClick={() => layoutActions.show(isShownKey, !showTag)} onClick={() => layoutActions.show(isShownKey, !showTag)}
className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" } className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }
id={isShownKey.join("-")}> id={isShownKey.join("-")}>
<a <DeepLink
className="nostyle" enabled={isDeepLinkingEnabled}
onClick={isDeepLinkingEnabled ? (e) => e.preventDefault() : null} isShown={showTag}
href= {isDeepLinkingEnabled ? `#/${tag}` : null}> path={tag}
<span>{tag}</span> text={tag} />
</a>
{ !tagDescription ? null : { !tagDescription ? null :
<small> <small>
<Markdown source={tagDescription} /> <Markdown source={tagDescription} />

View File

@@ -47,7 +47,7 @@ export default class ParamBody extends PureComponent {
updateValues = (props) => { updateValues = (props) => {
let { specSelectors, pathMethod, param, isExecute, consumesValue="" } = props let { specSelectors, pathMethod, param, isExecute, consumesValue="" } = props
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name"), param.get("in")) : {} let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name"), param.get("in")) : fromJS({})
let isXml = /xml/i.test(consumesValue) let isXml = /xml/i.test(consumesValue)
let isJson = /json/i.test(consumesValue) let isJson = /json/i.test(consumesValue)
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value") let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value")

View File

@@ -43,7 +43,11 @@ export default class ResponseBody extends React.Component {
// Image // Image
} else if (/^image\//i.test(contentType)) { } else if (/^image\//i.test(contentType)) {
bodyEl = <img style={{ maxWidth: "100%" }} src={ window.URL.createObjectURL(content) } /> if(contentType.includes("svg")) {
bodyEl = <div> { content } </div>
} else {
bodyEl = <img style={{ maxWidth: "100%" }} src={ window.URL.createObjectURL(content) } />
}
// Audio // Audio
} else if (/^audio\//i.test(contentType)) { } else if (/^audio\//i.test(contentType)) {

View File

@@ -47,6 +47,7 @@ module.exports = function SwaggerUI(opts) {
showMutatedRequest: true, showMutatedRequest: true,
defaultModelRendering: "example", defaultModelRendering: "example",
defaultModelExpandDepth: 1, defaultModelExpandDepth: 1,
defaultModelsExpandDepth: 1,
showExtensions: false, showExtensions: false,
// Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance. // Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance.

View File

@@ -46,7 +46,7 @@ export default function authorize ( { auth, authActions, errActions, configs, au
authId: name, authId: name,
source: "validation", source: "validation",
level: "error", level: "error",
message: "oauth2RedirectUri configuration is not passed. Oauth2 authorization cannot be performed." message: "oauth2RedirectUrl configuration is not passed. Oauth2 authorization cannot be performed."
}) })
return return
} }

View File

@@ -103,30 +103,30 @@ export function positionRangeForPath(yaml, path) {
let ast = cachedCompose(yaml) let ast = cachedCompose(yaml)
// simply walks the tree using current path recursively to the point that // simply walks the tree using astValue path recursively to the point that
// path is empty. // path is empty.
return find(ast) return find(ast)
function find(current) { function find(astValue, astKeyValue) {
if (current.tag === MAP_TAG) { if (astValue.tag === MAP_TAG) {
for (i = 0; i < current.value.length; i++) { for (i = 0; i < astValue.value.length; i++) {
var pair = current.value[i] var pair = astValue.value[i]
var key = pair[0] var key = pair[0]
var value = pair[1] var value = pair[1]
if (key.value === path[0]) { if (key.value === path[0]) {
path.shift() path.shift()
return find(value) return find(value, key)
} }
} }
} }
if (current.tag === SEQ_TAG) { if (astValue.tag === SEQ_TAG) {
var item = current.value[path[0]] var item = astValue.value[path[0]]
if (item && item.tag) { if (item && item.tag) {
path.shift() path.shift()
return find(item) return find(item, astKeyValue)
} }
} }
@@ -135,17 +135,35 @@ export function positionRangeForPath(yaml, path) {
return invalidRange return invalidRange
} }
return { const range = {
/* jshint camelcase: false */
start: { start: {
line: current.start_mark.line, line: astValue.start_mark.line,
column: current.start_mark.column column: astValue.start_mark.column,
pointer: astValue.start_mark.pointer,
}, },
end: { end: {
line: current.end_mark.line, line: astValue.end_mark.line,
column: current.end_mark.column column: astValue.end_mark.column,
pointer: astValue.end_mark.pointer,
} }
} }
if(astKeyValue) {
// eslint-disable-next-line camelcase
range.key_start = {
line: astKeyValue.start_mark.line,
column: astKeyValue.start_mark.column,
pointer: astKeyValue.start_mark.pointer,
}
// eslint-disable-next-line camelcase
range.key_end = {
line: astKeyValue.end_mark.line,
column: astKeyValue.end_mark.column,
pointer: astKeyValue.end_mark.pointer,
}
}
return range
} }
} }

View File

@@ -31,20 +31,32 @@ export const updateResolved = (ori, { layoutActions, getConfigs }) => (...args)
let swaggerUI = document.querySelector(".swagger-ui") let swaggerUI = document.querySelector(".swagger-ui")
let myScroller = zenscroll.createScroller(swaggerUI) let myScroller = zenscroll.createScroller(swaggerUI)
let target
if(tag && operationId) { if(tag && operationId) {
// Pre-expand and scroll to the operation // Pre-expand and scroll to the operation
layoutActions.show(["operations-tag", tag], true) layoutActions.show(["operations-tag", tag], true)
layoutActions.show(["operations", tag, operationId], true) layoutActions.show(["operations", tag, operationId], true)
let target = document.getElementById(`operations-${escapeDeepLinkPath(tag)}-${escapeDeepLinkPath(operationId)}`) target = document
myScroller.to(target) .getElementById(`operations-${escapeDeepLinkPath(tag)}-${escapeDeepLinkPath(operationId)}`)
} else if(tag) { } else if(tag) {
// Pre-expand and scroll to the tag // Pre-expand and scroll to the tag
layoutActions.show(["operations-tag", tag], true) layoutActions.show(["operations-tag", tag], true)
let target = document.getElementById(`operations-tag-${escapeDeepLinkPath(tag)}`) target = document.getElementById(`operations-tag-${escapeDeepLinkPath(tag)}`)
}
if(target) {
myScroller.to(target) myScroller.to(target)
setTimeout(() => {
// Backup functionality: if we're still at the top of the document,
// scroll on the entire page (not within the Swagger-UI container)
if(zenscroll.getY() === 0) {
zenscroll.to(target)
}
}, 50)
} }
} }

View File

@@ -1,6 +1,7 @@
import YAML from "js-yaml" import YAML from "js-yaml"
import parseUrl from "url-parse" import parseUrl from "url-parse"
import serializeError from "serialize-error" import serializeError from "serialize-error"
import isString from "lodash/isString"
import { isJSONObject } from "core/utils" import { isJSONObject } from "core/utils"
// Actions conform to FSA (flux-standard-actions) // Actions conform to FSA (flux-standard-actions)
@@ -22,22 +23,16 @@ export const UPDATE_OPERATION_VALUE = "spec_update_operation_value"
export const UPDATE_RESOLVED = "spec_update_resolved" export const UPDATE_RESOLVED = "spec_update_resolved"
export const SET_SCHEME = "set_scheme" export const SET_SCHEME = "set_scheme"
export function updateSpec(spec) { const toStr = (str) => isString(str) ? str : ""
if(spec instanceof Error) {
return {type: UPDATE_SPEC, error: true, payload: spec}
}
export function updateSpec(spec) {
const cleanSpec = (toStr(spec)).replace(/\t/g, " ")
if(typeof spec === "string") { if(typeof spec === "string") {
return { return {
type: UPDATE_SPEC, type: UPDATE_SPEC,
payload: spec.replace(/\t/g, " ") || "" payload: cleanSpec
} }
} }
return {
type: UPDATE_SPEC,
payload: ""
}
} }
export function updateResolved(spec) { export function updateResolved(spec) {
@@ -52,9 +47,6 @@ export function updateUrl(url) {
} }
export function updateJsonSpec(json) { export function updateJsonSpec(json) {
if(!json || typeof json !== "object") {
throw new Error("updateJson must only accept a simple JSON object")
}
return {type: UPDATE_JSON, payload: json} return {type: UPDATE_JSON, payload: json}
} }
@@ -76,7 +68,10 @@ export const parseToJson = (str) => ({specActions, specSelectors, errActions}) =
line: e.mark && e.mark.line ? e.mark.line + 1 : undefined line: e.mark && e.mark.line ? e.mark.line + 1 : undefined
}) })
} }
return specActions.updateJsonSpec(json) if(json) {
return specActions.updateJsonSpec(json)
}
return {}
} }
export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST }, getConfigs}) => { export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST }, getConfigs}) => {
@@ -130,18 +125,6 @@ export const resolveSpec = (json, url) => ({specActions, specSelectors, errActio
}) })
} }
export const formatIntoYaml = () => ({specActions, specSelectors}) => {
let { specStr } = specSelectors
let { updateSpec } = specActions
try {
let yaml = YAML.safeDump(YAML.safeLoad(specStr()), {indent: 2})
updateSpec(yaml)
} catch(e) {
updateSpec(e)
}
}
export function changeParam( path, paramName, paramIn, value, isXml ){ export function changeParam( path, paramName, paramIn, value, isXml ){
return { return {
type: UPDATE_PARAM, type: UPDATE_PARAM,

View File

@@ -256,10 +256,11 @@ export const allowTryItOutFor = () => {
// Get the parameter value by parameter name // Get the parameter value by parameter name
export function getParameter(state, pathMethod, name, inType) { export function getParameter(state, pathMethod, name, inType) {
pathMethod = pathMethod || []
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([])) let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
return params.filter( (p) => { return params.find( (p) => {
return Map.isMap(p) && p.get("name") === name && p.get("in") === inType return Map.isMap(p) && p.get("name") === name && p.get("in") === inType
}).first() }) || Map() // Always return a map
} }
export const hasHost = createSelector( export const hasHost = createSelector(
@@ -272,6 +273,7 @@ export const hasHost = createSelector(
// Get the parameter values, that the user filled out // Get the parameter values, that the user filled out
export function parameterValues(state, pathMethod, isXml) { export function parameterValues(state, pathMethod, isXml) {
pathMethod = pathMethod || []
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([])) let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
return params.reduce( (hash, p) => { return params.reduce( (hash, p) => {
let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value") let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value")
@@ -295,6 +297,7 @@ export function parametersIncludeType(parameters, typeValue="") {
// Get the consumes/produces value that the user selected // Get the consumes/produces value that the user selected
export function contentTypeValues(state, pathMethod) { export function contentTypeValues(state, pathMethod) {
pathMethod = pathMethod || []
let op = spec(state).getIn(["paths", ...pathMethod], fromJS({})) let op = spec(state).getIn(["paths", ...pathMethod], fromJS({}))
const parameters = op.get("parameters") || new List() const parameters = op.get("parameters") || new List()
@@ -313,6 +316,7 @@ export function contentTypeValues(state, pathMethod) {
// Get the consumes/produces by path // Get the consumes/produces by path
export function operationConsumes(state, pathMethod) { export function operationConsumes(state, pathMethod) {
pathMethod = pathMethod || []
return spec(state).getIn(["paths", ...pathMethod, "consumes"], fromJS({})) return spec(state).getIn(["paths", ...pathMethod, "consumes"], fromJS({}))
} }
@@ -329,6 +333,7 @@ export const canExecuteScheme = ( state, path, method ) => {
} }
export const validateBeforeExecute = ( state, pathMethod ) => { export const validateBeforeExecute = ( state, pathMethod ) => {
pathMethod = pathMethod || []
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([])) let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
let isValid = true let isValid = true

View File

@@ -61,6 +61,7 @@ import PrimitiveModel from "core/components/primitive-model"
import Property from "core/components/property" 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"
import DeepLink from "core/components/deep-link"
import Markdown from "core/components/providers/markdown" import Markdown from "core/components/providers/markdown"
@@ -121,7 +122,8 @@ export default function() {
OperationExt, OperationExt,
OperationExtRow, OperationExtRow,
ParameterExt, ParameterExt,
OperationContainer OperationContainer,
DeepLink
} }
} }

View File

@@ -32,12 +32,12 @@ describe("<Models/>", function(){
layoutActions: {}, layoutActions: {},
getConfigs: () => ({ getConfigs: () => ({
docExpansion: "list", docExpansion: "list",
defaultModelExpandDepth: 0 defaultModelsExpandDepth: 0
}) })
} }
it("passes defaultModelExpandDepth to ModelWrapper", function(){ it("passes defaultModelsExpandDepth to ModelWrapper", function(){
// When // When
let wrapper = shallow(<Models {...props}/>) let wrapper = shallow(<Models {...props}/>)

View File

@@ -3,11 +3,13 @@ import React from "react"
import expect, { createSpy } from "expect" import expect, { createSpy } from "expect"
import { render } from "enzyme" import { render } from "enzyme"
import { fromJS } from "immutable" import { fromJS } from "immutable"
import DeepLink from "components/deep-link"
import Operations from "components/operations" import Operations from "components/operations"
import {Collapse} from "components/layout-utils" import {Collapse} from "components/layout-utils"
const components = { const components = {
Collapse, Collapse,
DeepLink,
OperationContainer: ({ path, method }) => <span className="mocked-op" id={`${path}-${method}`} /> OperationContainer: ({ path, method }) => <span className="mocked-op" id={`${path}-${method}`} />
} }