mirror of
https://github.com/langgenius/dify.git
synced 2026-04-17 20:09:34 +08:00
refactor(ui): decouple CSS dependencies and improve test quality (#35242)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
3bccdd6c9a
commit
50a55513d4
@ -58,20 +58,4 @@ describe('AutomaticBtn', () => {
|
||||
expect(mockOnClick).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Styling', () => {
|
||||
it('should have secondary-accent variant', () => {
|
||||
render(<AutomaticBtn onClick={mockOnClick} />)
|
||||
|
||||
const button = screen.getByRole('button')
|
||||
expect(button.className).toContain('secondary-accent')
|
||||
})
|
||||
|
||||
it('should have small size', () => {
|
||||
render(<AutomaticBtn onClick={mockOnClick} />)
|
||||
|
||||
const button = screen.getByRole('button')
|
||||
expect(button.className).toContain('small')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -95,12 +95,6 @@ describe('APIKeyInfoPanel - Cloud Edition', () => {
|
||||
})
|
||||
|
||||
describe('Props and Styling', () => {
|
||||
it('should render button with primary variant', () => {
|
||||
scenarios.withAPIKeyNotSet()
|
||||
const button = screen.getByRole('button')
|
||||
expect(button).toHaveClass('btn-primary')
|
||||
})
|
||||
|
||||
it('should render panel container with correct classes', () => {
|
||||
const { container } = scenarios.withAPIKeyNotSet()
|
||||
const panel = container.firstChild as HTMLElement
|
||||
|
||||
@ -108,12 +108,6 @@ describe('APIKeyInfoPanel - Community Edition', () => {
|
||||
})
|
||||
|
||||
describe('Props and Styling', () => {
|
||||
it('should render button with primary variant', () => {
|
||||
scenarios.withAPIKeyNotSet()
|
||||
const button = screen.getByRole('button')
|
||||
expect(button).toHaveClass('btn-primary')
|
||||
})
|
||||
|
||||
it('should render panel container with correct classes', () => {
|
||||
const { container } = scenarios.withAPIKeyNotSet()
|
||||
const panel = container.firstChild as HTMLElement
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { RenderOptions } from '@testing-library/react'
|
||||
import type { Mock, MockedFunction } from 'vitest'
|
||||
import type { ModalContextState } from '@/context/modal-context'
|
||||
import { fireEvent, render } from '@testing-library/react'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { defaultPlan } from '@/app/components/billing/config'
|
||||
import { useModalContext as actualUseModalContext } from '@/context/modal-context'
|
||||
@ -81,6 +81,8 @@ type APIKeyInfoPanelRenderOptions = {
|
||||
mockOverrides?: MockOverrides
|
||||
} & Omit<RenderOptions, 'wrapper'>
|
||||
|
||||
const mainButtonName = /appOverview\.apiKeyInfo\.setAPIBtn/
|
||||
|
||||
// Setup function to configure mocks
|
||||
function setupMocks(overrides: MockOverrides = {}) {
|
||||
mockUseProviderContext.mockReturnValue({
|
||||
@ -137,7 +139,7 @@ export const scenarios = {
|
||||
export const assertions = {
|
||||
// Should render main button
|
||||
shouldRenderMainButton: () => {
|
||||
const button = document.querySelector('button.btn-primary')
|
||||
const button = screen.getByRole('button', { name: mainButtonName })
|
||||
expect(button).toBeInTheDocument()
|
||||
return button
|
||||
},
|
||||
@ -174,9 +176,8 @@ export const assertions = {
|
||||
export const interactions = {
|
||||
// Click the main button
|
||||
clickMainButton: () => {
|
||||
const button = document.querySelector('button.btn-primary')
|
||||
if (button)
|
||||
fireEvent.click(button)
|
||||
const button = screen.getByRole('button', { name: mainButtonName })
|
||||
fireEvent.click(button)
|
||||
return button
|
||||
},
|
||||
|
||||
@ -191,6 +192,7 @@ export const interactions = {
|
||||
|
||||
// Text content keys for assertions
|
||||
export const textKeys = {
|
||||
button: mainButtonName,
|
||||
selfHost: {
|
||||
titleRow1: /appOverview\.apiKeyInfo\.selfHost\.title\.row1/,
|
||||
titleRow2: /appOverview\.apiKeyInfo\.selfHost\.title\.row2/,
|
||||
|
||||
@ -84,49 +84,6 @@ describe('InlineDeleteConfirm', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Variant prop', () => {
|
||||
it('should render with delete variant by default', () => {
|
||||
const onConfirm = vi.fn()
|
||||
const onCancel = vi.fn()
|
||||
const { getByText } = render(
|
||||
<InlineDeleteConfirm onConfirm={onConfirm} onCancel={onCancel} />,
|
||||
)
|
||||
|
||||
const confirmButton = getByText('Yes').closest('button')
|
||||
expect(confirmButton?.className).toContain('btn-destructive-primary')
|
||||
})
|
||||
|
||||
it('should render without destructive class for warning variant', () => {
|
||||
const onConfirm = vi.fn()
|
||||
const onCancel = vi.fn()
|
||||
const { getByText } = render(
|
||||
<InlineDeleteConfirm
|
||||
variant="warning"
|
||||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
/>,
|
||||
)
|
||||
|
||||
const confirmButton = getByText('Yes').closest('button')
|
||||
expect(confirmButton?.className).not.toContain('btn-destructive-primary')
|
||||
})
|
||||
|
||||
it('should render without destructive class for info variant', () => {
|
||||
const onConfirm = vi.fn()
|
||||
const onCancel = vi.fn()
|
||||
const { getByText } = render(
|
||||
<InlineDeleteConfirm
|
||||
variant="info"
|
||||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
/>,
|
||||
)
|
||||
|
||||
const confirmButton = getByText('Yes').closest('button')
|
||||
expect(confirmButton?.className).not.toContain('btn-destructive-primary')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Custom className', () => {
|
||||
it('should apply custom className to wrapper', () => {
|
||||
const onConfirm = vi.fn()
|
||||
|
||||
@ -22,7 +22,6 @@ describe('NotionConnector', () => {
|
||||
})
|
||||
|
||||
expect(button).toBeInTheDocument()
|
||||
expect(button).toHaveClass('btn', 'btn-primary')
|
||||
})
|
||||
|
||||
it('should trigger the onSetting callback when the real button is clicked', async () => {
|
||||
|
||||
@ -4,7 +4,6 @@ import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogClose,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
@ -70,14 +69,16 @@ describe('AlertDialog wrapper', () => {
|
||||
})
|
||||
|
||||
describe('User Interactions', () => {
|
||||
it('should open and close dialog when trigger and close are clicked', async () => {
|
||||
it('should open and close dialog when trigger and cancel button are clicked', async () => {
|
||||
render(
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger>Open Dialog</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogTitle>Action Required</AlertDialogTitle>
|
||||
<AlertDialogDescription>Please confirm the action.</AlertDialogDescription>
|
||||
<AlertDialogClose>Cancel</AlertDialogClose>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton>Cancel</AlertDialogCancelButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>,
|
||||
)
|
||||
@ -109,8 +110,7 @@ describe('AlertDialog wrapper', () => {
|
||||
|
||||
expect(screen.getByTestId('actions')).toHaveClass('flex', 'items-start', 'justify-end', 'gap-2', 'self-stretch', 'p-6', 'custom-actions')
|
||||
const confirmButton = screen.getByRole('button', { name: 'Confirm' })
|
||||
expect(confirmButton).toHaveClass('btn-primary')
|
||||
expect(confirmButton).toHaveClass('btn-destructive-primary')
|
||||
expect(confirmButton).toHaveClass('bg-components-button-destructive-primary-bg')
|
||||
})
|
||||
|
||||
it('should keep dialog open after confirm click and close via cancel helper', async () => {
|
||||
|
||||
@ -10,7 +10,6 @@ export const AlertDialog = BaseAlertDialog.Root
|
||||
export const AlertDialogTrigger = BaseAlertDialog.Trigger
|
||||
export const AlertDialogTitle = BaseAlertDialog.Title
|
||||
export const AlertDialogDescription = BaseAlertDialog.Description
|
||||
export const AlertDialogClose = BaseAlertDialog.Close
|
||||
|
||||
type AlertDialogContentProps = {
|
||||
children: React.ReactNode
|
||||
|
||||
@ -53,7 +53,7 @@ function AvatarImage({
|
||||
}: AvatarImageProps) {
|
||||
return (
|
||||
<BaseAvatar.Image
|
||||
className={cn('inset-0 absolute size-full object-cover', className)}
|
||||
className={cn('absolute inset-0 size-full object-cover', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { Button } from '../index'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
describe('Button', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders children text', () => {
|
||||
@ -31,58 +29,79 @@ describe('Button', () => {
|
||||
expect(link).toHaveTextContent('Link')
|
||||
expect(link).toHaveAttribute('href', '/test')
|
||||
})
|
||||
|
||||
it('applies base layout classes', () => {
|
||||
render(<Button>Click me</Button>)
|
||||
const btn = screen.getByRole('button')
|
||||
expect(btn).toHaveClass('inline-flex', 'justify-center', 'items-center', 'cursor-pointer')
|
||||
})
|
||||
})
|
||||
|
||||
describe('variants', () => {
|
||||
it('applies default secondary variant', () => {
|
||||
render(<Button>Click me</Button>)
|
||||
expect(screen.getByRole('button').className).toContain('btn-secondary')
|
||||
const btn = screen.getByRole('button')
|
||||
expect(btn).toHaveClass('bg-components-button-secondary-bg', 'text-components-button-secondary-text')
|
||||
})
|
||||
|
||||
it.each([
|
||||
'primary',
|
||||
'secondary',
|
||||
'secondary-accent',
|
||||
'ghost',
|
||||
'ghost-accent',
|
||||
'tertiary',
|
||||
] as const)('applies %s variant', (variant) => {
|
||||
{ variant: 'primary' as const, expectedClass: 'bg-components-button-primary-bg' },
|
||||
{ variant: 'secondary' as const, expectedClass: 'bg-components-button-secondary-bg' },
|
||||
{ variant: 'secondary-accent' as const, expectedClass: 'text-components-button-secondary-accent-text' },
|
||||
{ variant: 'ghost' as const, expectedClass: 'text-components-button-ghost-text' },
|
||||
{ variant: 'ghost-accent' as const, expectedClass: 'hover:bg-state-accent-hover' },
|
||||
{ variant: 'tertiary' as const, expectedClass: 'bg-components-button-tertiary-bg' },
|
||||
])('applies $variant variant', ({ variant, expectedClass }) => {
|
||||
render(<Button variant={variant}>Click me</Button>)
|
||||
expect(screen.getByRole('button').className).toContain(`btn-${variant}`)
|
||||
expect(screen.getByRole('button')).toHaveClass(expectedClass)
|
||||
})
|
||||
|
||||
it('applies destructive tone with default variant', () => {
|
||||
render(<Button tone="destructive">Click me</Button>)
|
||||
expect(screen.getByRole('button').className).toContain('btn-destructive-secondary')
|
||||
expect(screen.getByRole('button')).toHaveClass('bg-components-button-destructive-secondary-bg')
|
||||
})
|
||||
|
||||
it('applies destructive tone with primary variant', () => {
|
||||
render(<Button variant="primary" tone="destructive">Click me</Button>)
|
||||
expect(screen.getByRole('button').className).toContain('btn-destructive-primary')
|
||||
expect(screen.getByRole('button')).toHaveClass('bg-components-button-destructive-primary-bg')
|
||||
})
|
||||
|
||||
it('applies destructive tone with tertiary variant', () => {
|
||||
render(<Button variant="tertiary" tone="destructive">Click me</Button>)
|
||||
expect(screen.getByRole('button')).toHaveClass('bg-components-button-destructive-tertiary-bg')
|
||||
})
|
||||
|
||||
it('applies destructive tone with ghost variant', () => {
|
||||
render(<Button variant="ghost" tone="destructive">Click me</Button>)
|
||||
expect(screen.getByRole('button')).toHaveClass('text-components-button-destructive-ghost-text')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sizes', () => {
|
||||
it('applies default medium size', () => {
|
||||
render(<Button>Click me</Button>)
|
||||
expect(screen.getByRole('button').className).toContain('btn-medium')
|
||||
expect(screen.getByRole('button')).toHaveClass('h-8', 'rounded-lg')
|
||||
})
|
||||
|
||||
it.each(['small', 'medium', 'large'] as const)('applies %s size', (size) => {
|
||||
it.each([
|
||||
{ size: 'small' as const, expectedClass: 'h-6' },
|
||||
{ size: 'medium' as const, expectedClass: 'h-8' },
|
||||
{ size: 'large' as const, expectedClass: 'h-9' },
|
||||
])('applies $size size', ({ size, expectedClass }) => {
|
||||
render(<Button size={size}>Click me</Button>)
|
||||
expect(screen.getByRole('button').className).toContain(`btn-${size}`)
|
||||
expect(screen.getByRole('button')).toHaveClass(expectedClass)
|
||||
})
|
||||
})
|
||||
|
||||
describe('loading', () => {
|
||||
it('shows spinner when loading', () => {
|
||||
render(<Button loading>Click me</Button>)
|
||||
expect(screen.getByRole('button').querySelector('.animate-spin')).toBeInTheDocument()
|
||||
expect(screen.getByRole('button').querySelector('[aria-hidden="true"]')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('hides spinner when not loading', () => {
|
||||
render(<Button loading={false}>Click me</Button>)
|
||||
expect(screen.getByRole('button').querySelector('.animate-spin')).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('button').querySelector('[aria-hidden="true"]')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('auto-disables when loading', () => {
|
||||
@ -137,6 +156,15 @@ describe('Button', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('className merging', () => {
|
||||
it('merges custom className with variant classes', () => {
|
||||
render(<Button className="custom-class">Click me</Button>)
|
||||
const btn = screen.getByRole('button')
|
||||
expect(btn).toHaveClass('custom-class')
|
||||
expect(btn).toHaveClass('inline-flex')
|
||||
})
|
||||
})
|
||||
|
||||
describe('ref forwarding', () => {
|
||||
it('forwards ref to the button element', () => {
|
||||
let buttonRef: HTMLButtonElement | null = null
|
||||
|
||||
@ -1,148 +0,0 @@
|
||||
@utility btn {
|
||||
@apply inline-flex justify-center items-center cursor-pointer whitespace-nowrap
|
||||
outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid;
|
||||
|
||||
&:is(:disabled, [data-disabled]) {
|
||||
@apply cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
@utility btn-small {
|
||||
@apply px-2 h-6 rounded-md text-xs font-medium;
|
||||
}
|
||||
|
||||
@utility btn-medium {
|
||||
@apply px-3.5 h-8 rounded-lg text-[13px] leading-4 font-medium;
|
||||
}
|
||||
|
||||
@utility btn-large {
|
||||
@apply px-4 h-9 rounded-[10px] text-sm font-semibold;
|
||||
}
|
||||
|
||||
@utility btn-primary {
|
||||
@apply shadow
|
||||
bg-components-button-primary-bg
|
||||
border-components-button-primary-border
|
||||
hover:bg-components-button-primary-bg-hover
|
||||
hover:border-components-button-primary-border-hover
|
||||
text-components-button-primary-text;
|
||||
|
||||
&:is(:disabled, [data-disabled]) {
|
||||
@apply shadow-none
|
||||
bg-components-button-primary-bg-disabled
|
||||
border-components-button-primary-border-disabled
|
||||
text-components-button-primary-text-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@utility btn-secondary {
|
||||
@apply border-[0.5px]
|
||||
shadow-xs
|
||||
backdrop-blur-[5px]
|
||||
bg-components-button-secondary-bg
|
||||
border-components-button-secondary-border
|
||||
hover:bg-components-button-secondary-bg-hover
|
||||
hover:border-components-button-secondary-border-hover
|
||||
text-components-button-secondary-text;
|
||||
|
||||
&:is(:disabled, [data-disabled]) {
|
||||
@apply backdrop-blur-xs
|
||||
bg-components-button-secondary-bg-disabled
|
||||
border-components-button-secondary-border-disabled
|
||||
text-components-button-secondary-text-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@utility btn-secondary-accent {
|
||||
@apply border-[0.5px]
|
||||
shadow-xs
|
||||
bg-components-button-secondary-bg
|
||||
border-components-button-secondary-border
|
||||
hover:bg-components-button-secondary-bg-hover
|
||||
hover:border-components-button-secondary-border-hover
|
||||
text-components-button-secondary-accent-text;
|
||||
|
||||
&:is(:disabled, [data-disabled]) {
|
||||
@apply bg-components-button-secondary-bg-disabled
|
||||
border-components-button-secondary-border-disabled
|
||||
text-components-button-secondary-accent-text-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@utility btn-tertiary {
|
||||
@apply bg-components-button-tertiary-bg
|
||||
hover:bg-components-button-tertiary-bg-hover
|
||||
text-components-button-tertiary-text;
|
||||
|
||||
&:is(:disabled, [data-disabled]) {
|
||||
@apply bg-components-button-tertiary-bg-disabled
|
||||
text-components-button-tertiary-text-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@utility btn-ghost {
|
||||
@apply hover:bg-components-button-ghost-bg-hover
|
||||
text-components-button-ghost-text;
|
||||
|
||||
&:is(:disabled, [data-disabled]) {
|
||||
@apply text-components-button-ghost-text-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@utility btn-ghost-accent {
|
||||
@apply hover:bg-state-accent-hover
|
||||
text-components-button-secondary-accent-text;
|
||||
|
||||
&:is(:disabled, [data-disabled]) {
|
||||
@apply text-components-button-secondary-accent-text-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@utility btn-destructive-primary {
|
||||
@apply bg-components-button-destructive-primary-bg
|
||||
border-components-button-destructive-primary-border
|
||||
hover:bg-components-button-destructive-primary-bg-hover
|
||||
hover:border-components-button-destructive-primary-border-hover
|
||||
text-components-button-destructive-primary-text;
|
||||
|
||||
&:is(:disabled, [data-disabled]) {
|
||||
@apply shadow-none
|
||||
bg-components-button-destructive-primary-bg-disabled
|
||||
border-components-button-destructive-primary-border-disabled
|
||||
text-components-button-destructive-primary-text-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@utility btn-destructive-secondary {
|
||||
@apply bg-components-button-destructive-secondary-bg
|
||||
border-components-button-destructive-secondary-border
|
||||
hover:bg-components-button-destructive-secondary-bg-hover
|
||||
hover:border-components-button-destructive-secondary-border-hover
|
||||
text-components-button-destructive-secondary-text;
|
||||
|
||||
&:is(:disabled, [data-disabled]) {
|
||||
@apply bg-components-button-destructive-secondary-bg-disabled
|
||||
border-components-button-destructive-secondary-border-disabled
|
||||
text-components-button-destructive-secondary-text-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@utility btn-destructive-tertiary {
|
||||
@apply bg-components-button-destructive-tertiary-bg
|
||||
hover:bg-components-button-destructive-tertiary-bg-hover
|
||||
text-components-button-destructive-tertiary-text;
|
||||
|
||||
&:is(:disabled, [data-disabled]) {
|
||||
@apply bg-components-button-destructive-tertiary-bg-disabled
|
||||
text-components-button-destructive-tertiary-text-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
@utility btn-destructive-ghost {
|
||||
@apply hover:bg-components-button-destructive-ghost-bg-hover
|
||||
text-components-button-destructive-ghost-text;
|
||||
|
||||
&:is(:disabled, [data-disabled]) {
|
||||
@apply text-components-button-destructive-ghost-text-disabled;
|
||||
}
|
||||
}
|
||||
@ -5,21 +5,47 @@ import { cva } from 'class-variance-authority'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
const buttonVariants = cva(
|
||||
'btn',
|
||||
'inline-flex cursor-pointer items-center justify-center whitespace-nowrap outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid data-[disabled]:cursor-not-allowed',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
'primary': 'btn-primary',
|
||||
'secondary': 'btn-secondary',
|
||||
'secondary-accent': 'btn-secondary-accent',
|
||||
'ghost': 'btn-ghost',
|
||||
'ghost-accent': 'btn-ghost-accent',
|
||||
'tertiary': 'btn-tertiary',
|
||||
'primary': [
|
||||
'border-components-button-primary-border bg-components-button-primary-bg text-components-button-primary-text shadow',
|
||||
'hover:border-components-button-primary-border-hover hover:bg-components-button-primary-bg-hover',
|
||||
'data-[disabled]:border-components-button-primary-border-disabled data-[disabled]:bg-components-button-primary-bg-disabled data-[disabled]:text-components-button-primary-text-disabled data-[disabled]:shadow-none',
|
||||
],
|
||||
'secondary': [
|
||||
'border-[0.5px] shadow-xs backdrop-blur-[5px]',
|
||||
'border-components-button-secondary-border bg-components-button-secondary-bg text-components-button-secondary-text',
|
||||
'hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover',
|
||||
'data-[disabled]:border-components-button-secondary-border-disabled data-[disabled]:bg-components-button-secondary-bg-disabled data-[disabled]:text-components-button-secondary-text-disabled data-[disabled]:backdrop-blur-xs',
|
||||
],
|
||||
'secondary-accent': [
|
||||
'border-[0.5px] shadow-xs',
|
||||
'border-components-button-secondary-border bg-components-button-secondary-bg text-components-button-secondary-accent-text',
|
||||
'hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover',
|
||||
'data-[disabled]:border-components-button-secondary-border-disabled data-[disabled]:bg-components-button-secondary-bg-disabled data-[disabled]:text-components-button-secondary-accent-text-disabled',
|
||||
],
|
||||
'tertiary': [
|
||||
'bg-components-button-tertiary-bg text-components-button-tertiary-text',
|
||||
'hover:bg-components-button-tertiary-bg-hover',
|
||||
'data-[disabled]:bg-components-button-tertiary-bg-disabled data-[disabled]:text-components-button-tertiary-text-disabled',
|
||||
],
|
||||
'ghost': [
|
||||
'text-components-button-ghost-text',
|
||||
'hover:bg-components-button-ghost-bg-hover',
|
||||
'data-[disabled]:text-components-button-ghost-text-disabled',
|
||||
],
|
||||
'ghost-accent': [
|
||||
'text-components-button-secondary-accent-text',
|
||||
'hover:bg-state-accent-hover',
|
||||
'data-[disabled]:text-components-button-secondary-accent-text-disabled',
|
||||
],
|
||||
},
|
||||
size: {
|
||||
small: 'btn-small',
|
||||
medium: 'btn-medium',
|
||||
large: 'btn-large',
|
||||
small: 'h-6 rounded-md px-2 text-xs font-medium',
|
||||
medium: 'h-8 rounded-lg px-3.5 text-[13px] leading-4 font-medium',
|
||||
large: 'h-9 rounded-[10px] px-4 text-sm font-semibold',
|
||||
},
|
||||
tone: {
|
||||
default: '',
|
||||
@ -27,10 +53,42 @@ const buttonVariants = cva(
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{ variant: 'primary', tone: 'destructive', class: 'btn-destructive-primary' },
|
||||
{ variant: 'secondary', tone: 'destructive', class: 'btn-destructive-secondary' },
|
||||
{ variant: 'tertiary', tone: 'destructive', class: 'btn-destructive-tertiary' },
|
||||
{ variant: 'ghost', tone: 'destructive', class: 'btn-destructive-ghost' },
|
||||
{
|
||||
variant: 'primary',
|
||||
tone: 'destructive',
|
||||
class: [
|
||||
'border-components-button-destructive-primary-border bg-components-button-destructive-primary-bg text-components-button-destructive-primary-text',
|
||||
'hover:border-components-button-destructive-primary-border-hover hover:bg-components-button-destructive-primary-bg-hover',
|
||||
'data-[disabled]:border-components-button-destructive-primary-border-disabled data-[disabled]:bg-components-button-destructive-primary-bg-disabled data-[disabled]:text-components-button-destructive-primary-text-disabled data-[disabled]:shadow-none',
|
||||
],
|
||||
},
|
||||
{
|
||||
variant: 'secondary',
|
||||
tone: 'destructive',
|
||||
class: [
|
||||
'border-components-button-destructive-secondary-border bg-components-button-destructive-secondary-bg text-components-button-destructive-secondary-text',
|
||||
'hover:border-components-button-destructive-secondary-border-hover hover:bg-components-button-destructive-secondary-bg-hover',
|
||||
'data-[disabled]:border-components-button-destructive-secondary-border-disabled data-[disabled]:bg-components-button-destructive-secondary-bg-disabled data-[disabled]:text-components-button-destructive-secondary-text-disabled',
|
||||
],
|
||||
},
|
||||
{
|
||||
variant: 'tertiary',
|
||||
tone: 'destructive',
|
||||
class: [
|
||||
'bg-components-button-destructive-tertiary-bg text-components-button-destructive-tertiary-text',
|
||||
'hover:bg-components-button-destructive-tertiary-bg-hover',
|
||||
'data-[disabled]:bg-components-button-destructive-tertiary-bg-disabled data-[disabled]:text-components-button-destructive-tertiary-text-disabled',
|
||||
],
|
||||
},
|
||||
{
|
||||
variant: 'ghost',
|
||||
tone: 'destructive',
|
||||
class: [
|
||||
'text-components-button-destructive-ghost-text',
|
||||
'hover:bg-components-button-destructive-ghost-bg-hover',
|
||||
'data-[disabled]:text-components-button-destructive-ghost-text-disabled',
|
||||
],
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
variant: 'secondary',
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
import { Dialog as BaseDialog } from '@base-ui/react/dialog'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogCloseButton,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '../index'
|
||||
|
||||
describe('Dialog wrapper', () => {
|
||||
@ -86,15 +82,4 @@ describe('Dialog wrapper', () => {
|
||||
expect(onClick).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Exports', () => {
|
||||
it('should map dialog aliases to the matching base dialog primitives', () => {
|
||||
expect(Dialog).toBe(BaseDialog.Root)
|
||||
expect(DialogTrigger).toBe(BaseDialog.Trigger)
|
||||
expect(DialogTitle).toBe(BaseDialog.Title)
|
||||
expect(DialogDescription).toBe(BaseDialog.Description)
|
||||
expect(DialogClose).toBe(BaseDialog.Close)
|
||||
expect(DialogPortal).toBe(BaseDialog.Portal)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -12,10 +12,10 @@ import * as React from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export const Dialog = BaseDialog.Root
|
||||
/** @public */
|
||||
export const DialogTrigger = BaseDialog.Trigger
|
||||
export const DialogTitle = BaseDialog.Title
|
||||
export const DialogDescription = BaseDialog.Description
|
||||
export const DialogClose = BaseDialog.Close
|
||||
export const DialogPortal = BaseDialog.Portal
|
||||
|
||||
type DialogCloseButtonProps = Omit<React.ComponentPropsWithoutRef<typeof BaseDialog.Close>, 'children'>
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import type { ComponentPropsWithoutRef, ReactNode } from 'react'
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import Link from '@/next/link'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@ -14,21 +12,6 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '../index'
|
||||
|
||||
vi.mock('@/next/link', () => ({
|
||||
default: ({
|
||||
href,
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
href: string
|
||||
children?: ReactNode
|
||||
} & Omit<ComponentPropsWithoutRef<'a'>, 'href'>) => (
|
||||
<a href={href} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}))
|
||||
|
||||
describe('dropdown-menu wrapper', () => {
|
||||
describe('DropdownMenuContent', () => {
|
||||
it('should position content at bottom-end with default placement when props are omitted', () => {
|
||||
@ -295,13 +278,13 @@ describe('dropdown-menu wrapper', () => {
|
||||
expect(link).not.toHaveAttribute('closeOnClick')
|
||||
})
|
||||
|
||||
it('should preserve link semantics when render prop uses a custom link component', () => {
|
||||
it('should preserve link semantics when render prop uses a custom anchor element', () => {
|
||||
render(
|
||||
<DropdownMenu open>
|
||||
<DropdownMenuTrigger aria-label="menu trigger">Open</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLinkItem
|
||||
render={<Link href="/account" />}
|
||||
render={<a href="/account" />}
|
||||
aria-label="account link"
|
||||
>
|
||||
Account settings
|
||||
|
||||
@ -6,7 +6,6 @@ import type {
|
||||
NumberFieldInputProps,
|
||||
NumberFieldUnitProps,
|
||||
} from '../index'
|
||||
import { NumberField as BaseNumberField } from '@base-ui/react/number-field'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import {
|
||||
NumberField,
|
||||
@ -67,17 +66,6 @@ const renderNumberField = ({
|
||||
}
|
||||
|
||||
describe('NumberField wrapper', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// Export mapping should stay aligned with the Base UI primitive.
|
||||
describe('Exports', () => {
|
||||
it('should map NumberField to the matching base primitive root', () => {
|
||||
expect(NumberField).toBe(BaseNumberField.Root)
|
||||
})
|
||||
})
|
||||
|
||||
// Group and input wrappers should preserve the design-system variants and DOM defaults.
|
||||
describe('Group and input', () => {
|
||||
it('should apply regular group classes by default and merge custom className', () => {
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
import { Popover as BasePopover } from '@base-ui/react/popover'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
Popover,
|
||||
PopoverClose,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
} from '..'
|
||||
|
||||
@ -93,15 +89,3 @@ describe('PopoverContent', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Popover aliases', () => {
|
||||
describe('Export mapping', () => {
|
||||
it('should map aliases to the matching base popover primitives when wrapper exports are imported', () => {
|
||||
expect(Popover).toBe(BasePopover.Root)
|
||||
expect(PopoverTrigger).toBe(BasePopover.Trigger)
|
||||
expect(PopoverClose).toBe(BasePopover.Close)
|
||||
expect(PopoverTitle).toBe(BasePopover.Title)
|
||||
expect(PopoverDescription).toBe(BasePopover.Description)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -9,7 +9,9 @@ import { cn } from '@/utils/classnames'
|
||||
export const Popover = BasePopover.Root
|
||||
export const PopoverTrigger = BasePopover.Trigger
|
||||
export const PopoverClose = BasePopover.Close
|
||||
/** @public */
|
||||
export const PopoverTitle = BasePopover.Title
|
||||
/** @public */
|
||||
export const PopoverDescription = BasePopover.Description
|
||||
|
||||
type PopoverContentProps = {
|
||||
|
||||
@ -9,7 +9,6 @@ import {
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '../index'
|
||||
import styles from '../index.module.css'
|
||||
|
||||
const renderScrollArea = (options: {
|
||||
rootClassName?: string
|
||||
@ -106,7 +105,7 @@ describe('scroll-area wrapper', () => {
|
||||
const thumb = screen.getByTestId('scroll-area-vertical-thumb')
|
||||
|
||||
expect(scrollbar).toHaveAttribute('data-orientation', 'vertical')
|
||||
expect(scrollbar).toHaveClass(styles.scrollbar)
|
||||
expect(scrollbar).toHaveAttribute('data-dify-scrollbar')
|
||||
expect(scrollbar).toHaveClass(
|
||||
'flex',
|
||||
'overflow-clip',
|
||||
@ -144,7 +143,7 @@ describe('scroll-area wrapper', () => {
|
||||
const thumb = screen.getByTestId('scroll-area-horizontal-thumb')
|
||||
|
||||
expect(scrollbar).toHaveAttribute('data-orientation', 'horizontal')
|
||||
expect(scrollbar).toHaveClass(styles.scrollbar)
|
||||
expect(scrollbar).toHaveAttribute('data-dify-scrollbar')
|
||||
expect(scrollbar).toHaveClass(
|
||||
'flex',
|
||||
'overflow-clip',
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import { ScrollArea as BaseScrollArea } from '@base-ui/react/scroll-area'
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import styles from './index.module.css'
|
||||
import './scroll-area.css'
|
||||
|
||||
export const ScrollAreaRoot = BaseScrollArea.Root
|
||||
type ScrollAreaRootProps = React.ComponentPropsWithRef<typeof BaseScrollArea.Root>
|
||||
@ -25,7 +25,6 @@ type ScrollAreaProps = Omit<ScrollAreaRootProps, 'children'> & {
|
||||
}
|
||||
|
||||
const scrollAreaScrollbarClassName = cn(
|
||||
styles.scrollbar,
|
||||
'flex touch-none overflow-clip p-1 opacity-100 transition-opacity select-none motion-reduce:transition-none',
|
||||
'pointer-events-none data-hovering:pointer-events-auto',
|
||||
'data-scrolling:pointer-events-auto',
|
||||
@ -68,6 +67,7 @@ export function ScrollAreaScrollbar({
|
||||
}: ScrollAreaScrollbarProps) {
|
||||
return (
|
||||
<BaseScrollArea.Scrollbar
|
||||
data-dify-scrollbar=""
|
||||
className={cn(scrollAreaScrollbarClassName, className)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
.scrollbar::before,
|
||||
.scrollbar::after {
|
||||
[data-dify-scrollbar]::before,
|
||||
[data-dify-scrollbar]::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
@ -9,7 +9,7 @@
|
||||
transition: opacity 150ms ease;
|
||||
}
|
||||
|
||||
.scrollbar[data-orientation='vertical']::before {
|
||||
[data-dify-scrollbar][data-orientation='vertical']::before {
|
||||
left: 50%;
|
||||
top: 4px;
|
||||
width: 4px;
|
||||
@ -18,7 +18,7 @@
|
||||
background: linear-gradient(to bottom, var(--scroll-area-edge-hint-bg, var(--color-components-panel-bg)), transparent);
|
||||
}
|
||||
|
||||
.scrollbar[data-orientation='vertical']::after {
|
||||
[data-dify-scrollbar][data-orientation='vertical']::after {
|
||||
left: 50%;
|
||||
bottom: 4px;
|
||||
width: 4px;
|
||||
@ -27,7 +27,7 @@
|
||||
background: linear-gradient(to top, var(--scroll-area-edge-hint-bg, var(--color-components-panel-bg)), transparent);
|
||||
}
|
||||
|
||||
.scrollbar[data-orientation='horizontal']::before {
|
||||
[data-dify-scrollbar][data-orientation='horizontal']::before {
|
||||
top: 50%;
|
||||
left: 4px;
|
||||
width: 12px;
|
||||
@ -36,7 +36,7 @@
|
||||
background: linear-gradient(to right, var(--scroll-area-edge-hint-bg, var(--color-components-panel-bg)), transparent);
|
||||
}
|
||||
|
||||
.scrollbar[data-orientation='horizontal']::after {
|
||||
[data-dify-scrollbar][data-orientation='horizontal']::after {
|
||||
top: 50%;
|
||||
right: 4px;
|
||||
width: 12px;
|
||||
@ -45,31 +45,31 @@
|
||||
background: linear-gradient(to left, var(--scroll-area-edge-hint-bg, var(--color-components-panel-bg)), transparent);
|
||||
}
|
||||
|
||||
.scrollbar[data-orientation='vertical']:not([data-overflow-y-start])::before {
|
||||
[data-dify-scrollbar][data-orientation='vertical']:not([data-overflow-y-start])::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.scrollbar[data-orientation='vertical']:not([data-overflow-y-end])::after {
|
||||
[data-dify-scrollbar][data-orientation='vertical']:not([data-overflow-y-end])::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.scrollbar[data-orientation='horizontal']:not([data-overflow-x-start])::before {
|
||||
[data-dify-scrollbar][data-orientation='horizontal']:not([data-overflow-x-start])::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.scrollbar[data-orientation='horizontal']:not([data-overflow-x-end])::after {
|
||||
[data-dify-scrollbar][data-orientation='horizontal']:not([data-overflow-x-end])::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.scrollbar[data-hovering] > [data-orientation],
|
||||
.scrollbar[data-scrolling] > [data-orientation],
|
||||
.scrollbar > [data-orientation]:active {
|
||||
[data-dify-scrollbar][data-hovering] > [data-orientation],
|
||||
[data-dify-scrollbar][data-scrolling] > [data-orientation],
|
||||
[data-dify-scrollbar] > [data-orientation]:active {
|
||||
background-color: var(--scroll-area-thumb-bg-active, var(--color-state-base-handle-hover));
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.scrollbar::before,
|
||||
.scrollbar::after {
|
||||
[data-dify-scrollbar]::before,
|
||||
[data-dify-scrollbar]::after {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../index'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../index'
|
||||
|
||||
describe('TooltipContent', () => {
|
||||
describe('Placement and offsets', () => {
|
||||
@ -105,11 +104,3 @@ describe('TooltipContent', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Tooltip aliases', () => {
|
||||
it('should map alias exports to BaseTooltip components when wrapper exports are imported', () => {
|
||||
expect(TooltipProvider).toBe(BaseTooltip.Provider)
|
||||
expect(Tooltip).toBe(BaseTooltip.Root)
|
||||
expect(TooltipTrigger).toBe(BaseTooltip.Trigger)
|
||||
})
|
||||
})
|
||||
|
||||
@ -85,21 +85,6 @@ describe('Actions', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// Button Variants Tests
|
||||
describe('Button Variants', () => {
|
||||
it('should have primary variant for choose button', () => {
|
||||
render(<Actions {...defaultProps} />)
|
||||
const chooseButton = screen.getByText(/operations\.choose/i).closest('button')
|
||||
expect(chooseButton).toHaveClass('btn-primary')
|
||||
})
|
||||
|
||||
it('should have secondary variant for details button', () => {
|
||||
render(<Actions {...defaultProps} />)
|
||||
const detailsButton = screen.getByText(/operations\.details/i).closest('button')
|
||||
expect(detailsButton).toHaveClass('btn-secondary')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Layout', () => {
|
||||
it('should have absolute positioning', () => {
|
||||
const { container } = render(<Actions {...defaultProps} />)
|
||||
|
||||
@ -160,24 +160,6 @@ describe('CSVUploader', () => {
|
||||
expect(mockUpdateFile).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should call updateFile with undefined when remove is clicked', () => {
|
||||
const mockUpdateFile = vi.fn()
|
||||
const mockFile: FileItem = {
|
||||
fileID: 'file-1',
|
||||
file: new File(['content'], 'test.csv', { type: 'text/csv' }) as CustomFile,
|
||||
progress: 100,
|
||||
}
|
||||
const { container } = render(
|
||||
<CSVUploader {...defaultProps} file={mockFile} updateFile={mockUpdateFile} />,
|
||||
)
|
||||
|
||||
const deleteButton = container.querySelector('.cursor-pointer')
|
||||
if (deleteButton)
|
||||
fireEvent.click(deleteButton)
|
||||
|
||||
expect(mockUpdateFile).toHaveBeenCalledWith()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Validation', () => {
|
||||
|
||||
@ -187,10 +187,7 @@ describe('EditMetadataBatchModal', () => {
|
||||
})
|
||||
|
||||
// Find the primary save button (not the one in SelectMetadataModal)
|
||||
const saveButtons = screen.getAllByText(/save/i)
|
||||
const modalSaveButton = saveButtons.find(btn => btn.closest('button')?.classList.contains('btn-primary'))
|
||||
if (modalSaveButton)
|
||||
fireEvent.click(modalSaveButton)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
|
||||
expect(onSave).toHaveBeenCalled()
|
||||
})
|
||||
@ -443,13 +440,10 @@ describe('EditMetadataBatchModal', () => {
|
||||
})
|
||||
|
||||
// Find the primary save button
|
||||
const saveButtons = screen.getAllByText(/save/i)
|
||||
const saveBtn = saveButtons.find(btn => btn.closest('button')?.classList.contains('btn-primary'))
|
||||
if (saveBtn) {
|
||||
fireEvent.click(saveBtn)
|
||||
fireEvent.click(saveBtn)
|
||||
fireEvent.click(saveBtn)
|
||||
}
|
||||
const saveBtn = screen.getByRole('button', { name: 'common.operation.save' })
|
||||
fireEvent.click(saveBtn)
|
||||
fireEvent.click(saveBtn)
|
||||
fireEvent.click(saveBtn)
|
||||
|
||||
expect(onSave).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
@ -462,10 +456,7 @@ describe('EditMetadataBatchModal', () => {
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const saveButtons = screen.getAllByText(/save/i)
|
||||
const saveBtn = saveButtons.find(btn => btn.closest('button')?.classList.contains('btn-primary'))
|
||||
if (saveBtn)
|
||||
fireEvent.click(saveBtn)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
|
||||
expect(onSave).toHaveBeenCalledWith(
|
||||
expect.any(Array),
|
||||
@ -486,10 +477,7 @@ describe('EditMetadataBatchModal', () => {
|
||||
if (checkboxContainer)
|
||||
fireEvent.click(checkboxContainer)
|
||||
|
||||
const saveButtons = screen.getAllByText(/save/i)
|
||||
const saveBtn = saveButtons.find(btn => btn.closest('button')?.classList.contains('btn-primary'))
|
||||
if (saveBtn)
|
||||
fireEvent.click(saveBtn)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSave).toHaveBeenCalledWith(
|
||||
@ -511,10 +499,7 @@ describe('EditMetadataBatchModal', () => {
|
||||
// Remove an item
|
||||
fireEvent.click(screen.getByTestId('remove-1'))
|
||||
|
||||
const saveButtons = screen.getAllByText(/save/i)
|
||||
const saveBtn = saveButtons.find(btn => btn.closest('button')?.classList.contains('btn-primary'))
|
||||
if (saveBtn)
|
||||
fireEvent.click(saveBtn)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
|
||||
expect(onSave).toHaveBeenCalled()
|
||||
// The first argument should not contain the deleted item (id '1')
|
||||
|
||||
@ -284,12 +284,7 @@ describe('DatasetMetadataDrawer', () => {
|
||||
fireEvent.change(inputs[0], { target: { value: 'renamed_field' } })
|
||||
|
||||
// Find and click save button
|
||||
const saveBtns = screen.getAllByText(/save/i)
|
||||
const primaryBtn = saveBtns.find(btn =>
|
||||
btn.closest('button')?.classList.contains('btn-primary'),
|
||||
)
|
||||
if (primaryBtn)
|
||||
fireEvent.click(primaryBtn)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onRename).toHaveBeenCalled()
|
||||
|
||||
@ -139,18 +139,6 @@ describe('SecretKeyButton', () => {
|
||||
const button = screen.getByRole('button')
|
||||
expect(button.className).toContain('px-3')
|
||||
})
|
||||
|
||||
it('should have small size', () => {
|
||||
render(<SecretKeyButton />)
|
||||
const button = screen.getByRole('button')
|
||||
expect(button.className).toContain('btn-small')
|
||||
})
|
||||
|
||||
it('should have ghost variant', () => {
|
||||
render(<SecretKeyButton />)
|
||||
const button = screen.getByRole('button')
|
||||
expect(button.className).toContain('btn-ghost')
|
||||
})
|
||||
})
|
||||
|
||||
describe('icon styling', () => {
|
||||
|
||||
@ -120,12 +120,6 @@ describe('SystemModel', () => {
|
||||
expect(screen.getByRole('button', { name: /system model settings/i })).toBeDisabled()
|
||||
})
|
||||
|
||||
it('should render the primary button variant when configuration is required', () => {
|
||||
render(<SystemModel {...defaultProps} notConfigured />)
|
||||
|
||||
expect(screen.getByRole('button', { name: /system model settings/i })).toHaveClass('btn-primary')
|
||||
})
|
||||
|
||||
it('should close dialog when cancel is clicked', async () => {
|
||||
render(<SystemModel {...defaultProps} />)
|
||||
fireEvent.click(screen.getByRole('button', { name: /system model settings/i }))
|
||||
|
||||
@ -156,29 +156,6 @@ describe('AddApiKeyButton', () => {
|
||||
|
||||
expect(screen.getByRole('button')).toHaveTextContent('Custom API Key')
|
||||
})
|
||||
|
||||
it('should apply button variant', () => {
|
||||
const pluginPayload = createPluginPayload()
|
||||
|
||||
render(
|
||||
<AddApiKeyButton
|
||||
pluginPayload={pluginPayload}
|
||||
buttonVariant="primary"
|
||||
/>,
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button').className).toContain('btn-primary')
|
||||
})
|
||||
|
||||
it('should use secondary-accent variant by default', () => {
|
||||
const pluginPayload = createPluginPayload()
|
||||
|
||||
render(<AddApiKeyButton pluginPayload={pluginPayload} />, { wrapper: createWrapper() })
|
||||
|
||||
// Verify the default button has secondary-accent variant class
|
||||
expect(screen.getByRole('button').className).toContain('btn-secondary-accent')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Props Testing', () => {
|
||||
@ -372,25 +349,6 @@ describe('AddOAuthButton', () => {
|
||||
|
||||
expect(screen.getByText('plugin.auth.setupOAuth')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply button variant to setup button', () => {
|
||||
const pluginPayload = createPluginPayload()
|
||||
mockGetPluginOAuthClientSchema.mockReturnValue({
|
||||
schema: [],
|
||||
is_oauth_custom_client_enabled: false,
|
||||
is_system_oauth_params_exists: false,
|
||||
})
|
||||
|
||||
render(
|
||||
<AddOAuthButton
|
||||
pluginPayload={pluginPayload}
|
||||
buttonVariant="secondary"
|
||||
/>,
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button').className).toContain('btn-secondary')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Rendering - Configured State', () => {
|
||||
|
||||
@ -88,23 +88,6 @@ describe('VersionMismatchModal', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('button variants', () => {
|
||||
it('should render cancel button with secondary variant', () => {
|
||||
render(<VersionMismatchModal {...defaultProps} />)
|
||||
|
||||
const cancelBtn = screen.getByRole('button', { name: /app\.newApp\.Cancel/ })
|
||||
expect(cancelBtn).toHaveClass('btn-secondary')
|
||||
})
|
||||
|
||||
it('should render confirm button with primary destructive variant', () => {
|
||||
render(<VersionMismatchModal {...defaultProps} />)
|
||||
|
||||
const confirmBtn = screen.getByRole('button', { name: /app\.newApp\.Confirm/ })
|
||||
expect(confirmBtn).toHaveClass('btn-primary')
|
||||
expect(confirmBtn).toHaveClass('btn-destructive-primary')
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle undefined versions gracefully', () => {
|
||||
render(<VersionMismatchModal {...defaultProps} versions={undefined} />)
|
||||
|
||||
@ -157,16 +157,6 @@ describe('ConfirmModal', () => {
|
||||
// Act & Assert - This will fail the test if user.click throws an unhandled error
|
||||
await user.click(confirmButton)
|
||||
})
|
||||
|
||||
it('should have correct button variants', () => {
|
||||
// Arrange & Act
|
||||
renderComponent()
|
||||
|
||||
// Assert
|
||||
const confirmButton = screen.getByText('common.operation.confirm')
|
||||
expect(confirmButton).toHaveClass('btn-primary')
|
||||
expect(confirmButton).toHaveClass('btn-destructive-primary')
|
||||
})
|
||||
})
|
||||
|
||||
// Edge Cases (REQUIRED)
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
|
||||
@import '../components/base/action-button/index.css';
|
||||
@import '../components/base/badge/index.css';
|
||||
@import '../components/base/ui/button/index.css';
|
||||
@import '../components/base/modal/index.css' layer(base);
|
||||
@import '../components/base/premium-badge/index.css';
|
||||
@import '../components/base/segmented-control/index.css';
|
||||
|
||||
@ -3309,11 +3309,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/avatar/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/video-gallery/VideoPlayer.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
|
||||
Loading…
Reference in New Issue
Block a user