Skip to content

Commit 309bd28

Browse files
Merge pull request #205 from patternfly/css-update
feat(API): Add CSS API table support
2 parents ecb1045 + 667eb46 commit 309bd28

File tree

7 files changed

+650
-0
lines changed

7 files changed

+650
-0
lines changed
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import { GET } from '../../../../../../../pages/api/[version]/[section]/[page]/css'
2+
3+
/**
4+
* Mock fetchApiIndex to return API index with CSS tokens
5+
*/
6+
jest.mock('../../../../../../../utils/apiIndex/fetch', () => ({
7+
fetchApiIndex: jest.fn().mockResolvedValue({
8+
versions: ['v6'],
9+
sections: {
10+
v6: ['components'],
11+
},
12+
pages: {
13+
'v6::components': ['alert', 'button'],
14+
},
15+
tabs: {
16+
'v6::components::alert': ['react', 'html'],
17+
'v6::components::button': ['react'],
18+
},
19+
css: {
20+
'v6::components::alert': [
21+
{
22+
name: '--pf-v6-c-alert--BackgroundColor',
23+
value: '#ffffff',
24+
var: '--pf-v6-c-alert--BackgroundColor',
25+
description: 'Alert background color',
26+
},
27+
{
28+
name: '--pf-v6-c-alert--Color',
29+
value: '#151515',
30+
var: '--pf-v6-c-alert--Color',
31+
description: 'Alert text color',
32+
},
33+
],
34+
'v6::components::button': [
35+
{
36+
name: '--pf-v6-c-button--BackgroundColor',
37+
value: '#0066cc',
38+
var: '--pf-v6-c-button--BackgroundColor',
39+
description: 'Button background color',
40+
},
41+
],
42+
},
43+
}),
44+
}))
45+
46+
beforeEach(() => {
47+
jest.clearAllMocks()
48+
})
49+
50+
it('returns CSS tokens for a valid page', async () => {
51+
const response = await GET({
52+
params: {
53+
version: 'v6',
54+
section: 'components',
55+
page: 'alert',
56+
},
57+
url: new URL('http://localhost/api/v6/components/alert/css'),
58+
} as any)
59+
const body = await response.json()
60+
61+
expect(response.status).toBe(200)
62+
expect(response.headers.get('Content-Type')).toBe('application/json; charset=utf-8')
63+
expect(Array.isArray(body)).toBe(true)
64+
expect(body).toHaveLength(2)
65+
expect(body[0]).toHaveProperty('name')
66+
expect(body[0]).toHaveProperty('value')
67+
expect(body[0]).toHaveProperty('var')
68+
expect(body[0].name).toBe('--pf-v6-c-alert--BackgroundColor')
69+
expect(body[0].value).toBe('#ffffff')
70+
})
71+
72+
it('returns CSS tokens for different pages', async () => {
73+
const buttonResponse = await GET({
74+
params: {
75+
version: 'v6',
76+
section: 'components',
77+
page: 'button',
78+
},
79+
url: new URL('http://localhost/api/v6/components/button/css'),
80+
} as any)
81+
const buttonBody = await buttonResponse.json()
82+
83+
expect(buttonResponse.status).toBe(200)
84+
expect(Array.isArray(buttonBody)).toBe(true)
85+
expect(buttonBody).toHaveLength(1)
86+
expect(buttonBody[0].name).toBe('--pf-v6-c-button--BackgroundColor')
87+
})
88+
89+
it('returns empty array when no CSS tokens are found for page', async () => {
90+
const response = await GET({
91+
params: {
92+
version: 'v6',
93+
section: 'components',
94+
page: 'nonexistent',
95+
},
96+
url: new URL('http://localhost/api/v6/components/nonexistent/css'),
97+
} as any)
98+
const body = await response.json()
99+
100+
expect(response.status).toBe(200)
101+
expect(Array.isArray(body)).toBe(true)
102+
expect(body).toHaveLength(0)
103+
})
104+
105+
it('returns 400 error when version parameter is missing', async () => {
106+
const response = await GET({
107+
params: {
108+
section: 'components',
109+
page: 'alert',
110+
},
111+
url: new URL('http://localhost/api/components/alert/css'),
112+
} as any)
113+
const body = await response.json()
114+
115+
expect(response.status).toBe(400)
116+
expect(body).toHaveProperty('error')
117+
expect(body.error).toContain('Version, section, and page parameters are required')
118+
})
119+
120+
it('returns 400 error when section parameter is missing', async () => {
121+
const response = await GET({
122+
params: {
123+
version: 'v6',
124+
page: 'alert',
125+
},
126+
url: new URL('http://localhost/api/v6/alert/css'),
127+
} as any)
128+
const body = await response.json()
129+
130+
expect(response.status).toBe(400)
131+
expect(body).toHaveProperty('error')
132+
expect(body.error).toContain('Version, section, and page parameters are required')
133+
})
134+
135+
it('returns 400 error when page parameter is missing', async () => {
136+
const response = await GET({
137+
params: {
138+
version: 'v6',
139+
section: 'components',
140+
},
141+
url: new URL('http://localhost/api/v6/components/css'),
142+
} as any)
143+
const body = await response.json()
144+
145+
expect(response.status).toBe(400)
146+
expect(body).toHaveProperty('error')
147+
expect(body.error).toContain('Version, section, and page parameters are required')
148+
})
149+
150+
it('returns 400 error when all parameters are missing', async () => {
151+
const response = await GET({
152+
params: {},
153+
url: new URL('http://localhost/api/css'),
154+
} as any)
155+
const body = await response.json()
156+
157+
expect(response.status).toBe(400)
158+
expect(body).toHaveProperty('error')
159+
expect(body.error).toContain('Version, section, and page parameters are required')
160+
})
161+
162+
it('returns 500 error when fetchApiIndex fails', async () => {
163+
// eslint-disable-next-line @typescript-eslint/no-require-imports
164+
const { fetchApiIndex } = require('../../../../../../../utils/apiIndex/fetch')
165+
fetchApiIndex.mockRejectedValueOnce(new Error('Network error'))
166+
167+
const response = await GET({
168+
params: {
169+
version: 'v6',
170+
section: 'components',
171+
page: 'alert',
172+
},
173+
url: new URL('http://localhost/api/v6/components/alert/css'),
174+
} as any)
175+
const body = await response.json()
176+
177+
expect(response.status).toBe(500)
178+
expect(body).toHaveProperty('error')
179+
expect(body).toHaveProperty('details')
180+
expect(body.error).toBe('Failed to load API index')
181+
expect(body.details).toBe('Network error')
182+
})
183+
184+
it('returns 500 error when fetchApiIndex throws a non-Error object', async () => {
185+
// eslint-disable-next-line @typescript-eslint/no-require-imports
186+
const { fetchApiIndex } = require('../../../../../../../utils/apiIndex/fetch')
187+
fetchApiIndex.mockRejectedValueOnce('String error')
188+
189+
const response = await GET({
190+
params: {
191+
version: 'v6',
192+
section: 'components',
193+
page: 'alert',
194+
},
195+
url: new URL('http://localhost/api/v6/components/alert/css'),
196+
} as any)
197+
const body = await response.json()
198+
199+
expect(response.status).toBe(500)
200+
expect(body).toHaveProperty('error')
201+
expect(body).toHaveProperty('details')
202+
expect(body.error).toBe('Failed to load API index')
203+
expect(body.details).toBe('String error')
204+
})
205+
206+
it('returns empty array when CSS tokens array exists but is empty', async () => {
207+
// eslint-disable-next-line @typescript-eslint/no-require-imports
208+
const { fetchApiIndex } = require('../../../../../../../utils/apiIndex/fetch')
209+
fetchApiIndex.mockResolvedValueOnce({
210+
versions: ['v6'],
211+
sections: {
212+
v6: ['components'],
213+
},
214+
pages: {
215+
'v6::components': ['empty'],
216+
},
217+
tabs: {
218+
'v6::components::empty': ['react'],
219+
},
220+
css: {
221+
'v6::components::empty': [],
222+
},
223+
})
224+
225+
const response = await GET({
226+
params: {
227+
version: 'v6',
228+
section: 'components',
229+
page: 'empty',
230+
},
231+
url: new URL('http://localhost/api/v6/components/empty/css'),
232+
} as any)
233+
const body = await response.json()
234+
235+
expect(response.status).toBe(200)
236+
expect(Array.isArray(body)).toBe(true)
237+
expect(body).toHaveLength(0)
238+
})
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { APIRoute } from 'astro'
2+
import { createJsonResponse, createIndexKey } from '../../../../../utils/apiHelpers'
3+
import { fetchApiIndex } from '../../../../../utils/apiIndex/fetch'
4+
5+
export const prerender = false
6+
7+
export const GET: APIRoute = async ({ params, url }) => {
8+
const { version, section, page } = params
9+
10+
if (!version || !section || !page) {
11+
return createJsonResponse(
12+
{ error: 'Version, section, and page parameters are required' },
13+
400,
14+
)
15+
}
16+
17+
try {
18+
const index = await fetchApiIndex(url)
19+
const pageKey = createIndexKey(version, section, page)
20+
const cssTokens = index.css[pageKey] || []
21+
22+
if (cssTokens.length === 0) {
23+
return createJsonResponse([])
24+
}
25+
26+
return createJsonResponse(cssTokens)
27+
} catch (error) {
28+
const details = error instanceof Error ? error.message : String(error)
29+
return createJsonResponse(
30+
{ error: 'Failed to load API index', details },
31+
500,
32+
)
33+
}
34+
}

src/pages/api/index.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,42 @@ export const GET: APIRoute = async () =>
205205
],
206206
},
207207
},
208+
{
209+
path: '/api/{version}/{section}/{page}/css',
210+
method: 'GET',
211+
description: 'Get CSS tokens for a specific page',
212+
parameters: [
213+
{
214+
name: 'version',
215+
in: 'path',
216+
required: true,
217+
type: 'string',
218+
example: 'v6',
219+
},
220+
{
221+
name: 'section',
222+
in: 'path',
223+
required: true,
224+
type: 'string',
225+
example: 'components',
226+
},
227+
{
228+
name: 'page',
229+
in: 'path',
230+
required: true,
231+
type: 'string',
232+
example: 'alert',
233+
},
234+
],
235+
returns: {
236+
type: 'array',
237+
items: 'object',
238+
description: 'Array of CSS token objects with tokenName, value, and variableName',
239+
example: [
240+
{ name: 'c_alert__Background', value: '#000000', var: 'c_alert__Background' },
241+
],
242+
},
243+
},
208244
{
209245
path: '/api/{version}/{section}/{page}/{tab}',
210246
method: 'GET',

0 commit comments

Comments
 (0)