feat(ui): give the app one visual identity as Movie Loop
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".
This commit is contained in:
1 parent
da32e49b0b
commit
8d0b7f39ee
122 files changed
+3129
-2089
No files matched your search
@@ -0,0 +1,84 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user