mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-21 21:47:00 +08:00
feat: 统一按钮
This commit is contained in:
@@ -1,7 +1,15 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import type { ExportContactType, ExportNameMode } from '../../../../shared/export'
|
import type { ExportContactType, ExportNameMode } from '../../../../shared/export'
|
||||||
import type { VoiceModelStatus } from '../../../../shared/voice-recognition'
|
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 type { Contact, ExportFormat, ExportRange, ExportStatus } from './exportTypes'
|
||||||
import { displayName, formatLabels, formatOrder, messageKinds } from './exportUtils'
|
import { displayName, formatLabels, formatOrder, messageKinds } from './exportUtils'
|
||||||
|
|
||||||
@@ -167,19 +175,19 @@ export function ExportConfigurationPanel({
|
|||||||
|
|
||||||
<section className={sectionClassName}>
|
<section className={sectionClassName}>
|
||||||
<h3 className={sectionTitleClassName}>导出格式</h3>
|
<h3 className={sectionTitleClassName}>导出格式</h3>
|
||||||
<div className="grid grid-cols-2 gap-2 min-[900px]:grid-cols-4">
|
<SegmentedControl
|
||||||
|
className="grid w-full grid-cols-2 gap-1.5 p-1.5 min-[900px]:grid-cols-4"
|
||||||
|
value={format}
|
||||||
|
onValueChange={(value) => onFormatChange(value as ExportFormat)}
|
||||||
|
aria-label="导出格式"
|
||||||
|
>
|
||||||
{formatOrder.map((value) => {
|
{formatOrder.map((value) => {
|
||||||
const active = format === value
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<SegmentedControlItem
|
||||||
key={value}
|
key={value}
|
||||||
variant="outline"
|
value={value}
|
||||||
aria-pressed={active}
|
className="!h-[78px] min-w-0 flex-col gap-1.5 whitespace-normal rounded-md px-2 text-foreground data-[state=checked]:text-primary"
|
||||||
className={`${
|
|
||||||
active ? 'active border-2 border-primary bg-primary/10 hover:bg-primary/15' : ''
|
|
||||||
} !h-[78px] min-w-0 flex-col gap-1.5 whitespace-normal px-2 text-foreground`}
|
|
||||||
disabled={!exportAll && exportContactCount > 1 && value !== 'html'}
|
disabled={!exportAll && exportContactCount > 1 && value !== 'html'}
|
||||||
onClick={() => onFormatChange(value)}
|
|
||||||
>
|
>
|
||||||
<strong className="text-[13px]">{formatLabels[value].label}</strong>
|
<strong className="text-[13px]">{formatLabels[value].label}</strong>
|
||||||
{formatLabels[value].hint && (
|
{formatLabels[value].hint && (
|
||||||
@@ -187,10 +195,10 @@ export function ExportConfigurationPanel({
|
|||||||
{formatLabels[value].hint}
|
{formatLabels[value].hint}
|
||||||
</small>
|
</small>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</SegmentedControlItem>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</SegmentedControl>
|
||||||
<p className={helperClassName}>
|
<p className={helperClassName}>
|
||||||
{exportAll
|
{exportAll
|
||||||
? '全部导出固定使用全部时间;每个群聊或联系人都会在自己的目录中生成所选格式的独立档案。'
|
? '全部导出固定使用全部时间;每个群聊或联系人都会在自己的目录中生成所选格式的独立档案。'
|
||||||
@@ -229,7 +237,12 @@ export function ExportConfigurationPanel({
|
|||||||
{status === 'completed' ? '已完成导出' : '消息数量将在开始导出后统计'}
|
{status === 'completed' ? '已完成导出' : '消息数量将在开始导出后统计'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<SegmentedControl
|
||||||
|
className="grid w-full grid-cols-2 gap-1.5 p-1.5 min-[900px]:grid-cols-3"
|
||||||
|
value={range}
|
||||||
|
onValueChange={(value) => onRangeChange(value as ExportRange)}
|
||||||
|
aria-label="时间范围"
|
||||||
|
>
|
||||||
{(exportAll
|
{(exportAll
|
||||||
? [['all', '全部时间']]
|
? [['all', '全部时间']]
|
||||||
: [
|
: [
|
||||||
@@ -240,17 +253,11 @@ export function ExportConfigurationPanel({
|
|||||||
['custom', '自定义时间']
|
['custom', '自定义时间']
|
||||||
]
|
]
|
||||||
).map(([value, label]) => (
|
).map(([value, label]) => (
|
||||||
<Button
|
<SegmentedControlItem key={value} value={value} className="w-full">
|
||||||
key={value}
|
|
||||||
variant={range === value ? 'default' : 'outline'}
|
|
||||||
aria-pressed={range === value}
|
|
||||||
className={range === value ? 'active' : ''}
|
|
||||||
onClick={() => onRangeChange(value as ExportRange)}
|
|
||||||
>
|
|
||||||
{label}
|
{label}
|
||||||
</Button>
|
</SegmentedControlItem>
|
||||||
))}
|
))}
|
||||||
</div>
|
</SegmentedControl>
|
||||||
{!exportAll && range === 'custom' && (
|
{!exportAll && range === 'custom' && (
|
||||||
<div className="mt-2.5 grid grid-cols-2 gap-2.5 rounded-lg bg-muted p-3">
|
<div className="mt-2.5 grid grid-cols-2 gap-2.5 rounded-lg bg-muted p-3">
|
||||||
<label className="grid gap-1.5 text-[11px] text-muted-foreground">
|
<label className="grid gap-1.5 text-[11px] text-muted-foreground">
|
||||||
@@ -295,23 +302,22 @@ export function ExportConfigurationPanel({
|
|||||||
|
|
||||||
<section className={sectionClassName}>
|
<section className={sectionClassName}>
|
||||||
<h3 className={sectionTitleClassName}>消息显示名称</h3>
|
<h3 className={sectionTitleClassName}>消息显示名称</h3>
|
||||||
<RadioGroup
|
<SegmentedControl
|
||||||
className="flex gap-2 rounded-lg bg-muted p-2.5"
|
className="grid w-full grid-cols-3 gap-1.5 p-1.5"
|
||||||
value={nameMode}
|
value={nameMode}
|
||||||
onValueChange={(value) => onNameModeChange(value as ExportNameMode)}
|
onValueChange={(value) => onNameModeChange(value as ExportNameMode)}
|
||||||
aria-label="消息显示名称"
|
aria-label="消息显示名称"
|
||||||
>
|
>
|
||||||
{nameOptions.map((option) => (
|
{nameOptions.map((option) => (
|
||||||
<label
|
<SegmentedControlItem
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className="flex min-h-8 flex-1 cursor-pointer items-center gap-2 rounded-md border border-border bg-surface px-2 text-xs text-foreground"
|
value={option.value}
|
||||||
htmlFor={`export-name-${option.value}`}
|
className="w-full justify-start gap-2 px-2 text-foreground data-[state=checked]:text-primary"
|
||||||
>
|
>
|
||||||
<RadioGroupItem id={`export-name-${option.value}`} value={option.value} />
|
|
||||||
<span>{option.label}</span>
|
<span>{option.label}</span>
|
||||||
</label>
|
</SegmentedControlItem>
|
||||||
))}
|
))}
|
||||||
</RadioGroup>
|
</SegmentedControl>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className={sectionClassName}>
|
<section className={sectionClassName}>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export function ReportToolbar({
|
|||||||
</Button>
|
</Button>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="outline" size="sm">
|
<Button variant="ghost" size="sm">
|
||||||
更多
|
更多
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|||||||
@@ -681,7 +681,7 @@ export function AISearchWorkspace({
|
|||||||
新问题
|
新问题
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="px-2"
|
className="px-2"
|
||||||
onClick={() => void copyAnswer()}
|
onClick={() => void copyAnswer()}
|
||||||
@@ -690,7 +690,7 @@ export function AISearchWorkspace({
|
|||||||
复制摘要
|
复制摘要
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="px-2"
|
className="px-2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const buttonVariants = cva(
|
|||||||
ghost:
|
ghost:
|
||||||
'text-foreground hover:bg-accent hover:text-accent-foreground active:bg-accent/75 disabled:text-disabled-foreground',
|
'text-foreground hover:bg-accent hover:text-accent-foreground active:bg-accent/75 disabled:text-disabled-foreground',
|
||||||
outline:
|
outline:
|
||||||
'border border-border bg-surface text-foreground hover:border-primary/25 hover:bg-accent active:bg-accent/75 disabled:border-disabled-border disabled:bg-disabled-surface disabled:text-disabled-foreground',
|
'border border-border-subtle bg-transparent text-foreground hover:border-primary/20 hover:bg-accent active:border-primary/30 active:bg-accent/75 disabled:border-disabled-border disabled:bg-disabled-surface disabled:text-disabled-foreground',
|
||||||
destructive:
|
destructive:
|
||||||
'bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80 disabled:bg-disabled-surface disabled:text-disabled-foreground',
|
'bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80 disabled:bg-disabled-surface disabled:text-disabled-foreground',
|
||||||
link: '!h-auto !p-0 text-primary underline-offset-4 hover:text-primary-hover hover:underline disabled:text-disabled-foreground'
|
link: '!h-auto !p-0 text-primary underline-offset-4 hover:text-primary-hover hover:underline disabled:text-disabled-foreground'
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ export function IconButton({
|
|||||||
label,
|
label,
|
||||||
tooltip = label,
|
tooltip = label,
|
||||||
children,
|
children,
|
||||||
|
variant = 'ghost',
|
||||||
...props
|
...props
|
||||||
}: IconButtonProps): React.ReactElement {
|
}: IconButtonProps): React.ReactElement {
|
||||||
const button = (
|
const button = (
|
||||||
<Button aria-label={label} size="icon" {...props}>
|
<Button aria-label={label} size="icon" variant={variant} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const SegmentedControlItem = React.forwardRef<
|
|||||||
<RadioGroupPrimitive.Item
|
<RadioGroupPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex h-8 min-w-0 items-center justify-center rounded-sm border-0 bg-transparent px-3 text-xs font-medium text-muted-foreground transition-colors duration-fast ease-tm-standard hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-surface data-[state=checked]:text-primary data-[state=checked]:shadow-surface',
|
'inline-flex h-8 min-w-0 items-center justify-center rounded-sm border-0 bg-transparent px-3 text-xs font-medium text-muted-foreground transition-[color,background-color,font-weight] duration-fast ease-tm-standard hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:text-disabled-foreground disabled:opacity-100 data-[state=checked]:bg-accent data-[state=checked]:font-semibold data-[state=checked]:text-accent-foreground',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -254,14 +254,14 @@ export function AgentHubWorkspace(): React.ReactElement {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => void copyLogs()}
|
onClick={() => void copyLogs()}
|
||||||
disabled={visibleLogs.length === 0}
|
disabled={visibleLogs.length === 0}
|
||||||
>
|
>
|
||||||
复制日志
|
复制日志
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" size="sm" onClick={() => void clearLogs()}>
|
<Button variant="ghost" size="sm" onClick={() => void clearLogs()}>
|
||||||
清空
|
清空
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function ReaderSkillOverview({
|
|||||||
</Button>
|
</Button>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button size="sm" variant="outline">
|
<Button size="sm" variant="ghost">
|
||||||
更多
|
更多
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|||||||
@@ -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 userEvent from '@testing-library/user-event'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { ExportWorkspace } from '../../src/renderer/src/components/export/ExportWorkspace'
|
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: '语音' }))
|
await userEvent.click(screen.getByRole('checkbox', { name: '语音' }))
|
||||||
|
|
||||||
const transcriptOption = await screen.findByRole('checkbox', {
|
const transcriptOption = await screen.findByRole('checkbox', {
|
||||||
|
|||||||
@@ -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 userEvent from '@testing-library/user-event'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { ExportWorkspace } from '../../src/renderer/src/components/export/ExportWorkspace'
|
import { ExportWorkspace } from '../../src/renderer/src/components/export/ExportWorkspace'
|
||||||
@@ -64,17 +64,21 @@ describe('ExportWorkspace multi-chat selection', () => {
|
|||||||
const { loadPreviewMessages } = renderWorkspace(onStartExport)
|
const { loadPreviewMessages } = renderWorkspace(onStartExport)
|
||||||
|
|
||||||
expect(screen.getAllByText('聊天 A')).toHaveLength(2)
|
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()
|
expect(await screen.findByText('聊天 A 的预览')).toBeVisible()
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: '+ 添加聊天' }))
|
await userEvent.click(screen.getByRole('button', { name: '+ 添加聊天' }))
|
||||||
await userEvent.click(screen.getByRole('button', { name: /聊天 B/ }))
|
await userEvent.click(screen.getByRole('button', { name: /聊天 B/ }))
|
||||||
|
|
||||||
expect(screen.getByText('已选 2 / 5 个')).toBeVisible()
|
expect(screen.getByText('已选 2 / 5 个')).toBeVisible()
|
||||||
expect(screen.getByRole('button', { name: 'CSV' })).toBeDisabled()
|
expect(screen.getByRole('radio', { name: 'CSV' })).toBeDisabled()
|
||||||
expect(screen.getByRole('button', { name: 'JSON' })).toBeDisabled()
|
expect(screen.getByRole('radio', { name: 'JSON' })).toBeDisabled()
|
||||||
expect(screen.getByRole('button', { name: 'Markdown' })).toBeDisabled()
|
expect(screen.getByRole('radio', { name: 'Markdown' })).toBeDisabled()
|
||||||
expect(screen.getByRole('button', { name: /HTML/ })).toHaveClass('active')
|
expect(
|
||||||
|
within(screen.getByRole('radiogroup', { name: '导出格式' })).getByRole('radio', {
|
||||||
|
name: /HTML/
|
||||||
|
})
|
||||||
|
).toBeChecked()
|
||||||
expect(await screen.findByText('聊天 B 的预览')).toBeVisible()
|
expect(await screen.findByText('聊天 B 的预览')).toBeVisible()
|
||||||
expect(screen.getByText('2 个聊天 · 合并预览')).toBeVisible()
|
expect(screen.getByText('2 个聊天 · 合并预览')).toBeVisible()
|
||||||
|
|
||||||
@@ -91,7 +95,7 @@ describe('ExportWorkspace multi-chat selection', () => {
|
|||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: '恢复默认' }))
|
await userEvent.click(screen.getByRole('button', { name: '恢复默认' }))
|
||||||
expect(screen.queryByText('已选 2 / 5 个')).not.toBeInTheDocument()
|
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(screen.getAllByText('聊天 A').length).toBeGreaterThanOrEqual(2)
|
||||||
expect(loadPreviewMessages).toHaveBeenCalledWith(contacts[0])
|
expect(loadPreviewMessages).toHaveBeenCalledWith(contacts[0])
|
||||||
expect(contacts[0].md5).toBe('contact-1')
|
expect(contacts[0].md5).toBe('contact-1')
|
||||||
@@ -123,10 +127,10 @@ describe('ExportWorkspace multi-chat selection', () => {
|
|||||||
await userEvent.click(screen.getByRole('button', { name: /全部导出/ }))
|
await userEvent.click(screen.getByRole('button', { name: /全部导出/ }))
|
||||||
|
|
||||||
expect(screen.getByText(/全部群聊 1 个和全部联系人 5 个/)).toBeVisible()
|
expect(screen.getByText(/全部群聊 1 个和全部联系人 5 个/)).toBeVisible()
|
||||||
expect(screen.getByRole('button', { name: 'CSV' })).toBeEnabled()
|
expect(screen.getByRole('radio', { name: 'CSV' })).toBeEnabled()
|
||||||
expect(screen.getByRole('button', { name: 'CSV' })).toHaveClass('active')
|
expect(screen.getByRole('radio', { name: 'CSV' })).toBeChecked()
|
||||||
expect(screen.getByRole('button', { name: 'JSON' })).toBeEnabled()
|
expect(screen.getByRole('radio', { name: 'JSON' })).toBeEnabled()
|
||||||
expect(screen.getByRole('button', { name: 'Markdown' })).toBeEnabled()
|
expect(screen.getByRole('radio', { name: 'Markdown' })).toBeEnabled()
|
||||||
expect(loadPreviewMessages).toHaveBeenCalledTimes(1)
|
expect(loadPreviewMessages).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: '开始导出' }))
|
await userEvent.click(screen.getByRole('button', { name: '开始导出' }))
|
||||||
@@ -203,7 +207,7 @@ describe('ExportWorkspace multi-chat selection', () => {
|
|||||||
const onStartExport = vi.fn(async () => ({ success: false }))
|
const onStartExport = vi.fn(async () => ({ success: false }))
|
||||||
renderWorkspace(onStartExport)
|
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-01T09:30')
|
||||||
await userEvent.type(screen.getByLabelText('结束时间'), '2026-08-02T18:45')
|
await userEvent.type(screen.getByLabelText('结束时间'), '2026-08-02T18:45')
|
||||||
await userEvent.click(screen.getByRole('button', { name: '选择位置' }))
|
await userEvent.click(screen.getByRole('button', { name: '选择位置' }))
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
Switch
|
Switch
|
||||||
} from '../../src/renderer/src/components/ui'
|
} 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', () => {
|
describe('TraceMemo UI control states', () => {
|
||||||
it('uses the compact, standard, and form control height contract', () => {
|
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')
|
expect(screen.getByRole('textbox', { name: '表单输入' })).toHaveClass('h-control-form')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('keeps secondary and icon actions visually quiet by default', () => {
|
||||||
|
render(
|
||||||
|
<>
|
||||||
|
<Button variant="outline">次要操作</Button>
|
||||||
|
<IconButton label="刷新" tooltip="">
|
||||||
|
↻
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
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(
|
||||||
|
<SegmentedControl aria-label="模式" defaultValue="one">
|
||||||
|
<SegmentedControlItem value="one">一</SegmentedControlItem>
|
||||||
|
<SegmentedControlItem value="two">二</SegmentedControlItem>
|
||||||
|
</SegmentedControl>
|
||||||
|
)
|
||||||
|
|
||||||
|
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', () => {
|
it('keeps disabled controls readable without the old opacity and surface shadow', () => {
|
||||||
render(
|
render(
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -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 contactList.getByRole('button', { name: /产品测试群/ }).click()
|
||||||
|
|
||||||
await expect(fixture.page.getByText('已选 2 / 5 个')).toBeVisible()
|
await expect(fixture.page.getByText('已选 2 / 5 个')).toBeVisible()
|
||||||
await expect(fixture.page.getByRole('button', { name: 'CSV' })).toBeDisabled()
|
await expect(fixture.page.getByRole('radio', { name: 'CSV' })).toBeDisabled()
|
||||||
await expect(fixture.page.getByRole('button', { name: /HTML/ })).toHaveClass(/active/)
|
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.getByText('文件传输助手、产品测试群 · 共 2 个聊天')).toBeVisible()
|
||||||
await expect(
|
await expect(
|
||||||
fixture.page.locator('.export-preview-bubble').filter({ hasText: '这是一条脱敏测试消息' })
|
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 = [
|
const compositeButtons = [
|
||||||
fixture.page.getByRole('button', { name: /^全部导出/ }),
|
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')
|
fixture.page.locator('.export-workspace > aside:first-child > button:last-child')
|
||||||
]
|
]
|
||||||
for (const button of compositeButtons) {
|
for (const button of compositeButtons) {
|
||||||
|
|||||||
Reference in New Issue
Block a user