Skip to content

Commit 71a8eb3

Browse files
authored
Show request content type in REST API example dropdown (#56275)
1 parent 971118f commit 71a8eb3

File tree

3 files changed

+246
-8
lines changed

3 files changed

+246
-8
lines changed

src/rest/components/RestCodeSamples.tsx

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export function RestCodeSamples({ operation, slug, heading }: Props) {
7171
javascript: getJSExample(operation, sample, currentVersion, allVersions),
7272
ghcli: getGHExample(operation, sample, currentVersion, allVersions),
7373
response: sample.response,
74+
request: sample.request,
7475
}))
7576

7677
// Menu options for the language selector
@@ -98,18 +99,46 @@ export function RestCodeSamples({ operation, slug, heading }: Props) {
9899
// there's more than one example and if the media types aren't all the same
99100
// for the examples (e.g. if all examples have content type `application/json`,
100101
// we won't show that information in the menu items).
101-
const showExampleOptionMediaType =
102+
const responseContentTypesDiffer =
102103
languageExamples.length > 1 &&
103104
!languageExamples.every(
104105
(example) => example.response.contentType === languageExamples[0].response.contentType,
105106
)
106-
const exampleSelectOptions = languageExamples.map((example, index) => ({
107-
text: showExampleOptionMediaType
108-
? `${example.description} (${example.response.contentType})`
109-
: example.description,
110-
// maps to the index of the example in the languageExamples array
111-
languageIndex: index,
112-
}))
107+
108+
// Check if request content types differ between examples
109+
const requestContentTypesDiffer =
110+
languageExamples.length > 1 &&
111+
!languageExamples.every(
112+
(example) => example.request?.contentType === languageExamples[0].request?.contentType,
113+
)
114+
115+
const showExampleOptionMediaType = responseContentTypesDiffer || requestContentTypesDiffer
116+
117+
const exampleSelectOptions = languageExamples.map((example, index) => {
118+
const requestContentType = example.request?.contentType
119+
const responseContentType = example.response.contentType
120+
121+
let text = example.description
122+
123+
if (showExampleOptionMediaType) {
124+
if (requestContentTypesDiffer && responseContentTypesDiffer) {
125+
// Show both request and response content types
126+
text = `${example.description} (${requestContentType}${responseContentType})`
127+
} else if (requestContentTypesDiffer) {
128+
// Show only request content type
129+
text = `${example.description} (${requestContentType})`
130+
} else if (responseContentTypesDiffer) {
131+
// Show only response content type
132+
text = `${example.description} (${responseContentType})`
133+
}
134+
}
135+
136+
return {
137+
text,
138+
// maps to the index of the example in the languageExamples array
139+
languageIndex: index,
140+
}
141+
})
113142

114143
const [selectedLanguage, setSelectedLanguage] = useState(languageSelectOptions[0])
115144
const [selectedExample, setSelectedExample] = useState(exampleSelectOptions[0])

src/rest/tests/content-type-logic.js

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { describe, expect, test } from 'vitest'
2+
3+
describe('Request Content Type Logic', () => {
4+
// Helper function to extract the logic from RestCodeSamples
5+
function shouldShowRequestContentType(codeExamples) {
6+
const requestContentTypesDiffer =
7+
codeExamples.length > 1 &&
8+
!codeExamples.every(
9+
(example) => example.request?.contentType === codeExamples[0].request?.contentType,
10+
)
11+
return requestContentTypesDiffer
12+
}
13+
14+
function shouldShowResponseContentType(codeExamples) {
15+
const responseContentTypesDiffer =
16+
codeExamples.length > 1 &&
17+
!codeExamples.every(
18+
(example) => example.response?.contentType === codeExamples[0].response?.contentType,
19+
)
20+
return responseContentTypesDiffer
21+
}
22+
23+
function generateExampleOptions(codeExamples) {
24+
const requestContentTypesDiffer = shouldShowRequestContentType(codeExamples)
25+
const responseContentTypesDiffer = shouldShowResponseContentType(codeExamples)
26+
const showExampleOptionMediaType = responseContentTypesDiffer || requestContentTypesDiffer
27+
28+
return codeExamples.map((example, index) => {
29+
const requestContentType = example.request?.contentType
30+
const responseContentType = example.response?.contentType
31+
32+
let text = example.request?.description || `Example ${index + 1}`
33+
34+
if (showExampleOptionMediaType) {
35+
if (requestContentTypesDiffer && responseContentTypesDiffer) {
36+
// Show both request and response content types
37+
text = `${text} (${requestContentType}${responseContentType})`
38+
} else if (requestContentTypesDiffer) {
39+
// Show only request content type
40+
text = `${text} (${requestContentType})`
41+
} else if (responseContentTypesDiffer) {
42+
// Show only response content type
43+
text = `${text} (${responseContentType})`
44+
}
45+
}
46+
47+
return text
48+
})
49+
}
50+
51+
test('detects request content types differ correctly', () => {
52+
const codeExamples = [
53+
{
54+
request: { contentType: 'text/plain', description: 'Example' },
55+
response: { contentType: 'text/html' },
56+
},
57+
{
58+
request: { contentType: 'text/x-markdown', description: 'Rendering markdown' },
59+
response: { contentType: 'text/html' },
60+
},
61+
]
62+
63+
expect(shouldShowRequestContentType(codeExamples)).toBe(true)
64+
expect(shouldShowResponseContentType(codeExamples)).toBe(false)
65+
})
66+
67+
test('detects response content types differ correctly', () => {
68+
const codeExamples = [
69+
{
70+
request: { contentType: 'application/json', description: 'JSON example' },
71+
response: { contentType: 'application/json' },
72+
},
73+
{
74+
request: { contentType: 'application/json', description: 'Another JSON example' },
75+
response: { contentType: 'text/html' },
76+
},
77+
]
78+
79+
expect(shouldShowRequestContentType(codeExamples)).toBe(false)
80+
expect(shouldShowResponseContentType(codeExamples)).toBe(true)
81+
})
82+
83+
test('generates correct options for markdown/raw scenario', () => {
84+
const markdownRawExamples = [
85+
{
86+
request: {
87+
contentType: 'text/plain',
88+
description: 'Example',
89+
},
90+
response: {
91+
contentType: 'text/html',
92+
},
93+
},
94+
{
95+
request: {
96+
contentType: 'text/x-markdown',
97+
description: 'Rendering markdown',
98+
},
99+
response: {
100+
contentType: 'text/html',
101+
},
102+
},
103+
]
104+
105+
const options = generateExampleOptions(markdownRawExamples)
106+
107+
expect(options).toEqual(['Example (text/plain)', 'Rendering markdown (text/x-markdown)'])
108+
})
109+
110+
test('generates correct options when both request and response differ', () => {
111+
const mixedExamples = [
112+
{
113+
request: {
114+
contentType: 'application/json',
115+
description: 'JSON request',
116+
},
117+
response: {
118+
contentType: 'application/json',
119+
},
120+
},
121+
{
122+
request: {
123+
contentType: 'text/plain',
124+
description: 'Plain text request',
125+
},
126+
response: {
127+
contentType: 'text/html',
128+
},
129+
},
130+
]
131+
132+
const options = generateExampleOptions(mixedExamples)
133+
134+
expect(options).toEqual([
135+
'JSON request (application/json → application/json)',
136+
'Plain text request (text/plain → text/html)',
137+
])
138+
})
139+
140+
test('does not show content types when they are all the same', () => {
141+
const sameContentTypeExamples = [
142+
{
143+
request: {
144+
contentType: 'application/json',
145+
description: 'First example',
146+
},
147+
response: {
148+
contentType: 'application/json',
149+
},
150+
},
151+
{
152+
request: {
153+
contentType: 'application/json',
154+
description: 'Second example',
155+
},
156+
response: {
157+
contentType: 'application/json',
158+
},
159+
},
160+
]
161+
162+
const options = generateExampleOptions(sameContentTypeExamples)
163+
164+
expect(options).toEqual(['First example', 'Second example'])
165+
})
166+
167+
test('handles single example correctly', () => {
168+
const singleExample = [
169+
{
170+
request: {
171+
contentType: 'application/json',
172+
description: 'Only example',
173+
},
174+
response: {
175+
contentType: 'application/json',
176+
},
177+
},
178+
]
179+
180+
expect(shouldShowRequestContentType(singleExample)).toBe(false)
181+
expect(shouldShowResponseContentType(singleExample)).toBe(false)
182+
183+
const options = generateExampleOptions(singleExample)
184+
expect(options).toEqual(['Only example'])
185+
})
186+
})

src/rest/tests/rendering.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,29 @@ describe('REST references docs', () => {
120120
}
121121
}
122122
})
123+
124+
test('markdown/raw endpoint shows request content types in example selector', async () => {
125+
// Test the specific endpoint that has multiple examples with different request content types
126+
const $ = await getDOM('/en/rest/markdown/markdown?apiVersion=2022-11-28')
127+
128+
// Find the render raw mode operation section by its specific ID
129+
const rawModeSection = $('#render-a-markdown-document-in-raw-mode--code-samples').parent()
130+
expect(rawModeSection.length).toBeGreaterThan(0)
131+
132+
// Should have an example selector dropdown since there are multiple examples
133+
const exampleSelector = rawModeSection.find('select[aria-labelledby], select').first()
134+
expect(exampleSelector.length).toBe(1)
135+
136+
// Get the option texts from the dropdown
137+
const optionTexts = exampleSelector
138+
.find('option')
139+
.map((i, option) => $(option).text().trim())
140+
.get()
141+
.filter((text) => text.length > 0)
142+
143+
// Should show request content types since they differ between examples
144+
expect(optionTexts).toEqual(['Example (text/plain)', 'Rendering markdown (text/x-markdown)'])
145+
})
123146
})
124147

125148
function formatErrors(differences) {

0 commit comments

Comments
 (0)