feat: 优化全局 UI 交互与页面视觉体验

统一按钮、输入框、选择控件和开关的交互状态
优化日报、导出、设置、Agent Hub 和 API Center 的UI
完善组件、单元及 Electron E2E 测试
This commit is contained in:
Wxw-Gu
2026-08-21 15:05:06 +08:00
parent 67b1df7e80
commit 262b15db84
41 changed files with 245 additions and 225 deletions
@@ -74,6 +74,23 @@ describe('AI model settings', () => {
expect(screen.getByLabelText('模型 ID')).toHaveAttribute('placeholder', '例如:deepseek-chat')
})
it('reveals and focuses the provider editor when editing an existing provider', async () => {
vi.mocked(window.api.listAIProviders).mockResolvedValue({
success: true,
providers: [provider]
})
render(<AIModelPage onRuntimeChange={vi.fn()} onNotice={vi.fn()} />)
await act(async () => {
await Promise.resolve()
})
await userEvent.click(screen.getByRole('button', { name: '编辑' }))
expect(scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth', block: 'start' })
expect(screen.getByLabelText('供应商名称')).toHaveFocus()
expect(screen.getByLabelText('供应商名称')).toHaveValue(provider.name)
})
it('does not reload providers when the parent callback identity changes', async () => {
const firstRuntimeChange = vi.fn()
const secondRuntimeChange = vi.fn()
+3 -3
View File
@@ -11,8 +11,8 @@ describe('empty states', () => {
})
it('labels an unavailable settings section explicitly', () => {
render(<SettingsEmptyState label="测试设置" />)
expect(screen.getByRole('heading', { name: '测试设置' })).toBeVisible()
expect(screen.getByText('该设置将在后续阶段接入。')).toBeVisible()
render(<SettingsEmptyState label="存储与导出" description="请前往导出工作区设置" />)
expect(screen.getByRole('heading', { name: '存储与导出' })).toBeVisible()
expect(screen.getByText('请前往导出工作区设置。')).toBeVisible()
})
})
+15 -1
View File
@@ -12,7 +12,8 @@ import {
SelectItem,
SelectTrigger,
SelectValue,
Switch
Switch,
Textarea
} from '../../src/renderer/src/components/ui'
import { IconButton } from '../../src/renderer/src/components/ui/icon-button'
import {
@@ -79,6 +80,7 @@ describe('TraceMemo UI control states', () => {
<>
<Button disabled></Button>
<Input aria-label="不可用输入" value="仍可读取" disabled readOnly />
<Textarea aria-label="不可用多行输入" value="仍可读取" disabled readOnly />
<Checkbox aria-label="不可用多选" disabled />
<RadioGroup aria-label="不可用单选组">
<RadioGroupItem value="disabled" aria-label="不可用单选" disabled />
@@ -90,6 +92,7 @@ describe('TraceMemo UI control states', () => {
for (const control of [
screen.getByRole('button', { name: '不可用操作' }),
screen.getByRole('textbox', { name: '不可用输入' }),
screen.getByRole('textbox', { name: '不可用多行输入' }),
screen.getByRole('checkbox', { name: '不可用多选' }),
screen.getByRole('radio', { name: '不可用单选' }),
screen.getByRole('switch', { name: '不可用开关' })
@@ -103,6 +106,15 @@ describe('TraceMemo UI control states', () => {
'disabled:text-disabled-foreground',
'disabled:!text-disabled-foreground'
)
expect(screen.getByRole('textbox', { name: '不可用多行输入' })).toHaveClass(
'border-border-subtle',
'disabled:border-disabled-border',
'disabled:bg-disabled-surface',
'disabled:text-disabled-foreground'
)
expect(screen.getByRole('textbox', { name: '不可用多行输入' })).not.toHaveClass(
'shadow-surface'
)
expect(screen.getByRole('checkbox', { name: '不可用多选' })).toHaveClass(
'border-muted-foreground/60'
)
@@ -148,6 +160,8 @@ describe('TraceMemo UI control states', () => {
expect(screen.getByRole('radio', { name: '舒适密度' })).toBeChecked()
const toggle = screen.getByRole('switch', { name: '显示头像' })
expect(toggle).toHaveClass('h-5', 'w-9', 'border-0', 'p-0.5')
expect(toggle.firstElementChild).toHaveClass('h-4', 'w-4')
await user.click(toggle)
expect(toggle).toBeChecked()
})
@@ -79,5 +79,10 @@ describe('TraceMemo UI infrastructure', () => {
await user.click(screen.getByRole('button', { name: '显示通知' }))
expect(screen.getByText('已保存')).toBeVisible()
expect(screen.getByText('设置已更新')).toBeVisible()
const close = screen.getByRole('button', { name: '关闭通知' })
expect(close).toHaveClass('h-7', 'w-7', 'border-0', 'bg-transparent')
await user.click(close)
expect(screen.queryByText('已保存')).not.toBeInTheDocument()
})
})
@@ -93,7 +93,13 @@ describe('voice recognition settings', () => {
...window.api,
getContacts: vi.fn().mockResolvedValue([
{ md5: 'contact-a', m_nsNickName: '联系人 A', m_nsUsrName: 'contact-a', type: 'user' },
{ md5: 'group-b', m_nsNickName: '群聊 B', m_nsUsrName: 'group-b@chatroom', type: 'group' }
{
md5: 'group-b',
m_nsNickName: '群聊 B',
m_nsUsrName: 'group-b@chatroom',
type: 'group',
avatar: 'data:image/png;base64,voice-fixture-avatar'
}
]),
getSelf: vi.fn().mockResolvedValue({
ready: true,
@@ -142,6 +148,9 @@ describe('voice recognition settings', () => {
expect(await screen.findByText('群聊 B')).toBeInTheDocument()
expect(await screen.findByText('7 条语音')).toBeInTheDocument()
expect(
document.querySelector('img[src="data:image/png;base64,voice-fixture-avatar"]')
).toBeInTheDocument()
expect(window.api.getVoiceBatchConversationSummaries).toHaveBeenCalledWith({
conversationIds: ['group-b'],
range: 'recent_30_days'