85 lines
2.5 KiB
TypeScript
85 lines
2.5 KiB
TypeScript
import { cleanup, render, screen, within } from '@testing-library/react';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import SpoonsPage from '../../src/app/(app)/spoons/page';
|
|
|
|
const { mockUseQuery, mockPush } = vi.hoisted(() => ({
|
|
mockUseQuery: vi.fn(),
|
|
mockPush: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('convex/react', () => ({
|
|
useConvexAuth: () => ({ isAuthenticated: false }),
|
|
useMutation: vi.fn(),
|
|
useQuery: mockUseQuery,
|
|
}));
|
|
|
|
vi.mock('next/navigation', () => ({
|
|
useParams: vi.fn(),
|
|
useRouter: () => ({ push: mockPush, replace: vi.fn() }),
|
|
useSearchParams: () => new URLSearchParams(),
|
|
}));
|
|
|
|
const spoon = {
|
|
_id: 'spoon-1',
|
|
name: 'useSend',
|
|
upstreamOwner: 'acme',
|
|
upstreamRepo: 'app',
|
|
forkOwner: 'me',
|
|
forkRepo: 'app',
|
|
status: 'active',
|
|
syncStatus: undefined,
|
|
syncCadence: 'daily',
|
|
effectiveUpstreamAheadBy: 0,
|
|
rawUpstreamAheadBy: 0,
|
|
forkAheadBy: 0,
|
|
ignoredUpstreamCount: 0,
|
|
lastCheckedAt: undefined,
|
|
};
|
|
|
|
describe('spoon list accessibility', () => {
|
|
afterEach(() => {
|
|
cleanup();
|
|
mockUseQuery.mockReset();
|
|
mockPush.mockReset();
|
|
});
|
|
|
|
it('does not nest interactive elements inside a role="link" container', () => {
|
|
mockUseQuery.mockImplementation((_query, args) => {
|
|
// threads list passes a limit; spoons list passes {}.
|
|
if (args && 'limit' in args) return [];
|
|
return [spoon];
|
|
});
|
|
|
|
const { container } = render(<SpoonsPage />);
|
|
|
|
// Invalid ARIA nested-interactive pattern must be gone.
|
|
expect(container.querySelector('[role="link"]')).toBeNull();
|
|
});
|
|
|
|
it('keeps a primary navigation link and a secondary "Open" link to the spoon detail', () => {
|
|
mockUseQuery.mockImplementation((_query, args) => {
|
|
if (args && 'limit' in args) return [];
|
|
return [spoon];
|
|
});
|
|
|
|
render(<SpoonsPage />);
|
|
|
|
const href = '/spoons/spoon-1';
|
|
|
|
// Primary link: the spoon name navigates to the detail route.
|
|
const nameLink = screen.getByRole('link', { name: /useSend/ });
|
|
expect(nameLink).toHaveAttribute('href', href);
|
|
|
|
// Secondary action: the "Open" link still exists and points to the detail.
|
|
const openLink = screen.getByRole('link', { name: /open/i });
|
|
expect(openLink).toHaveAttribute('href', href);
|
|
|
|
// Both real anchors point at the destination; no simulated role="link".
|
|
const links = screen.getAllByRole('link');
|
|
for (const link of links.filter((el) => within(el).queryByText('useSend'))) {
|
|
expect(link).toHaveAttribute('href', href);
|
|
}
|
|
});
|
|
});
|