Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions test/e2e-cypress/e2e/bugs/5460.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* @prettier
*/
// https://github.com/swagger-api/swagger-ui/issues/5460

describe("#5460: Displayed example not updated when switching between media types", () => {
const mediaTypeJson = "application/json"
const mediaTypeXml = "application/xml"
const mediaTypePlain = "text/plain"

beforeEach(() => {
cy.visit("/?url=/documents/bugs/5460.yaml")
.get("#operations-default-post_foo")
.click()
cy.get(".opblock-section-request-body .content-type").as("selectMediaType")
})

it("should update the displayed Example Value when switching from application/json (bar_json) to application/xml", () => {
cy.get("@selectMediaType").should("have.value", mediaTypeJson)
cy.get(".opblock-section-request-body .examples-select-element").select(
"bar_json"
)
cy.get(".opblock-section-request-body .body-param__example").should(
"contain.text",
"bar"
)

cy.get("@selectMediaType").select(mediaTypeXml)
cy.get(".opblock-section-request-body .body-param__example")
.should("contain.text", "<")
.should("not.contain.text", '"bar"')
})

it("should display the text/plain example after switching from application/json (bar_json) to text/plain", () => {
cy.get("@selectMediaType").should("have.value", mediaTypeJson)
cy.get(".opblock-section-request-body .examples-select-element").select(
"bar_json"
)
cy.get(".opblock-section-request-body .body-param__example").should(
"contain.text",
"bar"
)

cy.get("@selectMediaType").select(mediaTypePlain)
cy.get(".opblock-section-request-body .body-param__example")
.should("contain.text", "Hello, world!")
.should("not.contain.text", '"bar"')
})

it("should reset the Examples dropdown to the first example of the new media type when the previous active example does not exist", () => {
cy.get(".opblock-section-request-body .examples-select-element").select(
"bar_json"
)
cy.get(".opblock-section-request-body .examples-select-element").should(
"have.value",
"bar_json"
)

cy.get("@selectMediaType").select(mediaTypeXml)
cy.get(".opblock-section-request-body .examples-select-element").should(
"have.value",
"foo_xml"
)
})

it("should follow the full repro path: json/bar_json -> xml -> text/plain and display each media type's example", () => {
// Step 3: select bar_json under application/json
cy.get("@selectMediaType").should("have.value", mediaTypeJson)
cy.get(".opblock-section-request-body .examples-select-element").select(
"bar_json"
)
cy.get(".opblock-section-request-body .body-param__example").should(
"contain.text",
"bar"
)

// Step 4: switch to application/xml; Example Value pane must update
cy.get("@selectMediaType").select(mediaTypeXml)
cy.get(".opblock-section-request-body .body-param__example").should(
"contain.text",
"<"
)

// Step 5: switch to text/plain; Example Value pane must update again
cy.get("@selectMediaType").select(mediaTypePlain)
cy.get(".opblock-section-request-body .body-param__example")
.should("contain.text", "Hello, world!")
.should("not.contain.text", '"bar"')
.should("not.contain.text", "<foo>")
.should("not.contain.text", "<bar>")
})
})
36 changes: 36 additions & 0 deletions test/e2e-cypress/static/documents/bugs/5460.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
openapi: 3.0.2
info:
title: test
version: 0.0.0
paths:
/foo:
post:
requestBody:
description: A JSON object, XML object, or text string containing foo data
required: true
content:
application/json:
schema:
type: object
examples:
foo_json:
value:
foo: bar
bar_json:
value:
bar: foo
text/plain:
schema:
type: string
example: Hello, world!
application/xml:
schema:
type: object
examples:
foo_xml:
value: <foo>bar</foo>
bar_xml:
value: <bar>foo</bar>
responses:
201:
description: Created
131 changes: 131 additions & 0 deletions test/unit/components/examples-select.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/* eslint-disable camelcase */
/**
* @prettier
*/
import React from "react"
import { OrderedMap, fromJS } from "immutable"
import { mount } from "enzyme"

import ExamplesSelect from "core/components/examples-select"

describe("<ExamplesSelect/>", () => {
// Regression coverage for https://github.com/swagger-api/swagger-ui/issues/5460
// When the user switches media type, `examples` changes from under the
// component. If the previously-selected example key is not part of the new
// `examples`, the component must auto-snap to the first example of the new
// set and notify its parent via a synthetic `onSelect`.
describe("auto-snap when examples change and currentExampleKey is stale", () => {
it("fires a synthetic onSelect with the first new example key", () => {
const jsonExamples = fromJS({
foo_json: { value: { foo: "bar" } },
bar_json: { value: { bar: "foo" } },
})
const xmlExamples = fromJS({
foo_xml: { value: "<foo>bar</foo>" },
bar_xml: { value: "<bar>foo</bar>" },
})
const onSelect = jest.fn()

const wrapper = mount(
<ExamplesSelect
examples={jsonExamples}
currentExampleKey="bar_json"
onSelect={onSelect}
/>
)

// ignore the synthetic onSelect fired from componentDidMount
onSelect.mockClear()

wrapper.setProps({ examples: xmlExamples })

expect(onSelect).toHaveBeenCalledTimes(1)
expect(onSelect).toHaveBeenCalledWith("foo_xml", {
isSyntheticChange: true,
})
})

it("does not snap when the previous key exists in the new examples", () => {
const examplesA = fromJS({
shared: { value: "A-shared" },
only_a: { value: "A-only" },
})
const examplesB = fromJS({
shared: { value: "B-shared" },
only_b: { value: "B-only" },
})
const onSelect = jest.fn()

const wrapper = mount(
<ExamplesSelect
examples={examplesA}
currentExampleKey="shared"
onSelect={onSelect}
/>
)

onSelect.mockClear()
wrapper.setProps({ examples: examplesB })

expect(onSelect).not.toHaveBeenCalled()
})

it("fires no auto-snap when the underlying examples reference does not change", () => {
const examples = fromJS({
a: { value: "A" },
b: { value: "B" },
})
const onSelect = jest.fn()

const wrapper = mount(
<ExamplesSelect
examples={examples}
currentExampleKey="a"
onSelect={onSelect}
/>
)

onSelect.mockClear()
// identical reference; should not trigger the auto-snap branch
wrapper.setProps({ examples })

expect(onSelect).not.toHaveBeenCalled()
})
})

describe("componentDidMount", () => {
it("fires a synthetic onSelect with the first example key on mount", () => {
const examples = fromJS({
first_key: { value: "first" },
second_key: { value: "second" },
})
const onSelect = jest.fn()

mount(
<ExamplesSelect
examples={examples}
currentExampleKey="first_key"
onSelect={onSelect}
/>
)

expect(onSelect).toHaveBeenCalledWith("first_key", {
isSyntheticChange: true,
})
})

it("does not throw when examples is empty", () => {
const onSelect = jest.fn()

expect(() => {
mount(
<ExamplesSelect
examples={new OrderedMap()}
currentExampleKey={null}
onSelect={onSelect}
/>
)
}).not.toThrow()
})
})
})
Loading