Skip to content

Commit 72c5ed4

Browse files
refactor: better accessbility for tabs. (#68)
1 parent 8daecfa commit 72c5ed4

11 files changed

Lines changed: 196 additions & 145 deletions

playwright/helpers/app-test-helpers.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,18 @@ export const addWorkspaceTab = async (
125125
page: Page,
126126
{ kind = 'component' }: { kind?: 'component' | 'styles' } = {},
127127
) => {
128-
await page.getByRole('button', { name: 'Add tab options' }).click()
128+
await page.getByRole('button', { name: 'Add workspace tab' }).click()
129129
if (kind === 'styles') {
130-
await page.getByRole('menuitem', { name: 'styles' }).click()
130+
await page.getByRole('button', { name: 'Add styles tab' }).click()
131131
return
132132
}
133133

134-
await page.getByRole('menuitem', { name: 'module' }).click()
134+
await page.getByRole('button', { name: 'Add module tab' }).click()
135135
}
136136

137137
export const openWorkspaceTab = async (page: Page, fileName: string) => {
138138
const pattern = new RegExp(`^Open tab ${escapeRegex(fileName)}$`)
139-
await page.getByRole('tab', { name: pattern }).click()
139+
await page.getByRole('button', { name: pattern }).click()
140140
}
141141

142142
export const setWorkspaceTabSource = async (
@@ -162,7 +162,7 @@ export const setWorkspaceTabSource = async (
162162
}
163163

164164
export const setComponentEditorSource = async (page: Page, source: string) => {
165-
await page.getByRole('tab', { name: 'Open tab App.tsx' }).click()
165+
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
166166
const editorContent = page
167167
.locator('.editor-panel[data-editor-kind="component"] .cm-content')
168168
.first()
@@ -173,7 +173,7 @@ export const setComponentEditorSource = async (page: Page, source: string) => {
173173
}
174174

175175
export const setStylesEditorSource = async (page: Page, source: string) => {
176-
await page.getByRole('tab', { name: 'Open tab app.css' }).click()
176+
await page.getByRole('button', { name: 'Open tab app.css' }).click()
177177
const editorContent = page
178178
.locator('.editor-panel[data-editor-kind="styles"] .cm-content')
179179
.first()
@@ -227,9 +227,9 @@ export const ensurePanelToolsVisible = async (
227227
panelName: 'component' | 'styles',
228228
) => {
229229
if (panelName === 'styles') {
230-
await page.getByRole('tab', { name: 'Open tab app.css' }).click()
230+
await page.getByRole('button', { name: 'Open tab app.css' }).click()
231231
} else {
232-
await page.getByRole('tab', { name: 'Open tab App.tsx' }).click()
232+
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
233233
}
234234

235235
const button = getToolsButton(page, panelName)

playwright/layout-panels.spec.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,38 @@ test('supports theme toggles', async ({ page }) => {
2929
expect(previewBackgroundColor).toBe('rgb(36, 86, 168)')
3030
})
3131

32+
test('light theme defaults preview background to white', async ({ page }) => {
33+
await waitForInitialRender(page)
34+
35+
await page.getByLabel('Use light theme').click()
36+
37+
const previewBackgroundColor = await page.evaluate(() => {
38+
const previewHost = document.getElementById('preview-host')
39+
return previewHost ? getComputedStyle(previewHost).backgroundColor : ''
40+
})
41+
42+
expect(previewBackgroundColor).toBe('rgb(255, 255, 255)')
43+
})
44+
45+
test('dark theme defaults preview background to editor background', async ({ page }) => {
46+
await waitForInitialRender(page)
47+
48+
const colors = await page.evaluate(() => {
49+
const previewHost = document.getElementById('preview-host')
50+
const componentPanel = document.getElementById('editor-panel-component')
51+
52+
return {
53+
preview: previewHost ? getComputedStyle(previewHost).backgroundColor : '',
54+
editor: componentPanel ? getComputedStyle(componentPanel).backgroundColor : '',
55+
}
56+
})
57+
58+
const toRgbChannels = (value: string) =>
59+
(value.match(/\d+/g) ?? []).slice(0, 3).map(entry => Number.parseInt(entry, 10))
60+
61+
expect(toRgbChannels(colors.preview)).toEqual(toRgbChannels(colors.editor))
62+
})
63+
3264
test('fixed layout keeps preview panel height within editor stack height', async ({
3365
page,
3466
}) => {
@@ -122,7 +154,7 @@ test('prevents collapsing all three panels at once', async ({ page }) => {
122154
const stylesPanel = page.locator('#editor-panel-styles')
123155

124156
await getCollapseButton(page, 'component').click()
125-
await page.getByRole('tab', { name: 'Open tab app.css' }).click()
157+
await page.getByRole('button', { name: 'Open tab app.css' }).click()
126158
await getCollapseButton(page, 'styles').click()
127159

128160
await expect(componentPanel).toHaveClass(/panel--collapsed-vertical/)
@@ -139,7 +171,7 @@ test('prevents collapsing all three panels at once', async ({ page }) => {
139171
'At least one panel must remain expanded.',
140172
)
141173

142-
await page.getByRole('tab', { name: 'Open tab App.tsx' }).click()
174+
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
143175
await getCollapseButton(page, 'component').click()
144176
await expectCollapseButtonState(page, 'preview', {
145177
axis: 'horizontal',
@@ -199,7 +231,7 @@ test('gear tools toggles default inactive and switch active/inactive per panel',
199231
await expect(componentTools).toHaveAttribute('aria-pressed', 'false')
200232
await expect(componentTools).toHaveAttribute('title', 'Show component tools')
201233

202-
await page.getByRole('tab', { name: 'Open tab app.css' }).click()
234+
await page.getByRole('button', { name: 'Open tab app.css' }).click()
203235
await stylesTools.click()
204236
await expect(stylesPanel).not.toHaveClass(/panel--tools-hidden/)
205237
await expect(stylesTools).toHaveAttribute('aria-pressed', 'true')
@@ -213,7 +245,7 @@ test('fixed layout keeps inactive editor panel hidden', async ({ page }) => {
213245
const stylesPanel = page.locator('#editor-panel-styles')
214246

215247
const assertEntryPanelVisible = async () => {
216-
await page.getByRole('tab', { name: 'Open tab App.tsx' }).click()
248+
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
217249
await expect(componentPanel).toBeVisible()
218250
await expect(stylesPanel).toBeHidden()
219251
}

playwright/rendering-modes.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ test('renders in react mode with css modules', async ({ page }) => {
3939
await ensurePanelToolsVisible(page, 'component')
4040
await ensurePanelToolsVisible(page, 'styles')
4141

42-
await page.getByRole('tab', { name: 'Open tab App.tsx' }).click()
42+
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
4343
await page.getByRole('combobox', { name: 'Render mode' }).selectOption('react')
44-
await page.getByRole('tab', { name: 'Open tab app.css' }).click()
44+
await page.getByRole('button', { name: 'Open tab app.css' }).click()
4545
await page.getByRole('combobox', { name: 'Style mode' }).selectOption('module')
4646
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
4747
await expectPreviewHasRenderedContent(page)
@@ -416,13 +416,13 @@ test('requires render button when auto render is disabled', async ({ page }) =>
416416
const autoRenderToggle = page.getByLabel('Auto render')
417417
const renderButton = page.getByRole('button', { name: 'Render' })
418418

419-
await page.getByRole('tab', { name: 'Open tab App.tsx' }).click()
419+
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
420420
await autoRenderToggle.uncheck()
421421
await expect(renderButton).toBeVisible()
422422

423-
await page.getByRole('tab', { name: 'Open tab app.css' }).click()
423+
await page.getByRole('button', { name: 'Open tab app.css' }).click()
424424
await page.getByRole('combobox', { name: 'Style mode' }).selectOption('module')
425-
await page.getByRole('tab', { name: 'Open tab App.tsx' }).click()
425+
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
426426

427427
await renderButton.click()
428428
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')

playwright/workspace-tabs.spec.ts

Lines changed: 63 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,18 @@ test('removing active tab selects deterministic adjacent tab', async ({ page })
3939
await addWorkspaceTab(page)
4040
await addWorkspaceTab(page)
4141

42-
await page.getByRole('tab', { name: 'Open tab module-2.tsx' }).click()
43-
await expect(page.getByRole('tab', { name: 'Open tab module-2.tsx' })).toHaveAttribute(
44-
'aria-selected',
45-
'true',
46-
)
42+
await page.getByRole('button', { name: 'Open tab module-2.tsx' }).click()
43+
await expect(
44+
page.getByRole('button', { name: 'Open tab module-2.tsx' }),
45+
).toHaveAttribute('aria-current', 'true')
4746

4847
await page.getByRole('button', { name: 'Remove tab module-2.tsx' }).click()
4948
await confirmRemoveDialog(page)
5049

51-
await expect(page.getByRole('tab', { name: 'Open tab module-2.tsx' })).toHaveCount(0)
52-
await expect(page.getByRole('tab', { name: 'Open tab module-3.tsx' })).toHaveAttribute(
53-
'aria-selected',
54-
'true',
55-
)
50+
await expect(page.getByRole('button', { name: 'Open tab module-2.tsx' })).toHaveCount(0)
51+
await expect(
52+
page.getByRole('button', { name: 'Open tab module-3.tsx' }),
53+
).toHaveAttribute('aria-current', 'true')
5654
})
5755

5856
test('removing non-active tab does not change active tab', async ({ page }) => {
@@ -62,20 +60,18 @@ test('removing non-active tab does not change active tab', async ({ page }) => {
6260
await addWorkspaceTab(page)
6361
await addWorkspaceTab(page)
6462

65-
await page.getByRole('tab', { name: 'Open tab module-3.tsx' }).click()
66-
await expect(page.getByRole('tab', { name: 'Open tab module-3.tsx' })).toHaveAttribute(
67-
'aria-selected',
68-
'true',
69-
)
63+
await page.getByRole('button', { name: 'Open tab module-3.tsx' }).click()
64+
await expect(
65+
page.getByRole('button', { name: 'Open tab module-3.tsx' }),
66+
).toHaveAttribute('aria-current', 'true')
7067

7168
await page.getByRole('button', { name: 'Remove tab module-2.tsx' }).click()
7269
await confirmRemoveDialog(page)
7370

74-
await expect(page.getByRole('tab', { name: 'Open tab module-2.tsx' })).toHaveCount(0)
75-
await expect(page.getByRole('tab', { name: 'Open tab module-3.tsx' })).toHaveAttribute(
76-
'aria-selected',
77-
'true',
78-
)
71+
await expect(page.getByRole('button', { name: 'Open tab module-2.tsx' })).toHaveCount(0)
72+
await expect(
73+
page.getByRole('button', { name: 'Open tab module-3.tsx' }),
74+
).toHaveAttribute('aria-current', 'true')
7975
})
8076

8177
test('renaming module tab keeps name and path synchronized', async ({ page }) => {
@@ -87,9 +83,9 @@ test('renaming module tab keeps name and path synchronized', async ({ page }) =>
8783
to: 'card-item.tsx',
8884
})
8985

90-
const tab = page.getByRole('tab', { name: 'Open tab card-item.tsx' })
86+
const tab = page.getByRole('button', { name: 'Open tab card-item.tsx' })
9187
await expect(tab).toHaveAttribute('title', 'src/components/card-item.tsx')
92-
await expect(page.getByRole('tab', { name: 'Open tab module.tsx' })).toHaveCount(0)
88+
await expect(page.getByRole('button', { name: 'Open tab module.tsx' })).toHaveCount(0)
9389
})
9490

9591
test('renaming module tab preserves source content', async ({ page }) => {
@@ -107,8 +103,8 @@ test('renaming module tab preserves source content', async ({ page }) => {
107103
to: 'value-card.tsx',
108104
})
109105

110-
await page.getByRole('tab', { name: 'Open tab App.tsx' }).click()
111-
await page.getByRole('tab', { name: 'Open tab value-card.tsx' }).click()
106+
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
107+
await page.getByRole('button', { name: 'Open tab value-card.tsx' }).click()
112108

113109
const editorContent = page
114110
.locator('.editor-panel[data-editor-kind="component"] .cm-content')
@@ -125,27 +121,26 @@ test('active tab remains source of truth for visible editor panel', async ({ pag
125121
const componentPanel = page.locator('#editor-panel-component')
126122
const stylesPanel = page.locator('#editor-panel-styles')
127123

128-
await page.getByRole('tab', { name: 'Open tab app.css' }).click()
129-
await expect(page.getByRole('tab', { name: 'Open tab app.css' })).toHaveAttribute(
130-
'aria-selected',
124+
await page.getByRole('button', { name: 'Open tab app.css' }).click()
125+
await expect(page.getByRole('button', { name: 'Open tab app.css' })).toHaveAttribute(
126+
'aria-current',
131127
'true',
132128
)
133129
await expect(stylesPanel).not.toHaveAttribute('hidden', '')
134130
await expect(componentPanel).toHaveAttribute('hidden', '')
135131

136-
await page.getByRole('tab', { name: 'Open tab module-2.tsx' }).click()
137-
await expect(page.getByRole('tab', { name: 'Open tab module-2.tsx' })).toHaveAttribute(
138-
'aria-selected',
139-
'true',
140-
)
132+
await page.getByRole('button', { name: 'Open tab module-2.tsx' }).click()
133+
await expect(
134+
page.getByRole('button', { name: 'Open tab module-2.tsx' }),
135+
).toHaveAttribute('aria-current', 'true')
141136
await expect(componentPanel).not.toHaveAttribute('hidden', '')
142137
await expect(stylesPanel).toHaveAttribute('hidden', '')
143138

144139
await page.locator('#collapse-component').click()
145-
await page.getByRole('tab', { name: 'Open tab app.css' }).click()
140+
await page.getByRole('button', { name: 'Open tab app.css' }).click()
146141

147-
await expect(page.getByRole('tab', { name: 'Open tab app.css' })).toHaveAttribute(
148-
'aria-selected',
142+
await expect(page.getByRole('button', { name: 'Open tab app.css' })).toHaveAttribute(
143+
'aria-current',
149144
'true',
150145
)
151146
await expect(stylesPanel).not.toHaveAttribute('hidden', '')
@@ -158,31 +153,29 @@ test('startup restores last active workspace tab after reload', async ({ page })
158153
await addWorkspaceTab(page)
159154
await addWorkspaceTab(page)
160155

161-
await page.getByRole('tab', { name: 'Open tab module-2.tsx' }).click()
162-
await expect(page.getByRole('tab', { name: 'Open tab module-2.tsx' })).toHaveAttribute(
163-
'aria-selected',
164-
'true',
165-
)
156+
await page.getByRole('button', { name: 'Open tab module-2.tsx' }).click()
157+
await expect(
158+
page.getByRole('button', { name: 'Open tab module-2.tsx' }),
159+
).toHaveAttribute('aria-current', 'true')
166160

167161
await page.reload()
168162
await waitForInitialRender(page)
169163

170-
await expect(page.getByRole('tab', { name: 'Open tab module-2.tsx' })).toHaveAttribute(
171-
'aria-selected',
172-
'true',
173-
)
164+
await expect(
165+
page.getByRole('button', { name: 'Open tab module-2.tsx' }),
166+
).toHaveAttribute('aria-current', 'true')
174167
await expect(page.locator('#editor-panel-component')).not.toHaveAttribute('hidden', '')
175168
await expect(page.locator('#editor-panel-styles')).toHaveAttribute('hidden', '')
176169
})
177170

178171
test('add menu can create styles tab while component tab is active', async ({ page }) => {
179172
await waitForInitialRender(page)
180173

181-
await page.getByRole('tab', { name: 'Open tab App.tsx' }).click()
174+
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
182175
await addWorkspaceTab(page, { kind: 'styles' })
183176

184-
await expect(page.getByRole('tab', { name: 'Open tab module.css' })).toHaveAttribute(
185-
'aria-selected',
177+
await expect(page.getByRole('button', { name: 'Open tab module.css' })).toHaveAttribute(
178+
'aria-current',
186179
'true',
187180
)
188181
await expect(page.locator('#editor-panel-styles')).not.toHaveAttribute('hidden', '')
@@ -197,8 +190,8 @@ test('add menu stays closed until triggered and closes on outside click', async
197190
}) => {
198191
await waitForInitialRender(page)
199192

200-
const addButton = page.getByRole('button', { name: 'Add tab options' })
201-
const addMenu = page.getByRole('menu', { name: 'Add tab type' })
193+
const addButton = page.getByRole('button', { name: 'Add workspace tab' })
194+
const addMenu = page.getByRole('group', { name: 'Add workspace tab' })
202195

203196
await expect(addMenu).toBeHidden()
204197
await addButton.click()
@@ -207,3 +200,24 @@ test('add menu stays closed until triggered and closes on outside click', async
207200
await page.getByRole('status', { name: 'App status' }).click()
208201
await expect(addMenu).toBeHidden()
209202
})
203+
204+
test('add menu keyboard interaction manages focus on open and escape close', async ({
205+
page,
206+
}) => {
207+
await waitForInitialRender(page)
208+
209+
const addButton = page.getByRole('button', { name: 'Add workspace tab' })
210+
const addMenu = page.getByRole('group', { name: 'Add workspace tab' })
211+
const addModuleButton = page.getByRole('button', { name: 'Add module tab' })
212+
213+
await addButton.focus()
214+
await page.keyboard.press('ArrowDown')
215+
216+
await expect(addMenu).toBeVisible()
217+
await expect(addModuleButton).toBeFocused()
218+
219+
await page.keyboard.press('Escape')
220+
221+
await expect(addMenu).toBeHidden()
222+
await expect(addButton).toBeFocused()
223+
})

0 commit comments

Comments
 (0)