mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
Add About section to settings page with version info and tests (#119)
* feat(web): add About section to settings page - Add website link to hapi.run - Display app version from CLI package - Display protocol version from shared module - Add Vitest testing setup with settings page tests 🤖 Generated with Claude Code * test(web): add tests for website link and i18n key usage Address residual risks mentioned in PR review: - Test website link URL and security attributes (target, rel) - Verify correct i18n keys are used for About section via spy Simplify test setup by using real I18nProvider and en locale. 🤖 Generated with Claude Code
This commit is contained in:
@@ -239,6 +239,10 @@ export default {
|
||||
'settings.voice.title': 'Voice Assistant',
|
||||
'settings.voice.language': 'Voice Language',
|
||||
'settings.voice.autoDetect': 'Auto-detect',
|
||||
'settings.about.title': 'About',
|
||||
'settings.about.website': 'Website',
|
||||
'settings.about.appVersion': 'App Version',
|
||||
'settings.about.protocolVersion': 'Protocol Version',
|
||||
|
||||
// Misc
|
||||
'misc.noMachines': 'No machines available',
|
||||
|
||||
@@ -241,6 +241,10 @@ export default {
|
||||
'settings.voice.title': '语音助手',
|
||||
'settings.voice.language': '语音语言',
|
||||
'settings.voice.autoDetect': '自动检测',
|
||||
'settings.about.title': '关于',
|
||||
'settings.about.website': '官方网站',
|
||||
'settings.about.appVersion': '应用版本',
|
||||
'settings.about.protocolVersion': '协议版本',
|
||||
|
||||
// Misc
|
||||
'misc.noMachines': '无可用机器',
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { I18nContext, I18nProvider } from '@/lib/i18n-context'
|
||||
import { en } from '@/lib/locales'
|
||||
import { PROTOCOL_VERSION } from '@hapi/protocol'
|
||||
import SettingsPage from './index'
|
||||
|
||||
// Mock the router hooks
|
||||
vi.mock('@tanstack/react-router', () => ({
|
||||
useNavigate: () => vi.fn(),
|
||||
useRouter: () => ({ history: { back: vi.fn() } }),
|
||||
useLocation: () => '/settings',
|
||||
}))
|
||||
|
||||
// Mock useFontScale hook
|
||||
vi.mock('@/hooks/useFontScale', () => ({
|
||||
useFontScale: () => ({ fontScale: 1, setFontScale: vi.fn() }),
|
||||
getFontScaleOptions: () => [
|
||||
{ value: 0.875, label: '87.5%' },
|
||||
{ value: 1, label: '100%' },
|
||||
{ value: 1.125, label: '112.5%' },
|
||||
],
|
||||
}))
|
||||
|
||||
// Mock languages
|
||||
vi.mock('@/lib/languages', () => ({
|
||||
getElevenLabsSupportedLanguages: () => [
|
||||
{ code: null, name: 'Auto-detect' },
|
||||
{ code: 'en', name: 'English' },
|
||||
],
|
||||
getLanguageDisplayName: (lang: { code: string | null; name: string }) => lang.name,
|
||||
}))
|
||||
|
||||
function renderWithProviders(ui: React.ReactElement) {
|
||||
return render(
|
||||
<I18nProvider>
|
||||
{ui}
|
||||
</I18nProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function renderWithSpyT(ui: React.ReactElement) {
|
||||
const translations = en as Record<string, string>
|
||||
const spyT = vi.fn((key: string) => translations[key] ?? key)
|
||||
render(
|
||||
<I18nContext.Provider value={{ t: spyT, locale: 'en', setLocale: vi.fn() }}>
|
||||
{ui}
|
||||
</I18nContext.Provider>
|
||||
)
|
||||
return spyT
|
||||
}
|
||||
|
||||
describe('SettingsPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Mock localStorage
|
||||
const localStorageMock = {
|
||||
getItem: vi.fn(() => 'en'),
|
||||
setItem: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
}
|
||||
Object.defineProperty(window, 'localStorage', { value: localStorageMock })
|
||||
})
|
||||
|
||||
it('renders the About section', () => {
|
||||
renderWithProviders(<SettingsPage />)
|
||||
expect(screen.getByText('About')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('displays the App Version with correct value', () => {
|
||||
renderWithProviders(<SettingsPage />)
|
||||
expect(screen.getAllByText('App Version').length).toBeGreaterThanOrEqual(1)
|
||||
expect(screen.getAllByText(__APP_VERSION__).length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
it('displays the Protocol Version with correct value', () => {
|
||||
renderWithProviders(<SettingsPage />)
|
||||
expect(screen.getAllByText('Protocol Version').length).toBeGreaterThanOrEqual(1)
|
||||
expect(screen.getAllByText(String(PROTOCOL_VERSION)).length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
it('displays the website link with correct URL and security attributes', () => {
|
||||
renderWithProviders(<SettingsPage />)
|
||||
expect(screen.getAllByText('Website').length).toBeGreaterThanOrEqual(1)
|
||||
const links = screen.getAllByRole('link', { name: 'hapi.run' })
|
||||
expect(links.length).toBeGreaterThanOrEqual(1)
|
||||
const link = links[0]
|
||||
expect(link).toHaveAttribute('href', 'https://hapi.run')
|
||||
expect(link).toHaveAttribute('target', '_blank')
|
||||
expect(link).toHaveAttribute('rel', 'noopener noreferrer')
|
||||
})
|
||||
|
||||
it('uses correct i18n keys for About section', () => {
|
||||
const spyT = renderWithSpyT(<SettingsPage />)
|
||||
const calledKeys = spyT.mock.calls.map((call) => call[0])
|
||||
expect(calledKeys).toContain('settings.about.title')
|
||||
expect(calledKeys).toContain('settings.about.website')
|
||||
expect(calledKeys).toContain('settings.about.appVersion')
|
||||
expect(calledKeys).toContain('settings.about.protocolVersion')
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import { useTranslation, type Locale } from '@/lib/use-translation'
|
||||
import { useAppGoBack } from '@/hooks/useAppGoBack'
|
||||
import { getElevenLabsSupportedLanguages, getLanguageDisplayName, type Language } from '@/lib/languages'
|
||||
import { getFontScaleOptions, useFontScale, type FontScale } from '@/hooks/useFontScale'
|
||||
import { PROTOCOL_VERSION } from '@hapi/protocol'
|
||||
|
||||
const locales: { value: Locale; nativeLabel: string }[] = [
|
||||
{ value: 'en', nativeLabel: 'English' },
|
||||
@@ -335,6 +336,32 @@ export default function SettingsPage() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* About section */}
|
||||
<div className="border-b border-[var(--app-divider)]">
|
||||
<div className="px-3 py-2 text-xs font-semibold text-[var(--app-hint)] uppercase tracking-wide">
|
||||
{t('settings.about.title')}
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-between px-3 py-3">
|
||||
<span className="text-[var(--app-fg)]">{t('settings.about.website')}</span>
|
||||
<a
|
||||
href="https://hapi.run"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-[var(--app-link)] hover:underline"
|
||||
>
|
||||
hapi.run
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-between px-3 py-3">
|
||||
<span className="text-[var(--app-fg)]">{t('settings.about.appVersion')}</span>
|
||||
<span className="text-[var(--app-hint)]">{__APP_VERSION__}</span>
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-between px-3 py-3">
|
||||
<span className="text-[var(--app-fg)]">{t('settings.about.protocolVersion')}</span>
|
||||
<span className="text-[var(--app-hint)]">{PROTOCOL_VERSION}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
declare const __APP_VERSION__: string
|
||||
Reference in New Issue
Block a user