Gold is the primary in both themes, Bricolage Grotesque carries the wordmark, titles and the score, and Movie A / Movie B wear the blue and magenta of three-strip Technicolor on their posters instead of colouring whole rows. The loop closes on screen - connectors light, a gold line draws back to Movie A, the row glows, then the result arrives - in place of confetti, on every play mode, scaled to the chain's length. Every page shares one header and hairline lists; copy is sentence case with one verb per flow, and the Home hero shows a real finished loop. Also clamps link counts at zero: an opponent who had not moved yet showed "-1 links".
85 lines
2.5 KiB
TypeScript
85 lines
2.5 KiB
TypeScript
import { render, screen } from '@testing-library/react';
|
|
import { MemoryRouter } from 'react-router';
|
|
import { describe, expect, it } from 'vitest';
|
|
import PageHeader from './PageHeader';
|
|
|
|
/**
|
|
* The one page-title treatment. Rendered inside a MemoryRouter since an
|
|
* optional `back` link uses react-router's `Link` directly.
|
|
*/
|
|
describe('PageHeader', () => {
|
|
it('renders the title as the level-1 heading', () => {
|
|
render(
|
|
<MemoryRouter>
|
|
<PageHeader title="Daily Challenge" />
|
|
</MemoryRouter>,
|
|
);
|
|
expect(screen.getByRole('heading', { level: 1, name: 'Daily Challenge' })).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders the description when given', () => {
|
|
render(
|
|
<MemoryRouter>
|
|
<PageHeader title="Daily Challenge" description="One puzzle a day." />
|
|
</MemoryRouter>,
|
|
);
|
|
expect(screen.getByText('One puzzle a day.')).toBeInTheDocument();
|
|
});
|
|
|
|
it('omits the description when not given', () => {
|
|
render(
|
|
<MemoryRouter>
|
|
<PageHeader title="Daily Challenge" />
|
|
</MemoryRouter>,
|
|
);
|
|
expect(screen.queryByText('One puzzle a day.')).toBeNull();
|
|
});
|
|
|
|
it('renders page-level actions', () => {
|
|
render(
|
|
<MemoryRouter>
|
|
<PageHeader title="Daily Challenge" actions={<button type="button">Share</button>} />
|
|
</MemoryRouter>,
|
|
);
|
|
expect(screen.getByRole('button', { name: 'Share' })).toBeInTheDocument();
|
|
});
|
|
|
|
it('omits the actions slot when not given', () => {
|
|
render(
|
|
<MemoryRouter>
|
|
<PageHeader title="Daily Challenge" />
|
|
</MemoryRouter>,
|
|
);
|
|
expect(screen.queryByRole('button')).toBeNull();
|
|
});
|
|
|
|
it('renders a back link with the given label and href', () => {
|
|
render(
|
|
<MemoryRouter>
|
|
<PageHeader title="Daily Challenge" back={{ to: '/', label: 'Home' }} />
|
|
</MemoryRouter>,
|
|
);
|
|
const back = screen.getByRole('link', { name: /Home/ });
|
|
expect(back).toHaveAttribute('href', '/');
|
|
});
|
|
|
|
it('omits the back link when not given', () => {
|
|
render(
|
|
<MemoryRouter>
|
|
<PageHeader title="Daily Challenge" />
|
|
</MemoryRouter>,
|
|
);
|
|
expect(screen.queryByRole('link')).toBeNull();
|
|
});
|
|
|
|
it('merges a custom className', () => {
|
|
const { container } = render(
|
|
<MemoryRouter>
|
|
<PageHeader title="Daily Challenge" className="mt-4" />
|
|
</MemoryRouter>,
|
|
);
|
|
expect(container.firstElementChild?.className).toContain('mt-4');
|
|
expect(container.firstElementChild?.className).toContain('mb-6');
|
|
});
|
|
});
|