diff --git a/src/core/components/param-body.jsx b/src/core/components/param-body.jsx index 878b4546ab4..23238db7448 100644 --- a/src/core/components/param-body.jsx +++ b/src/core/components/param-body.jsx @@ -57,7 +57,10 @@ export default class ParamBody extends PureComponent { this.setState({ value: val }) this.onChange(val, {isXml: isXml, isEditBox: isExecute}) } else { - if (isXml) { + let xExampleValue = this.xExampleValue() + if (xExampleValue !== undefined) { + this.onChange(xExampleValue, {isXml: isXml, isEditBox: isExecute}) + } else if (isXml) { this.onChange(this.sample("xml"), {isXml: isXml, isEditBox: isExecute}) } else { this.onChange(this.sample(), {isEditBox: isExecute}) @@ -65,6 +68,29 @@ export default class ParamBody extends PureComponent { } } + xExampleValue = () => { + const { param } = this.props + const xExamples = param.get("x-examples") + if (!xExamples || typeof xExamples.get !== "function" || xExamples.size === 0) { + return undefined + } + const defaultExample = xExamples.get("default") + const example = defaultExample !== undefined ? defaultExample : xExamples.first() + if (example === undefined) { + return undefined + } + if (typeof example === "string") { + return example + } + if (typeof example.toJS === "function") { + return JSON.stringify(example.toJS(), null, 2) + } + if (typeof example === "object") { + return JSON.stringify(example, null, 2) + } + return String(example) + } + sample = (xml) => { let { param, fn} = this.props let schema = fn.inferSchema(param.toJS()) diff --git a/test/e2e-cypress/e2e/bugs/3233.cy.js b/test/e2e-cypress/e2e/bugs/3233.cy.js new file mode 100644 index 00000000000..b881744e253 --- /dev/null +++ b/test/e2e-cypress/e2e/bugs/3233.cy.js @@ -0,0 +1,25 @@ +describe("#3233: x-examples should populate the body parameter example for OAS 2.0", () => { + it("renders the x-examples 'default' value as the request body example", () => { + cy.visit("?url=/documents/bugs/3233.yaml") + .get("#operations-default-dataTargets") + .click() + .get(".opblock-section") + .within(() => { + cy.get(".body-param__example") + .should("be.visible") + .should("include.text", "targets") + .should("include.text", "1") + .should("include.text", "4") + }) + }) + + it("populates the request body textarea with x-examples when 'Try it out' is enabled", () => { + cy.visit("?url=/documents/bugs/3233.yaml") + .get("#operations-default-dataTargets") + .click() + .get(".try-out__btn") + .click() + .get("textarea.body-param__text") + .should("contain.value", "targets") + }) +}) diff --git a/test/e2e-cypress/static/documents/bugs/3233.yaml b/test/e2e-cypress/static/documents/bugs/3233.yaml new file mode 100644 index 00000000000..30ff607da7e --- /dev/null +++ b/test/e2e-cypress/static/documents/bugs/3233.yaml @@ -0,0 +1,28 @@ +swagger: "2.0" +info: + description: "OAS 2.0 sample with x-examples in body parameter (issue #3233)" + version: "0.0.1" + title: "Swagger Sample" +paths: + /data/targets: + post: + summary: "Returns validation results of given list of targets." + description: "" + operationId: "dataTargets" + consumes: + - application/json + produces: + - application/json + parameters: + - in: body + name: body + description: Targets list in JSON format. + required: true + schema: + type: object + x-examples: + default: + targets: [1, 2, 3, 4] + responses: + "200": + description: OK diff --git a/test/unit/components/param-body.jsx b/test/unit/components/param-body.jsx new file mode 100644 index 00000000000..31619e0bc37 --- /dev/null +++ b/test/unit/components/param-body.jsx @@ -0,0 +1,150 @@ +import React from "react" +import expect from "expect" +import { mount } from "enzyme" +import { fromJS } from "immutable" +import ParamBody from "core/components/param-body" + +describe("", () => { + const baseFn = { + inferSchema: () => ({}), + getSampleSchema: () => "GENERATED_SAMPLE", + } + + const baseSpecSelectors = { + parameterWithMetaByIdentity: (pathMethod, param) => param, + contentTypeValues: () => fromJS({ requestContentType: "application/json" }), + } + + const fakeGetComponent = () => () => null + + const createProps = (overrides = {}) => ({ + fn: baseFn, + getComponent: fakeGetComponent, + specSelectors: baseSpecSelectors, + pathMethod: ["/foo", "post"], + consumes: fromJS(["application/json"]), + consumesValue: "application/json", + isExecute: false, + onChange: jest.fn(), + onChangeConsumes: jest.fn(), + ...overrides, + }) + + it("emits the user-provided value when one is set", () => { + const onChange = jest.fn() + const props = createProps({ + param: fromJS({ + name: "body", + in: "body", + value: "{\"hello\":\"world\"}", + }), + onChange, + }) + + mount() + + expect(onChange).toHaveBeenCalledWith("{\"hello\":\"world\"}", false) + }) + + it("emits the generated sample when no value and no x-examples are present", () => { + const onChange = jest.fn() + const props = createProps({ + param: fromJS({ + name: "body", + in: "body", + schema: { type: "string" }, + }), + onChange, + }) + + mount() + + expect(onChange).toHaveBeenCalledWith("GENERATED_SAMPLE", undefined) + }) + + it("prefers the x-examples 'default' value over the generated sample", () => { + const onChange = jest.fn() + const props = createProps({ + param: fromJS({ + name: "body", + in: "body", + schema: { type: "object" }, + "x-examples": { + default: { targets: [1, 2, 3, 4] }, + }, + }), + onChange, + }) + + mount() + + expect(onChange).toHaveBeenCalledTimes(1) + expect(onChange).toHaveBeenCalledWith( + JSON.stringify({ targets: [1, 2, 3, 4] }, null, 2), + false + ) + }) + + it("falls back to the first x-examples entry when 'default' is missing", () => { + const onChange = jest.fn() + const props = createProps({ + param: fromJS({ + name: "body", + in: "body", + schema: { type: "object" }, + "x-examples": { + first: { foo: "bar" }, + second: { baz: "qux" }, + }, + }), + onChange, + }) + + mount() + + expect(onChange).toHaveBeenCalledTimes(1) + expect(onChange).toHaveBeenCalledWith( + JSON.stringify({ foo: "bar" }, null, 2), + false + ) + }) + + it("uses string x-examples values verbatim", () => { + const onChange = jest.fn() + const props = createProps({ + param: fromJS({ + name: "body", + in: "body", + schema: { type: "string" }, + "x-examples": { + default: "{\"targets\": \"[1, 2, 3, 4]\"}", + }, + }), + onChange, + }) + + mount() + + expect(onChange).toHaveBeenCalledWith( + "{\"targets\": \"[1, 2, 3, 4]\"}", + false + ) + }) + + it("ignores empty x-examples and falls back to the generated sample", () => { + const onChange = jest.fn() + const props = createProps({ + param: fromJS({ + name: "body", + in: "body", + schema: { type: "object" }, + "x-examples": {}, + }), + onChange, + }) + + mount() + + expect(onChange).toHaveBeenCalledWith("GENERATED_SAMPLE", undefined) + }) +})