feat: Multiple Examples for OpenAPI 3 Parameters, Request Bodies, and Responses (via #5427)

* add opt-in Prettier config

* remove legacy `examples` implementation

* create ExamplesSelect

* support `Response.examples` in OpenAPI 3

* create response controls group

* prettier reformat

* prepare to break up Parameters

* reunify Parameters and OAS3 Parameters

* Parameter Examples

* Example component

* handle parameter value stringification correctly

* FOR REVIEW: add prop for controlling Select

* use regular header for param examples in Try-It-Out

* manage active examples member via Redux

* Request Body Try-It-Out examples

* remove special Response description styling

* omit Example value display in Try-It-Out

* support disabled text inputs in JsonSchemaForm

* Example.omitValue => Example.showValue

* ExamplesSelectValueRetainer

* styling for disabled inputs

* remove console.log

* support "Modified Values" in ExamplesSelect

* remove Examples component
(wasn't used anywhere)

* use ParameterRow.getParamKey for active examples member keying

* split-rendering of examples in ParameterRow

* send disabled prop to JsonSchemaForm

* use content type to key request body active examples members

* remove debugger

* rewire RequestBodyEditor to be a controlled component

REVIEW: does this have perf implications?

* trigger synthetic onSelect events in ExamplesSelect

* prettier updates

* remove outdated Examples usage in RequestBody

* don't handle examples changes in ESVR

* make RequestBodyEditor semi-controlled

* don't default to an empty Map for request bodies

* add namespaceKey to ESVR for state mgmt

* don't key RequestBody activeExampleKeys on media type

* tweak ESVR isModifiedValueSelected calculation

* add trace class to ExamplesSelect

* remove usage of ESVR.currentNamespace

* reset to first example if currentExampleKey is invalid

* add default values to RequestBody rendering

* stringify things in ESVR

* avoid null select value (silences React warning)

* detect user inputs that match any examples member's value

* add trace class for json-schema-array

* shallowly convert namespace state, to preserve Immutable stucts in state

* stringify RBE values; don't trim JSON in editor

* match user input to an example when non-primitives are expressed in state as strings

* update Cypress

* don't apply sample values in JsonSchema_Object

* support disabling all JsonSchemaForm subcomponents

* Core tests

* style changes to accomodate Examples

* fix version-checking error in Response

* disable SCU for Responses

* don't stringify Select values

* ModelExample: default to Model tab if no example is available; provide a default no example message

* don't trim JSON ParamBody inputs

* read directly from 2.0 Response.schema instead of inferring a value

* show current Example information in RequestBody

* show label for Examples dropdown by default

* rework Response content ordering

* style disabled textareas like other read-only blocks

* meta: fix sourcemaps

* refactor ESVR setNameForNamespace

* protect second half of ternary expession

* cypress: `select.examples-select` => `.examples-select > select`

* clarify ModelExample.componentWillReceiveProps

* add gates/defaults to prevent issues in very bare-boned documents

* fix test block organization problem

* simplify RequestBodyEditor interface

* linter fixes

* prettier updates

* use plugin system for new components

* move ME Cypress helpers to other file
This commit is contained in:
kyle
2019-06-29 19:52:51 +01:00
committed by GitHub
parent 332ddaedcd
commit 23d7260f92
34 changed files with 3148 additions and 653 deletions

View File

@@ -0,0 +1,642 @@
/**
* @prettier
*/
const {
ParameterPrimitiveTestCases,
RequestBodyPrimitiveTestCases,
ResponsePrimitiveTestCases,
} = require("../../helpers/multiple-examples")
describe("OpenAPI 3.0 Multiple Examples - core features", () => {
describe("/String", () => {
describe("in a parameter", () => {
ParameterPrimitiveTestCases({
operationDomId: "#operations-default-post_String",
parameterName: "message",
exampleA: {
key: "StringExampleA",
value: "hello world",
},
exampleB: {
key: "StringExampleB",
value: "The quick brown fox jumps over the lazy dog",
},
customUserInput: "OpenAPIs.org <3",
})
})
describe("in a Request Body", () => {
RequestBodyPrimitiveTestCases({
operationDomId: "#operations-default-post_String",
exampleA: {
key: "StringExampleA",
value: "hello world",
serializedValue: "hello world",
summary: "Don't just string me along...",
},
exampleB: {
key: "StringExampleB",
value: "The quick brown fox jumps over the lazy dog",
serializedValue: "The quick brown fox jumps over the lazy dog",
summary: "I'm a pangram!",
},
customUserInput: "OpenAPIs.org <3",
})
})
describe("in a Response", () => {
ResponsePrimitiveTestCases({
operationDomId: "#operations-default-post_String",
exampleA: {
key: "StringExampleA",
value: "hello world",
summary: "Don't just string me along...",
},
exampleB: {
key: "StringExampleB",
value: "The quick brown fox jumps over the lazy dog",
summary: "I'm a pangram!",
},
exampleC: {
key: "StringExampleC",
value: "JavaScript rules",
summary: "A third example, for use in special places...",
},
})
})
})
describe("/Number", () => {
describe("in a parameter", () => {
ParameterPrimitiveTestCases({
operationDomId: "#operations-default-post_Number",
parameterName: "message",
exampleA: {
key: "NumberExampleA",
value: "7710263025",
},
exampleB: {
key: "NumberExampleB",
value: "9007199254740991",
},
exampleC: {
key: "NumberExampleC",
value: "0",
},
customUserInput: "9001",
})
})
describe("in a Request Body", () => {
RequestBodyPrimitiveTestCases({
operationDomId: "#operations-default-post_Number",
exampleA: {
key: "NumberExampleA",
value: "7710263025",
summary: "World population",
},
exampleB: {
key: "NumberExampleB",
value: "9007199254740991",
summary: "Number.MAX_SAFE_INTEGER",
},
exampleC: {
key: "NumberExampleC",
value: "0",
},
customUserInput: "1337",
})
})
describe("in a Response", () => {
ResponsePrimitiveTestCases({
operationDomId: "#operations-default-post_Number",
exampleA: {
key: "NumberExampleA",
value: "7710263025",
summary: "World population",
},
exampleB: {
key: "NumberExampleB",
value: "9007199254740991",
summary: "Number.MAX_SAFE_INTEGER",
},
exampleC: {
key: "NumberExampleC",
value: "0",
},
})
})
})
describe("/Boolean", () => {
describe("in a parameter", () => {
it("should render and apply the first example and value by default", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Boolean")
.click()
// Assert on the initial dropdown value
.get("table.parameters .examples-select > select")
.find(":selected")
.should("have.text", "The truth will set you free")
// Assert on the initial JsonSchemaForm value
.get(".parameters-col_description > select")
.should("have.attr", "disabled")
.get(".parameters-col_description > select")
.find(":selected")
.should("have.text", "true")
// Execute
.get(".try-out__btn")
.click()
.get(".execute")
.click()
// Assert on the request URL
.get(".request-url")
.contains(`?message=true`)
})
it("should render and apply the second value when chosen", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Boolean")
.click()
// Set the dropdown value, then assert on it
.get("table.parameters .examples-select > select")
.select("BooleanExampleB")
.find(":selected")
.should("have.text", "Friends don't lie to friends")
// Set the JsonSchemaForm value, then assert on it
.get(".parameters-col_description > select")
.find(":selected")
.should("have.text", "false")
// Execute
.get(".try-out__btn")
.click()
.get(".execute")
.click()
// Assert on the request URL
.get(".request-url")
.contains(`?message=false`)
})
it("should track value changes against valid examples", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Boolean")
.click()
.get(".try-out__btn")
.click()
// Set the JsonSchemaForm value, then assert on it
.get(".parameters-col_description > select")
.select("false")
.find(":selected")
.should("have.text", "false")
// Assert on the dropdown value
.get("table.parameters .examples-select > select")
.find(":selected")
.should("have.text", "Friends don't lie to friends")
// Execute
.get(".execute")
.click()
// Assert on the request URL
.get(".request-url")
.contains(`?message=false`)
})
})
describe("in a Request Body", () => {
RequestBodyPrimitiveTestCases({
operationDomId: "#operations-default-post_Boolean",
exampleA: {
key: "BooleanExampleA",
value: "true",
summary: "The truth will set you free",
},
exampleB: {
key: "BooleanExampleB",
value: "false",
summary: "Friends don't lie to friends",
},
customUserInput: "tralse",
})
})
describe("in a Response", () => {
it("should render and apply the first example and value by default", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Boolean")
.click()
// Assert on the initial dropdown value
.get(".responses-wrapper .examples-select > select")
.find(":selected")
.should("have.text", "The truth will set you free")
// Assert on the example value
.get(".example.microlight")
.should("have.text", "true")
})
it("should render and apply the second value when chosen", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Boolean")
.click()
// Set the dropdown value, then assert on it
.get(".responses-wrapper .examples-select > select")
.select("BooleanExampleB")
.find(":selected")
.should("have.text", "Friends don't lie to friends")
// Assert on the example value
.get(".example.microlight")
.should("have.text", "false")
})
})
})
describe("/Array", () => {
describe("in a Parameter", () => {
it("should have the first example's array entries by default", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
.get(".json-schema-form-item > input")
.then(inputs => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"a",
"b",
"c",
])
})
.get(".parameters-col_description .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of strings")
})
it("should switch to the second array's entries via dropdown", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
.get(".parameters-col_description .examples-select > select")
.select("ArrayExampleB")
.get(".json-schema-form-item > input")
.then(inputs => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1",
"2",
"3",
"4",
])
})
.get(".parameters-col_description .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of numbers")
})
it("should not allow modification of values in static mode", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
.get(".parameters-col_description .examples-select > select")
.select("ArrayExampleB")
// Add a new item
.get(".json-schema-form-item > input")
.should("have.attr", "disabled")
})
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()
.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 retain a modified value, and support returning to it", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
.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")
// Reset to an example
.get(".parameters-col_description .examples-select > select")
.select("ArrayExampleB")
// 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",
])
})
.get(".parameters-col_description .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of numbers")
// Return to the modified value
.get(".parameters-col_description .examples-select > select")
.select("__MODIFIED__VALUE__")
// Assert that our modified value is back
.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]")
})
})
describe("in a Request Body", () => {
it("should have the first example's array entries by default", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
// Check HighlightCode value
.get(".opblock-section-request-body .highlight-code")
.should("have.text", JSON.stringify(["a", "b", "c"], null, 2))
// Check dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of strings")
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.value", JSON.stringify(["a", "b", "c"], null, 2))
// Check dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of strings")
})
it("should switch to the second array's entries via dropdown", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
.get(".opblock-section-request-body .examples-select > select")
.select("ArrayExampleB")
.get(".opblock-section-request-body .highlight-code")
.should("have.text", JSON.stringify([1, 2, 3, 4], null, 2))
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of numbers")
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.text", JSON.stringify([1, 2, 3, 4], null, 2))
// Check dropdown value
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of numbers")
})
it("should allow modification of values", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Choose the second example
.get(".opblock-section-request-body .examples-select > select")
.select("ArrayExampleB")
// Change the value
.get(".opblock-section-request-body textarea")
.type(`{leftarrow}{leftarrow},{enter} 5`)
// Check that [Modified value] is displayed in dropdown
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "[Modified value]")
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.text", JSON.stringify([1, 2, 3, 4, 5], null, 2))
})
it("should retain a modified value, and support returning to it", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
// Switch to Try-It-Out
.get(".try-out__btn")
.click()
// Choose the second example as the example to start with
.get(".opblock-section-request-body .examples-select > select")
.select("ArrayExampleB")
// Change the value
.get(".opblock-section-request-body textarea")
.type(`{leftarrow}{leftarrow},{enter} 5`)
// Check that [Modified value] is displayed in dropdown
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "[Modified value]")
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.text", JSON.stringify([1, 2, 3, 4, 5], null, 2))
// Choose the second example
.get(".opblock-section-request-body .examples-select > select")
.select("ArrayExampleB")
// Check that the example is displayed in dropdown
.get(".opblock-section-request-body .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of numbers")
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.text", JSON.stringify([1, 2, 3, 4], null, 2))
// Switch back to the modified value
.get(".opblock-section-request-body .examples-select > select")
.select("__MODIFIED__VALUE__")
// Check textarea value
.get(".opblock-section-request-body textarea")
.should("have.text", JSON.stringify([1, 2, 3, 4, 5], null, 2))
})
})
describe("in a Response", () => {
it("should render and apply the first example and value by default", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
// Assert on the initial dropdown value
.get(".responses-wrapper .examples-select > select")
.find(":selected")
.should("have.text", "A lowly array of strings")
// Assert on the example value
.get(".example.microlight")
.should("have.text", JSON.stringify(["a", "b", "c"], null, 2))
})
it("should render and apply the second value when chosen", () => {
cy.visit(
"/?url=/documents/features/multiple-examples-core.openapi.yaml"
)
.get("#operations-default-post_Array")
.click()
// Set the dropdown value, then assert on it
.get(".responses-wrapper .examples-select > select")
.select("ArrayExampleB")
.find(":selected")
.should("have.text", "A lowly array of numbers")
// Assert on the example value
.get(".example.microlight")
.should("have.text", JSON.stringify([1, 2, 3, 4], null, 2))
})
})
})
describe("/Object", () => {
describe("in a Parameter", () => {
ParameterPrimitiveTestCases({
operationDomId: "#operations-default-post_Object",
parameterName: "data",
customUserInput: `{{} "openapiIsCool": true }`,
customExpectedUrlSubstring: "?openapiIsCool=true",
exampleA: {
key: "ObjectExampleA",
serializedValue:
"firstName=Kyle&lastName=Shockey&email=kyle.shockey%40smartbear.com",
value: JSON.stringify(
{
firstName: "Kyle",
lastName: "Shockey",
email: "kyle.shockey@smartbear.com",
},
null,
2
),
},
exampleB: {
key: "ObjectExampleB",
serializedValue:
"name=Abbey&type=kitten&color=calico&gender=female&age=11%20weeks",
value: JSON.stringify(
{
name: "Abbey",
type: "kitten",
color: "calico",
gender: "female",
age: "11 weeks",
},
null,
2
),
},
})
})
describe("in a Request Body", () => {
RequestBodyPrimitiveTestCases({
operationDomId: "#operations-default-post_Object",
primaryMediaType: "application/json",
// ↓ not a typo, Cypress requires escaping { when using `cy.type`
customUserInput: `{{} "openapiIsCool": true }`,
customExpectedUrlSubstring: "?openapiIsCool=true",
customUserInputExpectedCurlSubstring: `{\\"openapiIsCool\\":true}`,
exampleA: {
key: "ObjectExampleA",
serializedValue: `{\\"firstName\\":\\"Kyle\\",\\"lastName\\":\\"Shockey\\",\\"email\\":\\"kyle.shockey@smartbear.com\\"}`,
value: JSON.stringify(
{
firstName: "Kyle",
lastName: "Shockey",
email: "kyle.shockey@smartbear.com",
},
null,
2
),
summary: "A user's contact info",
},
exampleB: {
key: "ObjectExampleB",
serializedValue: `{\\"name\\":\\"Abbey\\",\\"type\\":\\"kitten\\",\\"color\\":\\"calico\\",\\"gender\\":\\"female\\",\\"age\\":\\"11 weeks\\"}`,
value: JSON.stringify(
{
name: "Abbey",
type: "kitten",
color: "calico",
gender: "female",
age: "11 weeks",
},
null,
2
),
summary: "A wonderful kitten's info",
},
})
})
describe("in a Response", () => {
ResponsePrimitiveTestCases({
operationDomId: "#operations-default-post_Object",
exampleA: {
key: "ObjectExampleA",
value: JSON.stringify(
{
firstName: "Kyle",
lastName: "Shockey",
email: "kyle.shockey@smartbear.com",
},
null,
2
),
summary: "A user's contact info",
},
exampleB: {
key: "ObjectExampleB",
value: JSON.stringify(
{
name: "Abbey",
type: "kitten",
color: "calico",
gender: "female",
age: "11 weeks",
},
null,
2
),
summary: "A wonderful kitten's info",
},
})
})
})
})