diff --git a/src/renderer/src/components/export/ExportConfigurationPanel.tsx b/src/renderer/src/components/export/ExportConfigurationPanel.tsx index a4c2cc6..02e503a 100644 --- a/src/renderer/src/components/export/ExportConfigurationPanel.tsx +++ b/src/renderer/src/components/export/ExportConfigurationPanel.tsx @@ -1,7 +1,15 @@ import React from 'react' import type { ExportContactType, ExportNameMode } from '../../../../shared/export' import type { VoiceModelStatus } from '../../../../shared/voice-recognition' -import { Button, Checkbox, Input, RadioGroup, RadioGroupItem } from '../ui' +import { + Button, + Checkbox, + Input, + RadioGroup, + RadioGroupItem, + SegmentedControl, + SegmentedControlItem +} from '../ui' import type { Contact, ExportFormat, ExportRange, ExportStatus } from './exportTypes' import { displayName, formatLabels, formatOrder, messageKinds } from './exportUtils' @@ -167,19 +175,19 @@ export function ExportConfigurationPanel({

导出格式

-
+ onFormatChange(value as ExportFormat)} + aria-label="导出格式" + > {formatOrder.map((value) => { - const active = format === value return ( - + ) })} -
+

{exportAll ? '全部导出固定使用全部时间;每个群聊或联系人都会在自己的目录中生成所选格式的独立档案。' @@ -229,7 +237,12 @@ export function ExportConfigurationPanel({ {status === 'completed' ? '已完成导出' : '消息数量将在开始导出后统计'} -

+ onRangeChange(value as ExportRange)} + aria-label="时间范围" + > {(exportAll ? [['all', '全部时间']] : [ @@ -240,17 +253,11 @@ export function ExportConfigurationPanel({ ['custom', '自定义时间'] ] ).map(([value, label]) => ( - + ))} -
+ {!exportAll && range === 'custom' && (
diff --git a/src/renderer/src/features/api-center/components/ReaderSkillOverview.tsx b/src/renderer/src/features/api-center/components/ReaderSkillOverview.tsx index 0dee7f4..4148487 100644 --- a/src/renderer/src/features/api-center/components/ReaderSkillOverview.tsx +++ b/src/renderer/src/features/api-center/components/ReaderSkillOverview.tsx @@ -65,7 +65,7 @@ export function ReaderSkillOverview({ - diff --git a/tests/component/export-voice-transcript.test.tsx b/tests/component/export-voice-transcript.test.tsx index a04f6cf..58a4e53 100644 --- a/tests/component/export-voice-transcript.test.tsx +++ b/tests/component/export-voice-transcript.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vitest' import { ExportWorkspace } from '../../src/renderer/src/components/export/ExportWorkspace' @@ -56,7 +56,11 @@ describe('export voice transcripts', () => { /> ) - await userEvent.click(screen.getAllByRole('button', { name: /HTML/ })[0]) + await userEvent.click( + within(screen.getByRole('radiogroup', { name: '导出格式' })).getByRole('radio', { + name: /HTML/ + }) + ) await userEvent.click(screen.getByRole('checkbox', { name: '语音' })) const transcriptOption = await screen.findByRole('checkbox', { diff --git a/tests/component/export-workspace.test.tsx b/tests/component/export-workspace.test.tsx index 37a7127..79348df 100644 --- a/tests/component/export-workspace.test.tsx +++ b/tests/component/export-workspace.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vitest' import { ExportWorkspace } from '../../src/renderer/src/components/export/ExportWorkspace' @@ -64,17 +64,21 @@ describe('ExportWorkspace multi-chat selection', () => { const { loadPreviewMessages } = renderWorkspace(onStartExport) expect(screen.getAllByText('聊天 A')).toHaveLength(2) - expect(screen.getByRole('button', { name: 'CSV' })).toBeEnabled() + expect(screen.getByRole('radio', { name: 'CSV' })).toBeEnabled() expect(await screen.findByText('聊天 A 的预览')).toBeVisible() await userEvent.click(screen.getByRole('button', { name: '+ 添加聊天' })) await userEvent.click(screen.getByRole('button', { name: /聊天 B/ })) expect(screen.getByText('已选 2 / 5 个')).toBeVisible() - expect(screen.getByRole('button', { name: 'CSV' })).toBeDisabled() - expect(screen.getByRole('button', { name: 'JSON' })).toBeDisabled() - expect(screen.getByRole('button', { name: 'Markdown' })).toBeDisabled() - expect(screen.getByRole('button', { name: /HTML/ })).toHaveClass('active') + expect(screen.getByRole('radio', { name: 'CSV' })).toBeDisabled() + expect(screen.getByRole('radio', { name: 'JSON' })).toBeDisabled() + expect(screen.getByRole('radio', { name: 'Markdown' })).toBeDisabled() + expect( + within(screen.getByRole('radiogroup', { name: '导出格式' })).getByRole('radio', { + name: /HTML/ + }) + ).toBeChecked() expect(await screen.findByText('聊天 B 的预览')).toBeVisible() expect(screen.getByText('2 个聊天 · 合并预览')).toBeVisible() @@ -91,7 +95,7 @@ describe('ExportWorkspace multi-chat selection', () => { await userEvent.click(screen.getByRole('button', { name: '恢复默认' })) expect(screen.queryByText('已选 2 / 5 个')).not.toBeInTheDocument() - expect(screen.getByRole('button', { name: 'CSV' })).toBeEnabled() + expect(screen.getByRole('radio', { name: 'CSV' })).toBeEnabled() expect(screen.getAllByText('聊天 A').length).toBeGreaterThanOrEqual(2) expect(loadPreviewMessages).toHaveBeenCalledWith(contacts[0]) expect(contacts[0].md5).toBe('contact-1') @@ -123,10 +127,10 @@ describe('ExportWorkspace multi-chat selection', () => { await userEvent.click(screen.getByRole('button', { name: /全部导出/ })) expect(screen.getByText(/全部群聊 1 个和全部联系人 5 个/)).toBeVisible() - expect(screen.getByRole('button', { name: 'CSV' })).toBeEnabled() - expect(screen.getByRole('button', { name: 'CSV' })).toHaveClass('active') - expect(screen.getByRole('button', { name: 'JSON' })).toBeEnabled() - expect(screen.getByRole('button', { name: 'Markdown' })).toBeEnabled() + expect(screen.getByRole('radio', { name: 'CSV' })).toBeEnabled() + expect(screen.getByRole('radio', { name: 'CSV' })).toBeChecked() + expect(screen.getByRole('radio', { name: 'JSON' })).toBeEnabled() + expect(screen.getByRole('radio', { name: 'Markdown' })).toBeEnabled() expect(loadPreviewMessages).toHaveBeenCalledTimes(1) await userEvent.click(screen.getByRole('button', { name: '开始导出' })) @@ -203,7 +207,7 @@ describe('ExportWorkspace multi-chat selection', () => { const onStartExport = vi.fn(async () => ({ success: false })) renderWorkspace(onStartExport) - await userEvent.click(screen.getByRole('button', { name: '自定义时间' })) + await userEvent.click(screen.getByRole('radio', { name: '自定义时间' })) await userEvent.type(screen.getByLabelText('开始时间'), '2026-08-01T09:30') await userEvent.type(screen.getByLabelText('结束时间'), '2026-08-02T18:45') await userEvent.click(screen.getByRole('button', { name: '选择位置' })) diff --git a/tests/component/ui-control-states.test.tsx b/tests/component/ui-control-states.test.tsx index e46532c..ebc5823 100644 --- a/tests/component/ui-control-states.test.tsx +++ b/tests/component/ui-control-states.test.tsx @@ -14,6 +14,11 @@ import { SelectValue, Switch } from '../../src/renderer/src/components/ui' +import { IconButton } from '../../src/renderer/src/components/ui/icon-button' +import { + SegmentedControl, + SegmentedControlItem +} from '../../src/renderer/src/components/ui/segmented-control' describe('TraceMemo UI control states', () => { it('uses the compact, standard, and form control height contract', () => { @@ -32,6 +37,43 @@ describe('TraceMemo UI control states', () => { expect(screen.getByRole('textbox', { name: '表单输入' })).toHaveClass('h-control-form') }) + it('keeps secondary and icon actions visually quiet by default', () => { + render( + <> + + + ↻ + + + ) + + expect(screen.getByRole('button', { name: '次要操作' })).toHaveClass( + 'border-border-subtle', + 'bg-transparent' + ) + expect(screen.getByRole('button', { name: '刷新' })).toHaveClass( + 'text-foreground', + 'hover:bg-accent' + ) + expect(screen.getByRole('button', { name: '刷新' })).not.toHaveClass('bg-primary') + }) + + it('uses a surface-free selected contract for segmented choices', () => { + render( + + + + + ) + + const selected = screen.getByRole('radio', { name: '一' }) + expect(selected).toHaveClass( + 'data-[state=checked]:bg-accent', + 'data-[state=checked]:font-semibold' + ) + expect(selected).not.toHaveClass('data-[state=checked]:shadow-surface') + }) + it('keeps disabled controls readable without the old opacity and surface shadow', () => { render( <> diff --git a/tests/e2e/app.spec.ts b/tests/e2e/app.spec.ts index b78cbf5..ea1ce38 100644 --- a/tests/e2e/app.spec.ts +++ b/tests/e2e/app.spec.ts @@ -631,8 +631,11 @@ test('EXPORT-01 multi-chat selection stays local to export and forces HTML', asy await contactList.getByRole('button', { name: /产品测试群/ }).click() await expect(fixture.page.getByText('已选 2 / 5 个')).toBeVisible() - await expect(fixture.page.getByRole('button', { name: 'CSV' })).toBeDisabled() - await expect(fixture.page.getByRole('button', { name: /HTML/ })).toHaveClass(/active/) + await expect(fixture.page.getByRole('radio', { name: 'CSV' })).toBeDisabled() + const htmlFormat = fixture.page + .getByRole('radiogroup', { name: '导出格式' }) + .getByRole('radio', { name: /HTML/ }) + await expect(htmlFormat).toBeChecked() await expect(fixture.page.getByText('文件传输助手、产品测试群 · 共 2 个聊天')).toBeVisible() await expect( fixture.page.locator('.export-preview-bubble').filter({ hasText: '这是一条脱敏测试消息' }) @@ -663,7 +666,9 @@ test('EXPORT-02 large contact list stays bounded and searchable', async () => { const compositeButtons = [ fixture.page.getByRole('button', { name: /^全部导出/ }), - fixture.page.getByRole('button', { name: /HTML/ }), + fixture.page + .getByRole('radiogroup', { name: '导出格式' }) + .getByRole('radio', { name: /HTML/ }), fixture.page.locator('.export-workspace > aside:first-child > button:last-child') ] for (const button of compositeButtons) {