feat(ui-settings): add an About section with company copy and version check
Registers a new `settings.section` (id `about`) in ui-settings-general that introduces PineSound, names the product build, shows the current version, and offers a check-updates control (a client-side placeholder comparing against a known-latest constant pending a real update channel). Adds a 16px info icon to ui-primitives and wires the section into the settings nav. Tests cover the registration and the About component; READMEs and translation-pairing hashes are updated. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { SlotRegistry } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { usePinnedBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { apply, inject } from '@deepseek-ai/dsh-client-ui-settings-general/client'
|
||||
import { AboutSection } from '../src/client/AboutSection.tsx'
|
||||
import { CloseLabel, HeaderContent, TriggerContent } from '../src/client/chrome.tsx'
|
||||
import { GeneralSection } from '../src/client/GeneralSection.tsx'
|
||||
import { SettingsDocumentAction } from '../src/client/SettingsDocumentAction.tsx'
|
||||
@@ -73,6 +74,10 @@ function generalEntry(slots: SlotRegistry) {
|
||||
return slots.entries('settings.section').find(e => e.component === GeneralSection)
|
||||
}
|
||||
|
||||
function aboutEntry(slots: SlotRegistry) {
|
||||
return slots.entries('settings.section').find(e => e.component === AboutSection)
|
||||
}
|
||||
|
||||
describe('ui-settings-general apply', () => {
|
||||
it('declares the services it uses', () => {
|
||||
expect(inject).toEqual(['slots', 'locale', 'connection'])
|
||||
@@ -91,6 +96,9 @@ describe('ui-settings-general apply', () => {
|
||||
expect(resolveSlotLabel(entry.options.label)).toBe('通用设置')
|
||||
expect(before.slots.spec('settings.general.item')).toEqual({ kind: 'list', scope: 'root' })
|
||||
expect(before.slots.entries('settings.general.item')).toEqual([])
|
||||
const about = aboutEntry(before.slots)!
|
||||
expect(about.options).toMatchObject({ id: 'about', order: 100 })
|
||||
expect(resolveSlotLabel(about.options.label)).toBe('关于')
|
||||
// The onboarding hole stays declared for feature-owned steps; this plugin
|
||||
// no longer seats one.
|
||||
expect(before.slots.entries('settings.onboarding')).toEqual([])
|
||||
@@ -110,7 +118,7 @@ describe('ui-settings-general apply', () => {
|
||||
for (const [name, component] of SEATS) {
|
||||
expect(after.slots.entries(name)[0]!.component).toBe(component)
|
||||
// The self-inflicted ledger notifications hit the duplicate guard.
|
||||
expect(after.slots.entries(name)).toHaveLength(1)
|
||||
expect(after.slots.entries(name)).toHaveLength(name === 'settings.section' ? 2 : 1)
|
||||
}
|
||||
await vi.waitFor(() => {
|
||||
expect(after.slots.spec('settings.general.item')).toEqual({ kind: 'list', scope: 'root' })
|
||||
@@ -142,7 +150,7 @@ describe('ui-settings-general apply', () => {
|
||||
// subscription), not re-registration.
|
||||
SEATS.forEach(([name], i) => {
|
||||
expect(b.slots.getVersion(name)).toBe(zhVersions[i]!)
|
||||
expect(b.slots.entries(name)).toHaveLength(1)
|
||||
expect(b.slots.entries(name)).toHaveLength(name === 'settings.section' ? 2 : 1)
|
||||
})
|
||||
expect(resolveSlotLabel(generalEntry(b.slots)!.options.label)).toBe('General')
|
||||
b.locale.setLocale('zh')
|
||||
|
||||
@@ -4,6 +4,8 @@ import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/re
|
||||
import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react'
|
||||
import type { GeneralSectionComponentProps } from '../src/client/GeneralSection.tsx'
|
||||
import { GeneralSection } from '../src/client/GeneralSection.tsx'
|
||||
import { AboutSection } from '../src/client/AboutSection.tsx'
|
||||
import type { AboutSectionComponentProps } from '../src/client/AboutSection.tsx'
|
||||
import { CloseLabel, HeaderContent, TriggerContent } from '../src/client/chrome.tsx'
|
||||
import type { TriggerContentProps } from '../src/client/chrome.tsx'
|
||||
import { SettingsDocumentAction } from '../src/client/SettingsDocumentAction.tsx'
|
||||
@@ -58,6 +60,28 @@ describe('GeneralSection', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('AboutSection', () => {
|
||||
const aboutProps: AboutSectionComponentProps = { close: vi.fn(), t } as never
|
||||
|
||||
it('renders the company copy and the current version', () => {
|
||||
render(<AboutSection {...aboutProps} />)
|
||||
expect(screen.getByText('PineSound')).toBeTruthy()
|
||||
expect(screen.getByText('AI audio creation platform')).toBeTruthy()
|
||||
expect(screen.getByText(en['about.currentVersion'])).toBeTruthy()
|
||||
expect(screen.getByText('0.1.0-rc.5')).toBeTruthy()
|
||||
expect(screen.getByRole('button', { name: en['about.checkUpdates'] })).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checks for updates and reports the latest version', async () => {
|
||||
render(<AboutSection {...aboutProps} />)
|
||||
fireEvent.click(screen.getByRole('button', { name: en['about.checkUpdates'] }))
|
||||
expect(screen.getByText(en['about.checking'])).toBeTruthy()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('status').textContent).toContain(en['about.upToDate'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('SettingsDocumentAction', () => {
|
||||
it('appears only for a file-backed provider and requests its Host-owned document', async () => {
|
||||
const openDocument = vi.fn(() => Promise.resolve({
|
||||
|
||||
@@ -76,10 +76,11 @@ describe('ui-settings apply', () => {
|
||||
declare(b.slots)
|
||||
await b.ctx.plugin({ inject: [...inject], apply }).await()
|
||||
const { sections } = injectedOf(b.slots).hooks
|
||||
// This package registers the General section itself; every other section
|
||||
// arrives from a feature registrant.
|
||||
// This package registers the General and About sections itself; every
|
||||
// other section arrives from a feature registrant.
|
||||
const GENERAL = { id: 'general', order: 0, label: 'general.nav' }
|
||||
expect(sections.getSnapshot()).toEqual([GENERAL])
|
||||
const ABOUT = { id: 'about', order: 100, label: 'about.nav' }
|
||||
expect(sections.getSnapshot()).toEqual([GENERAL, ABOUT])
|
||||
b.slots.register({ name: 'settings.section', id: 'z', order: 20, label: 'Z' } as never, () => null)
|
||||
// No order and no label: both projection defaults apply.
|
||||
b.slots.register({ name: 'settings.section', id: 'a' } as never, () => null)
|
||||
@@ -88,6 +89,7 @@ describe('ui-settings apply', () => {
|
||||
GENERAL,
|
||||
{ id: 'a', order: 0, label: '' },
|
||||
{ id: 'z', order: 20, label: 'Z' },
|
||||
ABOUT,
|
||||
])
|
||||
// Snapshot identity is stable until the ledger moves (uSES contract).
|
||||
expect(sections.getSnapshot()).toBe(rows)
|
||||
|
||||
Reference in New Issue
Block a user