diff --git a/web/__tests__/navigation-utils.test.ts b/web/__tests__/navigation-utils.test.ts
index 9a388505d6..fa4986e63d 100644
--- a/web/__tests__/navigation-utils.test.ts
+++ b/web/__tests__/navigation-utils.test.ts
@@ -160,8 +160,7 @@ describe('Navigation Utilities', () => {
page: 1,
limit: '',
keyword: 'test',
- empty: null,
- undefined,
+ filter: '',
})
expect(path).toBe('/datasets/123/documents?page=1&keyword=test')
diff --git a/web/__tests__/real-browser-flicker.test.tsx b/web/__tests__/real-browser-flicker.test.tsx
index f71e8de515..0a0ea0c062 100644
--- a/web/__tests__/real-browser-flicker.test.tsx
+++ b/web/__tests__/real-browser-flicker.test.tsx
@@ -39,28 +39,38 @@ const setupMockEnvironment = (storedTheme: string | null, systemPrefersDark = fa
const isDarkQuery = DARK_MODE_MEDIA_QUERY.test(query)
const matches = isDarkQuery ? systemPrefersDark : false
+ const handleAddListener = (listener: (event: MediaQueryListEvent) => void) => {
+ listeners.add(listener)
+ }
+
+ const handleRemoveListener = (listener: (event: MediaQueryListEvent) => void) => {
+ listeners.delete(listener)
+ }
+
+ const handleAddEventListener = (_event: string, listener: EventListener) => {
+ if (typeof listener === 'function')
+ listeners.add(listener as (event: MediaQueryListEvent) => void)
+ }
+
+ const handleRemoveEventListener = (_event: string, listener: EventListener) => {
+ if (typeof listener === 'function')
+ listeners.delete(listener as (event: MediaQueryListEvent) => void)
+ }
+
+ const handleDispatchEvent = (event: Event) => {
+ listeners.forEach(listener => listener(event as MediaQueryListEvent))
+ return true
+ }
+
const mediaQueryList: MediaQueryList = {
matches,
media: query,
onchange: null,
- addListener: (listener: MediaQueryListListener) => {
- listeners.add(listener)
- },
- removeListener: (listener: MediaQueryListListener) => {
- listeners.delete(listener)
- },
- addEventListener: (_event, listener: EventListener) => {
- if (typeof listener === 'function')
- listeners.add(listener as MediaQueryListListener)
- },
- removeEventListener: (_event, listener: EventListener) => {
- if (typeof listener === 'function')
- listeners.delete(listener as MediaQueryListListener)
- },
- dispatchEvent: (event: Event) => {
- listeners.forEach(listener => listener(event as MediaQueryListEvent))
- return true
- },
+ addListener: handleAddListener,
+ removeListener: handleRemoveListener,
+ addEventListener: handleAddEventListener,
+ removeEventListener: handleRemoveEventListener,
+ dispatchEvent: handleDispatchEvent,
}
return mediaQueryList
@@ -69,6 +79,121 @@ const setupMockEnvironment = (storedTheme: string | null, systemPrefersDark = fa
jest.spyOn(window, 'matchMedia').mockImplementation(mockMatchMedia)
}
+// Helper function to create timing page component
+const createTimingPageComponent = (
+ timingData: Array<{ phase: string; timestamp: number; styles: { backgroundColor: string; color: string } }>,
+) => {
+ const recordTiming = (phase: string, styles: { backgroundColor: string; color: string }) => {
+ timingData.push({
+ phase,
+ timestamp: performance.now(),
+ styles,
+ })
+ }
+
+ const TimingPageComponent = () => {
+ const [mounted, setMounted] = useState(false)
+ const { theme } = useTheme()
+ const isDark = mounted ? theme === 'dark' : false
+
+ const currentStyles = {
+ backgroundColor: isDark ? '#1f2937' : '#ffffff',
+ color: isDark ? '#ffffff' : '#000000',
+ }
+
+ recordTiming(mounted ? 'CSR' : 'Initial', currentStyles)
+
+ useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ return (
+
+
+ Phase: {mounted ? 'CSR' : 'Initial'} | Theme: {theme} | Visual: {isDark ? 'dark' : 'light'}
+
+
+ )
+ }
+
+ return TimingPageComponent
+}
+
+// Helper function to create CSS test component
+const createCSSTestComponent = (
+ cssStates: Array<{ className: string; timestamp: number }>,
+) => {
+ const recordCSSState = (className: string) => {
+ cssStates.push({
+ className,
+ timestamp: performance.now(),
+ })
+ }
+
+ const CSSTestComponent = () => {
+ const [mounted, setMounted] = useState(false)
+ const { theme } = useTheme()
+ const isDark = mounted ? theme === 'dark' : false
+
+ const className = `min-h-screen ${isDark ? 'bg-gray-900 text-white' : 'bg-white text-black'}`
+
+ recordCSSState(className)
+
+ useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ return (
+
+ )
+ }
+
+ return CSSTestComponent
+}
+
+// Helper function to create performance test component
+const createPerformanceTestComponent = (
+ performanceMarks: Array<{ event: string; timestamp: number }>,
+) => {
+ const recordPerformanceMark = (event: string) => {
+ performanceMarks.push({ event, timestamp: performance.now() })
+ }
+
+ const PerformanceTestComponent = () => {
+ const [mounted, setMounted] = useState(false)
+ const { theme } = useTheme()
+
+ recordPerformanceMark('component-render')
+
+ useEffect(() => {
+ recordPerformanceMark('mount-start')
+ setMounted(true)
+ recordPerformanceMark('mount-complete')
+ }, [])
+
+ useEffect(() => {
+ if (theme)
+ recordPerformanceMark('theme-available')
+ }, [theme])
+
+ return (
+
+ Mounted: {mounted.toString()} | Theme: {theme || 'loading'}
+
+ )
+ }
+
+ return PerformanceTestComponent
+}
+
// Simulate real page component based on Dify's actual theme usage
const PageComponent = () => {
const [mounted, setMounted] = useState(false)
@@ -227,39 +352,7 @@ describe('Real Browser Environment Dark Mode Flicker Test', () => {
setupMockEnvironment('dark')
const timingData: Array<{ phase: string; timestamp: number; styles: any }> = []
-
- const TimingPageComponent = () => {
- const [mounted, setMounted] = useState(false)
- const { theme } = useTheme()
- const isDark = mounted ? theme === 'dark' : false
-
- // Record timing and styles for each render phase
- const currentStyles = {
- backgroundColor: isDark ? '#1f2937' : '#ffffff',
- color: isDark ? '#ffffff' : '#000000',
- }
-
- timingData.push({
- phase: mounted ? 'CSR' : 'Initial',
- timestamp: performance.now(),
- styles: currentStyles,
- })
-
- useEffect(() => {
- setMounted(true)
- }, [])
-
- return (
-
-
- Phase: {mounted ? 'CSR' : 'Initial'} | Theme: {theme} | Visual: {isDark ? 'dark' : 'light'}
-
-
- )
- }
+ const TimingPageComponent = createTimingPageComponent(timingData)
render(
@@ -295,33 +388,7 @@ describe('Real Browser Environment Dark Mode Flicker Test', () => {
setupMockEnvironment('dark')
const cssStates: Array<{ className: string; timestamp: number }> = []
-
- const CSSTestComponent = () => {
- const [mounted, setMounted] = useState(false)
- const { theme } = useTheme()
- const isDark = mounted ? theme === 'dark' : false
-
- // Simulate Tailwind CSS class application
- const className = `min-h-screen ${isDark ? 'bg-gray-900 text-white' : 'bg-white text-black'}`
-
- cssStates.push({
- className,
- timestamp: performance.now(),
- })
-
- useEffect(() => {
- setMounted(true)
- }, [])
-
- return (
-
- )
- }
+ const CSSTestComponent = createCSSTestComponent(cssStates)
render(
@@ -413,34 +480,12 @@ describe('Real Browser Environment Dark Mode Flicker Test', () => {
test('verifies ThemeProvider position fix reduces initialization delay', async () => {
const performanceMarks: Array<{ event: string; timestamp: number }> = []
- const PerformanceTestComponent = () => {
- const [mounted, setMounted] = useState(false)
- const { theme } = useTheme()
-
- performanceMarks.push({ event: 'component-render', timestamp: performance.now() })
-
- useEffect(() => {
- performanceMarks.push({ event: 'mount-start', timestamp: performance.now() })
- setMounted(true)
- performanceMarks.push({ event: 'mount-complete', timestamp: performance.now() })
- }, [])
-
- useEffect(() => {
- if (theme)
- performanceMarks.push({ event: 'theme-available', timestamp: performance.now() })
- }, [theme])
-
- return (
-
- Mounted: {mounted.toString()} | Theme: {theme || 'loading'}
-
- )
- }
-
setupMockEnvironment('dark')
expect(window.localStorage.getItem('theme')).toBe('dark')
+ const PerformanceTestComponent = createPerformanceTestComponent(performanceMarks)
+
render(
diff --git a/web/__tests__/unified-tags-logic.test.ts b/web/__tests__/unified-tags-logic.test.ts
index c920e28e0a..ec73a6a268 100644
--- a/web/__tests__/unified-tags-logic.test.ts
+++ b/web/__tests__/unified-tags-logic.test.ts
@@ -70,14 +70,18 @@ describe('Unified Tags Editing - Pure Logic Tests', () => {
})
describe('Fallback Logic (from layout-main.tsx)', () => {
+ type Tag = { id: string; name: string }
+ type AppDetail = { tags: Tag[] }
+ type FallbackResult = { tags?: Tag[] } | null
+ // no-op
it('should trigger fallback when tags are missing or empty', () => {
- const appDetailWithoutTags = { tags: [] }
- const appDetailWithTags = { tags: [{ id: 'tag1' }] }
- const appDetailWithUndefinedTags = { tags: undefined as any }
+ const appDetailWithoutTags: AppDetail = { tags: [] }
+ const appDetailWithTags: AppDetail = { tags: [{ id: 'tag1', name: 't' }] }
+ const appDetailWithUndefinedTags: { tags: Tag[] | undefined } = { tags: undefined }
// This simulates the condition in layout-main.tsx
- const shouldFallback1 = !appDetailWithoutTags.tags || appDetailWithoutTags.tags.length === 0
- const shouldFallback2 = !appDetailWithTags.tags || appDetailWithTags.tags.length === 0
+ const shouldFallback1 = appDetailWithoutTags.tags.length === 0
+ const shouldFallback2 = appDetailWithTags.tags.length === 0
const shouldFallback3 = !appDetailWithUndefinedTags.tags || appDetailWithUndefinedTags.tags.length === 0
expect(shouldFallback1).toBe(true) // Empty array should trigger fallback
@@ -86,24 +90,26 @@ describe('Unified Tags Editing - Pure Logic Tests', () => {
})
it('should preserve tags when fallback succeeds', () => {
- const originalAppDetail = { tags: [] as any[] }
- const fallbackResult = { tags: [{ id: 'tag1', name: 'fallback-tag' }] }
+ const originalAppDetail: AppDetail = { tags: [] }
+ const fallbackResult: { tags?: Tag[] } = { tags: [{ id: 'tag1', name: 'fallback-tag' }] }
// This simulates the successful fallback in layout-main.tsx
- if (fallbackResult?.tags)
- originalAppDetail.tags = fallbackResult.tags
+ const tags = fallbackResult.tags
+ if (tags)
+ originalAppDetail.tags = tags
expect(originalAppDetail.tags).toEqual(fallbackResult.tags)
expect(originalAppDetail.tags.length).toBe(1)
})
it('should continue with empty tags when fallback fails', () => {
- const originalAppDetail: { tags: any[] } = { tags: [] }
- const fallbackResult: { tags?: any[] } | null = null
+ const originalAppDetail: AppDetail = { tags: [] }
+ const fallbackResult = null as FallbackResult
// This simulates fallback failure in layout-main.tsx
- if (fallbackResult?.tags)
- originalAppDetail.tags = fallbackResult.tags
+ const tags: Tag[] | undefined = fallbackResult && 'tags' in fallbackResult ? fallbackResult.tags : undefined
+ if (tags)
+ originalAppDetail.tags = tags
expect(originalAppDetail.tags).toEqual([])
})