open-design/apps/web/tests/components/MissingBrandFontsBanner.test.tsx
Chris Seifert c5ea97a0e8
feat: add "Use system fonts" dismiss to the missing-fonts banner (#2816)
* feat: add "Use system fonts" dismiss to the missing-fonts banner

The 'Missing brand fonts' banner only offered Upload fonts. Now there's
a 'Use system fonts' button next to it that hides the banner for that
project (saved in localStorage) when you're fine with the fallback. It
does not change how fonts render; it just stops nagging.

Pulled the banner into a shared MissingBrandFontsBanner component and
gave the warning card the bottom margin it was missing.

* Resync the missing-fonts banner dismissal when the project changes

FileWorkspace renders MissingBrandFontsBanner without a per-project key, so
the same instance is reused as the user moves between projects. The dismissal
state was read with useState only on first mount, so dismissing project A left
the banner hidden for project B in the same panel even though only A was
written to localStorage. That broke the per-project scoping this banner adds.

Added a useEffect that re-reads the dismissal whenever projectId changes, and a
rerender test that dismisses p1, switches to p2 (banner returns), and switches
back to p1 (stays dismissed).
2026-05-24 14:25:04 +00:00

75 lines
3.4 KiB
TypeScript

// @vitest-environment jsdom
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { MissingBrandFontsBanner } from '../../src/components/MissingBrandFontsBanner';
beforeEach(() => {
window.localStorage.clear();
});
afterEach(() => {
cleanup();
window.localStorage.clear();
});
describe('MissingBrandFontsBanner (issue #2814)', () => {
it('shows Upload fonts + Use system fonts when not dismissed', () => {
render(<MissingBrandFontsBanner projectId="p1" onUploadAssets={() => {}} />);
expect(screen.getByText('Missing brand fonts')).toBeTruthy();
expect(screen.getByRole('button', { name: /upload fonts/i })).toBeTruthy();
expect(screen.getByRole('button', { name: /use system fonts/i })).toBeTruthy();
});
it('dismisses the banner for the project when Use system fonts is clicked', () => {
const { container } = render(
<MissingBrandFontsBanner projectId="p1" onUploadAssets={() => {}} />,
);
fireEvent.click(screen.getByRole('button', { name: /use system fonts/i }));
expect(container.querySelector('.ds-project-warning-card')).toBeNull();
expect(window.localStorage.getItem('od:font-banner-dismissed:p1')).toBe('1');
});
it('renders nothing when already dismissed for that project', () => {
window.localStorage.setItem('od:font-banner-dismissed:p1', '1');
const { container } = render(<MissingBrandFontsBanner projectId="p1" />);
expect(container.firstChild).toBeNull();
});
it('keeps the dismissal scoped per project', () => {
window.localStorage.setItem('od:font-banner-dismissed:p1', '1');
render(<MissingBrandFontsBanner projectId="p2" />);
// p2 was not dismissed, so the banner still shows.
expect(screen.getByText('Missing brand fonts')).toBeTruthy();
});
it('resyncs dismissal when the same instance switches projects', () => {
// FileWorkspace renders the banner without a per-project key, so one
// instance is reused as the user moves between projects.
const { rerender, container } = render(<MissingBrandFontsBanner projectId="p1" />);
fireEvent.click(screen.getByRole('button', { name: /use system fonts/i }));
expect(container.querySelector('.ds-project-warning-card')).toBeNull();
expect(window.localStorage.getItem('od:font-banner-dismissed:p1')).toBe('1');
// Switching to a project that was never dismissed shows the banner again.
rerender(<MissingBrandFontsBanner projectId="p2" />);
expect(screen.getByText('Missing brand fonts')).toBeTruthy();
// Switching back to the dismissed project hides it again.
rerender(<MissingBrandFontsBanner projectId="p1" />);
expect(container.querySelector('.ds-project-warning-card')).toBeNull();
});
it('invokes onUploadAssets when Upload fonts is clicked', () => {
const onUploadAssets = vi.fn();
render(<MissingBrandFontsBanner projectId="p1" onUploadAssets={onUploadAssets} />);
fireEvent.click(screen.getByRole('button', { name: /upload fonts/i }));
expect(onUploadAssets).toHaveBeenCalledOnce();
});
it('omits Upload fonts when no handler is provided', () => {
render(<MissingBrandFontsBanner projectId="p3" />);
expect(screen.queryByRole('button', { name: /upload fonts/i })).toBeNull();
expect(screen.getByRole('button', { name: /use system fonts/i })).toBeTruthy();
});
});