Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"tauri": "tauri",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext ts,tsx --fix",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"tauri": "tauri",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext ts,tsx --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"test": "vitest run",
"test:watch": "vitest watch",
"test:ci": "vitest run --reporter=dot --passWithNoTests"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.14",
"@tauri-apps/api": "^2",
Expand Down
10 changes: 9 additions & 1 deletion apps/desktop/src/OpenWithDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ export default function OpenWithDialog({
}
};

const first = document.querySelector<HTMLInputElement>(
'input[name="owd-browser"]'
);
first?.focus();

window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
Expand Down Expand Up @@ -86,6 +91,7 @@ export default function OpenWithDialog({
<div
role='dialog'
aria-modal='true'
aria-labelledby='owd-title'
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)]'
>
<button
Expand All @@ -97,7 +103,9 @@ export default function OpenWithDialog({
</button>

<header className='px-6 pt-6'>
<h2 className='text-lg font-semibold text-zinc-100'>Open with</h2>
<h2 id='owd-title' className='text-lg font-semibold text-zinc-100'>
Open with
</h2>
<p className='mt-1 text-sm text-zinc-400'>
Choose the browser profile that should receive this launch request.
</p>
Expand Down
50 changes: 50 additions & 0 deletions apps/desktop/src/tests/Combobox.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi } from 'vitest';
import { Combobox } from '../components/ui/Select';

describe('Combobox keyboard interactions', () => {
const options = [
{ value: '1', label: 'One' },
{ value: '2', label: 'Two' },
{ value: '3', label: 'Three' },
];

it('opens with ArrowDown/Enter and lets you search, commit custom entry with Enter', async () => {
const onChange = vi.fn();
const user = userEvent.setup();
render(<Combobox options={options} value={''} onChange={onChange} />);

const trigger = screen.getByRole('combobox');

await user.click(trigger);

expect(trigger).toHaveAttribute('aria-expanded', 'true');

const input = screen.getByRole('textbox');
await user.type(input, 'CustomName');

await user.keyboard('{Enter}');
expect(onChange).toHaveBeenCalledWith('CustomName');
});

it('navigates filtered options with Arrow keys and selects with Enter', async () => {
const onChange = vi.fn();
const user = userEvent.setup();
render(<Combobox options={options} value={''} onChange={onChange} />);

const trigger = screen.getByRole('combobox');
await user.click(trigger);

const input = screen.getByRole('textbox');
await user.type(input, 'T');

await user.keyboard('{ArrowDown}');
const two = screen.getByRole('option', { name: 'Two' });
expect(two).toHaveFocus();

await user.keyboard('{Enter}');
expect(onChange).toHaveBeenCalledWith('Two');
});
});
15 changes: 9 additions & 6 deletions apps/desktop/src/tests/OpenWithDialog.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import '@testing-library/jest-dom/vitest';
import userEvent from '@testing-library/user-event';
import { axe } from 'jest-axe';
import OpenWithDialog, { type BrowserProfile } from '../OpenWithDialog';
Expand Down Expand Up @@ -32,11 +32,14 @@ describe('OpenWithDialog (accessibility + keyboard)', () => {

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

await user.tab();
await user.tab();
await user.tab();
await new Promise(r => setTimeout(r, 10));
}
})();
expect(firstRadio).toHaveFocus();

await user.keyboard('{Escape}');
Expand All @@ -48,6 +51,6 @@ describe('OpenWithDialog (accessibility + keyboard)', () => {
<OpenWithDialog open={true} browsers={browsers} onChoose={() => {}} />
);
const results = await axe(container);
expect(results).toHaveNoViolations();
expect(results.violations).toHaveLength(0);
});
});
54 changes: 54 additions & 0 deletions apps/desktop/src/tests/Select.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi } from 'vitest';
import { Select } from '../components/ui/Select';

describe('Select keyboard interactions', () => {
const options = [
{ value: 'a', label: 'Alpha' },
{ value: 'b', label: 'Bravo', disabled: true },
{ value: 'c', label: 'Charlie' },
];

it('opens with Enter and Space and closes with Escape, focuses trigger after close', async () => {
const onChange = vi.fn();
const user = userEvent.setup();
render(<Select options={options} value={undefined} onChange={onChange} />);

const trigger = screen.getAllByRole('button')[0];

await user.click(trigger);
expect(trigger).toHaveAttribute('aria-expanded', 'true');

await user.keyboard('{Escape}');
expect(trigger).toHaveAttribute('aria-expanded', 'false');
expect(trigger).toHaveFocus();

await user.keyboard(' ');
expect(trigger).toHaveAttribute('aria-expanded', 'true');
});

it('navigates options with ArrowDown/ArrowUp skipping disabled options and commits with Enter', async () => {
const onChange = vi.fn();
const user = userEvent.setup();
render(<Select options={options} onChange={onChange} />);

const trigger = screen.getAllByRole('button')[0];

await user.click(trigger);
expect(trigger).toHaveAttribute('aria-expanded', 'true');

const alpha = screen.getByRole('option', { name: 'Alpha' });
expect(alpha).toHaveFocus();

await user.keyboard('{ArrowDown}');
const charlie = screen.getByRole('option', { name: 'Charlie' });
expect(charlie).toHaveFocus();

await user.keyboard('{Enter}');
expect(onChange).toHaveBeenCalledWith('c');
expect(trigger).toHaveAttribute('aria-expanded', 'false');
expect(trigger).toHaveFocus();
});
});
9 changes: 9 additions & 0 deletions apps/desktop/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
include: ['src/tests/**/*.test.tsx', 'src/tests/**/*.test.ts'],
},
});