mirror of
https://github.com/supabase/supabase.git
synced 2026-10-12 12:55:06 +03:00
## What kind of change does this PR introduce? Accessibility / lint hardening (Safari keyboard focus). ## What is the current behavior? `supabase/require-explicit-tabindex` is `'warn'`. Studio’s ratchet was at 0 but the rule was still ratcheted; www / docs / design-system still had raw `<button>` / `role="button"` call sites without an explicit `tabIndex`. [DEPR-627](https://linear.app/supabase/issue/DEPR-627) · follow-up to #47984 / #48040 ## What is the new behavior? - Shared config: `'supabase/require-explicit-tabindex': 'error'` - Swept www / docs / design-system (+ Studio test fixtures the ratchet skipped) - Removed the rule from the Studio ratchet + baselines ## To test Prefer **Safari**. This PR only adds explicit `tabIndex` to raw `<button>` / `role="button"` call sites — not links, and not controls that already go through `Button` from `ui`. ### Marketing (`www`) ([staging link](https://zone-www-dot-com-git-danny-depr-627-promote-req-7ae43c-supabase.vercel.app/)) - [x] Homepage frameworks / dashboard feature tabs — Tab through each tab button - [x] Product pages (e.g. `/auth`, `/database`) — section tab switchers - [x] Narrow viewport — open the hamburger; Tab through menu buttons - [x] `/partners/catalog` — filter / view controls - [x] Blog view toggle (list ↔ grid) ### Docs ([staging link](https://docs-git-danny-depr-627-promote-require-explici-25e46d-supabase.vercel.app/)) - [x] **Desktop (≥ lg):** top-right **⋯ menu** (hamburger icon) — opens a dropdown that includes Theme. Not a separate theme button. - [x] **Mobile (< lg):** top-right **hamburger** opens the sheet; close (X) is the raw button we tagged. Theme inside the sheet uses `ThemeToggle` / `DropdownMenuTrigger` from `ui` (already supposed to set `tabIndex`). - [x] **Code blocks** — copy / language controls - [x] **Is this helpful?** — X / check are `Button` from `ui` (should already Tab). After voting **while signed in**, the follow-up “What went well?” / “How can we improve?” text button is the raw one we tagged. - [x] **AI Tools → Copy as Markdown** (right rail on a guide) — this is the only GuidesSidebar control this PR changed. “On this page” TOC items are **links**, not covered by this lint. - [x] **Reference docs** (e.g. JS client reference) — section headers that expand/collapse in the left nav (`Collapsible.Trigger`) - [x] **Troubleshooting index** — type in the search field, then Tab to the **clear (X)** control ### Dashboard (`studio`) No production UI changes in this PR (tests + lint config only). Quick Safari smoke that prior tabindex work still holds: - [x] Project sidebar — Tab through primary nav links - [x] Settings → General — Tab through inputs / buttons - [x] Storage → Files — Tab a bucket row / file actions
149 lines
4.7 KiB
TypeScript
149 lines
4.7 KiB
TypeScript
import { render, screen, waitFor } from '@testing-library/react'
|
||
import userEvent from '@testing-library/user-event'
|
||
import type { ComponentProps } from 'react'
|
||
import { describe, expect, it, vi } from 'vitest'
|
||
|
||
import { PLATFORM_WEBHOOKS_MOCK_DATA } from './PlatformWebhooks.mock'
|
||
import { PlatformWebhooksEndpointDetails } from './PlatformWebhooksEndpointDetails'
|
||
|
||
vi.mock('@/components/ui/DataTable/DataTableColumn/DataTableColumnStatusCode', () => ({
|
||
DataTableColumnStatusCode: ({ value }: { value: number }) => <span>{value}</span>,
|
||
}))
|
||
|
||
vi.mock('@/components/ui/ShortcutTooltip', () => ({
|
||
ShortcutTooltip: ({ children }: { children: unknown }) => <>{children}</>,
|
||
}))
|
||
|
||
vi.mock('@/state/shortcuts/useShortcut', () => ({
|
||
useShortcut: vi.fn(),
|
||
}))
|
||
|
||
vi.mock('@/components/ui/ButtonTooltip', () => ({
|
||
ButtonTooltip: ({
|
||
icon,
|
||
children,
|
||
size: _size,
|
||
tooltip: _tooltip,
|
||
type: _type,
|
||
...props
|
||
}: any) => (
|
||
<button tabIndex={0} type="button" {...props}>
|
||
{icon}
|
||
{children}
|
||
</button>
|
||
),
|
||
}))
|
||
|
||
vi.mock('ui-patterns/TimestampInfo', async () => {
|
||
return {
|
||
TimestampInfo: ({ utcTimestamp, className }: { utcTimestamp: string; className?: string }) => (
|
||
<span className={className}>{utcTimestamp}</span>
|
||
),
|
||
}
|
||
})
|
||
|
||
describe('PlatformWebhooksEndpointDetails', () => {
|
||
const selectedEndpoint = PLATFORM_WEBHOOKS_MOCK_DATA.organization.endpoints[0]
|
||
const allDeliveries = PLATFORM_WEBHOOKS_MOCK_DATA.organization.deliveries.filter(
|
||
(delivery) => delivery.endpointId === selectedEndpoint.id
|
||
)
|
||
|
||
const renderComponent = (
|
||
props?: Partial<ComponentProps<typeof PlatformWebhooksEndpointDetails>>
|
||
) =>
|
||
render(
|
||
<PlatformWebhooksEndpointDetails
|
||
deliverySearch=""
|
||
filteredDeliveries={allDeliveries}
|
||
selectedEndpoint={selectedEndpoint}
|
||
onCopyUrl={vi.fn()}
|
||
onDeliverySearchChange={vi.fn()}
|
||
onOpenDelivery={vi.fn()}
|
||
onRetryDelivery={vi.fn()}
|
||
{...props}
|
||
/>
|
||
)
|
||
|
||
it('renders paginated deliveries with previous and next controls', async () => {
|
||
const user = userEvent.setup()
|
||
|
||
renderComponent()
|
||
|
||
expect(screen.getByText('Showing 1 to 5 of 12 deliveries')).toBeInTheDocument()
|
||
expect(screen.queryByText('organization.member_removed')).not.toBeInTheDocument()
|
||
|
||
await user.click(screen.getByLabelText('Next page'))
|
||
|
||
expect(screen.getByText('Showing 6 to 10 of 12 deliveries')).toBeInTheDocument()
|
||
expect(screen.getByText('organization.member_removed')).toBeInTheDocument()
|
||
|
||
await user.click(screen.getByLabelText('Previous page'))
|
||
|
||
expect(screen.getByText('Showing 1 to 5 of 12 deliveries')).toBeInTheDocument()
|
||
expect(screen.queryByText('organization.member_removed')).not.toBeInTheDocument()
|
||
})
|
||
|
||
it('sorts deliveries by event type from the header', async () => {
|
||
const user = userEvent.setup()
|
||
|
||
renderComponent()
|
||
|
||
await user.click(screen.getByRole('button', { name: 'Event type' }))
|
||
|
||
expect(screen.getAllByRole('row')[1]).toHaveTextContent('organization.member_invited')
|
||
|
||
await user.click(screen.getByRole('button', { name: 'Event type' }))
|
||
|
||
expect(screen.getAllByRole('row')[1]).toHaveTextContent('project.updated')
|
||
})
|
||
|
||
it('resets to the first page when the delivery search changes', async () => {
|
||
const user = userEvent.setup()
|
||
const projectDeliveries = allDeliveries.filter((delivery) =>
|
||
delivery.eventType.includes('project')
|
||
)
|
||
const { rerender } = renderComponent()
|
||
|
||
await user.click(screen.getByLabelText('Next page'))
|
||
expect(screen.getByText('Showing 6 to 10 of 12 deliveries')).toBeInTheDocument()
|
||
|
||
rerender(
|
||
<PlatformWebhooksEndpointDetails
|
||
deliverySearch="project"
|
||
filteredDeliveries={projectDeliveries}
|
||
selectedEndpoint={selectedEndpoint}
|
||
onCopyUrl={vi.fn()}
|
||
onDeliverySearchChange={vi.fn()}
|
||
onOpenDelivery={vi.fn()}
|
||
onRetryDelivery={vi.fn()}
|
||
/>
|
||
)
|
||
|
||
await waitFor(() => {
|
||
expect(screen.getByText('Showing 1 to 5 of 8 deliveries')).toBeInTheDocument()
|
||
})
|
||
})
|
||
|
||
it('retries a failed delivery without opening the delivery row', async () => {
|
||
const user = userEvent.setup()
|
||
const onOpenDelivery = vi.fn()
|
||
const onRetryDelivery = vi.fn()
|
||
|
||
renderComponent({ onOpenDelivery, onRetryDelivery })
|
||
|
||
await user.click(screen.getByLabelText('Retry org-delivery-2'))
|
||
|
||
expect(onRetryDelivery).toHaveBeenCalledWith('org-delivery-2')
|
||
expect(onOpenDelivery).not.toHaveBeenCalled()
|
||
})
|
||
|
||
it('renders a zero response code instead of the placeholder', () => {
|
||
renderComponent({
|
||
filteredDeliveries: [{ ...allDeliveries[0], id: 'org-delivery-zero', responseCode: 0 }],
|
||
})
|
||
|
||
expect(screen.getByText('0')).toBeInTheDocument()
|
||
expect(screen.queryByText('–')).not.toBeInTheDocument()
|
||
})
|
||
})
|