diff --git a/frontend/src/components/settings/AboutSection.tsx b/frontend/src/components/settings/AboutSection.tsx index a3feee02..83c8016e 100644 --- a/frontend/src/components/settings/AboutSection.tsx +++ b/frontend/src/components/settings/AboutSection.tsx @@ -2,6 +2,7 @@ import { useLicense } from '@/context/LicenseContext'; import { TierBadge } from '@/components/TierBadge'; import { TogglePill } from '@/components/ui/toggle-pill'; import { useWhatsNewPreference } from '@/hooks/useWhatsNewPreference'; +import { whatsNewEntries } from '@/whats-new/entries'; import { SettingsSection } from './SettingsSection'; import { SettingsField } from './SettingsField'; import { @@ -44,18 +45,24 @@ export function AboutSection() { ) : null} - - - - - + {/* Hidden until an entry exists, matching the nav trigger: the toggle + would otherwise control an icon that cannot appear. */} + {whatsNewEntries.length > 0 && ( + + + + + + )} ({ useWhatsNewPreference: () => ({ enabled: true, setEnabled: mockSetEnabled, hasUnseen: false, markSeen: vi.fn() }), })); +// The shipped entries.json is empty, so populate it here; the empty state has its own file. +vi.mock('@/whats-new/entries', () => ({ + whatsNewEntries: [{ id: 'entry-a', title: 'A feature', blurb: 'Does a thing.' }], +})); + describe('AboutSection', () => { it('renders Plan status and Source, License, and Licensing docs links with exact URLs', () => { render(); @@ -73,9 +78,16 @@ describe('AboutSection', () => { }); describe('AboutSection Preferences', () => { + it('shows the Preferences section once an entry exists', () => { + render(); + expect(screen.getByText('Preferences')).toBeTruthy(); + expect(screen.getByText("Show What's New")).toBeTruthy(); + }); + it('toggling "Show What\'s New" calls setEnabled', async () => { render(); - await userEvent.click(screen.getByRole('switch')); + // Name-scoped so a second toggle landing in About cannot break this. + await userEvent.click(screen.getByRole('switch', { name: /Show What's New/i })); expect(mockSetEnabled).toHaveBeenCalledWith(false); }); }); diff --git a/frontend/src/components/settings/__tests__/AboutSection.whatsNewEmpty.test.tsx b/frontend/src/components/settings/__tests__/AboutSection.whatsNewEmpty.test.tsx new file mode 100644 index 00000000..181a3c7d --- /dev/null +++ b/frontend/src/components/settings/__tests__/AboutSection.whatsNewEmpty.test.tsx @@ -0,0 +1,59 @@ +import { describe, it, expect, vi, beforeAll } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { AboutSection } from '../AboutSection'; + +beforeAll(() => { + // Vite injects this at build time; tests need a stand-in. + (globalThis as unknown as { __APP_VERSION__: string }).__APP_VERSION__ = '0.0.0-test'; +}); + +// Separate file so entries can be mocked empty at module scope, matching the +// state that ships until the first entry is authored. +vi.mock('@/whats-new/entries', () => ({ whatsNewEntries: [] })); + +vi.mock('@/context/LicenseContext', () => ({ + useLicense: () => ({ + license: { + tier: 'community', + status: 'community', + customerName: null, + productName: null, + maskedKey: null, + validUntil: null, + trialDaysRemaining: null, + instanceId: 'abcdef0123456789', + portalUrl: null, + isLifetime: false, + }, + isPaid: false, + loading: false, + licenseStatus: 'ready', + licenseReady: true, + refresh: vi.fn(), + activate: vi.fn(), + deactivate: vi.fn(), + }), +})); + +vi.mock('@/components/TierBadge', () => ({ + TierBadge: () => Community, +})); + +vi.mock('@/hooks/useWhatsNewPreference', () => ({ + useWhatsNewPreference: () => ({ enabled: true, setEnabled: vi.fn(), hasUnseen: false, markSeen: vi.fn() }), +})); + +describe("AboutSection with no What's New entries authored", () => { + it('hides the Preferences section entirely, so no toggle describes an absent icon', () => { + render(); + expect(screen.queryByText('Preferences')).toBeNull(); + expect(screen.queryByText("Show What's New")).toBeNull(); + expect(screen.queryByRole('switch')).toBeNull(); + }); + + it('still renders the rest of the About panel', () => { + render(); + expect(screen.getByText('Plan status')).toBeTruthy(); + expect(screen.getByText('Source code')).toBeTruthy(); + }); +});