From 5eb23cdd48542be8f4df0f6a4764135821eb7aa1 Mon Sep 17 00:00:00 2001 From: Tim Lai Date: Wed, 6 May 2020 13:57:58 -0700 Subject: [PATCH] ft: JsonSchema components are now ImmutableJS compliant (#5952) bug: JsonSchema components should validate schema properties exists - schema - type - format - enum bug: fix a debounce error in JsonSchema_string if value is null ft: new simplified JsonSchemaArrayItemText component test: use immutableJS for `json-schema-form` test test: add dev scripts to run `cypress open` test: new cypress `schema-form` tests --- package.json | 2 + src/core/components/parameter-row.jsx | 35 +- src/core/json-schema-components.jsx | 199 ++-- .../wrap-components/json-schema-string.jsx | 5 +- src/core/plugins/view/root-injects.jsx | 1 - .../documents/features/schema-form-core.yaml | 339 +++++++ .../features/schema-form-missing-values.yaml | 119 +++ .../e2e-cypress/tests/features/schema-form.js | 879 ++++++++++++++++++ test/mocha/components/json-schema-form.jsx | 42 +- 9 files changed, 1508 insertions(+), 113 deletions(-) create mode 100644 test/e2e-cypress/static/documents/features/schema-form-core.yaml create mode 100644 test/e2e-cypress/static/documents/features/schema-form-missing-values.yaml create mode 100644 test/e2e-cypress/tests/features/schema-form.js diff --git a/package.json b/package.json index b9ebdf49..e7998a0b 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,8 @@ "hot-e2e-cypress-server": "webpack-dev-server --config webpack/dev-e2e.babel.js --content-base test/e2e-cypress/static", "hot-e2e-selenium-server": "webpack-dev-server --config webpack/dev-e2e.babel.js --content-base test/e2e-selenium/static", "e2e-cypress": "run-p -r hot-e2e-cypress-server mock-api test-e2e-cypress", + "dev-test-e2e-cypress-open": "cypress open", + "dev-e2e-cypress": "run-p -r hot-e2e-cypress-server mock-api dev-e2e-cypress-open", "e2e-selenium": "run-p -r hot-e2e-selenium-server mock-api test-e2e-selenium", "open-static": "node -e \"require('open')('http://localhost:3002')\"", "security-audit": "run-s -sc security-audit:all security-audit:prod", diff --git a/src/core/components/parameter-row.jsx b/src/core/components/parameter-row.jsx index b0371fdd..b46dd49f 100644 --- a/src/core/components/parameter-row.jsx +++ b/src/core/components/parameter-row.jsx @@ -42,7 +42,7 @@ export default class ParameterRow extends Component { if(isOAS3) { let { schema } = getParameterSchema(parameterWithMeta, { isOAS3 }) - enumValue = schema.get("enum") + enumValue = schema ? schema.get("enum") : undefined } else { enumValue = parameterWithMeta ? parameterWithMeta.get("enum") : undefined } @@ -59,7 +59,7 @@ export default class ParameterRow extends Component { if ( value !== undefined && value !== paramValue ) { this.onChangeWrapper(numberToString(value)) } - + // todo: could check if schema here; if not, do not call. impact? this.setDefaultValue() } @@ -97,17 +97,16 @@ export default class ParameterRow extends Component { let { specSelectors, pathMethod, rawParam, oas3Selectors } = this.props const paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map() - const { schema } = getParameterSchema(paramWithMeta, { isOAS3: specSelectors.isOAS3() }) - const parameterMediaType = paramWithMeta .get("content", Map()) .keySeq() .first() - const generatedSampleValue = getSampleSchema(schema.toJS(), parameterMediaType, { + // getSampleSchema could return null + const generatedSampleValue = schema ? getSampleSchema(schema.toJS(), parameterMediaType, { includeWriteOnly: true - }) + }) : null if (!paramWithMeta || paramWithMeta.get("value") !== undefined) { return @@ -121,14 +120,14 @@ export default class ParameterRow extends Component { if (specSelectors.isSwagger2()) { initialValue = paramWithMeta.get("x-example") || paramWithMeta.getIn(["schema", "example"]) - || schema.getIn(["default"]) + || (schema && schema.getIn(["default"])) } else if (specSelectors.isOAS3()) { const currentExampleKey = oas3Selectors.activeExamplesMember(...pathMethod, "parameters", this.getParamKey()) initialValue = paramWithMeta.getIn(["examples", currentExampleKey, "value"]) || paramWithMeta.getIn(["content", parameterMediaType, "example"]) || paramWithMeta.get("example") - || schema.get("example") - || schema.get("default") + || (schema && schema.get("example")) + || (schema && schema.get("default")) || paramWithMeta.get("default") // ensures support for `parameterMacro` } @@ -144,7 +143,7 @@ export default class ParameterRow extends Component { if(initialValue !== undefined) { this.onChangeWrapper(initialValue) } else if( - schema.get("type") === "object" + schema && schema.get("type") === "object" && generatedSampleValue && !paramWithMeta.get("examples") ) { @@ -212,12 +211,12 @@ export default class ParameterRow extends Component { let { schema } = getParameterSchema(param, { isOAS3 }) let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map() - let format = schema.get("format") - let type = schema.get("type") + let format = schema ? schema.get("format") : null + let type = schema ? schema.get("type") : null + let itemType = schema ? schema.getIn(["items", "type"]) : null let isFormData = inType === "formData" let isFormDataSupported = "FormData" in win let required = param.get("required") - let itemType = schema.getIn(["items", "type"]) let value = paramWithMeta ? paramWithMeta.get("value") : "" let commonExt = showCommonExtensions ? getCommonExtensions(schema) : null @@ -229,24 +228,26 @@ export default class ParameterRow extends Component { let paramExample // undefined let isDisplayParamEnum = false - if ( param !== undefined ) { + if ( param !== undefined && schema ) { paramItems = schema.get("items") } if (paramItems !== undefined) { paramEnum = paramItems.get("enum") paramDefaultValue = paramItems.get("default") - } else { + } else if (schema) { paramEnum = schema.get("enum") } - if ( paramEnum !== undefined && paramEnum.size > 0) { + if ( paramEnum && paramEnum.size && paramEnum.size > 0) { isDisplayParamEnum = true } // Default and Example Value for readonly doc if ( param !== undefined ) { - paramDefaultValue = schema.get("default") + if (schema) { + paramDefaultValue = schema.get("default") + } if (paramDefaultValue === undefined) { paramDefaultValue = param.get("default") } diff --git a/src/core/json-schema-components.jsx b/src/core/json-schema-components.jsx index 6c63263c..50a3b729 100644 --- a/src/core/json-schema-components.jsx +++ b/src/core/json-schema-components.jsx @@ -45,37 +45,37 @@ export class JsonSchemaForm extends Component { render() { let { schema, errors, value, onChange, getComponent, fn, disabled } = this.props - - if(schema.toJS) - schema = schema.toJS() - - let { type, format="" } = schema - // In the json schema rendering code, we optimistically query our system for a number of components. - // If the component doesn't exist, we optionally suppress these warnings. + const format = schema && schema.get ? schema.get("format") : null + const type = schema && schema.get ? schema.get("type") : null let getComponentSilently = (name) => getComponent(name, false, { failSilently: true }) - - let Comp = (format ? + let Comp = type ? format ? getComponentSilently(`JsonSchema_${type}_${format}`) : - getComponentSilently(`JsonSchema_${type}`)) || - getComponentSilently("JsonSchema_string") - + getComponentSilently(`JsonSchema_${type}`) : + getComponent("JsonSchema_string") + if (!Comp) { + Comp = getComponent("JsonSchema_string") + } return } - } export class JsonSchema_string extends Component { static propTypes = JsonSchemaPropShape static defaultProps = JsonSchemaDefaultProps onChange = (e) => { - const value = this.props.schema["type"] === "file" ? e.target.files[0] : e.target.value + const value = this.props.schema && this.props.schema["type"] === "file" ? e.target.files[0] : e.target.value this.props.onChange(value, this.props.keyName) } onEnumChange = (val) => this.props.onChange(val) render() { let { getComponent, value, schema, errors, required, description, disabled } = this.props - let enumValue = schema["enum"] - + const enumValue = schema && schema.get ? schema.get("enum") : null + const format = schema && schema.get ? schema.get("format") : null + const type = schema && schema.get ? schema.get("type") : null + const schemaIn = schema && schema.get ? schema.get("in") : null + if (!value) { + value = "" // value should not be null; this fixes a Debounce error + } errors = errors.toJS ? errors.toJS() : [] if ( enumValue ) { @@ -89,9 +89,9 @@ export class JsonSchema_string extends Component { onChange={ this.onEnumChange }/>) } - const isDisabled = disabled || (schema["in"] === "formData" && !("FormData" in window)) + const isDisabled = disabled || (schemaIn && schemaIn === "formData" && !("FormData" in window)) const Input = getComponent("Input") - if (schema["type"] === "file") { + if (type && type === "file") { return ( this.props.onChange(this.state.value) + onChange = () => { + this.props.onChange(this.state.value) + } onItemChange = (itemVal, i) => { - this.setState(state => ({ - value: state.value.set(i, itemVal) + this.setState(({ value }) => ({ + value: value.set(i, itemVal) }), this.onChange) } removeItem = (i) => { - this.setState(state => ({ - value: state.value.remove(i) + this.setState(({ value }) => ({ + value: value.delete(i) }), this.onChange) } - + addItem = () => { - this.setState(state => { - state.value = valueOrEmptyList(state.value) - return { - value: state.value.push("") - } - }, this.onChange) + let newValue = valueOrEmptyList(this.state.value) + this.setState(() => ({ + value: newValue.push("") + }), this.onChange) } onEnumChange = (value) => { @@ -161,69 +161,120 @@ export class JsonSchema_array extends PureComponent { let { getComponent, required, schema, errors, fn, disabled } = this.props errors = errors.toJS ? errors.toJS() : [] + const value = this.state.value // expect Im List + const shouldRenderValue = + value && value.count && value.count() > 0 ? true : false + const schemaItemsEnum = schema.getIn(["items", "enum"]) + const schemaItemsType = schema.getIn(["items", "type"]) + const schemaItemsFormat = schema.getIn(["items", "format"]) + const schemaItemsSchema = schema.getIn(["items", "schema"]) + let ArrayItemsComponent + let isArrayItemText = false + if (schemaItemsType && schemaItemsFormat) { + ArrayItemsComponent = getComponent(`JsonSchema_${schemaItemsType}_${schemaItemsFormat}`) + } else if (schemaItemsType === "boolean" || schemaItemsType === "array" || schemaItemsType === "object") { + ArrayItemsComponent = getComponent(`JsonSchema_${schemaItemsType}`) + } + // if ArrayItemsComponent not assigned or does not exist, + // use default schemaItemsType === "string" & JsonSchemaArrayItemText component + if (!ArrayItemsComponent) { + isArrayItemText = true + } - let itemSchema = fn.inferSchema(schema.items) - - const JsonSchemaForm = getComponent("JsonSchemaForm") - const Button = getComponent("Button") - - let enumValue = itemSchema["enum"] - let value = this.state.value - - if ( enumValue ) { + if ( schemaItemsEnum ) { const Select = getComponent("Select") return () } } diff --git a/src/core/plugins/oas3/wrap-components/json-schema-string.jsx b/src/core/plugins/oas3/wrap-components/json-schema-string.jsx index ceec8521..96534b0f 100644 --- a/src/core/plugins/oas3/wrap-components/json-schema-string.jsx +++ b/src/core/plugins/oas3/wrap-components/json-schema-string.jsx @@ -9,10 +9,11 @@ export default OAS3ComponentWrapFactory(({ Ori, ...props }) => { onChange } = props - const { type, format } = schema + const format = schema && schema.get ? schema.get("format") : null + const type = schema && schema.get ? schema.get("type") : null const Input = getComponent("Input") - if(type === "string" && (format === "binary" || format === "base64")) { + if(type && type === "string" && (format && (format === "binary" || format === "base64"))) { return { return target } - export const getComponent = (getSystem, getStore, getComponents, componentName, container, config = {}) => { if(typeof componentName !== "string") diff --git a/test/e2e-cypress/static/documents/features/schema-form-core.yaml b/test/e2e-cypress/static/documents/features/schema-form-core.yaml new file mode 100644 index 00000000..4c3cdaa5 --- /dev/null +++ b/test/e2e-cypress/static/documents/features/schema-form-core.yaml @@ -0,0 +1,339 @@ +openapi: 3.0.0 +info: + title: "Schema in Parameters" + description: |- + This document has examples for examining the `schema` within a set of parameters + * String Enum (/pet/findByStatus) + * Array of Strings (/pet/findByTags) + * Array of Boolean (/petOwner/listOfServiceTrainer) + * Array of Objects (/petOwners/createWithList) + * Array of Enum (/petOwner/findByPreference) + + This document also covers a debounce test for `schema` type `string + * String (/petOwner) + + This documents does not cover: + * Array of Arrays + + Additional notes + * Provides additional coverage and examples not covered in the Multiple Examples Core Document (Test) + * Code component reference `JsonSchemaForm` + * `pet` and `tag` schemas are reduced from Swagger Petstore + version: "1.0.0" +paths: + /pet/findByStatus: + get: + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/petOwner/{petOwnerId}': + get: + tags: + - petOwner + summary: Find pet owner by ID + description: Returns a single pet owner if ID found, list if no ID provided + operationId: getPetOwnerById + parameters: + - name: petOwnerId + in: path + description: ID of pet owner to return + required: false + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/PetOwner' + application/json: + schema: + $ref: '#/components/schemas/PetOwner' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + /petOwner/listOfServiceTrainer: + get: + tags: + - petOwner + summary: List of Service Trainers + description: >- + Expect boolean, but allow both true and false + operationId: listOfServiceTrainer + parameters: + - name: tags + in: query + description: Boolean to filter by + required: false + explode: true + schema: + type: array + items: + type: boolean + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + '400': + description: Invalid tag value + /petOwner/findByPreference: + get: + tags: + - petOwner + summary: Find by Pet Owner Preferences + description: >- + Expect enum + operationId: findByPreference + parameters: + - name: tags + in: query + description: Enum to filter by + required: false + explode: true + schema: + type: array + items: + type: string + enum: + - dog + - cat + - bird + - fish + - other + default: dog + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + '400': + description: Invalid tag value + components: + /petOwner/createWithList: + post: + tags: + - petOwner + summary: Creates list of pet owners with given input array + operationId: petOwnerCreateWithList + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetOwner' + '400': + description: Invalid values + schemas: + Pet: + x-swagger-router-model: io.swagger.petstore.model.Pet + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + # remove category property + petOwners: + type: array + items: + $ref: '#/components/schemas/PetOwner' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + xml: + name: tag + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + type: object + # remove ApiResponse + PetOwner: + type: "object" + properties: + id: + type: "integer" + format: "int64" + example: 10 + petId: + type: "integer" + format: "int64" + example: 201 + petOwnerFirstName: + type: "string" + example: "John" + petOwnerLastName: + type: "string" + example: "Smith" + petOwnerContact: + type: "string" + example: "john.smith@fakeemail.co" + petOwnerStatus: + type: "integer" + format: "int32" + description: "Pet Owner Status" + example: 302 + petOwnerPreferences: + type: "string" + description: "Pet Owner Preferred Pet Types" + enum: + - "dog" + - "cat" + - "bird" + - "fish" + - "other" + petOwnerServiceTrainer: + type: "boolean" + description: "Pet Onwer is Service Trainer" + default: false + Tag: + x-swagger-router-model: io.swagger.petstore.model.Tag + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + type: object + requestBodies: + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store diff --git a/test/e2e-cypress/static/documents/features/schema-form-missing-values.yaml b/test/e2e-cypress/static/documents/features/schema-form-missing-values.yaml new file mode 100644 index 00000000..b1d7f137 --- /dev/null +++ b/test/e2e-cypress/static/documents/features/schema-form-missing-values.yaml @@ -0,0 +1,119 @@ +openapi: 3.0.0 +info: + description: No type for schema + version: "1" + title: "No type" +paths: + /case-one-no-schema: + parameters: + - name: namespace + description: The custom resource's namespace + # schema: + in: path + required: true + get: + description: sf + responses: + default: + description: one + /case-one-no-type-or-format: + parameters: + - name: namespace + description: The custom resource's namespace + schema: + in: path + required: true + get: + description: sf + responses: + default: + description: one + /case-one-type-only-no-format: + parameters: + - name: namespace + description: The custom resource's namespace + schema: + type: integer + in: path + required: true + get: + description: sf + responses: + default: + description: one + /case-one-format-only-no-type: + parameters: + - name: namespace + description: The custom resource's namespace + schema: + format: int64 + in: path + required: true + get: + description: sf + responses: + default: + description: one + /case-two-no-schema: + get: + description: sf + responses: + default: + description: one + parameters: + - name: namespace + in: path + description: The custom resource's namespace + required: true + # schema: + /case-two-no-type-or-format: + get: + description: sf + responses: + default: + description: one + parameters: + - name: namespace + in: path + description: The custom resource's namespace + required: true + schema: + /case-two-type-only-no-format: + get: + description: sf + responses: + default: + description: one + parameters: + - name: namespace + in: path + description: The custom resource's namespace + required: true + schema: + type: integer + /case-two-format-only-no-type: + get: + description: sf + responses: + default: + description: one + parameters: + - name: namespace + in: path + description: The custom resource's namespace + required: true + schema: + format: int64 + /case-two-not-a-real-type: + get: + description: sf + responses: + default: + description: one + parameters: + - name: namespace + in: path + description: The custom resource's namespace + required: true + schema: + type: "NotARealType" diff --git a/test/e2e-cypress/tests/features/schema-form.js b/test/e2e-cypress/tests/features/schema-form.js new file mode 100644 index 00000000..95bef5ec --- /dev/null +++ b/test/e2e-cypress/tests/features/schema-form.js @@ -0,0 +1,879 @@ +/** + * @prettier + */ + +describe("OpenAPI 3.0 Additional JsonSchemaForm in a Parameter", () => { + describe("incomplete API definition with missing schema key or schema value(s)", () => { + describe("parameter exists as global", () => { + it("should render when parameter exists as global, but missing schema key", () => { + cy.visit( + "/?url=/documents/features/schema-form-missing-values.yaml" + ) + .get("#operations-default-get_case_one_no_schema") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists as global, but missing all schema values", () => { + cy.visit( + "/?url=/documents/features/schema-form-missing-values.yaml" + ) + .get("#operations-default-get_case_one_no_type_or_format") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists as global, schema key exists, but missing schema values: format", () => { + cy.visit( + "/?url=/documents/features/schema-form-missing-values.yaml" + ) + .get("#operations-default-get_case_one_format_only_no_type") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists as global, schema key exists, but missing schema value: type", () => { + cy.visit( + "/?url=/documents/features/schema-form-missing-values.yaml" + ) + .get("#operations-default-get_case_one_type_only_no_format") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + }) + describe("parameter exists in method", () => { + it("should render when parameter exists in method, but missing schema key", () => { + cy.visit( + "/?url=/documents/features/schema-form-missing-values.yaml" + ) + .get("#operations-default-get_case_two_no_schema") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists in method, schema key exists, but missing all schema values", () => { + cy.visit( + "/?url=/documents/features/schema-form-missing-values.yaml" + ) + .get("#operations-default-get_case_two_no_type_or_format") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists in method, schema key exists, but missing schema value: format", () => { + cy.visit( + "/?url=/documents/features/schema-form-missing-values.yaml" + ) + .get("#operations-default-get_case_one_type_only_no_format") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + it("should render when parameter exists in method, schema key exists, but missing schema value: type", () => { + cy.visit( + "/?url=/documents/features/schema-form-missing-values.yaml" + ) + .get("#operations-default-get_case_one_format_only_no_type") + .click() + .get(".opblock-description .renderedMarkdown p") + .should("have.text", "sf") + }) + }) + }) + describe("/Array", () => { + describe("in a Parameter", () => { + it("should allow modification of values in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("5") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + "5", + ]) + }) + .get(".parameters-col_description .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + }) + it("should allow removal of added value in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("5") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + "5", + ]) + }) + .get(".parameters-col_description .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + // Remove the last item that was just added + .get(".json-schema-form-item:last-of-type > .json-schema-form-item-remove") + .click() + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + ]) + }) + }) + it("should allow removal of nth of values in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("5") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "2", + "3", + "4", + "5", + ]) + }) + .get(".parameters-col_description .examples-select > select") + .find(":selected") + .should("have.text", "[Modified value]") + // Remove the second item in list + .get(".json-schema-form-item:nth-child(2) > .json-schema-form-item-remove") + .click() + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "1", + "3", + "4", + "5" + ]) + }) + }) + it("should allow execution of operation in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + }) + it("should add empty item and allow execution of operation in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/multiple-examples-core.openapi.yaml" + ) + .get("#operations-default-post_Array") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description .examples-select > select") + .select("ArrayExampleB") + // Add a new item + .get(".json-schema-form-item-add") + .click() + // Execute without prior typing a value + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + }) + }) + }) + describe("Petstore", () => { + describe("/pet/findByStatus", () => { + it("should render the operation, execute with default value", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-default-findPetsByStatus") + .click() + // Expand operation + .get(".opblock-title span") + .should("have.text", "Parameters") + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "available") + }) + it("should render the operation, modify value, and execute with modfied value", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-default-findPetsByStatus") + .click() + // Expand operation + .get(".opblock-title span") + .should("have.text", "Parameters") + // Expand Try It Out + .get(".try-out__btn") + .click() + // Select + .get(".parameters-col_description > select") + .select("pending") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "pending") + }) + }) + describe("/pet/findByTags", () => { + it("should allow modification of values in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > input") + .type("spotted") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + ]) + }) + }) + it("should allow removal of added value in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("spotted") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + ]) + }) + // Remove the last item that was just added + .get(".json-schema-form-item:last-of-type > .json-schema-form-item-remove") + .click() + .get(".json-schema-form-item > input") + .should("not.exist") + }) + it("should allow removal of nth of values in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("spotted") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + ]) + }) + // Add a 2nd new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("large") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "large" + ]) + }) + // Add a 3rd new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("puppy") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "large", + "puppy" + ]) + }) + // Remove the second item in list + .get(".json-schema-form-item:nth-child(2) > .json-schema-form-item-remove") + .click() + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "puppy" + ]) + }) + }) + it("should allow execution of operation without modifications in Try-It-Out (debounce)", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "findByTags") + }) + it("should add empty item and allow execution of operation in Try-It-Out (debounce)", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + // Execute without prior typing a value + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "findByTags") + }) + it("should add modified item and allow execution of operation in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > input") + .type("spotted") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + ]) + }) + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "spotted") + }) + it("should add 3 modified items, remove the middle child, and allow execution of operation Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-pet-findPetsByTags") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Add a new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("spotted") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + ]) + }) + // Add a 2nd new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("large") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "large" + ]) + }) + // Add a 3rd new item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > input") + .type("puppy") + // Assert against the input fields + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "large", + "puppy" + ]) + }) + // Remove the second item in list + .get(".json-schema-form-item:nth-child(2) > .json-schema-form-item-remove") + .click() + .get(".json-schema-form-item > input") + .then(inputs => { + expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ + "spotted", + "puppy" + ]) + }) + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "tags=spotted&tags=puppy") + .should("not.have.text", "large") + }) + }) + describe("/petOwner/{petOwnerId}", () => { + // This is a (GET) debounce test for schema type: string + it("should render the operation, and allow execute of operation with empty value (debounce)", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-getPetOwnerById") + .click() + // Expand operation + .get(".opblock-title span") + .should("have.text", "Parameters") + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "petOwner") + }) + it("should render the operation, and input field, and allow execute of operation", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-getPetOwnerById") + .click() + // Expand operation + .get(".opblock-title span") + .should("have.text", "Parameters") + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".parameters-col_description > input") + .type("123") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "petOwner") + .should("contain.text", "123") + }) + }) + describe("/petOwner/listOfServiceTrainer", () => { + it("should allow execution of operation with value=true in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-listOfServiceTrainer") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // add 1st item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > select") + .select("true") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "tags=true") + }) + it("should allow execution of operation with value=false in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-listOfServiceTrainer") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // add 1st item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > select") + .select("false") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "tags=false") + }) + it("should allow execution of operation with value=true&value=false in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-listOfServiceTrainer") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // add 1st item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > select") + .select("true") + // add 2nd item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > select") + .select("false") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "tags=true&tags=false") + }) + it("should allow execution of operation with value=false after removing value=true in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-listOfServiceTrainer") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // add 1st item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item > select") + .select("true") + // add 2nd item + .get(".json-schema-form-item-add") + .click() + .get(".json-schema-form-item:last-of-type > select") + .select("false") + // remove 1st item + .get(".json-schema-form-item:nth-child(1) > .json-schema-form-item-remove") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "tags=false") + }) + it("should allow execution of operation with value=(empty) in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-listOfServiceTrainer") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "listOfServiceTrainer") + }) + }) + describe("/petOwner/findByPreference", () => { + it("should allow execution of operation with value=(empty) in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-findByPreference") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "findByPreference") + }) + it("should allow execution of operation with selected value in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-findByPreference") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Select + .get(".parameters-col_description > select") + .select("dog") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "findByPreference") + .should("contain.text", "dog") + }) + it("should allow execution of operation with multiple selected values in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-findByPreference") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Select + .get(".parameters-col_description > select") + .select(["dog", "cat"]) + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "findByPreference") + .should("contain.text", "dog") + .should("contain.text", "cat") + }) + }) + describe("/petOwner/createWithList", () => { + it("should allow execution of operation with default text in textArea in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-petOwnerCreateWithList") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "createWithList") + }) + it("should allow execution of operation with cleared textArea in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-petOwnerCreateWithList") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".body-param__text") + .clear() + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "createWithList") + }) + it("should allow execution of operation with modified textArea in Try-It-Out", () => { + cy.visit( + "/?url=/documents/features/schema-form-core.yaml" + ) + .get("#operations-petOwner-petOwnerCreateWithList") + .click() + // Expand Try It Out + .get(".try-out__btn") + .click() + .get(".body-param__text") + .clear() + // note: adding this much type adds 6+ seconds to test + .type(`[ + { + "id": 10, + "petId": 201, + "petOwnerFirstName": "John", + }, + { + "id": 11, + "petId": 201, + "petOwnerFirstName": "Jane", + } + ]` + ) + .should("contain.text", "Jane") + .should("contain.text", "201") + // Execute + .get(".execute.opblock-control__btn") + .click() + // Expect new element to be visible after Execute + .get(".btn-clear.opblock-control__btn") + .should("have.text", "Clear") + // Compare Request URL + .get(".request-url pre.microlight") + .should("contain.text", "createWithList") + // Compare Curl + .get(".curl") + .should("contain.text", "Jane") + .should("contain.text", "201") + }) + }) + }) +}) + + diff --git a/test/mocha/components/json-schema-form.jsx b/test/mocha/components/json-schema-form.jsx index 36843f54..895a1994 100644 --- a/test/mocha/components/json-schema-form.jsx +++ b/test/mocha/components/json-schema-form.jsx @@ -1,6 +1,6 @@ /* eslint-env mocha */ import React from "react" -import { List } from "immutable" +import Immutable, { List } from "immutable" import expect, { createSpy } from "expect" import { Select, Input, TextArea } from "components/layout-utils" import { mount, render } from "enzyme" @@ -25,10 +25,10 @@ describe("", function(){ onChange: () => {}, keyName: "", fn: {}, - schema: { + schema: Immutable.fromJS({ type: "string", enum: ["one", "two"] - } + }) } let wrapper = render() @@ -48,10 +48,10 @@ describe("", function(){ onChange: () => {}, keyName: "", fn: {}, - schema: { + schema: Immutable.fromJS({ type: "string", enum: ["one", "two"] - }, + }), disabled: true } @@ -70,10 +70,10 @@ describe("", function(){ keyName: "", fn: {}, required: true, - schema: { + schema: Immutable.fromJS({ type: "string", enum: ["one", "two"] - } + }) } let wrapper = render() @@ -93,9 +93,9 @@ describe("", function(){ onChange: () => {}, keyName: "", fn: {}, - schema: { + schema: Immutable.fromJS({ type: "boolean" - } + }) } let wrapper = render() @@ -116,10 +116,10 @@ describe("", function(){ onChange: () => {}, keyName: "", fn: {}, - schema: { + schema: Immutable.fromJS({ type: "boolean", enum: ["true"] - } + }) } let wrapper = render() @@ -139,10 +139,10 @@ describe("", function(){ onChange: () => {}, keyName: "", fn: {}, - schema: { + schema: Immutable.fromJS({ type: "boolean", required: true - } + }) } let wrapper = render() @@ -164,10 +164,10 @@ describe("", function(){ keyName: "", fn: {}, required: true, - schema: { + schema: Immutable.fromJS({ type: "boolean", enum: ["true"] - } + }) } let wrapper = render() @@ -191,7 +191,7 @@ describe("", function(){ keyName: "", fn: {}, errors: List(), - schema: { + schema: Immutable.fromJS({ type: "object", properties: { id: { @@ -199,7 +199,7 @@ describe("", function(){ example: "abc123" } } - } + }) } let wrapper = mount() @@ -219,9 +219,9 @@ describe("", function(){ onChange: () => {}, keyName: "", fn: {}, - schema: { + schema: Immutable.fromJS({ type: "NotARealType" - } + }) } @@ -240,10 +240,10 @@ describe("", function(){ onChange: () => {}, keyName: "", fn: {}, - schema: { + schema: Immutable.fromJS({ type: "NotARealType", format: "NotARealFormat" - } + }) }