diff --git a/src/core/components/model-collapse.jsx b/src/core/components/model-collapse.jsx index 2caf37e9..0d7ec9cd 100644 --- a/src/core/components/model-collapse.jsx +++ b/src/core/components/model-collapse.jsx @@ -1,5 +1,6 @@ import React, { Component } from "react" import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" import Im from "immutable" export default class ModelCollapse extends Component { @@ -13,7 +14,8 @@ export default class ModelCollapse extends Component { onToggle: PropTypes.func, hideSelfOnExpand: PropTypes.bool, layoutActions: PropTypes.object, - layoutSelectors: PropTypes.object.isRequired + layoutSelectors: PropTypes.object.isRequired, + specPath: ImPropTypes.list.isRequired, } static defaultProps = { @@ -21,7 +23,8 @@ export default class ModelCollapse extends Component { expanded: false, title: null, onToggle: () => {}, - hideSelfOnExpand: false + hideSelfOnExpand: false, + specPath: Im.List([]), } constructor(props, context) { @@ -63,11 +66,10 @@ export default class ModelCollapse extends Component { onLoad = (ref) => { if(ref) { - const name = this.props.modelName const scrollToKey = this.props.layoutSelectors.getScrollToKey() - if( Im.is(scrollToKey, Im.fromJS(["models", name])) ) this.toggleCollapsed() - this.props.layoutActions.readyToScroll(["models", name], ref.parentElement) + if( Im.is(scrollToKey, this.props.specPath) ) this.toggleCollapsed() + this.props.layoutActions.readyToScroll(this.props.specPath, ref.parentElement) } } @@ -83,7 +85,7 @@ export default class ModelCollapse extends Component { } return ( - + { title && {title} } diff --git a/src/core/components/model-wrapper.jsx b/src/core/components/model-wrapper.jsx index 8859ac42..5ed38095 100644 --- a/src/core/components/model-wrapper.jsx +++ b/src/core/components/model-wrapper.jsx @@ -1,15 +1,15 @@ import React, { Component, } from "react" import PropTypes from "prop-types" -//import layoutActions from "actions/layout" - +import ImPropTypes from "react-immutable-proptypes" export default class ModelWrapper extends Component { - static propTypes = { schema: PropTypes.object.isRequired, name: PropTypes.string, displayName: PropTypes.string, + fullPath: PropTypes.array.isRequired, + specPath: ImPropTypes.list.isRequired, getComponent: PropTypes.func.isRequired, getConfigs: PropTypes.func.isRequired, specSelectors: PropTypes.object.isRequired, @@ -20,15 +20,10 @@ export default class ModelWrapper extends Component { includeWriteOnly: PropTypes.bool, } - getSchemaBasePath = () => { - const isOAS3 = this.props.specSelectors.isOAS3() - return isOAS3 ? ["components", "schemas"] : ["definitions"] - } - onToggle = (name,isShown) => { // If this prop is present, we'll have deepLinking for it if(this.props.layoutActions) { - this.props.layoutActions.show([...this.getSchemaBasePath(), name],isShown) + this.props.layoutActions.show(this.props.fullPath, isShown) } } @@ -39,7 +34,7 @@ export default class ModelWrapper extends Component { let expanded if(this.props.layoutSelectors) { // If this is prop is present, we'll have deepLinking for it - expanded = this.props.layoutSelectors.isShown(["models",this.props.name]) + expanded = this.props.layoutSelectors.isShown(this.props.fullPath) } return
diff --git a/src/core/components/models.jsx b/src/core/components/models.jsx index 1e779ced..2058b05b 100644 --- a/src/core/components/models.jsx +++ b/src/core/components/models.jsx @@ -23,16 +23,22 @@ export default class Models extends Component { handleToggle = (name, isExpanded) => { const { layoutActions } = this.props - layoutActions.show(["models", name], isExpanded) + layoutActions.show([...this.getSchemaBasePath(), name], isExpanded) if(isExpanded) { this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name]) } } - onLoad = (ref) => { + onLoadModels = (ref) => { + if (ref) { + this.props.layoutActions.readyToScroll(this.getSchemaBasePath(), ref) + } + } + + onLoadModel = (ref) => { if (ref) { const name = ref.getAttribute("data-name") - this.props.layoutActions.readyToScroll(["models", name], ref) + this.props.layoutActions.readyToScroll([...this.getSchemaBasePath(), name], ref) } } @@ -42,8 +48,8 @@ export default class Models extends Component { let { docExpansion, defaultModelsExpandDepth } = getConfigs() if (!definitions.size || defaultModelsExpandDepth < 0) return null - let showModels = layoutSelectors.isShown("models", defaultModelsExpandDepth > 0 && docExpansion !== "none") const specPathBase = this.getSchemaBasePath() + let showModels = layoutSelectors.isShown(specPathBase, defaultModelsExpandDepth > 0 && docExpansion !== "none") const isOAS3 = specSelectors.isOAS3() const ModelWrapper = getComponent("ModelWrapper") @@ -51,8 +57,8 @@ export default class Models extends Component { const ModelCollapse = getComponent("ModelCollapse") const JumpToPath = getComponent("JumpToPath") - return
-

layoutActions.show("models", !showModels)}> + return
+

layoutActions.show(specPathBase, !showModels)}> {isOAS3 ? "Schemas" : "Models" } @@ -63,6 +69,7 @@ export default class Models extends Component { definitions.entrySeq().map(([name])=>{ const fullPath = [...specPathBase, name] + const specPath = Im.List(fullPath) const schemaValue = specSelectors.specResolvedSubtree(fullPath) const rawSchemaValue = specSelectors.specJson().getIn(fullPath) @@ -71,20 +78,19 @@ export default class Models extends Component { const rawSchema = Map.isMap(rawSchemaValue) ? rawSchemaValue : Im.Map() const displayName = schema.get("title") || rawSchema.get("title") || name - const isShown = layoutSelectors.isShown( ["models", name], false ) + const isShown = layoutSelectors.isShown(fullPath, false) if( isShown && (schema.size === 0 && rawSchema.size > 0) ) { // Firing an action in a container render is not great, // but it works for now. - this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name]) + this.props.specActions.requestResolvedSubtree(fullPath) } - const specPath = Im.List([...specPathBase, name]) - const content = return
+ data-name={name} ref={this.onLoadModel} > 0 && isShown } >{content} diff --git a/test/e2e-cypress/static/documents/features/models.openapi.yaml b/test/e2e-cypress/static/documents/features/models.openapi.yaml new file mode 100644 index 00000000..11d2a4ad --- /dev/null +++ b/test/e2e-cypress/static/documents/features/models.openapi.yaml @@ -0,0 +1,93 @@ +openapi: "3.0.0" + +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + \ No newline at end of file diff --git a/test/e2e-cypress/static/documents/features/models.swagger.yaml b/test/e2e-cypress/static/documents/features/models.swagger.yaml new file mode 100644 index 00000000..eb4cf680 --- /dev/null +++ b/test/e2e-cypress/static/documents/features/models.swagger.yaml @@ -0,0 +1,92 @@ +swagger: "2.0" + +definitions: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/definitions/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/definitions/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + \ No newline at end of file diff --git a/test/e2e-cypress/tests/features/model-collapse.js b/test/e2e-cypress/tests/features/model-collapse.js new file mode 100644 index 00000000..c0b0d4f0 --- /dev/null +++ b/test/e2e-cypress/tests/features/model-collapse.js @@ -0,0 +1,62 @@ +describe("Model collapse/expand feature", () => { + describe("in Swagger 2", () => { + const swagger2BaseUrl = "/?deepLinking=true&url=/documents/features/models.swagger.yaml" + const urlFragment = "#/definitions/Pet" + ModelCollapseTest(swagger2BaseUrl, urlFragment) + }) + describe("in OpenAPI 3", () => { + const openAPI3BaseUrl = "/?deepLinking=true&url=/documents/features/models.openapi.yaml" + ModelCollapseTest(openAPI3BaseUrl) + }) +}) + +function ModelCollapseTest(baseUrl, urlFragment) { + it("Models section should be expanded on load", () => { + cy.visit(baseUrl) + .get(".models") + .should("have.class", "is-open") + .get("#model-Pet") + .should("exist") + }) + + it("Models section should collapse and expand when toggled", () => { + cy.visit(baseUrl) + .get(".models h4") + .click() + .get(".models") + .should("not.have.class", "is-open") + .get("#model-Order") + .should("not.exist") + .get(".models h4") + .click() + .get(".models") + .should("have.class", "is-open") + .get("#model-Order") + .should("exist") + }) + + it("Model should collapse and expand when toggled clicking title", () => { + cy.visit(baseUrl) + .get("#model-User .model-box .pointer:nth-child(1)") + .click() + .get("#model-User .model-box .model .inner-object") + .should("exist") + .get("#model-User .model-box .pointer:nth-child(1)") + .click() + .get("#model-User .model-box .model .inner-object") + .should("not.exist") + }) + + it("Model should collapse and expand when toggled clicking arrow", () => { + cy.visit(baseUrl) + .get("#model-User .model-box .pointer:nth-child(2)") + .click() + .get("#model-User .model-box .model .inner-object") + .should("exist") + .get("#model-User .model-box .pointer:nth-child(2)") + .click() + .get("#model-User .model-box .model .inner-object") + .should("not.exist") + }) +} +