// packages/web/src/components/ui/__tests__/Mascot.test.tsx import { describe, it, expect } from 'vitest'; import { render } from '@testing-library/react'; import { Mascot } from '../Mascot'; describe('Mascot', () => { it('renders an SVG with aria-hidden and presentation role', () => { const { container } = render(); const wrapper = container.firstElementChild as HTMLElement; expect(wrapper.getAttribute('role')).toBe('presentation'); const svg = wrapper.querySelector('svg'); expect(svg).toBeTruthy(); expect(svg!.getAttribute('aria-hidden')).toBe('true'); }); it('applies idle palette gradient stops', () => { const { container } = render(); const stops = container.querySelectorAll('stop'); const colors = Array.from(stops).map(s => s.getAttribute('stop-color')); expect(colors).toContain('#c8f0de'); expect(colors).toContain('#6dbf96'); }); it('applies sleeping palette gradient stops', () => { const { container } = render(); const stops = container.querySelectorAll('stop'); const colors = Array.from(stops).map(s => s.getAttribute('stop-color')); expect(colors).toContain('#ddd4f0'); expect(colors).toContain('#a898cc'); }); it('applies excited palette gradient stops', () => { const { container } = render(); const stops = container.querySelectorAll('stop'); const colors = Array.from(stops).map(s => s.getAttribute('stop-color')); expect(colors).toContain('#fde0c8'); expect(colors).toContain('#e8a870'); }); it('applies lonely palette gradient stops', () => { const { container } = render(); const stops = container.querySelectorAll('stop'); const colors = Array.from(stops).map(s => s.getAttribute('stop-color')); expect(colors).toContain('#c0dced'); expect(colors).toContain('#78aec8'); }); it('renders upright viewBox for idle state', () => { const { container } = render(); const svg = container.querySelector('svg'); expect(svg!.getAttribute('viewBox')).toBe('0 0 200 200'); }); it('renders wide viewBox for sleeping state', () => { const { container } = render(); const svg = container.querySelector('svg'); expect(svg!.getAttribute('viewBox')).toBe('0 0 220 130'); }); it('renders closed-eye arcs (path elements) for sleeping state', () => { const { container } = render(); const eyeWhites = container.querySelectorAll('[data-eye="white"]'); expect(eyeWhites.length).toBe(0); const closedEyes = container.querySelectorAll('[data-eye="closed"]'); expect(closedEyes.length).toBe(2); }); it('renders open eyes (ellipses) for non-sleeping states', () => { const { container } = render(); const eyeWhites = container.querySelectorAll('[data-eye="white"]'); expect(eyeWhites.length).toBe(2); }); it('renders a z-particle container for sleeping state', () => { const { container } = render(); const zBox = container.querySelector('[data-mascot="z-container"]'); expect(zBox).toBeTruthy(); }); it('does not render z-particle container for non-sleeping states', () => { const { container } = render(); const zBox = container.querySelector('[data-mascot="z-container"]'); expect(zBox).toBeFalsy(); }); it('renders a ground shadow', () => { const { container } = render(); const shadow = container.querySelector('[data-mascot="shadow"]'); expect(shadow).toBeTruthy(); }); it('applies custom className replacing default size', () => { const { container } = render(); const wrapper = container.firstElementChild as HTMLElement; expect(wrapper.className).toContain('w-48'); expect(wrapper.className).toContain('h-48'); // Custom className replaces default w-32 h-32 to avoid Tailwind conflicts expect(wrapper.className).not.toContain('w-32'); }); it('uses default w-32 h-32 when no className provided', () => { const { container } = render(); const wrapper = container.firstElementChild as HTMLElement; expect(wrapper.className).toContain('w-32'); expect(wrapper.className).toContain('h-32'); }); it('renders the sleeping mouth ellipse', () => { const { container } = render(); const mouth = container.querySelector('[data-mascot="mouth"]'); expect(mouth).toBeTruthy(); expect(mouth!.tagName.toLowerCase()).toBe('ellipse'); }); it('renders the upright mouth as a path for idle', () => { const { container } = render(); const mouth = container.querySelector('[data-mascot="mouth"]'); expect(mouth).toBeTruthy(); expect(mouth!.tagName.toLowerCase()).toBe('path'); }); describe('Mascot animation lifecycle', () => { it('renders without crashing when element.animate is unavailable (jsdom)', () => { expect(() => render()).not.toThrow(); }); it('renders without crashing for each state', () => { for (const state of ['idle', 'sleeping', 'excited', 'lonely'] as const) { const { unmount } = render(); unmount(); } }); }); });