dify/web/app/components/app-sidebar/__tests__/sidebar-animation-issues.spec.tsx
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

266 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { fireEvent, render, screen } from '@testing-library/react'
import * as React from 'react'
// Simple Mock Components that reproduce the exact UI issues
const MockNavLink = ({ name, mode }: { name: string; mode: string }) => {
return (
<a
className={`group flex h-9 items-center rounded-md py-2 text-sm font-normal ${mode === 'expand' ? 'px-3' : 'px-2.5'} `}
data-testid={`nav-link-${name}`}
data-mode={mode}
>
{/* Icon with inconsistent margin - reproduces issue #2 */}
<svg
className={`h-4 w-4 shrink-0 ${mode === 'expand' ? 'mr-2' : 'mr-0'}`}
data-testid={`nav-icon-${name}`}
/>
{/* Text that appears/disappears abruptly - reproduces issue #2 */}
{mode === 'expand' && <span data-testid={`nav-text-${name}`}>{name}</span>}
</a>
)
}
const MockSidebarToggleButton = ({
expand,
onToggle,
}: {
expand: boolean
onToggle: () => void
}) => {
return (
<div
className={`flex shrink-0 flex-col border-r border-divider-burn bg-background-default-subtle transition-all ${expand ? 'w-[216px]' : 'w-14'} `}
data-testid="sidebar-container"
>
{/* Top section with variable padding - reproduces issue #1 */}
<div className={`shrink-0 ${expand ? 'p-2' : 'p-1'}`} data-testid="top-section">
App Info Area
</div>
{/* Navigation section - reproduces issue #2 */}
<nav className={`grow space-y-1 ${expand ? 'p-4' : 'px-2.5 py-4'}`} data-testid="navigation">
<MockNavLink name="Orchestrate" mode={expand ? 'expand' : 'collapse'} />
<MockNavLink name="API Access" mode={expand ? 'expand' : 'collapse'} />
<MockNavLink name="Logs & Annotations" mode={expand ? 'expand' : 'collapse'} />
<MockNavLink name="Monitoring" mode={expand ? 'expand' : 'collapse'} />
</nav>
{/* Toggle button section with consistent padding - issue #1 FIXED */}
<div className="shrink-0 px-4 py-3" data-testid="toggle-section">
<button
type="button"
className="flex h-6 w-6 cursor-pointer items-center justify-center"
onClick={onToggle}
data-testid="toggle-button"
>
{expand ? '→' : '←'}
</button>
</div>
</div>
)
}
const MockAppInfo = ({ expand }: { expand: boolean }) => {
return (
<div data-testid="app-info" data-expand={expand}>
<button type="button" className="block w-full">
{/* Container with layout mode switching - reproduces issue #3 */}
<div
className={`flex rounded-lg ${expand ? 'flex-col gap-2 p-2 pb-2.5' : 'items-start justify-center gap-1 p-1'}`}
>
{/* Icon container with justify-between to flex-col switch - reproduces issue #3 */}
<div
className={`flex items-center self-stretch ${expand ? 'justify-between' : 'flex-col gap-1'}`}
data-testid="icon-container"
>
{/* Icon with size changes - reproduces issue #3 */}
<div
data-testid="app-icon"
data-size={expand ? 'large' : 'small'}
style={{
width: expand ? '40px' : '24px',
height: expand ? '40px' : '24px',
backgroundColor: '#000',
transition: 'all 0.3s ease', // This broad transition causes bounce
}}
>
Icon
</div>
<div className="flex items-center justify-center rounded-md p-0.5">
<div className="flex h-5 w-5 items-center justify-center"></div>
</div>
</div>
{/* Text that appears/disappears conditionally */}
{expand && (
<div className="flex flex-col items-start gap-1">
<div className="flex w-full">
<div className="truncate system-md-semibold text-text-secondary">Test App</div>
</div>
<div className="system-2xs-medium-uppercase text-text-tertiary">chatflow</div>
</div>
)}
</div>
</button>
</div>
)
}
describe('Sidebar Animation Issues Reproduction', () => {
beforeEach(() => {
// Mock getBoundingClientRect for position testing
Element.prototype.getBoundingClientRect = vi.fn(() => ({
width: 200,
height: 40,
x: 10,
y: 10,
left: 10,
right: 210,
top: 10,
bottom: 50,
toJSON: vi.fn(),
}))
})
describe('Issue #1: Toggle Button Position Movement - FIXED', () => {
it('should verify consistent padding prevents button position shift', () => {
let expanded = false
const handleToggle = () => {
expanded = !expanded
}
const { rerender } = render(
<MockSidebarToggleButton expand={false} onToggle={handleToggle} />,
)
// Check collapsed state padding
const toggleSection = screen.getByTestId('toggle-section')
expect(toggleSection).toHaveClass('px-4') // Consistent padding
expect(toggleSection).not.toHaveClass('px-5')
expect(toggleSection).not.toHaveClass('px-6')
// Switch to expanded state
rerender(<MockSidebarToggleButton expand={true} onToggle={handleToggle} />)
// Check expanded state padding - should be the same
expect(toggleSection).toHaveClass('px-4') // Same consistent padding
expect(toggleSection).not.toHaveClass('px-5')
expect(toggleSection).not.toHaveClass('px-6')
})
it('should verify sidebar width animation is working correctly', () => {
const handleToggle = vi.fn()
const { rerender } = render(
<MockSidebarToggleButton expand={false} onToggle={handleToggle} />,
)
const container = screen.getByTestId('sidebar-container')
// Collapsed state
expect(container).toHaveClass('w-14')
expect(container).toHaveClass('transition-all')
// Expanded state
rerender(<MockSidebarToggleButton expand={true} onToggle={handleToggle} />)
expect(container).toHaveClass('w-[216px]')
})
})
describe('Issue #2: Navigation Text Squeeze Animation', () => {
it('should reproduce text squeeze effect from padding and margin changes', () => {
const { rerender } = render(<MockNavLink name="Orchestrate" mode="collapse" />)
const link = screen.getByTestId('nav-link-Orchestrate')
const icon = screen.getByTestId('nav-icon-Orchestrate')
// Collapsed state checks
expect(link).toHaveClass('px-2.5') // 10px padding
expect(icon).toHaveClass('mr-0') // No margin
expect(screen.queryByTestId('nav-text-Orchestrate')).not.toBeInTheDocument()
// Switch to expanded state
rerender(<MockNavLink name="Orchestrate" mode="expand" />)
// Expanded state checks
expect(link).toHaveClass('px-3') // 12px padding (+2px)
expect(icon).toHaveClass('mr-2') // 8px margin (+8px)
expect(screen.getByTestId('nav-text-Orchestrate')).toBeInTheDocument()
})
it('should document the abrupt text rendering issue', () => {
const { rerender } = render(<MockNavLink name="API Access" mode="collapse" />)
// Text completely absent
expect(screen.queryByTestId('nav-text-API Access')).not.toBeInTheDocument()
rerender(<MockNavLink name="API Access" mode="expand" />)
// Text suddenly appears - no transition
expect(screen.getByTestId('nav-text-API Access')).toBeInTheDocument()
})
})
describe('Issue #3: App Icon Bounce Animation', () => {
it('should reproduce icon bounce from layout mode switching', () => {
const { rerender } = render(<MockAppInfo expand={true} />)
const iconContainer = screen.getByTestId('icon-container')
const appIcon = screen.getByTestId('app-icon')
// Expanded state layout
expect(iconContainer).toHaveClass('justify-between')
expect(iconContainer).not.toHaveClass('flex-col')
expect(appIcon).toHaveAttribute('data-size', 'large')
// Switch to collapsed state
rerender(<MockAppInfo expand={false} />)
// Collapsed state layout - completely different layout mode
expect(iconContainer).toHaveClass('flex-col')
expect(iconContainer).toHaveClass('gap-1')
expect(iconContainer).not.toHaveClass('justify-between')
expect(appIcon).toHaveAttribute('data-size', 'small')
})
it('should identify the problematic transition-all property', () => {
render(<MockAppInfo expand={true} />)
const appIcon = screen.getByTestId('app-icon')
const computedStyle = window.getComputedStyle(appIcon)
// The problematic broad transition
expect(computedStyle.transition).toContain('all')
})
})
describe('Interactive Toggle Test', () => {
it('should demonstrate all issues in a single interactive test', () => {
let expanded = false
const handleToggle = () => {
expanded = !expanded
}
const { rerender } = render(
<div data-testid="complete-sidebar">
<MockSidebarToggleButton expand={expanded} onToggle={handleToggle} />
<MockAppInfo expand={expanded} />
</div>,
)
const toggleButton = screen.getByTestId('toggle-button')
// Initial state verification
expect(expanded).toBe(false)
// Simulate toggle click
fireEvent.click(toggleButton)
expanded = true
rerender(
<div data-testid="complete-sidebar">
<MockSidebarToggleButton expand={expanded} onToggle={handleToggle} />
<MockAppInfo expand={expanded} />
</div>,
)
})
})
})