Skip to content

Commit 8ffb1ae

Browse files
authored
fix(a11n): provide proper a11n for response example tabs (#7464)
- Update tabs to use <button> elements instead of <a> - Add aria roles for tablist, tabs, and tabpanel - Add aria attributes for additional a11y compliance and screen reader accessibility - Replace ids with data-name attribute for tabpanels - Add cypress test 7463 and update swos-63 - Move tabs test file to tests/a11y directory - Rename test file to be more descriptive of what is being tested. - Add id attributes to both tabs and tabpanels to leverage aria-controls and aria-labelledby attributes Co-authored-by: Calvin Gonzalez <[email protected]> Co-authored-by: Vladimir Gorej <[email protected]> Closes #7463 Refs #7350
1 parent 00d5b30 commit 8ffb1ae

File tree

3 files changed

+112
-34
lines changed

3 files changed

+112
-34
lines changed

src/core/components/model-example.jsx

+75-32
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React from "react"
22
import PropTypes from "prop-types"
33
import ImPropTypes from "react-immutable-proptypes"
4+
import cx from "classnames"
5+
import randomBytes from "randombytes"
46

57
export default class ModelExample extends React.Component {
68
static propTypes = {
@@ -31,11 +33,11 @@ export default class ModelExample extends React.Component {
3133
}
3234

3335
this.state = {
34-
activeTab: activeTab
36+
activeTab,
3537
}
3638
}
3739

38-
activeTab =( e ) => {
40+
activeTab = ( e ) => {
3941
let { target : { dataset : { name } } } = e
4042

4143
this.setState({
@@ -58,42 +60,83 @@ export default class ModelExample extends React.Component {
5860
let { defaultModelExpandDepth } = getConfigs()
5961
const ModelWrapper = getComponent("ModelWrapper")
6062
const HighlightCode = getComponent("highlightCode")
63+
const exampleTabId = randomBytes(5).toString("base64")
64+
const examplePanelId = randomBytes(5).toString("base64")
65+
const modelTabId = randomBytes(5).toString("base64")
66+
const modelPanelId = randomBytes(5).toString("base64")
6167

6268
let isOAS3 = specSelectors.isOAS3()
6369

64-
return <div className="model-example">
65-
<ul className="tab">
66-
<li className={ "tabitem" + ( this.state.activeTab === "example" ? " active" : "") }>
67-
<a className="tablinks" data-name="example" onClick={ this.activeTab }>{isExecute ? "Edit Value" : "Example Value"}</a>
68-
</li>
69-
{ schema ? <li className={ "tabitem" + ( this.state.activeTab === "model" ? " active" : "") }>
70-
<a className={ "tablinks" + ( isExecute ? " inactive" : "" )} data-name="model" onClick={ this.activeTab }>
71-
{isOAS3 ? "Schema" : "Model" }
72-
</a>
73-
</li> : null }
74-
</ul>
75-
<div>
76-
{
77-
this.state.activeTab === "example" ? (
78-
example ? example : (
70+
return (
71+
<div className="model-example">
72+
<ul className="tab" role="tablist">
73+
<li className={cx("tabitem", { active: this.state.activeTab === "example" })} role="presentation">
74+
<button
75+
aria-controls={examplePanelId}
76+
aria-selected={this.state.activeTab === "example"}
77+
className="tablinks"
78+
data-name="example"
79+
id={exampleTabId}
80+
onClick={ this.activeTab }
81+
role="tab"
82+
>
83+
{isExecute ? "Edit Value" : "Example Value"}
84+
</button>
85+
</li>
86+
{ schema && (
87+
<li className={cx("tabitem", { active: this.state.activeTab === "model" })} role="presentation">
88+
<button
89+
aria-controls={modelPanelId}
90+
aria-selected={this.state.activeTab === "model"}
91+
className={cx("tablinks", { inactive: isExecute })}
92+
data-name="model"
93+
id={modelTabId}
94+
onClick={ this.activeTab }
95+
role="tab"
96+
>
97+
{isOAS3 ? "Schema" : "Model" }
98+
</button>
99+
</li>
100+
)}
101+
</ul>
102+
{this.state.activeTab === "example" && (
103+
<div
104+
aria-hidden={this.state.activeTab !== "example"}
105+
aria-labelledby={exampleTabId}
106+
data-name="examplePanel"
107+
id={examplePanelId}
108+
role="tabpanel"
109+
tabIndex="0"
110+
>
111+
{example ? example : (
79112
<HighlightCode value="(no example available)" getConfigs={ getConfigs } />
80-
)
81-
) : null
82-
}
83-
{
84-
this.state.activeTab === "model" && <ModelWrapper schema={ schema }
85-
getComponent={ getComponent }
86-
getConfigs={ getConfigs }
87-
specSelectors={ specSelectors }
88-
expandDepth={ defaultModelExpandDepth }
89-
specPath={specPath}
90-
includeReadOnly = {includeReadOnly}
91-
includeWriteOnly = {includeWriteOnly}/>
113+
)}
114+
</div>
115+
)}
92116

93-
94-
}
117+
{this.state.activeTab === "model" && (
118+
<div
119+
aria-hidden={this.state.activeTab === "example"}
120+
aria-labelledby={modelTabId}
121+
data-name="modelPanel"
122+
id={modelPanelId}
123+
role="tabpanel"
124+
tabIndex="0"
125+
>
126+
<ModelWrapper
127+
schema={ schema }
128+
getComponent={ getComponent }
129+
getConfigs={ getConfigs }
130+
specSelectors={ specSelectors }
131+
expandDepth={ defaultModelExpandDepth }
132+
specPath={specPath}
133+
includeReadOnly = {includeReadOnly}
134+
includeWriteOnly = {includeWriteOnly}
135+
/>
136+
</div>
137+
)}
95138
</div>
96-
</div>
139+
)
97140
}
98141

99142
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
describe("Response tab elements", () => {
2+
describe("ModelExample within Operation", () => {
3+
it("should render Example tabpanel by default", () => {
4+
cy
5+
.visit("/?url=/documents/petstore-expanded.openapi.yaml")
6+
.get("#operations-default-addPet")
7+
.click()
8+
.get("div[data-name=examplePanel]")
9+
.first()
10+
.should("have.attr", "aria-hidden", "false")
11+
})
12+
it("should click Schema tab button and render Schema tabpanel for OpenAPI 3", () => {
13+
cy
14+
.visit("/?url=/documents/petstore-expanded.openapi.yaml")
15+
.get("#operations-default-addPet")
16+
.click()
17+
.get("button.tablinks[data-name=model]")
18+
.first()
19+
.click()
20+
.get("div[data-name=modelPanel]")
21+
.first()
22+
.should("have.attr", "aria-hidden", "false")
23+
})
24+
it("should click Model tab button and render Model tabpanel for OpenAPI 2", () => {
25+
cy
26+
.visit("/?url=/documents/petstore.swagger.yaml")
27+
.get("#operations-pet-addPet")
28+
.click()
29+
.get("button.tablinks[data-name=model]")
30+
.click()
31+
.get("div[data-name=modelPanel]")
32+
.should("have.attr", "aria-hidden", "false")
33+
})
34+
})
35+
})

test/e2e-cypress/tests/bugs/swos-63.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe("SWOS-63: Schema/Model labeling", () => {
1919
.visit("/?url=/documents/petstore-expanded.openapi.yaml")
2020
.get("#operations-default-findPets")
2121
.click()
22-
.get("a.tablinks[data-name=model]")
22+
.get("button.tablinks[data-name=model]")
2323
.contains("Schema")
2424
})
2525
it("should render `Models` for OpenAPI 2", () => {
@@ -28,7 +28,7 @@ describe("SWOS-63: Schema/Model labeling", () => {
2828
.get("section.models > h4")
2929
.get("#operations-pet-addPet")
3030
.click()
31-
.get("a.tablinks[data-name=model]")
31+
.get("button.tablinks[data-name=model]")
3232
.contains("Model")
3333
})
3434
})

0 commit comments

Comments
 (0)