mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-21 21:47:00 +08:00
feat: 优化全局 UI 交互与页面视觉体验
统一按钮、输入框、选择控件和开关的交互状态 优化日报、导出、设置、Agent Hub 和 API Center 的UI 完善组件、单元及 Electron E2E 测试
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -307,6 +307,7 @@ test('SETTINGS-02 basic settings controls fit a narrow viewport and keep their s
|
||||
.click()
|
||||
|
||||
await expect(fixture.page.getByRole('heading', { name: '账号与数据库' })).toBeVisible()
|
||||
await expect(fixture.page.getByRole('button', { name: '存储与导出' })).toHaveCount(0)
|
||||
const autoLoginSwitch = fixture.page.getByRole('switch', { name: '启动时自动连接数据库' })
|
||||
await expect(autoLoginSwitch).toBeVisible()
|
||||
await autoLoginSwitch.click()
|
||||
@@ -438,6 +439,12 @@ test('SETTINGS-05 AI model editor keeps provider and capability semantics', asyn
|
||||
await expect(fixture.page.getByRole('heading', { name: 'AI 模型' })).toBeVisible()
|
||||
await expect(fixture.page.getByRole('button', { name: '当前默认' })).toBeDisabled()
|
||||
|
||||
await fixture.page.getByRole('button', { name: '编辑' }).click()
|
||||
await expect(fixture.page.getByRole('heading', { name: '编辑供应商' })).toBeVisible()
|
||||
await expect(fixture.page.getByLabel('供应商名称')).toBeFocused()
|
||||
await expect(fixture.page.locator('#ai-provider-editor')).toBeInViewport()
|
||||
await fixture.page.getByRole('button', { name: '关闭' }).click()
|
||||
|
||||
await fixture.page.getByRole('button', { name: '添加供应商' }).click()
|
||||
await expect(fixture.page.getByRole('heading', { name: '新增供应商' })).toBeVisible()
|
||||
await expect(fixture.page.getByLabel('供应商名称')).toBeFocused()
|
||||
@@ -493,6 +500,7 @@ test('SETTINGS-07 voice recognition controls keep selection semantics at a narro
|
||||
|
||||
const categoryTabs = fixture.page.getByRole('tablist', { name: '会话类别' })
|
||||
await categoryTabs.scrollIntoViewIfNeeded()
|
||||
await expect(fixture.page.locator('.voice-conversation-avatar img')).toHaveCount(1)
|
||||
await expect(categoryTabs.getByRole('tab', { name: /群聊/ })).toHaveAttribute(
|
||||
'data-state',
|
||||
'active'
|
||||
@@ -826,6 +834,15 @@ test('ASK-01 uses the local fixed AI service and keeps evidence in the UI', asyn
|
||||
await expect(fixture.page.getByText(/固定假回答:测试数据中的核心流程正常/)).toBeVisible({
|
||||
timeout: 15_000
|
||||
})
|
||||
|
||||
await fixture.page.getByRole('button', { name: /历史提问/ }).click()
|
||||
await fixture.page.getByRole('button', { name: '测试群讨论了什么?', exact: true }).click()
|
||||
const toastClose = fixture.page.getByRole('button', { name: '关闭通知' })
|
||||
await expect(toastClose).toBeVisible()
|
||||
expect(await toastClose.boundingBox()).toMatchObject({ width: 28, height: 28 })
|
||||
await toastClose.click()
|
||||
await expect(toastClose).toHaveCount(0)
|
||||
|
||||
expect(
|
||||
await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)
|
||||
).toBe(true)
|
||||
|
||||
@@ -55,6 +55,7 @@ export async function launchTestApp(
|
||||
})
|
||||
const page = await app.firstWindow()
|
||||
await page.waitForLoadState('domcontentloaded')
|
||||
if (options.now) await page.clock.setFixedTime(options.now)
|
||||
const setWindowContentSize = async (size: { width: number; height: number }): Promise<void> => {
|
||||
await app.evaluate(({ BrowserWindow, screen }, nextSize) => {
|
||||
const [window] = BrowserWindow.getAllWindows()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { SETTINGS_NAVIGATION } from '../../src/renderer/src/features/settings/model/settingsNavigation'
|
||||
import {
|
||||
SETTINGS_CATEGORY_LABELS,
|
||||
SETTINGS_NAVIGATION
|
||||
} from '../../src/renderer/src/features/settings/model/settingsNavigation'
|
||||
|
||||
describe('settings navigation', () => {
|
||||
it('places text-to-speech under intelligent capabilities', () => {
|
||||
@@ -10,4 +13,11 @@ describe('settings navigation', () => {
|
||||
'ai-model'
|
||||
])
|
||||
})
|
||||
|
||||
it('temporarily hides storage and export from visible navigation', () => {
|
||||
expect(SETTINGS_NAVIGATION.flatMap((group) => group.items)).not.toEqual(
|
||||
expect.arrayContaining([{ id: 'storage-export', label: '存储与导出' }])
|
||||
)
|
||||
expect(SETTINGS_CATEGORY_LABELS['storage-export']).toBe('存储与导出')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user