-
-
Notifications
You must be signed in to change notification settings - Fork 213
Fix/keyboard accessibility #769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3cfbb7e
3cff745
a62d846
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| import { render, screen, fireEvent } from "@testing-library/react"; | ||
| import "@testing-library/jest-dom"; | ||
| import PlaygroundSidebar from "../../components/PlaygroundSidebar"; | ||
| import { vi } from "vitest"; | ||
|
|
||
| // Mock the store | ||
| const mockSetEditorsVisible = vi.fn(); | ||
| const mockSetPreviewVisible = vi.fn(); | ||
| const mockSetProblemPanelVisible = vi.fn(); | ||
| const mockSetAIChatOpen = vi.fn(); | ||
| const mockSetSettingsOpen = vi.fn(); | ||
|
|
||
| vi.mock("../../store/store", () => ({ | ||
| default: () => ({ | ||
| isEditorsVisible: true, | ||
| isPreviewVisible: true, | ||
| isProblemPanelVisible: false, | ||
| isAIChatOpen: false, | ||
| setEditorsVisible: mockSetEditorsVisible, | ||
| setPreviewVisible: mockSetPreviewVisible, | ||
| setProblemPanelVisible: mockSetProblemPanelVisible, | ||
| setAIChatOpen: mockSetAIChatOpen, | ||
| setSettingsOpen: mockSetSettingsOpen, | ||
| generateShareableLink: vi.fn(() => "https://example.com"), | ||
| textColor: "#000000", | ||
| backgroundColor: "#ffffff", | ||
| }), | ||
| })); | ||
|
|
||
| vi.mock("../../components/Tour", () => ({ | ||
| default: { start: vi.fn() }, | ||
| })); | ||
|
|
||
| vi.mock("../../components/FullScreenModal", () => ({ | ||
| default: () => <div data-testid="fullscreen-modal">FullScreen</div>, | ||
| })); | ||
|
|
||
| vi.mock("../../components/SettingsModal", () => ({ | ||
| default: () => <div data-testid="settings-modal">Settings</div>, | ||
| })); | ||
|
|
||
| describe("Keyboard Accessibility", () => { | ||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| describe("PlaygroundSidebar - Top Nav", () => { | ||
| it("activates Editor button on Enter key", () => { | ||
| render(<PlaygroundSidebar />); | ||
| const editorButton = screen.getByRole("button", { name: /Editor/i }); | ||
| fireEvent.keyDown(editorButton, { key: "Enter" }); | ||
| expect(mockSetEditorsVisible).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("activates Editor button on Space key", () => { | ||
| render(<PlaygroundSidebar />); | ||
| const editorButton = screen.getByRole("button", { name: /Editor/i }); | ||
| fireEvent.keyDown(editorButton, { key: " " }); | ||
| expect(mockSetEditorsVisible).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("does not activate Editor button on other keys", () => { | ||
| render(<PlaygroundSidebar />); | ||
| const editorButton = screen.getByRole("button", { name: /Editor/i }); | ||
| fireEvent.keyDown(editorButton, { key: "Tab" }); | ||
| expect(mockSetEditorsVisible).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("activates Preview button on Enter key", () => { | ||
| render(<PlaygroundSidebar />); | ||
| const previewButton = screen.getByRole("button", { name: /Preview/i }); | ||
| fireEvent.keyDown(previewButton, { key: "Enter" }); | ||
| expect(mockSetPreviewVisible).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("activates Problems button on Space key", () => { | ||
| render(<PlaygroundSidebar />); | ||
| const problemsButton = screen.getByRole("button", { name: /Problems/i }); | ||
| fireEvent.keyDown(problemsButton, { key: " " }); | ||
| expect(mockSetProblemPanelVisible).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("activates AI Assistant button on Enter key", () => { | ||
| render(<PlaygroundSidebar />); | ||
| const aiButton = screen.getByRole("button", { name: /AI Assistant/i }); | ||
| fireEvent.keyDown(aiButton, { key: "Enter" }); | ||
| expect(mockSetAIChatOpen).toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| describe("PlaygroundSidebar - Bottom Nav", () => { | ||
| it("activates Share button on Enter key", () => { | ||
| render(<PlaygroundSidebar />); | ||
| const shareButton = screen.getByRole("button", { name: /Share/i }); | ||
| fireEvent.keyDown(shareButton, { key: "Enter" }); | ||
| // Share calls handleShare which is async, just verify no error | ||
| expect(shareButton).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("activates Settings button on Space key", () => { | ||
| render(<PlaygroundSidebar />); | ||
| const settingsButton = screen.getByRole("button", { name: /Settings/i }); | ||
| fireEvent.keyDown(settingsButton, { key: " " }); | ||
| expect(mockSetSettingsOpen).toHaveBeenCalledWith(true); | ||
| }); | ||
|
|
||
| it("activates Start Tour button on Enter key", () => { | ||
| render(<PlaygroundSidebar />); | ||
| const tourButton = screen.getByRole("button", { name: /Start Tour/i }); | ||
| fireEvent.keyDown(tourButton, { key: "Enter" }); | ||
| expect(tourButton).toBeInTheDocument(); | ||
| }); | ||
|
Comment on lines
+92
to
+112
|
||
| }); | ||
|
|
||
| describe("PlaygroundSidebar - focusability", () => { | ||
| it("all nav items have tabIndex=0 for keyboard focus", () => { | ||
| render(<PlaygroundSidebar />); | ||
| const buttons = screen.getAllByRole("button"); | ||
| buttons.forEach((button) => { | ||
| expect(button).toHaveAttribute("tabindex", "0"); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
|
Comment on lines
+42
to
+124
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
FullScreenModal'sMdFullscreenicon now hasrole="button",tabIndex={0}, andaria-labelattributes, but it is rendered inside the "Fullscreen" nav item container inPlaygroundSidebar, which also hasrole="button"andtabIndex={0}. This creates a nested interactive elements structure — arole="button"element inside anotherrole="button"— which is invalid per the ARIA spec (interactive controls cannot be nested inside each other). Screen readers and assistive technologies may handle this unpredictably.