// @vitest-environment jsdom /** * What the IconButton page claims, pinned: the label, the tooltip on hover, * and `disabledReason` in place of the tooltip (stage 2 of the migration). */ import { afterEach, describe, expect, it, vi } from 'vitest' import { cleanup, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { IconButton } from './IconButton' afterEach(cleanup) const icon = describe('IconButton', () => { it('is a native button named by its aria-label, type button', async () => { const user = userEvent.setup() const onClick = vi.fn() render( {icon} , ) const button = screen.getByRole('button', { name: 'Edit' }) expect(button.tagName).toBe('BUTTON') expect(button.getAttribute('type')).toBe('button') await user.click(button) expect(onClick).toHaveBeenCalledTimes(1) }) it('href: a link that looks like the button, with its tooltip', async () => { const user = userEvent.setup() render( {icon} , ) expect(screen.queryByRole('button')).toBeNull() const link = screen.getByRole('link', { name: 'Open' }) expect(link.getAttribute('href')).toBe('/documents/12') expect(link.className).toContain('rounded-lg') await user.hover(link) expect((await screen.findByRole('tooltip')).textContent).toBe('Open') }) it('href while it cannot be used: the button, since a link cannot be disabled', () => { render( {icon} , ) expect(screen.queryByRole('link')).toBeNull() expect(screen.getByRole('button', { name: 'Open' }).getAttribute('aria-disabled')).toBe('true') }) it('shows the tooltip on hover, with its shortcut', async () => { const user = userEvent.setup() render( {icon} , ) expect(screen.queryByRole('tooltip')).toBeNull() await user.hover(screen.getByRole('button', { name: 'Edit' })) // 300ms before it opens (D23) - it used to be instant. expect((await screen.findByRole('tooltip')).textContent).toBe('EditE') }) it('disabledReason: disabled, reachable by Tab, the reason replaces the tooltip', async () => { const user = userEvent.setup() const onClick = vi.fn() render( {icon} , ) const button = screen.getByRole('button', { name: 'Edit' }) expect(button.getAttribute('aria-disabled')).toBe('true') expect(button.hasAttribute('disabled')).toBe(false) await user.click(button) expect(onClick).not.toHaveBeenCalled() await user.tab() expect(document.activeElement).toBe(button) // Already open, in fact: the Tab above focused it, and focus opens // with no delay at all. await user.hover(button) expect((await screen.findByRole('tooltip')).textContent).toBe('Read only') }) it('Space and Enter are the action, and nothing while disabled with a reason', async () => { const user = userEvent.setup() const onClick = vi.fn() const { unmount } = render( {icon} , ) await user.tab() await user.keyboard('{Enter}') await user.keyboard(' ') expect(onClick).toHaveBeenCalledTimes(2) unmount() const held = vi.fn() render( {icon} , ) await user.tab() await user.keyboard('{Enter}') await user.keyboard(' ') expect(held).not.toHaveBeenCalled() }) it('disabled without a reason: a real disabled button, out of the Tab order', async () => { const user = userEvent.setup() render( <> {icon} , ) expect(screen.getByRole('button', { name: 'Edit' }).hasAttribute('disabled')).toBe(true) await user.tab() expect(document.activeElement).toBe(screen.getByRole('button', { name: 'After' })) }) /* A flex parent with no `items-*` stretches its children. `shrink-0` does not stop that — it is the other direction — so this button grew to 228px tall in a 260px row (Katerina, 2026-09-11). jsdom computes no layout, so the class is what can be asserted here; the story beside it is what shows the pixels. */ it('keeps its own size in a flex parent that would stretch it', () => { render(
{icon}
, ) expect(screen.getByRole('button', { name: 'Edit' }).className).toContain('self-center') }) })