mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 01:48:04 +08:00
feat(tests): add integration tests for explore app list, installed apps, and sidebar lifecycle flows (#32248)
Co-authored-by: CodingOnStar <hanxujiang@dify.com>
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import type { Banner } from '@/models/app'
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { BannerItem } from './banner-item'
|
||||
import { BannerItem } from '../banner-item'
|
||||
|
||||
const mockScrollTo = vi.fn()
|
||||
const mockSlideNodes = vi.fn()
|
||||
@ -16,17 +16,6 @@ vi.mock('@/app/components/base/carousel', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => {
|
||||
const translations: Record<string, string> = {
|
||||
'banner.viewMore': 'View More',
|
||||
}
|
||||
return translations[key] || key
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
const createMockBanner = (overrides: Partial<Banner> = {}): Banner => ({
|
||||
id: 'banner-1',
|
||||
status: 'enabled',
|
||||
@ -40,14 +29,11 @@ const createMockBanner = (overrides: Partial<Banner> = {}): Banner => ({
|
||||
...overrides,
|
||||
} as Banner)
|
||||
|
||||
// Mock ResizeObserver methods declared at module level and initialized
|
||||
const mockResizeObserverObserve = vi.fn()
|
||||
const mockResizeObserverDisconnect = vi.fn()
|
||||
|
||||
// Create mock class outside of describe block for proper hoisting
|
||||
class MockResizeObserver {
|
||||
constructor(_callback: ResizeObserverCallback) {
|
||||
// Store callback if needed
|
||||
}
|
||||
|
||||
observe(...args: Parameters<ResizeObserver['observe']>) {
|
||||
@ -59,7 +45,6 @@ class MockResizeObserver {
|
||||
}
|
||||
|
||||
unobserve() {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,7 +57,6 @@ describe('BannerItem', () => {
|
||||
|
||||
vi.stubGlobal('ResizeObserver', MockResizeObserver)
|
||||
|
||||
// Mock window.innerWidth for responsive tests
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
@ -147,7 +131,7 @@ describe('BannerItem', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('View More')).toBeInTheDocument()
|
||||
expect(screen.getByText('explore.banner.viewMore')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -257,7 +241,6 @@ describe('BannerItem', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Component should render without issues
|
||||
expect(screen.getByText('Test Banner Title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -271,7 +254,6 @@ describe('BannerItem', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Component should render with isPaused
|
||||
expect(screen.getByText('Test Banner Title')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@ -320,7 +302,6 @@ describe('BannerItem', () => {
|
||||
})
|
||||
|
||||
it('sets maxWidth when window width is below breakpoint', () => {
|
||||
// Set window width below RESPONSIVE_BREAKPOINT (1200)
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
@ -335,12 +316,10 @@ describe('BannerItem', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Component should render and apply responsive styles
|
||||
expect(screen.getByText('Test Banner Title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('applies responsive styles when below breakpoint', () => {
|
||||
// Set window width below RESPONSIVE_BREAKPOINT (1200)
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
@ -355,8 +334,7 @@ describe('BannerItem', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// The component should render even with responsive mode
|
||||
expect(screen.getByText('View More')).toBeInTheDocument()
|
||||
expect(screen.getByText('explore.banner.viewMore')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -432,8 +410,6 @@ describe('BannerItem', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// With selectedIndex=0 and 3 slides, nextIndex should be 1
|
||||
// The second indicator button should show the "next slide" state
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons).toHaveLength(3)
|
||||
})
|
||||
@ -3,7 +3,7 @@ import type { Banner as BannerType } from '@/models/app'
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { act } from 'react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import Banner from './banner'
|
||||
import Banner from '../banner'
|
||||
|
||||
const mockUseGetBanners = vi.fn()
|
||||
|
||||
@ -53,7 +53,7 @@ vi.mock('@/app/components/base/carousel', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('./banner-item', () => ({
|
||||
vi.mock('../banner-item', () => ({
|
||||
BannerItem: ({ banner, autoplayDelay, isPaused }: {
|
||||
banner: BannerType
|
||||
autoplayDelay: number
|
||||
@ -105,7 +105,6 @@ describe('Banner', () => {
|
||||
|
||||
render(<Banner />)
|
||||
|
||||
// Loading component renders a spinner
|
||||
const loadingWrapper = document.querySelector('[style*="min-height"]')
|
||||
expect(loadingWrapper).toBeInTheDocument()
|
||||
})
|
||||
@ -266,7 +265,6 @@ describe('Banner', () => {
|
||||
|
||||
const carousel = screen.getByTestId('carousel')
|
||||
|
||||
// Enter and then leave
|
||||
fireEvent.mouseEnter(carousel)
|
||||
fireEvent.mouseLeave(carousel)
|
||||
|
||||
@ -285,7 +283,6 @@ describe('Banner', () => {
|
||||
|
||||
render(<Banner />)
|
||||
|
||||
// Trigger resize event
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
})
|
||||
@ -303,12 +300,10 @@ describe('Banner', () => {
|
||||
|
||||
render(<Banner />)
|
||||
|
||||
// Trigger resize event
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
})
|
||||
|
||||
// Wait for debounce delay (50ms)
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(50)
|
||||
})
|
||||
@ -326,31 +321,25 @@ describe('Banner', () => {
|
||||
|
||||
render(<Banner />)
|
||||
|
||||
// Trigger first resize event
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
})
|
||||
|
||||
// Wait partial time
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(30)
|
||||
})
|
||||
|
||||
// Trigger second resize event
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
})
|
||||
|
||||
// Wait another 30ms (total 60ms from second resize but only 30ms after)
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(30)
|
||||
})
|
||||
|
||||
// Should still be paused (debounce resets)
|
||||
let bannerItem = screen.getByTestId('banner-item')
|
||||
expect(bannerItem).toHaveAttribute('data-is-paused', 'true')
|
||||
|
||||
// Wait remaining time
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(20)
|
||||
})
|
||||
@ -388,7 +377,6 @@ describe('Banner', () => {
|
||||
|
||||
const { unmount } = render(<Banner />)
|
||||
|
||||
// Trigger resize to create timer
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
})
|
||||
@ -462,10 +450,8 @@ describe('Banner', () => {
|
||||
|
||||
const { rerender } = render(<Banner />)
|
||||
|
||||
// Re-render with same props
|
||||
rerender(<Banner />)
|
||||
|
||||
// Component should still be present (memo doesn't break rendering)
|
||||
expect(screen.getByTestId('carousel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@ -1,7 +1,7 @@
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { act } from 'react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { IndicatorButton } from './indicator-button'
|
||||
import { IndicatorButton } from '../indicator-button'
|
||||
|
||||
describe('IndicatorButton', () => {
|
||||
beforeEach(() => {
|
||||
@ -164,7 +164,6 @@ describe('IndicatorButton', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Check for conic-gradient style which indicates progress indicator
|
||||
const progressIndicator = container.querySelector('[style*="conic-gradient"]')
|
||||
expect(progressIndicator).not.toBeInTheDocument()
|
||||
})
|
||||
@ -221,10 +220,8 @@ describe('IndicatorButton', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Initially no progress indicator
|
||||
expect(container.querySelector('[style*="conic-gradient"]')).not.toBeInTheDocument()
|
||||
|
||||
// Rerender with isNextSlide=true
|
||||
rerender(
|
||||
<IndicatorButton
|
||||
index={1}
|
||||
@ -237,7 +234,6 @@ describe('IndicatorButton', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Now progress indicator should be visible
|
||||
expect(container.querySelector('[style*="conic-gradient"]')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -255,11 +251,9 @@ describe('IndicatorButton', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Progress indicator should be present
|
||||
const progressIndicator = container.querySelector('[style*="conic-gradient"]')
|
||||
expect(progressIndicator).toBeInTheDocument()
|
||||
|
||||
// Rerender with new resetKey - this should reset the progress animation
|
||||
rerender(
|
||||
<IndicatorButton
|
||||
index={1}
|
||||
@ -273,7 +267,6 @@ describe('IndicatorButton', () => {
|
||||
)
|
||||
|
||||
const newProgressIndicator = container.querySelector('[style*="conic-gradient"]')
|
||||
// The progress indicator should still be present after reset
|
||||
expect(newProgressIndicator).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -293,8 +286,6 @@ describe('IndicatorButton', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// The component should still render but animation should be paused
|
||||
// requestAnimationFrame might still be called for polling but progress won't update
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
mockRequestAnimationFrame.mockRestore()
|
||||
})
|
||||
@ -315,7 +306,6 @@ describe('IndicatorButton', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Trigger animation frame
|
||||
act(() => {
|
||||
vi.advanceTimersToNextTimer()
|
||||
})
|
||||
@ -342,12 +332,10 @@ describe('IndicatorButton', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Trigger animation frame
|
||||
act(() => {
|
||||
vi.advanceTimersToNextTimer()
|
||||
})
|
||||
|
||||
// Change isNextSlide to false - this should cancel the animation frame
|
||||
rerender(
|
||||
<IndicatorButton
|
||||
index={1}
|
||||
@ -368,7 +356,6 @@ describe('IndicatorButton', () => {
|
||||
const mockOnClick = vi.fn()
|
||||
const mockRequestAnimationFrame = vi.spyOn(window, 'requestAnimationFrame')
|
||||
|
||||
// Mock document.hidden to be true
|
||||
Object.defineProperty(document, 'hidden', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
@ -387,10 +374,8 @@ describe('IndicatorButton', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Component should still render
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
|
||||
// Reset document.hidden
|
||||
Object.defineProperty(document, 'hidden', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
@ -415,7 +400,6 @@ describe('IndicatorButton', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Progress indicator should be visible (animation running)
|
||||
expect(container.querySelector('[style*="conic-gradient"]')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user