import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; jest.mock('@/assets/images/doormile-mark.png', () => 'doormile-mark.png', { virtual: true }); jest.mock('lucide-react', () => new Proxy({}, { get: (_t, prop) => (prop === '__esModule' ? true : (props) => ), }) ); import AILauncher from '@/components/assistant/DoormileAI/AILauncher'; /** * The floating control that opens MileTruth, in place of the bare 32px brand * mark that used to sit in the header between search and the bell. * * Two things here are requirements rather than styling, and both are the kind * that a refactor silently undoes: the outline is the brand crimson on hover * and on keyboard focus, and the button steps aside while the panel is open * instead of sitting on top of a docked panel. */ const setup = (props = {}) => { const onToggle = jest.fn(); const utils = render(); return { ...utils, onToggle }; }; describe('the MileTruth launcher', () => { it('is named, not just a mark', () => { // The header version was an unlabelled logo among utility icons, with // nothing on screen saying what it opened. setup(); expect(screen.getByText('Doormile')).toBeInTheDocument(); }); it('opens the assistant when clicked', () => { const { onToggle } = setup(); fireEvent.click(screen.getByRole('button', { name: /open miletruth/i })); expect(onToggle).toHaveBeenCalledTimes(1); }); it('outlines in brand red on hover, not the default focus blue', () => { // `ring-brand` is #C8102E. A `/20` wash — what the header icon used — reads // as a pale smudge on a white pill rather than as an outline. const { container } = setup(); const cls = container.querySelector('button').className; expect(cls).toMatch(/hover:ring-brand(?!\/)/); expect(cls).toMatch(/hover:ring-2/); }); it('outlines the same way for keyboard focus', () => { const { container } = setup(); const cls = container.querySelector('button').className; expect(cls).toMatch(/focus-visible:ring-brand(?!\/)/); // and never falls back to the user agent's own outline, which is the blue // ring this replaces. expect(cls).toMatch(/focus-visible:outline-none/); }); it('floats bottom-right, below the panel layer', () => { // The panel is z-index 1200 and its scrim 1300. A launcher above either // would sit on top of an open panel. const { container } = setup(); const cls = container.querySelector('button').className; expect(cls).toMatch(/fixed/); expect(cls).toMatch(/bottom-5/); expect(cls).toMatch(/right-5/); expect(cls).toMatch(/z-\[1100\]/); }); it('gets out of the way while the panel is open', () => { // The panel docks to the right on a wide screen and covers the page behind // a scrim on a narrow one. Either way this button would be underneath it. const { container } = render(); expect(container.querySelector('button')).toBeNull(); }); it('the mark can never outgrow its slot', () => { // The header icon hit this twice: a 36px image inside a 32px button, // overflowing on every side and breaking the row's alignment. const { container } = setup(); const img = container.querySelector('img'); expect(img.className).toMatch(/object-contain/); expect(img.className).not.toMatch(/max-w-none/); expect(img).toHaveAttribute('aria-hidden', 'true'); }); });