Files
supabase/apps/studio/components/interfaces/Platform/Webhooks/PlatformWebhooksEndpointDetails.test.tsx
Danny White 6f6badae51 fix(eslint): promote require-explicit-tabindex to error (#48170)
## 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
2026-07-23 05:21:15 +10:00

149 lines
4.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
})
})