housekeeping: factor out components for easier BaseLayout reuse (#4604)

* improve: wrap schemes to encapsulate rendering logic
* improve: wrap filter to encapsulate rendering logic
* improve: wrap info section to encapsulate rendering logic
* improve: wrap servers plugin to encapsulate rendering logic
* improve: added tests for schemes-wrapper rendering logic
* improve: added tests for info-wrapper rendering logic, also do not render info if info is undefined
* improve: added tests for filter rendering logic
* improve: added tests for servers-wrapper rendering logic
* `InfoWrapper` -> `InfoContainer`
* add `containers` alias to Babel configuration
* `SchemesWrapper` -> `SchemesContainer`
* drop `container` from container file names
* `ServersWrapper` -> `ServersContainer`
* `Filter` -> `FilterContainer`
* follow `core/containers` pattern in BasePreset
This commit is contained in:
Sofiia
2018-06-15 04:06:20 +02:00
committed by kyle
parent 0eb591b78a
commit 8416b043ea
12 changed files with 511 additions and 80 deletions

View File

@@ -18,6 +18,10 @@
"expose": "components",
"src": "src/core/components"
},
{
"expose": "containers",
"src": "src/core/containers"
},
{
"expose": "core",
"src": "src/core"

View File

@@ -6,67 +6,35 @@ export default class BaseLayout extends React.Component {
static propTypes = {
errSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
oas3Selectors: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired
}
onFilterChange =(e) => {
let {target: {value}} = e
this.props.layoutActions.updateFilter(value)
}
render() {
let {
specSelectors,
specActions,
getComponent,
layoutSelectors,
oas3Selectors,
oas3Actions
} = this.props
let info = specSelectors.info()
let url = specSelectors.url()
let basePath = specSelectors.basePath()
let host = specSelectors.host()
let securityDefinitions = specSelectors.securityDefinitions()
let externalDocs = specSelectors.externalDocs()
let schemes = specSelectors.schemes()
let servers = specSelectors.servers()
let {specSelectors, getComponent} = this.props
let SvgAssets = getComponent("SvgAssets")
let InfoContainer = getComponent("InfoContainer", true)
let VersionPragmaFilter = getComponent("VersionPragmaFilter")
let Info = getComponent("info")
let Operations = getComponent("operations", true)
let Models = getComponent("Models", true)
let AuthorizeBtn = getComponent("authorizeBtn", true)
let Row = getComponent("Row")
let Col = getComponent("Col")
let Servers = getComponent("Servers")
let ServersContainer = getComponent("ServersContainer", true)
let Errors = getComponent("errors", true)
const SchemesContainer = getComponent("SchemesContainer", true)
const FilterContainer = getComponent("FilterContainer", true)
let isSwagger2 = specSelectors.isSwagger2()
let isOAS3 = specSelectors.isOAS3()
let isLoading = specSelectors.loadingStatus() === "loading"
let isFailed = specSelectors.loadingStatus() === "failed"
let filter = layoutSelectors.currentFilter()
let inputStyle = {}
if(isFailed) inputStyle.color = "red"
if(isLoading) inputStyle.color = "#aaa"
const Schemes = getComponent("schemes")
const isSpecEmpty = !specSelectors.specStr()
if(isSpecEmpty) {
let loadingMessage
let isLoading = specSelectors.loadingStatus() === "loading"
if(isLoading) {
loadingMessage = <div className="loading"></div>
} else {
@@ -88,53 +56,15 @@ export default class BaseLayout extends React.Component {
<Errors/>
<Row className="information-container">
<Col mobile={12}>
{ info.count() ? (
<Info info={ info } url={ url } host={ host } basePath={ basePath } externalDocs={externalDocs} getComponent={getComponent}/>
) : null }
<InfoContainer/>
</Col>
</Row>
{ schemes && schemes.size || securityDefinitions ? (
<div className="scheme-container">
<Col className="schemes wrapper" mobile={12}>
{ schemes && schemes.size ? (
<Schemes
currentScheme={specSelectors.operationScheme()}
schemes={ schemes }
specActions={ specActions } />
) : null }
{ securityDefinitions ? (
<AuthorizeBtn />
) : null }
</Col>
</div>
) : null }
<SchemesContainer/>
{ servers && servers.size ? (
<div className="global-server-container">
<Col className="servers wrapper" mobile={12}>
<span className="servers-title">Server</span>
<Servers
servers={servers}
currentServer={oas3Selectors.selectedServer()}
setSelectedServer={oas3Actions.setSelectedServer}
setServerVariableValue={oas3Actions.setServerVariableValue}
getServerVariable={oas3Selectors.serverVariableValue}
getEffectiveServerValue={oas3Selectors.serverEffectiveValue}
/>
</Col>
</div>
<ServersContainer/>
) : null}
{
filter === null || filter === false ? null :
<div className="filter-container">
<Col className="filter wrapper" mobile={12}>
<input className="operation-filter-input" placeholder="Filter by tag" type="text" onChange={this.onFilterChange} value={filter === true || filter === "true" ? "" : filter} disabled={isLoading} style={inputStyle} />
</Col>
</div>
}
<FilterContainer/>
<Row>
<Col mobile={12} desktop={12} >

View File

@@ -0,0 +1,44 @@
import React from "react"
import PropTypes from "prop-types"
export default class FilterContainer extends React.Component {
static propTypes = {
specSelectors: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
}
onFilterChange = (e) => {
const {target: {value}} = e
this.props.layoutActions.updateFilter(value)
}
render () {
const {specSelectors, layoutSelectors, getComponent} = this.props
const Col = getComponent("Col")
const isLoading = specSelectors.loadingStatus() === "loading"
const isFailed = specSelectors.loadingStatus() === "failed"
const filter = layoutSelectors.currentFilter()
const inputStyle = {}
if (isFailed) inputStyle.color = "red"
if (isLoading) inputStyle.color = "#aaa"
return (
<div>
{filter === null || filter === false ? null :
<div className="filter-container">
<Col className="filter wrapper" mobile={12}>
<input className="operation-filter-input" placeholder="Filter by tag" type="text"
onChange={this.onFilterChange} value={filter === true || filter === "true" ? "" : filter}
disabled={isLoading} style={inputStyle}/>
</Col>
</div>
}
</div>
)
}
}

View File

@@ -0,0 +1,32 @@
import React from "react"
import PropTypes from "prop-types"
export default class InfoContainer extends React.Component {
static propTypes = {
specActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
}
render () {
const {specSelectors, getComponent} = this.props
const info = specSelectors.info()
const url = specSelectors.url()
const basePath = specSelectors.basePath()
const host = specSelectors.host()
const externalDocs = specSelectors.externalDocs()
const Info = getComponent("info")
return (
<div>
{info && info.count() ? (
<Info info={info} url={url} host={host} basePath={basePath} externalDocs={externalDocs}
getComponent={getComponent}/>
) : null}
</div>
)
}
}

View File

@@ -0,0 +1,43 @@
import React from "react"
import PropTypes from "prop-types"
export default class SchemesContainer extends React.Component {
static propTypes = {
specActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
}
render () {
const {specActions, specSelectors, getComponent} = this.props
const currentScheme = specSelectors.operationScheme()
const schemes = specSelectors.schemes()
const securityDefinitions = specSelectors.securityDefinitions()
const Col = getComponent("Col")
const AuthorizeBtn = getComponent("authorizeBtn", true)
const Schemes = getComponent("schemes")
return (
<div>
{schemes && schemes.size || securityDefinitions ? (
<div className="scheme-container">
<Col className="schemes wrapper" mobile={12}>
{schemes && schemes.size ? (
<Schemes
currentScheme={currentScheme}
schemes={schemes}
specActions={specActions}
/>
) : null}
{securityDefinitions ? (
<AuthorizeBtn/>
) : null}
</Col>
</div>
) : null}
</div>
)
}
}

View File

@@ -2,6 +2,7 @@ import Callbacks from "./callbacks"
import RequestBody from "./request-body"
import OperationLink from "./operation-link.jsx"
import Servers from "./servers"
import ServersContainer from "./servers-container"
import RequestBodyEditor from "./request-body-editor"
import HttpAuth from "./http-auth"
import OperationServers from "./operation-servers"
@@ -11,6 +12,7 @@ export default {
HttpAuth,
RequestBody,
Servers,
ServersContainer,
RequestBodyEditor,
OperationServers,
operationLink: OperationLink

View File

@@ -0,0 +1,42 @@
import React from "react"
import PropTypes from "prop-types"
export default class ServersContainer extends React.Component {
static propTypes = {
specSelectors: PropTypes.object.isRequired,
oas3Selectors: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
}
render () {
const {specSelectors, oas3Selectors, oas3Actions, getComponent} = this.props
const servers = specSelectors.servers()
const Col = getComponent("Col")
const Servers = getComponent("Servers")
return (
<div>
{servers && servers.size ? (
<div className="global-server-container">
<Col className="servers wrapper" mobile={12}>
<span className="servers-title">Server</span>
<Servers
servers={servers}
currentServer={oas3Selectors.selectedServer()}
setSelectedServer={oas3Actions.setSelectedServer}
setServerVariableValue={oas3Actions.setServerVariableValue}
getServerVariable={oas3Selectors.serverVariableValue}
getEffectiveServerValue={oas3Selectors.serverEffectiveValue}
/>
</Col>
</div>
) : null}
</div>
)
}
}

View File

@@ -51,10 +51,13 @@ import Info, {
InfoUrl,
InfoBasePath
} from "core/components/info"
import InfoContainer from "core/containers/info"
import Footer from "core/components/footer"
import FilterContainer from "core/containers/filter"
import ParamBody from "core/components/param-body"
import Curl from "core/components/curl"
import Schemes from "core/components/schemes"
import SchemesContainer from "core/containers/schemes"
import ModelCollapse from "core/components/model-collapse"
import ModelExample from "core/components/model-example"
import ModelWrapper from "core/components/model-wrapper"
@@ -95,6 +98,7 @@ export default function() {
clear: Clear,
liveResponse: LiveResponse,
info: Info,
InfoContainer,
onlineValidatorBadge: OnlineValidatorBadge,
operations: Operations,
operation: Operation,
@@ -110,9 +114,11 @@ export default function() {
contentType: ContentType,
overview: Overview,
footer: Footer,
FilterContainer,
ParamBody: ParamBody,
curl: Curl,
schemes: Schemes,
SchemesContainer,
modelExample: ModelExample,
ModelWrapper,
ModelCollapse,

65
test/components/filter.js Normal file
View File

@@ -0,0 +1,65 @@
/* eslint-env mocha */
import React from "react"
import expect from "expect"
import { mount } from "enzyme"
import FilterContainer from "containers/filter"
import { Col } from "components/layout-utils"
describe("<FilterContainer/>", function(){
const mockedProps = {
specSelectors: {
loadingStatus() {}
},
layoutSelectors: {
currentFilter() {}
},
getComponent: () => {return Col}
}
it("renders FilterContainer if filter is provided", function(){
// Given
let props = {...mockedProps}
props.layoutSelectors = {...mockedProps.specSelectors}
props.layoutSelectors.currentFilter = function() {return true}
// When
let wrapper = mount(<FilterContainer {...props}/>)
// Then
const renderedColInsideFilter = wrapper.find(Col)
expect(renderedColInsideFilter.length).toEqual(1)
})
it("does not render FilterContainer if filter is null", function(){
// Given
let props = {...mockedProps}
props.layoutSelectors = {...mockedProps.specSelectors}
props.layoutSelectors.currentFilter = function() {return null}
// When
let wrapper = mount(<FilterContainer {...props}/>)
// Then
const renderedColInsideFilter = wrapper.find(Col)
expect(renderedColInsideFilter.length).toEqual(0)
})
it("does not render FilterContainer if filter is false", function(){
// Given
let props = {...mockedProps}
props.layoutSelectors = {...mockedProps.specSelectors}
props.layoutSelectors.currentFilter = function() {return false}
// When
let wrapper = mount(<FilterContainer {...props}/>)
// Then
const renderedColInsideFilter = wrapper.find(Col)
expect(renderedColInsideFilter.length).toEqual(0)
})
})

View File

@@ -0,0 +1,67 @@
/* eslint-env mocha */
import React from "react"
import expect from "expect"
import { mount } from "enzyme"
import { fromJS } from "immutable"
import InfoContainer from "containers/info"
describe("<InfoContainer/>", function () {
const components = {
info: () => <span className="mocked-info"/>
}
const mockedProps = {
specSelectors: {
info () {},
url () {},
basePath () {},
host () {},
externalDocs () {}
},
getComponent: c => components[c]
}
it("renders Info inside InfoContainer if info is provided", function () {
// Given
let props = {...mockedProps}
props.specSelectors = {...mockedProps.specSelectors}
props.specSelectors.info = function () {return fromJS(["info1", "info2"])}
// When
let wrapper = mount(<InfoContainer {...props}/>)
// Then
const renderedInfo = wrapper.find("span.mocked-info")
expect(renderedInfo.length).toEqual(1)
})
it("does not render Info inside InfoContainer if no info is provided", function () {
// Given
let props = {...mockedProps}
props.specSelectors = {...mockedProps.specSelectors}
props.specSelectors.info = function () {return fromJS([])}
// When
let wrapper = mount(<InfoContainer {...props}/>)
// Then
const renderedInfo = wrapper.find("span.mocked-info")
expect(renderedInfo.length).toEqual(0)
})
it("does not render Info inside InfoContainer if info is undefined", function () {
// Given
let props = {...mockedProps}
// When
let wrapper = mount(<InfoContainer {...props}/>)
// Then
const renderedInfo = wrapper.find("span.mocked-info")
expect(renderedInfo.length).toEqual(0)
})
})

View File

@@ -0,0 +1,119 @@
/* eslint-env mocha */
import React from "react"
import expect from "expect"
import { mount, render } from "enzyme"
import { fromJS } from "immutable"
import SchemesContainer from "containers/schemes"
import Schemes from "components/schemes"
import { Col } from "components/layout-utils"
describe("<SchemesContainer/>", function(){
const components = {
schemes: Schemes,
Col,
authorizeBtn: () => <span className="mocked-button" id="mocked-button" />
}
const mockedProps = {
specSelectors: {
securityDefinitions() {},
operationScheme() {},
schemes() {}
},
specActions: {
setScheme() {}
},
getComponent: c => components[c]
}
const twoSecurityDefinitions = {
"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"
}
}
it("renders Schemes inside SchemesContainer if schemes are provided", function(){
// Given
let props = {...mockedProps}
props.specSelectors = {...mockedProps.specSelectors}
props.specSelectors.operationScheme = function() {return "http"}
props.specSelectors.schemes = function() {return fromJS(["http", "https"])}
// When
let wrapper = mount(<SchemesContainer {...props}/>)
// Then
const renderedSchemes = wrapper.find(Schemes)
expect(renderedSchemes.length).toEqual(1)
})
it("does not render Schemes inside SchemeWrapper if empty array of schemes is provided", function(){
// Given
let props = {...mockedProps}
props.specSelectors = {...mockedProps.specSelectors}
props.specSelectors.schemes = function() {return fromJS([])}
// When
let wrapper = mount(<SchemesContainer {...props}/>)
// Then
const renderedSchemes = wrapper.find(Schemes)
expect(renderedSchemes.length).toEqual(0)
})
it("does not render Schemes inside SchemeWrapper if provided schemes are undefined", function(){
// Given
let props = {...mockedProps}
props.specSelectors = {...mockedProps.specSelectors}
props.specSelectors.schemes = function() {return undefined}
// When
let wrapper = mount(<SchemesContainer {...props}/>)
// Then
const renderedSchemes = wrapper.find(Schemes)
expect(renderedSchemes.length).toEqual(0)
})
it("renders AuthorizeBtn inside SchemesContainer if security definition is provided", function(){
// Given
let props = {...mockedProps}
props.specSelectors = {...mockedProps.specSelectors}
props.specSelectors.securityDefinitions = function () {return fromJS(twoSecurityDefinitions)}
// When
let wrapper = render(<SchemesContainer {...props}/>)
// Then
const renderedAuthorizeBtn = wrapper.find("span.mocked-button")
expect(renderedAuthorizeBtn.length).toEqual(1)
})
it("does not render AuthorizeBtn if security definition is not provided", function(){
// Given
let props = {...mockedProps}
// When
let wrapper = render(<SchemesContainer {...props}/>)
// Then
const renderedAuthorizeBtn = wrapper.find("span.mocked-button")
expect(renderedAuthorizeBtn.length).toEqual(0)
})
})

View File

@@ -0,0 +1,77 @@
/* eslint-env mocha */
import React from "react"
import expect from "expect"
import { mount } from "enzyme"
import { fromJS } from "immutable"
import ServersContainer from "core/plugins/oas3/components/servers-container"
import Servers from "core/plugins/oas3/components/servers"
import { Col } from "components/layout-utils"
describe("<ServersContainer/>", function(){
const components = {
Servers,
Col
}
const mockedProps = {
specSelectors: {
servers() {}
},
oas3Selectors: {
selectedServer() {},
serverVariableValue() {},
serverEffectiveValue() {}
},
oas3Actions: {
setSelectedServer() {},
setServerVariableValue() {}
},
getComponent: c => components[c]
}
it("renders Servers inside ServersContainer if servers are provided", function(){
// Given
let props = {...mockedProps}
props.specSelectors = {...mockedProps.specSelectors}
props.specSelectors.servers = function() {return fromJS([{url: "http://server1.com"}])}
props.oas3Selectors = {...mockedProps.oas3Selectors}
props.oas3Selectors.selectedServer = function() {return "http://server1.com"}
// When
let wrapper = mount(<ServersContainer {...props}/>)
// Then
const renderedServers = wrapper.find(Servers)
expect(renderedServers.length).toEqual(1)
})
it("does not render Servers inside ServersContainer if servers are empty", function(){
// Given
let props = {...mockedProps}
props.specSelectors = {...mockedProps.specSelectors}
props.specSelectors.servers = function() {return fromJS([])}
// When
let wrapper = mount(<ServersContainer {...props}/>)
// Then
const renderedServers = wrapper.find(Servers)
expect(renderedServers.length).toEqual(0)
})
it("does not render Servers inside ServersContainer if servers are undefined", function(){
// Given
let props = {...mockedProps}
// When
let wrapper = mount(<ServersContainer {...props}/>)
// Then
const renderedServers = wrapper.find(Servers)
expect(renderedServers.length).toEqual(0)
})
})