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