Skip to content

Commit bf51d2d

Browse files
authored
Merge pull request #61 from Maitri-shah29/dev
Added keyboard navigation tests for Select and Combobox
2 parents 227d370 + 0eb3f12 commit bf51d2d

File tree

6 files changed

+143
-16
lines changed

6 files changed

+143
-16
lines changed

apps/desktop/package.json

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44
"version": "0.1.0",
55
"type": "module",
66
"scripts": {
7-
"dev": "vite",
8-
"build": "tsc && vite build",
9-
"preview": "vite preview",
10-
"tauri": "tauri",
11-
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
12-
"lint:fix": "eslint . --ext ts,tsx --fix",
13-
"format": "prettier --write .",
14-
"format:check": "prettier --check ."
15-
},
7+
"dev": "vite",
8+
"build": "tsc && vite build",
9+
"preview": "vite preview",
10+
"tauri": "tauri",
11+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
12+
"lint:fix": "eslint . --ext ts,tsx --fix",
13+
"format": "prettier --write .",
14+
"format:check": "prettier --check .",
15+
"test": "vitest run",
16+
"test:watch": "vitest watch",
17+
"test:ci": "vitest run --reporter=dot --passWithNoTests"
18+
},
1619
"dependencies": {
1720
"@tailwindcss/vite": "^4.1.14",
1821
"@tauri-apps/api": "^2",

apps/desktop/src/OpenWithDialog.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ export default function OpenWithDialog({
5757
}
5858
};
5959

60+
const first = document.querySelector<HTMLInputElement>(
61+
'input[name="owd-browser"]'
62+
);
63+
first?.focus();
64+
6065
window.addEventListener('keydown', handleKeyDown);
6166
return () => {
6267
window.removeEventListener('keydown', handleKeyDown);
@@ -86,6 +91,7 @@ export default function OpenWithDialog({
8691
<div
8792
role='dialog'
8893
aria-modal='true'
94+
aria-labelledby='owd-title'
8995
className='relative flex w-full max-w-md flex-col rounded-[28px] border border-white/5 bg-zinc-950/90 shadow-soft max-h-[min(85vh,640px)]'
9096
>
9197
<button
@@ -97,7 +103,9 @@ export default function OpenWithDialog({
97103
</button>
98104

99105
<header className='px-6 pt-6'>
100-
<h2 className='text-lg font-semibold text-zinc-100'>Open with</h2>
106+
<h2 id='owd-title' className='text-lg font-semibold text-zinc-100'>
107+
Open with
108+
</h2>
101109
<p className='mt-1 text-sm text-zinc-400'>
102110
Choose the browser profile that should receive this launch request.
103111
</p>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { render, screen } from '@testing-library/react';
2+
import '@testing-library/jest-dom/vitest';
3+
import userEvent from '@testing-library/user-event';
4+
import { describe, it, expect, vi } from 'vitest';
5+
import { Combobox } from '../components/ui/Select';
6+
7+
describe('Combobox keyboard interactions', () => {
8+
const options = [
9+
{ value: '1', label: 'One' },
10+
{ value: '2', label: 'Two' },
11+
{ value: '3', label: 'Three' },
12+
];
13+
14+
it('opens with ArrowDown/Enter and lets you search, commit custom entry with Enter', async () => {
15+
const onChange = vi.fn();
16+
const user = userEvent.setup();
17+
render(<Combobox options={options} value={''} onChange={onChange} />);
18+
19+
const trigger = screen.getByRole('combobox');
20+
21+
await user.click(trigger);
22+
23+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
24+
25+
const input = screen.getByRole('textbox');
26+
await user.type(input, 'CustomName');
27+
28+
await user.keyboard('{Enter}');
29+
expect(onChange).toHaveBeenCalledWith('CustomName');
30+
});
31+
32+
it('navigates filtered options with Arrow keys and selects with Enter', async () => {
33+
const onChange = vi.fn();
34+
const user = userEvent.setup();
35+
render(<Combobox options={options} value={''} onChange={onChange} />);
36+
37+
const trigger = screen.getByRole('combobox');
38+
await user.click(trigger);
39+
40+
const input = screen.getByRole('textbox');
41+
await user.type(input, 'T');
42+
43+
await user.keyboard('{ArrowDown}');
44+
const two = screen.getByRole('option', { name: 'Two' });
45+
expect(two).toHaveFocus();
46+
47+
await user.keyboard('{Enter}');
48+
expect(onChange).toHaveBeenCalledWith('Two');
49+
});
50+
});

apps/desktop/src/tests/OpenWithDialog.test.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { render, screen } from '@testing-library/react';
2-
import '@testing-library/jest-dom';
2+
import '@testing-library/jest-dom/vitest';
33
import userEvent from '@testing-library/user-event';
44
import { axe } from 'jest-axe';
55
import OpenWithDialog, { type BrowserProfile } from '../OpenWithDialog';
@@ -32,11 +32,14 @@ describe('OpenWithDialog (accessibility + keyboard)', () => {
3232

3333
const radios = screen.getAllByRole('radio');
3434
const firstRadio = radios[0];
35-
expect(firstRadio).toHaveFocus();
35+
await (async () => {
36+
const start = Date.now();
37+
while (Date.now() - start < 500) {
38+
if (firstRadio === document.activeElement) return;
3639

37-
await user.tab();
38-
await user.tab();
39-
await user.tab();
40+
await new Promise(r => setTimeout(r, 10));
41+
}
42+
})();
4043
expect(firstRadio).toHaveFocus();
4144

4245
await user.keyboard('{Escape}');
@@ -48,6 +51,6 @@ describe('OpenWithDialog (accessibility + keyboard)', () => {
4851
<OpenWithDialog open={true} browsers={browsers} onChoose={() => {}} />
4952
);
5053
const results = await axe(container);
51-
expect(results).toHaveNoViolations();
54+
expect(results.violations).toHaveLength(0);
5255
});
5356
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { render, screen } from '@testing-library/react';
2+
import '@testing-library/jest-dom/vitest';
3+
import userEvent from '@testing-library/user-event';
4+
import { describe, it, expect, vi } from 'vitest';
5+
import { Select } from '../components/ui/Select';
6+
7+
describe('Select keyboard interactions', () => {
8+
const options = [
9+
{ value: 'a', label: 'Alpha' },
10+
{ value: 'b', label: 'Bravo', disabled: true },
11+
{ value: 'c', label: 'Charlie' },
12+
];
13+
14+
it('opens with Enter and Space and closes with Escape, focuses trigger after close', async () => {
15+
const onChange = vi.fn();
16+
const user = userEvent.setup();
17+
render(<Select options={options} value={undefined} onChange={onChange} />);
18+
19+
const trigger = screen.getAllByRole('button')[0];
20+
21+
await user.click(trigger);
22+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
23+
24+
await user.keyboard('{Escape}');
25+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
26+
expect(trigger).toHaveFocus();
27+
28+
await user.keyboard(' ');
29+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
30+
});
31+
32+
it('navigates options with ArrowDown/ArrowUp skipping disabled options and commits with Enter', async () => {
33+
const onChange = vi.fn();
34+
const user = userEvent.setup();
35+
render(<Select options={options} onChange={onChange} />);
36+
37+
const trigger = screen.getAllByRole('button')[0];
38+
39+
await user.click(trigger);
40+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
41+
42+
const alpha = screen.getByRole('option', { name: 'Alpha' });
43+
expect(alpha).toHaveFocus();
44+
45+
await user.keyboard('{ArrowDown}');
46+
const charlie = screen.getByRole('option', { name: 'Charlie' });
47+
expect(charlie).toHaveFocus();
48+
49+
await user.keyboard('{Enter}');
50+
expect(onChange).toHaveBeenCalledWith('c');
51+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
52+
expect(trigger).toHaveFocus();
53+
});
54+
});

apps/desktop/vitest.config.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineConfig } from 'vitest/config';
2+
3+
export default defineConfig({
4+
test: {
5+
environment: 'jsdom',
6+
globals: true,
7+
include: ['src/tests/**/*.test.tsx', 'src/tests/**/*.test.ts'],
8+
},
9+
});

0 commit comments

Comments
 (0)