// 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();
}
});
});
});