menu-selection-indicator.test.tsx2.8 KBView on GitHub
/**
 * How a menu marks the row you picked.
 *
 * shadcn's `RadioItem` ships a filled `Circle` in a LEFT gutter, and that default reached the
 * agent workspace's Folder and Default file submenus: a solid black disc beside one row, on the
 * side that belongs to the option's own icon, in a menu whose checkbox rows were already ticking
 * on the right. One question, two marks, two sides.
 *
 * The rule is in apps/mail/docs/crystallized.md → Counts, ticks and ordinals: a `Check` on the
 * RIGHT, the same for a radio set as for a checkbox set, in a lane that is always reserved. This
 * test is the guard on it — the dot is a one-line regression to reintroduce, and nothing else in
 * the suite would notice.
 */

import { render, screen } from '@testing-library/react';

import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';

function Menu() {
  return (
    <DropdownMenu defaultOpen>
      <DropdownMenuTrigger>Open</DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuRadioGroup value="core">
          <DropdownMenuRadioItem value="core">Core</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="background">Background</DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
        <DropdownMenuCheckboxItem checked>Show archived</DropdownMenuCheckboxItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

describe('menu selection indicator', () => {
  it('marks the chosen radio row with a tick, not a filled dot', () => {
    const { baseElement } = render(<Menu />);

    const chosen = screen.getByRole('menuitemradio', { name: 'Core' });
    expect(chosen).toHaveAttribute('aria-checked', 'true');
    expect(chosen.querySelector('.lucide-check')).toBeInTheDocument();
    // The dot, in any of the shapes it comes in.
    expect(baseElement.querySelector('.lucide-circle')).not.toBeInTheDocument();
    expect(baseElement.querySelector('.fill-current')).not.toBeInTheDocument();
  });

  it('puts the tick on the right, in a lane every row reserves', () => {
    render(<Menu />);

    for (const name of ['Core', 'Background']) {
      const row = screen.getByRole('menuitemradio', { name });
      // Reserved on the unchosen row too: indenting only the chosen one makes the whole menu
      // jump sideways every time you pick something.
      expect(row.className).toContain('pr-8');
      expect(row.className).not.toContain('pl-8');
    }
  });

  it('ticks a checkbox row the same way, so one menu reads as one system', () => {
    render(<Menu />);

    const box = screen.getByRole('menuitemcheckbox', { name: 'Show archived' });
    expect(box.querySelector('.lucide-check')).toBeInTheDocument();
    expect(box.className).toContain('pr-8');
  });
});