mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-03 14:18:02 +00:00
chore: hide the What's New setting until an entry exists (#1770)
The nav trigger already stays out of the top bar while entries.json is empty, but the Settings toggle rendered unconditionally, so About showed a control whose helper text described a sparkle icon that could not appear. The Preferences section now follows the same rule as the trigger. Also gives the toggle an accessible name. Its visible text is only ON/OFF, so a screen reader previously announced the state without the setting it belongs to.
This commit is contained in:
@@ -2,6 +2,7 @@ import { useLicense } from '@/context/LicenseContext';
|
|||||||
import { TierBadge } from '@/components/TierBadge';
|
import { TierBadge } from '@/components/TierBadge';
|
||||||
import { TogglePill } from '@/components/ui/toggle-pill';
|
import { TogglePill } from '@/components/ui/toggle-pill';
|
||||||
import { useWhatsNewPreference } from '@/hooks/useWhatsNewPreference';
|
import { useWhatsNewPreference } from '@/hooks/useWhatsNewPreference';
|
||||||
|
import { whatsNewEntries } from '@/whats-new/entries';
|
||||||
import { SettingsSection } from './SettingsSection';
|
import { SettingsSection } from './SettingsSection';
|
||||||
import { SettingsField } from './SettingsField';
|
import { SettingsField } from './SettingsField';
|
||||||
import {
|
import {
|
||||||
@@ -44,18 +45,24 @@ export function AboutSection() {
|
|||||||
) : null}
|
) : null}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<SettingsSection title="Preferences">
|
{/* Hidden until an entry exists, matching the nav trigger: the toggle
|
||||||
<SettingsField
|
would otherwise control an icon that cannot appear. */}
|
||||||
label="Show What's New"
|
{whatsNewEntries.length > 0 && (
|
||||||
helper="Highlight the sparkle icon in the top bar when a new feature ships."
|
<SettingsSection title="Preferences">
|
||||||
>
|
<SettingsField
|
||||||
<TogglePill
|
label="Show What's New"
|
||||||
id="whats-new-enabled"
|
helper="Highlight the sparkle icon in the top bar when a new feature ships."
|
||||||
checked={whatsNewEnabled}
|
>
|
||||||
onChange={setWhatsNewEnabled}
|
<TogglePill
|
||||||
/>
|
id="whats-new-enabled"
|
||||||
</SettingsField>
|
// Visible text is only ON/OFF, so the setting needs a name.
|
||||||
</SettingsSection>
|
aria-label="Show What's New"
|
||||||
|
checked={whatsNewEnabled}
|
||||||
|
onChange={setWhatsNewEnabled}
|
||||||
|
/>
|
||||||
|
</SettingsField>
|
||||||
|
</SettingsSection>
|
||||||
|
)}
|
||||||
|
|
||||||
<SettingsSection title="Links">
|
<SettingsSection title="Links">
|
||||||
<SettingsField
|
<SettingsField
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ vi.mock('@/hooks/useWhatsNewPreference', () => ({
|
|||||||
useWhatsNewPreference: () => ({ enabled: true, setEnabled: mockSetEnabled, hasUnseen: false, markSeen: vi.fn() }),
|
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', () => {
|
describe('AboutSection', () => {
|
||||||
it('renders Plan status and Source, License, and Licensing docs links with exact URLs', () => {
|
it('renders Plan status and Source, License, and Licensing docs links with exact URLs', () => {
|
||||||
render(<AboutSection />);
|
render(<AboutSection />);
|
||||||
@@ -73,9 +78,16 @@ describe('AboutSection', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('AboutSection Preferences', () => {
|
describe('AboutSection Preferences', () => {
|
||||||
|
it('shows the Preferences section once an entry exists', () => {
|
||||||
|
render(<AboutSection />);
|
||||||
|
expect(screen.getByText('Preferences')).toBeTruthy();
|
||||||
|
expect(screen.getByText("Show What's New")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it('toggling "Show What\'s New" calls setEnabled', async () => {
|
it('toggling "Show What\'s New" calls setEnabled', async () => {
|
||||||
render(<AboutSection />);
|
render(<AboutSection />);
|
||||||
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);
|
expect(mockSetEnabled).toHaveBeenCalledWith(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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: () => <span>Community</span>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
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(<AboutSection />);
|
||||||
|
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(<AboutSection />);
|
||||||
|
expect(screen.getByText('Plan status')).toBeTruthy();
|
||||||
|
expect(screen.getByText('Source code')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user