diff --git a/src/main/index.ts b/src/main/index.ts
index d5c3890892..86a67c0728 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -182,6 +182,7 @@ if (!app.requestSingleInstanceLock()) {
// const [x, y] = testWindow1.getPosition()
// testWindow2.setPosition(x + 50, y + 50)
// })
+
/************FOR TESTING ONLY END****************/
// Set app user model id for windows
diff --git a/src/renderer/src/windows/dataRefactorTest/README.md b/src/renderer/src/windows/dataRefactorTest/README.md
index 28009e7ad2..104fbac7a3 100644
--- a/src/renderer/src/windows/dataRefactorTest/README.md
+++ b/src/renderer/src/windows/dataRefactorTest/README.md
@@ -1,6 +1,6 @@
-# PreferenceService 测试窗口
+# 数据重构项目测试窗口
-专用于测试 PreferenceService 和 usePreference hooks 功能的独立测试窗口系统。
+专用于测试数据重构项目各项功能的独立测试窗口系统,包括 PreferenceService、CacheService、DataApiService 和相关 React hooks。
## 🎯 当前实现
@@ -18,6 +18,8 @@
## 测试组件
+## PreferenceService 测试模块
+
### 1. PreferenceService 基础测试
- 直接测试服务层API:get, set, getCachedValue, isCached, preload, getMultiple
@@ -53,6 +55,77 @@
- 性能测试
- 多个hook实例同步测试
+## CacheService 测试模块
+
+### 1. CacheService 直接API测试
+
+- **三层缓存架构测试**:Memory cache、Shared cache、Persist cache
+- **基础操作**: get, set, has, delete 方法的完整测试
+- **TTL支持**: 可配置的过期时间测试(2s、5s、10s)
+- **跨窗口同步**: Shared cache 和 Persist cache 的实时同步验证
+- **数据类型支持**: 字符串、数字、对象、数组等多种数据类型
+- **性能优化**: 显示操作计数和自动刷新机制
+
+### 2. Cache Hooks 基础测试
+
+- **useCache Hook**: 测试内存缓存的React集成
+ - 默认值自动设置
+ - 实时值更新和类型安全
+ - Hook生命周期管理
+- **useSharedCache Hook**: 测试跨窗口缓存同步
+ - 跨窗口实时同步验证
+ - 广播机制测试
+ - 并发更新处理
+- **usePersistCache Hook**: 测试持久化缓存
+ - 类型安全的预定义Schema
+ - localStorage持久化
+ - 默认值回退机制
+- **数据类型测试**:
+ - 数字类型滑块控制
+ - 复杂对象结构更新
+ - 实时渲染统计
+
+### 3. Cache 高级功能测试
+
+- **TTL过期机制**:
+ - 实时倒计时进度条
+ - 自动过期验证
+ - 懒加载清理机制
+- **Hook引用保护**:
+ - 活跃Hook的key删除保护
+ - 引用计数验证
+ - 错误处理测试
+- **深度相等性优化**:
+ - 相同引用跳过测试
+ - 相同内容深度比较
+ - 性能优化验证
+- **性能测试**:
+ - 快速更新测试(100次/秒)
+ - 订阅触发统计
+ - 渲染次数监控
+- **多Hook同步**:
+ - 同一key的多个hook实例
+ - 跨缓存类型同步测试
+
+### 4. Cache 压力测试
+
+- **快速操作测试**:
+ - 1000次操作/10秒高频测试
+ - 每秒操作数统计
+ - 错误率监控
+- **并发更新测试**:
+ - 多个Hook同时更新
+ - 跨窗口并发处理
+ - 数据一致性验证
+- **大数据测试**:
+ - 10KB、100KB、1MB对象存储
+ - 内存使用估算
+ - 存储限制警告
+- **存储限制测试**:
+ - localStorage容量测试
+ - 缓存大小监控
+ - 性能影响评估
+
## 启动方式
**自动启动**:应用正常启动时会自动创建两个测试窗口,窗口会自动错位显示避免重叠
@@ -79,36 +152,75 @@ src/renderer/src/windows/dataRefactorTest/
├── entryPoint.tsx # 窗口入口
├── TestApp.tsx # 主应用组件
└── components/
+ # PreferenceService 测试组件
├── PreferenceServiceTests.tsx # 服务层测试
├── PreferenceBasicTests.tsx # 基础Hook测试
├── PreferenceHookTests.tsx # 高级Hook测试
- └── PreferenceMultipleTests.tsx # 批量操作测试
+ ├── PreferenceMultipleTests.tsx # 批量操作测试
+
+ # CacheService 测试组件
+ ├── CacheServiceTests.tsx # 直接API测试
+ ├── CacheBasicTests.tsx # Hook基础测试
+ ├── CacheAdvancedTests.tsx # 高级功能测试
+ ├── CacheStressTests.tsx # 压力测试
+
+ # DataApiService 测试组件
+ ├── DataApiBasicTests.tsx # 基础CRUD测试
+ ├── DataApiAdvancedTests.tsx # 高级功能测试
+ ├── DataApiHookTests.tsx # React Hooks测试
+ └── DataApiStressTests.tsx # 压力测试
```
## 跨窗口同步测试
🔄 **测试场景**:
+### PreferenceService 跨窗口同步
1. **实时同步验证**:在窗口#1中修改某个偏好设置,立即观察窗口#2是否同步更新
2. **并发修改测试**:在两个窗口中快速连续修改同一设置,验证数据一致性
3. **批量操作同步**:在一个窗口中批量更新多个设置,观察另一个窗口的同步表现
4. **Hook实例同步**:验证多个usePreference hook实例是否正确同步
+### CacheService 跨窗口同步
+1. **Shared Cache同步**:在窗口#1中设置共享缓存,观察窗口#2的实时更新
+2. **Persist Cache同步**:修改持久化缓存,验证所有窗口的localStorage同步
+3. **TTL跨窗口验证**:在一个窗口设置TTL,观察其他窗口的过期行为
+4. **并发缓存操作**:多窗口同时操作同一缓存key,验证数据一致性
+5. **Hook引用保护**:在一个窗口尝试删除其他窗口正在使用的缓存key
+
📋 **测试步骤**:
+### PreferenceService 测试步骤
1. 同时打开两个测试窗口(自动启动)
2. 选择相同的偏好设置键进行测试
3. 在窗口#1中修改值,观察窗口#2的反应
4. 检查"Hook 高级功能测试"中的订阅触发次数是否增加
5. 验证缓存状态和实时数据的一致性
+### CacheService 测试步骤
+1. 同时打开两个测试窗口(自动启动)
+2. **Memory Cache测试**:仅在当前窗口有效,其他窗口不受影响
+3. **Shared Cache测试**:在窗口#1设置共享缓存,立即检查窗口#2是否同步
+4. **Persist Cache测试**:修改持久化缓存,验证localStorage和跨窗口同步
+5. **TTL测试**:设置带过期时间的缓存,观察倒计时和跨窗口过期行为
+6. **压力测试**:运行高频操作,监控性能指标和错误率
+7. **引用保护测试**:在Hook活跃时尝试删除key,验证保护机制
+
## 注意事项
-⚠️ **所有测试操作都会影响真实的数据库存储!**
+⚠️ **重要警告**:
-- 测试使用真实的偏好设置系统
-- 修改的值会同步到主应用和所有测试窗口
-- 可以在主应用、测试窗口#1、测试窗口#2之间看到实时同步效果
+### PreferenceService 警告
+- **真实数据库存储**:测试使用真实的偏好设置系统
+- **跨应用同步**:修改的值会同步到主应用和所有测试窗口
+- **持久化影响**:所有更改都会持久化到SQLite数据库
+
+### CacheService 警告
+- **内存占用**:压力测试可能消耗大量内存,影响浏览器性能
+- **localStorage影响**:大数据测试会占用浏览器存储空间(最大5-10MB)
+- **性能影响**:高频操作测试可能短暂影响UI响应性
+- **跨窗口影响**:Shared和Persist缓存会影响所有打开的窗口
+- **TTL清理**:过期缓存会自动清理,可能影响其他功能的测试数据
## 开发模式特性
@@ -119,6 +231,7 @@ src/renderer/src/windows/dataRefactorTest/
## 💡 快速开始
+### PreferenceService 快速测试
1. **启动应用** - 自动打开2个测试窗口
2. **选择测试** - 在"usePreference Hook 测试"中选择要测试的偏好设置键
3. **🎛️ Slider联动测试** - 选择数值类型偏好设置,拖动Slider观察实时变化
@@ -126,11 +239,34 @@ src/renderer/src/windows/dataRefactorTest/
5. **批量Slider测试** - 切换到"数值设置场景",同时拖动多个滑块测试批量同步
6. **高级测试** - 使用"Hook 高级功能测试"验证订阅和缓存机制
+### CacheService 快速测试
+1. **基础操作** - 使用"CacheService 直接API测试"进行get/set/delete操作
+2. **Hook测试** - 在"Cache Hooks 基础测试"中测试不同数据类型和默认值
+3. **TTL验证** - 设置2秒TTL缓存,观察实时倒计时和自动过期
+4. **跨窗口同步** - 设置Shared Cache,在另一窗口验证实时同步
+5. **持久化测试** - 修改Persist Cache,刷新页面验证localStorage持久化
+6. **压力测试** - 运行"快速操作测试",观察高频操作的性能表现
+7. **引用保护** - 启用Hook后尝试删除key,验证保护机制
+
## 🔧 技术实现
+### 基础架构
- **窗口管理**: DataRefactorMigrateService 单例管理多个测试窗口
-- **数据同步**: 基于真实的 PreferenceService 和 IPC 通信
-- **实时主题**: 使用 useSyncExternalStore 实现主题、缩放等设置的实时UI响应
- **跨窗口识别**: 多源窗口编号支持,确保每个窗口都有唯一标识
- **UI框架**: Ant Design + styled-components + React 18
-- **类型安全**: 完整的 TypeScript 类型检查和偏好设置键约束
+- **类型安全**: 完整的 TypeScript 类型检查和类型约束
+
+### PreferenceService 技术实现
+- **数据同步**: 基于真实的 PreferenceService 和 IPC 通信
+- **实时主题**: 使用 useSyncExternalStore 实现主题、缩放等设置的实时UI响应
+- **类型约束**: 偏好设置键的完整TypeScript类型检查
+
+### CacheService 技术实现
+- **三层缓存**: Memory (Map) + Shared (Map + IPC) + Persist (Map + localStorage)
+- **React集成**: useSyncExternalStore 实现外部状态订阅
+- **性能优化**: Object.is() 浅比较 + 深度相等性检查,跳过无效更新
+- **TTL管理**: 懒加载过期检查,基于时间戳的精确控制
+- **IPC同步**: 跨进程消息广播,支持批量操作和增量更新
+- **引用跟踪**: Set-based Hook引用计数,防止意外删除
+- **错误处理**: 完善的try-catch机制和用户友好的错误提示
+- **内存管理**: 自动清理、定时器管理和资源释放
diff --git a/src/renderer/src/windows/dataRefactorTest/TestApp.tsx b/src/renderer/src/windows/dataRefactorTest/TestApp.tsx
index 53610eb6b7..5757db127d 100644
--- a/src/renderer/src/windows/dataRefactorTest/TestApp.tsx
+++ b/src/renderer/src/windows/dataRefactorTest/TestApp.tsx
@@ -2,11 +2,15 @@ import { AppLogo } from '@renderer/config/env'
import { usePreference } from '@renderer/data/hooks/usePreference'
import { loggerService } from '@renderer/services/LoggerService'
import { ThemeMode } from '@shared/data/preference/preferenceTypes'
-import { Button, Card, Col, Divider, Layout, Row, Space, Typography } from 'antd'
+import { Button, Card, Col, Divider, Layout, Row, Space, Typography, Tabs } from 'antd'
import { Activity, AlertTriangle, Database, FlaskConical, Settings, TestTube, TrendingUp, Zap } from 'lucide-react'
import React from 'react'
import styled from 'styled-components'
+import CacheAdvancedTests from './components/CacheAdvancedTests'
+import CacheBasicTests from './components/CacheBasicTests'
+import CacheServiceTests from './components/CacheServiceTests'
+import CacheStressTests from './components/CacheStressTests'
import DataApiAdvancedTests from './components/DataApiAdvancedTests'
import DataApiBasicTests from './components/DataApiBasicTests'
import DataApiHookTests from './components/DataApiHookTests'
@@ -100,149 +104,256 @@ const TestApp: React.FC = () => {
- 此测试窗口用于验证数据重构项目的各项功能,包括 PreferenceService、DataApiService 和相关 React hooks
+ 此测试窗口用于验证数据重构项目的各项功能,包括 PreferenceService、CacheService、DataApiService 和相关 React hooks
的完整测试套件。
- PreferenceService 测试使用真实的偏好设置系统,DataApiService 测试使用专用的测试路由和假数据。
+ PreferenceService 测试使用真实的偏好设置系统,CacheService 测试使用三层缓存架构,DataApiService 测试使用专用的测试路由和假数据。
📋 跨窗口测试指南:在一个窗口中修改偏好设置,观察其他窗口是否实时同步更新。
+ 🗄️ 缓存系统测试:三层缓存架构(Memory/Shared/Persist),支持跨窗口同步、TTL过期、性能优化。
+
+
🚀 数据API测试:包含基础CRUD、高级功能、React hooks和压力测试,全面验证数据请求架构。
- {/* PreferenceService Basic Tests */}
+ {/* Main Content Tabs */}
-
-
- PreferenceService 基础测试
-
- }
- size="small"
- style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
-
-
-
+
+
+ PreferenceService 测试
+
+ ),
+ children: (
+
+ {/* PreferenceService Basic Tests */}
+
+
+
+ PreferenceService 基础测试
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
- {/* Basic Hook Tests */}
-
-
-
- usePreference Hook 测试
-
- }
- size="small"
- style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
-
-
-
+ {/* Basic Hook Tests */}
+
+
+
+ usePreference Hook 测试
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
- {/* Hook Tests */}
-
-
-
- Hook 高级功能测试
-
- }
- size="small"
- style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
-
-
-
+ {/* Hook Tests */}
+
+
+
+ Hook 高级功能测试
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
- {/* Multiple Preferences Tests */}
-
-
-
- usePreferences 批量操作测试
-
- }
- size="small"
- style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
-
-
-
-
+ {/* Multiple Preferences Tests */}
+
+
+
+ usePreferences 批量操作测试
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
+
+ )
+ },
+ {
+ key: 'cache',
+ label: (
+
+
+ CacheService 测试
+
+ ),
+ children: (
+
+ {/* Cache Service Tests */}
+
+
+
+ CacheService 直接API测试
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
-
-
-
- DataApiService 功能测试
-
-
+ {/* Cache Basic Tests */}
+
+
+
+ Cache Hooks 基础测试
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
-
- {/* DataApi Basic Tests */}
-
-
-
- DataApi 基础功能测试 (CRUD操作)
-
- }
- size="small"
- style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
-
-
-
+ {/* Cache Advanced Tests */}
+
+
+
+ Cache 高级功能测试
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
- {/* DataApi Advanced Tests */}
-
-
-
- DataApi 高级功能测试 (取消、重试、批量)
-
- }
- size="small"
- style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
-
-
-
+ {/* Cache Stress Tests */}
+
+
+
+ Cache 压力测试
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
+
+ )
+ },
+ {
+ key: 'dataapi',
+ label: (
+
+
+ DataApiService 测试
+
+ ),
+ children: (
+
+ {/* DataApi Basic Tests */}
+
+
+
+ DataApi 基础功能测试 (CRUD操作)
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
- {/* DataApi Hook Tests */}
-
-
-
- DataApi React Hooks 测试
-
- }
- size="small"
- style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
-
-
-
+ {/* DataApi Advanced Tests */}
+
+
+
+ DataApi 高级功能测试 (取消、重试、批量)
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
- {/* DataApi Stress Tests */}
-
-
-
- DataApi 压力测试 (性能与错误处理)
-
- }
- size="small"
- style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
-
-
+ {/* DataApi Hook Tests */}
+
+
+
+ DataApi React Hooks 测试
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
+
+ {/* DataApi Stress Tests */}
+
+
+
+ DataApi 压力测试 (性能与错误处理)
+
+ }
+ size="small"
+ style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
+
+
+
+
+ )
+ }
+ ]}
+ />
@@ -290,4 +401,42 @@ const Container = styled.div`
margin: 0 auto;
`
+const StyledTabs = styled(Tabs)<{ $isDark: boolean }>`
+ .ant-tabs-nav {
+ background: ${props => props.$isDark ? '#262626' : '#fafafa'};
+ border-radius: 6px 6px 0 0;
+ margin-bottom: 0;
+ }
+
+ .ant-tabs-tab {
+ color: ${props => props.$isDark ? '#d9d9d9' : '#666'} !important;
+
+ &:hover {
+ color: ${props => props.$isDark ? '#fff' : '#000'} !important;
+ }
+
+ &.ant-tabs-tab-active {
+ color: ${props => props.$isDark ? '#1890ff' : '#1890ff'} !important;
+
+ .ant-tabs-tab-btn {
+ color: ${props => props.$isDark ? '#1890ff' : '#1890ff'} !important;
+ }
+ }
+ }
+
+ .ant-tabs-ink-bar {
+ background: ${props => props.$isDark ? '#1890ff' : '#1890ff'};
+ }
+
+ .ant-tabs-content {
+ background: ${props => props.$isDark ? '#1f1f1f' : '#fff'};
+ border-radius: 0 0 6px 6px;
+ padding: 24px 0;
+ }
+
+ .ant-tabs-tabpane {
+ color: ${props => props.$isDark ? '#fff' : '#000'};
+ }
+`
+
export default TestApp
diff --git a/src/renderer/src/windows/dataRefactorTest/components/CacheAdvancedTests.tsx b/src/renderer/src/windows/dataRefactorTest/components/CacheAdvancedTests.tsx
new file mode 100644
index 0000000000..e8809e07ee
--- /dev/null
+++ b/src/renderer/src/windows/dataRefactorTest/components/CacheAdvancedTests.tsx
@@ -0,0 +1,472 @@
+import { cacheService } from '@renderer/data/CacheService'
+import { useCache, useSharedCache } from '@renderer/data/hooks/useCache'
+import { usePreference } from '@renderer/data/hooks/usePreference'
+import { loggerService } from '@renderer/services/LoggerService'
+import { ThemeMode } from '@shared/data/preference/preferenceTypes'
+import { Button, Input, message, Space, Typography, Card, Row, Col, Divider, Progress, Badge, Tag } from 'antd'
+import { Clock, Shield, Zap, Activity, AlertTriangle, CheckCircle, XCircle, Timer } from 'lucide-react'
+import React, { useState, useEffect, useRef, useCallback } from 'react'
+import styled from 'styled-components'
+
+const { Text, Title } = Typography
+
+const logger = loggerService.withContext('CacheAdvancedTests')
+
+/**
+ * Advanced cache testing component
+ * Tests TTL expiration, hook reference tracking, deep equality, performance
+ */
+const CacheAdvancedTests: React.FC = () => {
+ const [currentTheme] = usePreference('ui.theme_mode')
+ const isDarkTheme = currentTheme === ThemeMode.dark
+
+ // TTL Testing
+ const [ttlKey] = useState('test-ttl-cache')
+ const [ttlValue, setTtlValue] = useCache(ttlKey)
+ const [ttlExpireTime, setTtlExpireTime] = useState(null)
+ const [ttlProgress, setTtlProgress] = useState(0)
+
+ // Hook Reference Tracking
+ const [protectedKey] = useState('test-protected-cache')
+ const [protectedValue, setProtectedValue] = useCache(protectedKey, 'protected-value')
+ const [deleteAttemptResult, setDeleteAttemptResult] = useState('')
+
+ // Deep Equality Testing
+ const [deepEqualKey] = useState('test-deep-equal')
+ const [objectValue, setObjectValue] = useCache(deepEqualKey, { nested: { count: 0 }, tags: ['initial'] })
+ const [updateSkipCount, setUpdateSkipCount] = useState(0)
+
+ // Performance Testing
+ const [perfKey] = useState('test-performance')
+ const [perfValue, setPerfValue] = useCache(perfKey, 0)
+ const [rapidUpdateCount, setRapidUpdateCount] = useState(0)
+ const [subscriptionTriggers, setSubscriptionTriggers] = useState(0)
+ const renderCountRef = useRef(0)
+ const [displayRenderCount, setDisplayRenderCount] = useState(0)
+
+ // Multi-hook testing
+ const [multiKey] = useState('test-multi-hook')
+ const [value1] = useCache(multiKey, 'hook-1-default')
+ const [value2] = useCache(multiKey, 'hook-2-default')
+ const [value3] = useSharedCache(multiKey, 'hook-3-shared')
+
+ const intervalRef = useRef()
+ const performanceTestRef = useRef()
+
+ // Update render count without causing re-renders
+ renderCountRef.current += 1
+
+ // Track subscription changes
+ useEffect(() => {
+ const unsubscribe = cacheService.subscribe(perfKey, () => {
+ setSubscriptionTriggers(prev => prev + 1)
+ })
+ return unsubscribe
+ }, [perfKey])
+
+ // TTL Testing Functions
+ const startTTLTest = useCallback((ttlMs: number) => {
+ const testValue = { message: 'TTL Test', timestamp: Date.now() }
+ cacheService.set(ttlKey, testValue, ttlMs)
+ setTtlValue(testValue)
+
+ const expireAt = Date.now() + ttlMs
+ setTtlExpireTime(expireAt)
+
+ // Clear previous interval
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current)
+ }
+
+ // Update progress every 100ms
+ intervalRef.current = setInterval(() => {
+ const now = Date.now()
+ const remaining = Math.max(0, expireAt - now)
+ const progress = Math.max(0, 100 - (remaining / ttlMs) * 100)
+
+ setTtlProgress(progress)
+
+ if (remaining <= 0) {
+ clearInterval(intervalRef.current!)
+ setTtlExpireTime(null)
+ message.info('TTL expired, checking value...')
+
+ // Check if value is actually expired
+ setTimeout(() => {
+ const currentValue = cacheService.get(ttlKey)
+ if (currentValue === undefined) {
+ message.success('TTL expiration working correctly!')
+ } else {
+ message.warning('TTL expiration may have failed')
+ }
+ }, 100)
+ }
+ }, 100)
+
+ message.info(`TTL test started: ${ttlMs}ms`)
+ logger.info('TTL test started', { key: ttlKey, ttl: ttlMs, expireAt })
+ }, [ttlKey, setTtlValue])
+
+ // Hook Reference Tracking Test
+ const testDeleteProtection = () => {
+ try {
+ const deleted = cacheService.delete(protectedKey)
+ setDeleteAttemptResult(deleted ? 'Deleted (unexpected!)' : 'Protected (expected)')
+ logger.info('Delete protection test', { key: protectedKey, deleted })
+ } catch (error) {
+ setDeleteAttemptResult(`Error: ${(error as Error).message}`)
+ logger.error('Delete protection test error', error as Error)
+ }
+ }
+
+ // Deep Equality Testing
+ const testDeepEquality = (operation: string) => {
+ const currentCount = updateSkipCount
+
+ switch (operation) {
+ case 'same-reference':
+ // Set same reference - should skip
+ setObjectValue(objectValue)
+ break
+
+ case 'same-content':
+ // Set same content but different reference - should skip with deep comparison
+ setObjectValue({ nested: { count: objectValue?.nested?.count || 0 }, tags: [...(objectValue?.tags || [])] })
+ break
+
+ case 'different-content':
+ // Set different content - should update
+ setObjectValue({
+ nested: { count: (objectValue?.nested?.count || 0) + 1 },
+ tags: [...(objectValue?.tags || []), `update-${Date.now()}`]
+ })
+ break
+ }
+
+ // Check if update count changed
+ setTimeout(() => {
+ if (currentCount === updateSkipCount) {
+ message.success('Update skipped due to equality check')
+ } else {
+ message.info('Update applied due to content change')
+ }
+ }, 100)
+
+ logger.info('Deep equality test', { operation, currentCount, objectValue })
+ }
+
+ // Performance Testing
+ const startRapidUpdates = () => {
+ let count = 0
+ const startTime = Date.now()
+
+ performanceTestRef.current = setInterval(() => {
+ count++
+ setPerfValue(count)
+ setRapidUpdateCount(count)
+
+ if (count >= 100) {
+ clearInterval(performanceTestRef.current!)
+ const duration = Date.now() - startTime
+ message.success(`Rapid updates test completed: ${count} updates in ${duration}ms`)
+ logger.info('Rapid updates test completed', { count, duration })
+ }
+ }, 10) // Update every 10ms
+ }
+
+ const stopRapidUpdates = () => {
+ if (performanceTestRef.current) {
+ clearInterval(performanceTestRef.current)
+ message.info('Rapid updates test stopped')
+ }
+ }
+
+ // Cleanup
+ useEffect(() => {
+ return () => {
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current)
+ }
+ if (performanceTestRef.current) {
+ clearInterval(performanceTestRef.current)
+ }
+ }
+ }, [])
+
+ return (
+
+
+
+
+ Advanced Features • Renders: {displayRenderCount || renderCountRef.current} • Subscriptions: {subscriptionTriggers}
+
+
+
+
+
+ {/* TTL Testing */}
+
+
+
+ TTL Expiration Testing
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ Key: {ttlKey}
+
+
+
+
+
+
+
+ {ttlExpireTime && (
+
+
Expiration Progress:
+
+ )}
+
+
+ Current Value:
+ {ttlValue ? JSON.stringify(ttlValue, null, 2) : 'undefined'}
+
+
+
+
+
+ {/* Hook Reference Tracking */}
+
+
+
+ Hook Reference Protection
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ Key: {protectedKey}
+
+
+ }
+ >
+ Attempt to Delete Key
+
+
+ {deleteAttemptResult && (
+
+ {deleteAttemptResult}
+
+ )}
+
+
+ Current Value:
+ {JSON.stringify(protectedValue, null, 2)}
+
+
+
+
+
+
+
+ {/* Deep Equality Testing */}
+
+
+
+ Deep Equality Optimization
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ Key: {deepEqualKey}
+ Skip Count:
+
+
+
+
+
+
+
+
+ Current Object:
+ {JSON.stringify(objectValue, null, 2)}
+
+
+
+
+
+ {/* Performance Testing */}
+
+
+
+ Performance Testing
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ Key: {perfKey}
+ Updates:
+
+
+ }>
+ Start Rapid Updates
+
+ }>
+ Stop
+
+
+
+
+ Performance Value:
+ {JSON.stringify(perfValue, null, 2)}
+
+
+
+
+
+
+
+
+ {/* Multi-Hook Synchronization */}
+
+
+ Multi-Hook Synchronization Test
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ Testing multiple hooks using the same key: {multiKey}
+
+
+
+
+
+ {JSON.stringify(value1, null, 2)}
+
+
+
+
+
+
+ {JSON.stringify(value2, null, 2)}
+
+
+
+
+
+
+ {JSON.stringify(value3, null, 2)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 💡 高级功能测试: TTL过期机制、Hook引用保护、深度相等性优化、性能测试、多Hook同步验证
+
+
+
+
+ )
+}
+
+const TestContainer = styled.div<{ $isDark: boolean }>`
+ color: ${props => props.$isDark ? '#fff' : '#000'};
+`
+
+const ResultDisplay = styled.div<{ $isDark: boolean }>`
+ background: ${props => props.$isDark ? '#0d1117' : '#f6f8fa'};
+ border: 1px solid ${props => props.$isDark ? '#30363d' : '#d0d7de'};
+ border-radius: 6px;
+ padding: 8px;
+ font-size: 11px;
+ max-height: 120px;
+ overflow-y: auto;
+
+ pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-break: break-all;
+ color: ${props => props.$isDark ? '#e6edf3' : '#1f2328'};
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
+ }
+`
+
+export default CacheAdvancedTests
\ No newline at end of file
diff --git a/src/renderer/src/windows/dataRefactorTest/components/CacheBasicTests.tsx b/src/renderer/src/windows/dataRefactorTest/components/CacheBasicTests.tsx
new file mode 100644
index 0000000000..d44e6e84a5
--- /dev/null
+++ b/src/renderer/src/windows/dataRefactorTest/components/CacheBasicTests.tsx
@@ -0,0 +1,438 @@
+import { useCache, useSharedCache, usePersistCache } from '@renderer/data/hooks/useCache'
+import { usePreference } from '@renderer/data/hooks/usePreference'
+import { loggerService } from '@renderer/services/LoggerService'
+import type { PersistCacheKey } from '@shared/data/cache/cacheSchemas'
+import { ThemeMode } from '@shared/data/preference/preferenceTypes'
+import { Button, Input, message, Select, Space, Typography, Card, Row, Col, Divider, Slider } from 'antd'
+import { Zap, Database, Eye, Edit, RefreshCw, Users, HardDrive } from 'lucide-react'
+import React, { useState, useEffect, useRef } from 'react'
+import styled from 'styled-components'
+
+const { Text, Title } = Typography
+const { Option } = Select
+const { TextArea } = Input
+
+const logger = loggerService.withContext('CacheBasicTests')
+
+/**
+ * Basic cache hooks testing component
+ * Tests useCache, useSharedCache, and usePersistCache hooks
+ */
+const CacheBasicTests: React.FC = () => {
+ const [currentTheme] = usePreference('ui.theme_mode')
+ const isDarkTheme = currentTheme === ThemeMode.dark
+
+ // useCache testing
+ const [memoryCacheKey, setMemoryCacheKey] = useState('test-hook-memory-1')
+ const [memoryCacheDefault, setMemoryCacheDefault] = useState('default-memory-value')
+ const [newMemoryValue, setNewMemoryValue] = useState('')
+ const [memoryValue, setMemoryValue] = useCache(memoryCacheKey, memoryCacheDefault)
+
+ // useSharedCache testing
+ const [sharedCacheKey, setSharedCacheKey] = useState('test-hook-shared-1')
+ const [sharedCacheDefault, setSharedCacheDefault] = useState('default-shared-value')
+ const [newSharedValue, setNewSharedValue] = useState('')
+ const [sharedValue, setSharedValue] = useSharedCache(sharedCacheKey, sharedCacheDefault)
+
+ // usePersistCache testing
+ const [persistCacheKey, setPersistCacheKey] = useState('example-1')
+ const [newPersistValue, setNewPersistValue] = useState('')
+ const [persistValue, setPersistValue] = usePersistCache(persistCacheKey)
+
+ // Testing different data types
+ const [numberKey] = useState('test-number-cache')
+ const [numberValue, setNumberValue] = useCache(numberKey, 42)
+
+ const [objectKey] = useState('test-object-cache')
+ const [objectValue, setObjectValue] = useCache(objectKey, { name: 'test', count: 0, active: true })
+
+ // Stats
+ const renderCountRef = useRef(0)
+ const [displayRenderCount, setDisplayRenderCount] = useState(0)
+ const [updateCount, setUpdateCount] = useState(0)
+
+ // Available persist keys
+ const persistKeys: PersistCacheKey[] = ['example-1', 'example-2', 'example-3', 'example-4']
+
+ // Update render count without causing re-renders
+ renderCountRef.current += 1
+
+ const parseValue = (value: string): any => {
+ if (!value) return undefined
+ try {
+ return JSON.parse(value)
+ } catch {
+ return value
+ }
+ }
+
+ const formatValue = (value: any): string => {
+ if (value === undefined) return 'undefined'
+ if (value === null) return 'null'
+ if (typeof value === 'string') return `"${value}"`
+ return JSON.stringify(value, null, 2)
+ }
+
+ // Memory cache operations
+ const handleMemoryUpdate = () => {
+ try {
+ const parsed = parseValue(newMemoryValue)
+ setMemoryValue(parsed)
+ setNewMemoryValue('')
+ setUpdateCount(prev => prev + 1)
+ message.success(`Memory cache updated: ${memoryCacheKey}`)
+ logger.info('Memory cache updated via hook', { key: memoryCacheKey, value: parsed })
+ } catch (error) {
+ message.error(`Memory cache update failed: ${(error as Error).message}`)
+ }
+ }
+
+ // Shared cache operations
+ const handleSharedUpdate = () => {
+ try {
+ const parsed = parseValue(newSharedValue)
+ setSharedValue(parsed)
+ setNewSharedValue('')
+ setUpdateCount(prev => prev + 1)
+ message.success(`Shared cache updated: ${sharedCacheKey} (broadcasted to other windows)`)
+ logger.info('Shared cache updated via hook', { key: sharedCacheKey, value: parsed })
+ } catch (error) {
+ message.error(`Shared cache update failed: ${(error as Error).message}`)
+ }
+ }
+
+ // Persist cache operations
+ const handlePersistUpdate = () => {
+ try {
+ let parsed: any
+ // Handle different types based on schema
+ if (persistCacheKey === 'example-1') {
+ parsed = newPersistValue // string
+ } else if (persistCacheKey === 'example-2') {
+ parsed = parseInt(newPersistValue) || 0 // number
+ } else if (persistCacheKey === 'example-3') {
+ parsed = newPersistValue === 'true' // boolean
+ } else if (persistCacheKey === 'example-4') {
+ parsed = parseValue(newPersistValue) // object
+ }
+
+ setPersistValue(parsed as any)
+ setNewPersistValue('')
+ setUpdateCount(prev => prev + 1)
+ message.success(`Persist cache updated: ${persistCacheKey} (saved + broadcasted)`)
+ logger.info('Persist cache updated via hook', { key: persistCacheKey, value: parsed })
+ } catch (error) {
+ message.error(`Persist cache update failed: ${(error as Error).message}`)
+ }
+ }
+
+ // Test different data types
+ const handleNumberUpdate = (newValue: number) => {
+ setNumberValue(newValue)
+ setUpdateCount(prev => prev + 1)
+ logger.info('Number cache updated', { value: newValue })
+ }
+
+ const handleObjectUpdate = (field: string, value: any) => {
+ setObjectValue(prev => ({ ...prev, [field]: value }))
+ setUpdateCount(prev => prev + 1)
+ logger.info('Object cache updated', { field, value })
+ }
+
+ return (
+
+
+
+
+ React Hook Tests • Renders: {displayRenderCount || renderCountRef.current} • Updates: {updateCount}
+
+
+
+
+
+ {/* useCache Testing */}
+
+
+
+ useCache Hook
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ setMemoryCacheKey(e.target.value)}
+ prefix={}
+ />
+
+ setMemoryCacheDefault(e.target.value)}
+ prefix={}
+ />
+
+ setNewMemoryValue(e.target.value)}
+ onPressEnter={handleMemoryUpdate}
+ prefix={}
+ />
+
+
+
+
+ Current Value:
+ {formatValue(memoryValue)}
+
+
+
+
+
+ {/* useSharedCache Testing */}
+
+
+
+ useSharedCache Hook
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ setSharedCacheKey(e.target.value)}
+ prefix={}
+ />
+
+ setSharedCacheDefault(e.target.value)}
+ prefix={}
+ />
+
+ setNewSharedValue(e.target.value)}
+ onPressEnter={handleSharedUpdate}
+ prefix={}
+ />
+
+
+
+
+ Current Value:
+ {formatValue(sharedValue)}
+
+
+
+
+
+ {/* usePersistCache Testing */}
+
+
+
+ usePersistCache Hook
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+
+
+ setNewPersistValue(e.target.value)}
+ onPressEnter={handlePersistUpdate}
+ prefix={}
+ />
+
+
+
+
+ Current Value:
+ {formatValue(persistValue)}
+
+
+
+
+
+
+
+
+ {/* Data Type Testing */}
+
+
+
+
+ Number Type Testing
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ Key: {numberKey}
+ Current Value: {numberValue}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Object Type Testing
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ Key: {objectKey}
+
+
+ handleObjectUpdate('name', e.target.value)}
+ style={{ width: 120 }}
+ />
+ handleObjectUpdate('count', parseInt(e.target.value) || 0)}
+ style={{ width: 80 }}
+ />
+
+
+
+
+ {formatValue(objectValue)}
+
+
+
+
+
+
+
+
+ 💡 提示: useCache 仅在当前窗口有效 • useSharedCache 跨窗口实时同步 • usePersistCache 类型安全的持久化存储
+
+
+
+
+ )
+}
+
+const TestContainer = styled.div<{ $isDark: boolean }>`
+ color: ${props => props.$isDark ? '#fff' : '#000'};
+`
+
+const ResultDisplay = styled.div<{ $isDark: boolean }>`
+ background: ${props => props.$isDark ? '#0d1117' : '#f6f8fa'};
+ border: 1px solid ${props => props.$isDark ? '#30363d' : '#d0d7de'};
+ border-radius: 6px;
+ padding: 8px;
+ font-size: 11px;
+ max-height: 100px;
+ overflow-y: auto;
+
+ pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-break: break-all;
+ color: ${props => props.$isDark ? '#e6edf3' : '#1f2328'};
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
+ }
+`
+
+export default CacheBasicTests
\ No newline at end of file
diff --git a/src/renderer/src/windows/dataRefactorTest/components/CacheServiceTests.tsx b/src/renderer/src/windows/dataRefactorTest/components/CacheServiceTests.tsx
new file mode 100644
index 0000000000..d9e00a192a
--- /dev/null
+++ b/src/renderer/src/windows/dataRefactorTest/components/CacheServiceTests.tsx
@@ -0,0 +1,443 @@
+import { cacheService } from '@renderer/data/CacheService'
+import { loggerService } from '@renderer/services/LoggerService'
+import type { PersistCacheKey, PersistCacheSchema } from '@shared/data/cache/cacheSchemas'
+import { usePreference } from '@renderer/data/hooks/usePreference'
+import { ThemeMode } from '@shared/data/preference/preferenceTypes'
+import { Button, Input, message, Select, Space, Typography, Card, Row, Col, Divider } from 'antd'
+import { Database, Clock, Trash2, Eye, Edit, Zap } from 'lucide-react'
+import React, { useState, useEffect } from 'react'
+import styled from 'styled-components'
+
+const { Text, Title } = Typography
+const { Option } = Select
+const { TextArea } = Input
+
+const logger = loggerService.withContext('CacheServiceTests')
+
+/**
+ * Direct CacheService API testing component
+ * Tests memory, shared, and persist cache operations
+ */
+const CacheServiceTests: React.FC = () => {
+ const [currentTheme] = usePreference('ui.theme_mode')
+ const isDarkTheme = currentTheme === ThemeMode.dark
+
+ // State for test operations
+ const [memoryKey, setMemoryKey] = useState('test-memory-1')
+ const [memoryValue, setMemoryValue] = useState('{"type": "memory", "data": "test"}')
+ const [memoryTTL, setMemoryTTL] = useState('5000')
+
+ const [sharedKey, setSharedKey] = useState('test-shared-1')
+ const [sharedValue, setSharedValue] = useState('{"type": "shared", "data": "cross-window"}')
+ const [sharedTTL, setSharedTTL] = useState('10000')
+
+ const [persistKey, setPersistKey] = useState('example-1')
+ const [persistValue, setPersistValue] = useState('updated-example-value')
+
+ // Display states
+ const [memoryResult, setMemoryResult] = useState(null)
+ const [sharedResult, setSharedResult] = useState(null)
+ const [persistResult, setPersistResult] = useState(null)
+
+ const [updateCount, setUpdateCount] = useState(0)
+
+ // Available persist keys from schema
+ const persistKeys: PersistCacheKey[] = ['example-1', 'example-2', 'example-3', 'example-4']
+
+ const parseValue = (value: string): any => {
+ if (!value) return undefined
+ try {
+ return JSON.parse(value)
+ } catch {
+ return value // Return as string if not valid JSON
+ }
+ }
+
+ const formatValue = (value: any): string => {
+ if (value === undefined) return 'undefined'
+ if (value === null) return 'null'
+ if (typeof value === 'string') return `"${value}"`
+ return JSON.stringify(value, null, 2)
+ }
+
+ // Memory Cache Operations
+ const handleMemorySet = () => {
+ try {
+ const parsed = parseValue(memoryValue)
+ const ttl = memoryTTL ? parseInt(memoryTTL) : undefined
+ cacheService.set(memoryKey, parsed, ttl)
+ message.success(`Memory cache set: ${memoryKey}`)
+ setUpdateCount(prev => prev + 1)
+ logger.info('Memory cache set', { key: memoryKey, value: parsed, ttl })
+ } catch (error) {
+ message.error(`Memory cache set failed: ${(error as Error).message}`)
+ logger.error('Memory cache set failed', error as Error)
+ }
+ }
+
+ const handleMemoryGet = () => {
+ try {
+ const result = cacheService.get(memoryKey)
+ setMemoryResult(result)
+ message.info(`Memory cache get: ${memoryKey}`)
+ logger.info('Memory cache get', { key: memoryKey, result })
+ } catch (error) {
+ message.error(`Memory cache get failed: ${(error as Error).message}`)
+ logger.error('Memory cache get failed', error as Error)
+ }
+ }
+
+ const handleMemoryHas = () => {
+ try {
+ const exists = cacheService.has(memoryKey)
+ message.info(`Memory cache has ${memoryKey}: ${exists}`)
+ logger.info('Memory cache has', { key: memoryKey, exists })
+ } catch (error) {
+ message.error(`Memory cache has failed: ${(error as Error).message}`)
+ }
+ }
+
+ const handleMemoryDelete = () => {
+ try {
+ const deleted = cacheService.delete(memoryKey)
+ message.info(`Memory cache delete ${memoryKey}: ${deleted}`)
+ setMemoryResult(undefined)
+ logger.info('Memory cache delete', { key: memoryKey, deleted })
+ } catch (error) {
+ message.error(`Memory cache delete failed: ${(error as Error).message}`)
+ }
+ }
+
+ // Shared Cache Operations
+ const handleSharedSet = () => {
+ try {
+ const parsed = parseValue(sharedValue)
+ const ttl = sharedTTL ? parseInt(sharedTTL) : undefined
+ cacheService.setShared(sharedKey, parsed, ttl)
+ message.success(`Shared cache set: ${sharedKey} (broadcasted to other windows)`)
+ setUpdateCount(prev => prev + 1)
+ logger.info('Shared cache set', { key: sharedKey, value: parsed, ttl })
+ } catch (error) {
+ message.error(`Shared cache set failed: ${(error as Error).message}`)
+ logger.error('Shared cache set failed', error as Error)
+ }
+ }
+
+ const handleSharedGet = () => {
+ try {
+ const result = cacheService.getShared(sharedKey)
+ setSharedResult(result)
+ message.info(`Shared cache get: ${sharedKey}`)
+ logger.info('Shared cache get', { key: sharedKey, result })
+ } catch (error) {
+ message.error(`Shared cache get failed: ${(error as Error).message}`)
+ logger.error('Shared cache get failed', error as Error)
+ }
+ }
+
+ const handleSharedHas = () => {
+ try {
+ const exists = cacheService.hasShared(sharedKey)
+ message.info(`Shared cache has ${sharedKey}: ${exists}`)
+ logger.info('Shared cache has', { key: sharedKey, exists })
+ } catch (error) {
+ message.error(`Shared cache has failed: ${(error as Error).message}`)
+ }
+ }
+
+ const handleSharedDelete = () => {
+ try {
+ const deleted = cacheService.deleteShared(sharedKey)
+ message.info(`Shared cache delete ${sharedKey}: ${deleted} (broadcasted to other windows)`)
+ setSharedResult(undefined)
+ logger.info('Shared cache delete', { key: sharedKey, deleted })
+ } catch (error) {
+ message.error(`Shared cache delete failed: ${(error as Error).message}`)
+ }
+ }
+
+ // Persist Cache Operations
+ const handlePersistSet = () => {
+ try {
+ let parsed: any
+ // Handle different types based on the schema
+ if (persistKey === 'example-1') {
+ parsed = persistValue // string
+ } else if (persistKey === 'example-2') {
+ parsed = parseInt(persistValue) || 0 // number
+ } else if (persistKey === 'example-3') {
+ parsed = persistValue === 'true' // boolean
+ } else if (persistKey === 'example-4') {
+ parsed = parseValue(persistValue) // object
+ }
+
+ cacheService.setPersist(persistKey, parsed as PersistCacheSchema[typeof persistKey])
+ message.success(`Persist cache set: ${persistKey} (saved to localStorage + broadcasted)`)
+ setUpdateCount(prev => prev + 1)
+ logger.info('Persist cache set', { key: persistKey, value: parsed })
+ } catch (error) {
+ message.error(`Persist cache set failed: ${(error as Error).message}`)
+ logger.error('Persist cache set failed', error as Error)
+ }
+ }
+
+ const handlePersistGet = () => {
+ try {
+ const result = cacheService.getPersist(persistKey)
+ setPersistResult(result)
+ message.info(`Persist cache get: ${persistKey}`)
+ logger.info('Persist cache get', { key: persistKey, result })
+ } catch (error) {
+ message.error(`Persist cache get failed: ${(error as Error).message}`)
+ logger.error('Persist cache get failed', error as Error)
+ }
+ }
+
+ const handlePersistHas = () => {
+ try {
+ const exists = cacheService.hasPersist(persistKey)
+ message.info(`Persist cache has ${persistKey}: ${exists}`)
+ logger.info('Persist cache has', { key: persistKey, exists })
+ } catch (error) {
+ message.error(`Persist cache has failed: ${(error as Error).message}`)
+ }
+ }
+
+ // Auto-refresh results
+ useEffect(() => {
+ const interval = setInterval(() => {
+ // Auto-get current values for display
+ try {
+ const memResult = cacheService.get(memoryKey)
+ const sharedResult = cacheService.getShared(sharedKey)
+ const persistResult = cacheService.getPersist(persistKey)
+
+ setMemoryResult(memResult)
+ setSharedResult(sharedResult)
+ setPersistResult(persistResult)
+ } catch (error) {
+ logger.error('Auto-refresh failed', error as Error)
+ }
+ }, 1000)
+
+ return () => clearInterval(interval)
+ }, [memoryKey, sharedKey, persistKey])
+
+ return (
+
+
+
+
+ 直接测试 CacheService API • Updates: {updateCount} • Auto-refresh: 1s
+
+
+
+
+ {/* Memory Cache Section */}
+
+
+
+ Memory Cache
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ setMemoryKey(e.target.value)}
+ prefix={}
+ />
+
+
+
+
+
+ {/* Shared Cache Section */}
+
+
+
+ Shared Cache
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ setSharedKey(e.target.value)}
+ prefix={}
+ />
+
+
+
+
+
+ {/* Persist Cache Section */}
+
+
+
+ Persist Cache
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ 💡 提示: Memory cache 仅在当前窗口有效 • Shared cache 跨窗口同步 • Persist cache 持久化到 localStorage
+
+
+
+
+ )
+}
+
+const TestContainer = styled.div<{ $isDark: boolean }>`
+ color: ${props => props.$isDark ? '#fff' : '#000'};
+`
+
+const ResultDisplay = styled.div<{ $isDark: boolean }>`
+ background: ${props => props.$isDark ? '#0d1117' : '#f6f8fa'};
+ border: 1px solid ${props => props.$isDark ? '#30363d' : '#d0d7de'};
+ border-radius: 6px;
+ padding: 8px;
+ font-size: 11px;
+
+ pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-break: break-all;
+ color: ${props => props.$isDark ? '#e6edf3' : '#1f2328'};
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
+ }
+`
+
+export default CacheServiceTests
\ No newline at end of file
diff --git a/src/renderer/src/windows/dataRefactorTest/components/CacheStressTests.tsx b/src/renderer/src/windows/dataRefactorTest/components/CacheStressTests.tsx
new file mode 100644
index 0000000000..34542e55ae
--- /dev/null
+++ b/src/renderer/src/windows/dataRefactorTest/components/CacheStressTests.tsx
@@ -0,0 +1,524 @@
+import { cacheService } from '@renderer/data/CacheService'
+import { useCache, useSharedCache } from '@renderer/data/hooks/useCache'
+import { usePreference } from '@renderer/data/hooks/usePreference'
+import { loggerService } from '@renderer/services/LoggerService'
+import { ThemeMode } from '@shared/data/preference/preferenceTypes'
+import { Button, Input, message, Space, Typography, Card, Row, Col, Progress, Statistic, Tag, Alert } from 'antd'
+import { Zap, AlertTriangle, TrendingUp, HardDrive, Users, Clock, Database } from 'lucide-react'
+import React, { useState, useEffect, useRef, useCallback } from 'react'
+import styled from 'styled-components'
+
+const { Text, Title } = Typography
+
+const logger = loggerService.withContext('CacheStressTests')
+
+/**
+ * Cache stress testing component
+ * Tests performance limits, memory usage, concurrent operations
+ */
+const CacheStressTests: React.FC = () => {
+ const [currentTheme] = usePreference('ui.theme_mode')
+ const isDarkTheme = currentTheme === ThemeMode.dark
+
+ // Test States
+ const [isRunning, setIsRunning] = useState(false)
+ const [testProgress, setTestProgress] = useState(0)
+ const [testResults, setTestResults] = useState({})
+
+ // Performance Metrics
+ const [operationsPerSecond, setOperationsPerSecond] = useState(0)
+ const [totalOperations, setTotalOperations] = useState(0)
+ const [memoryUsage, setMemoryUsage] = useState(0)
+ const renderCountRef = useRef(0)
+ const [displayRenderCount, setDisplayRenderCount] = useState(0)
+ const [errorCount, setErrorCount] = useState(0)
+
+ // Concurrent Testing
+ const [concurrentValue1, setConcurrentValue1] = useCache('concurrent-test-1', 0)
+ const [concurrentValue2, setConcurrentValue2] = useCache('concurrent-test-2', 0)
+ const [concurrentShared, setConcurrentShared] = useSharedCache('concurrent-shared', 0)
+
+ // Large Data Testing
+ const [largeDataKey] = useState('large-data-test')
+ const [largeDataSize, setLargeDataSize] = useState(0)
+ const [largeDataValue, setLargeDataValue] = useCache(largeDataKey)
+
+ // Timers and refs
+ const testTimerRef = useRef()
+ const metricsTimerRef = useRef()
+ const concurrentTimerRef = useRef()
+
+ // Update render count without causing re-renders
+ renderCountRef.current += 1
+
+ // Memory Usage Estimation
+ const estimateMemoryUsage = useCallback(() => {
+ try {
+ // Rough estimation based on localStorage size and objects
+ const persistSize = localStorage.getItem('cs_cache_persist')?.length || 0
+ const estimatedSize = persistSize + (totalOperations * 50) // Rough estimate
+ setMemoryUsage(estimatedSize)
+ } catch (error) {
+ logger.error('Memory usage estimation failed', error as Error)
+ }
+ }, [totalOperations])
+
+ useEffect(() => {
+ estimateMemoryUsage()
+ }, [totalOperations, estimateMemoryUsage])
+
+ // Rapid Fire Test
+ const runRapidFireTest = useCallback(async () => {
+ setIsRunning(true)
+ setTestProgress(0)
+ setTotalOperations(0)
+ setErrorCount(0)
+
+ const startTime = Date.now()
+ const testDuration = 10000 // 10 seconds
+ const targetOperations = 1000
+
+ let operationCount = 0
+ let errors = 0
+ let shouldContinue = true // Use local variable instead of state
+
+ const performOperation = () => {
+ if (!shouldContinue) return // Check local flag
+
+ try {
+ const key = `rapid-test-${operationCount % 100}`
+ const value = { id: operationCount, timestamp: Date.now(), data: Math.random().toString(36) }
+
+ // Alternate between different cache types
+ if (operationCount % 3 === 0) {
+ cacheService.set(key, value)
+ } else if (operationCount % 3 === 1) {
+ cacheService.setShared(key, value)
+ } else {
+ cacheService.get(key)
+ }
+
+ operationCount++
+ setTotalOperations(operationCount)
+ setTestProgress((operationCount / targetOperations) * 100)
+
+ // Calculate operations per second
+ const elapsed = Date.now() - startTime
+ setOperationsPerSecond(Math.round((operationCount / elapsed) * 1000))
+
+ if (operationCount >= targetOperations || elapsed >= testDuration) {
+ shouldContinue = false
+ setIsRunning(false)
+ setTestResults({
+ duration: elapsed,
+ operations: operationCount,
+ opsPerSecond: Math.round((operationCount / elapsed) * 1000),
+ errors
+ })
+ message.success(`Rapid fire test completed: ${operationCount} operations in ${elapsed}ms`)
+ logger.info('Rapid fire test completed', { operationCount, elapsed, errors })
+ return
+ }
+
+ // Schedule next operation
+ setTimeout(performOperation, 1)
+ } catch (error) {
+ errors++
+ setErrorCount(errors)
+ logger.error('Rapid fire test operation failed', error as Error)
+ if (shouldContinue) {
+ setTimeout(performOperation, 1)
+ }
+ }
+ }
+
+ // Start the test
+ performOperation()
+
+ // Store a reference to the shouldContinue flag for stopping
+ testTimerRef.current = setTimeout(() => {
+ // This timer will be cleared if the test is stopped early
+ shouldContinue = false
+ setIsRunning(false)
+ }, testDuration)
+ }, [])
+
+ // Concurrent Updates Test
+ const startConcurrentTest = () => {
+ let count1 = 0
+ let count2 = 0
+ let sharedCount = 0
+
+ concurrentTimerRef.current = setInterval(() => {
+ // Simulate concurrent updates from different sources
+ setConcurrentValue1(++count1)
+ setConcurrentValue2(++count2)
+ setConcurrentShared(++sharedCount)
+
+ if (count1 >= 100) {
+ clearInterval(concurrentTimerRef.current!)
+ message.success('Concurrent updates test completed')
+ }
+ }, 50) // Update every 50ms
+
+ message.info('Concurrent updates test started')
+ }
+
+ const stopConcurrentTest = () => {
+ if (concurrentTimerRef.current) {
+ clearInterval(concurrentTimerRef.current)
+ message.info('Concurrent updates test stopped')
+ }
+ }
+
+ // Large Data Test
+ const generateLargeData = (sizeKB: number) => {
+ const targetSize = sizeKB * 1024
+ const baseString = 'a'.repeat(1024) // 1KB string
+ const chunks = Math.floor(targetSize / 1024)
+
+ const largeObject = {
+ id: Date.now(),
+ size: sizeKB,
+ chunks: chunks,
+ data: Array(chunks).fill(baseString),
+ metadata: {
+ created: new Date().toISOString(),
+ type: 'stress-test',
+ description: `Large data test object of ${sizeKB}KB`
+ }
+ }
+
+ try {
+ setLargeDataValue(largeObject)
+ setLargeDataSize(sizeKB)
+ message.success(`Large data test: ${sizeKB}KB object stored`)
+ logger.info('Large data test completed', { sizeKB, chunks })
+ } catch (error) {
+ message.error(`Large data test failed: ${(error as Error).message}`)
+ logger.error('Large data test failed', error as Error)
+ }
+ }
+
+ // LocalStorage Limit Test
+ const testLocalStorageLimit = async () => {
+ try {
+ let testSize = 1
+ let maxSize = 0
+
+ while (testSize <= 10240) { // Test up to 10MB
+ try {
+ const testData = 'x'.repeat(testSize * 1024) // testSize KB
+ localStorage.setItem('storage-limit-test', testData)
+ localStorage.removeItem('storage-limit-test')
+ maxSize = testSize
+ testSize *= 2
+ } catch (error) {
+ break
+ }
+ }
+
+ message.info(`LocalStorage limit test: ~${maxSize}KB available`)
+ logger.info('LocalStorage limit test completed', { maxSize })
+ } catch (error) {
+ message.error(`LocalStorage limit test failed: ${(error as Error).message}`)
+ }
+ }
+
+ // Stop all tests
+ const stopAllTests = () => {
+ setIsRunning(false)
+ if (testTimerRef.current) {
+ clearTimeout(testTimerRef.current)
+ testTimerRef.current = undefined
+ }
+ if (concurrentTimerRef.current) {
+ clearInterval(concurrentTimerRef.current)
+ concurrentTimerRef.current = undefined
+ }
+ message.info('All tests stopped')
+ }
+
+ // Cleanup
+ useEffect(() => {
+ return () => {
+ if (testTimerRef.current) clearTimeout(testTimerRef.current)
+ if (metricsTimerRef.current) clearInterval(metricsTimerRef.current)
+ if (concurrentTimerRef.current) clearInterval(concurrentTimerRef.current)
+ }
+ }, [])
+
+ return (
+
+
+
+
+ Stress Testing • Renders: {displayRenderCount || renderCountRef.current} • Errors: {errorCount}
+
+
+
+
+ {/* Performance Metrics */}
+
+
+ }
+ />
+
+
+ }
+ />
+
+
+ }
+ />
+
+
+ 0 ? '#ff4d4f' : undefined }}
+ prefix={}
+ />
+
+
+
+
+ {/* Rapid Fire Test */}
+
+
+
+ Rapid Fire Operations
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ High-frequency cache operations test (1000 ops in 10s)
+
+
+
+
+
+ {/* Concurrent Updates Test */}
+
+
+
+ Concurrent Updates
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ Multiple hooks updating simultaneously
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+ Start Concurrent Test
+
+
+
+
+
+
+
+
+
+ {/* Large Data Test */}
+
+
+
+ Large Data Storage
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ Test cache with large objects
+
+
+
+
+
+
+
+ {largeDataSize > 0 && (
+
+ )}
+
+
+ Large Data Key: {largeDataKey}
+
+ Current Size: {largeDataSize}KB
+
+
+
+
+
+ {/* Storage Limits Test */}
+
+
+
+ Storage Limits
+
+ }
+ size="small"
+ style={{
+ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff',
+ borderColor: isDarkTheme ? '#303030' : '#d9d9d9'
+ }}
+ >
+
+ Test localStorage capacity and limits
+
+
+
+ }
+ >
+ Test Storage Limits
+
+
+
+ Persist Cache Size Check
+
+ Current persist cache: ~{Math.round(JSON.stringify(localStorage.getItem('cs_cache_persist')).length / 1024)}KB
+
+
+
+
+
+
+
+
+
+ ⚠️ 压力测试: 高频操作、并发更新、大数据存储、存储限制测试 • 可能会影响浏览器性能
+
+
+
+
+ )
+}
+
+const TestContainer = styled.div<{ $isDark: boolean }>`
+ color: ${props => props.$isDark ? '#fff' : '#000'};
+`
+
+const ResultDisplay = styled.div<{ $isDark: boolean }>`
+ background: ${props => props.$isDark ? '#0d1117' : '#f6f8fa'};
+ border: 1px solid ${props => props.$isDark ? '#30363d' : '#d0d7de'};
+ border-radius: 6px;
+ padding: 8px;
+ font-size: 11px;
+ max-height: 120px;
+ overflow-y: auto;
+
+ pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-break: break-all;
+ color: ${props => props.$isDark ? '#e6edf3' : '#1f2328'};
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
+ }
+`
+
+export default CacheStressTests
\ No newline at end of file
diff --git a/src/renderer/src/windows/dataRefactorTest/components/PreferenceBasicTests.tsx b/src/renderer/src/windows/dataRefactorTest/components/PreferenceBasicTests.tsx
index 5bd3f5cbe6..4981aa6d1b 100644
--- a/src/renderer/src/windows/dataRefactorTest/components/PreferenceBasicTests.tsx
+++ b/src/renderer/src/windows/dataRefactorTest/components/PreferenceBasicTests.tsx
@@ -77,7 +77,7 @@ const PreferenceBasicTests: React.FC = () => {
]
return (
-
+
{/* Key Selection */}
@@ -96,7 +96,7 @@ const PreferenceBasicTests: React.FC = () => {
{/* Current Value Display */}
-
+
当前值:
{value !== undefined ? (
@@ -319,31 +319,31 @@ const PreferenceBasicTests: React.FC = () => {
)
}
-const TestContainer = styled.div<{ isDark: boolean }>`
+const TestContainer = styled.div<{ $isDark: boolean }>`
padding: 16px;
- background: ${(props) => (props.isDark ? '#262626' : '#fafafa')};
+ background: ${(props) => (props.$isDark ? '#262626' : '#fafafa')};
border-radius: 8px;
.ant-typography {
- color: ${(props) => (props.isDark ? '#fff' : 'inherit')} !important;
+ color: ${(props) => (props.$isDark ? '#fff' : 'inherit')} !important;
}
.ant-select-selector {
- background-color: ${(props) => (props.isDark ? '#1f1f1f' : '#fff')} !important;
- border-color: ${(props) => (props.isDark ? '#434343' : '#d9d9d9')} !important;
- color: ${(props) => (props.isDark ? '#fff' : '#000')} !important;
+ background-color: ${(props) => (props.$isDark ? '#1f1f1f' : '#fff')} !important;
+ border-color: ${(props) => (props.$isDark ? '#434343' : '#d9d9d9')} !important;
+ color: ${(props) => (props.$isDark ? '#fff' : '#000')} !important;
}
.ant-input {
- background-color: ${(props) => (props.isDark ? '#1f1f1f' : '#fff')} !important;
- border-color: ${(props) => (props.isDark ? '#434343' : '#d9d9d9')} !important;
- color: ${(props) => (props.isDark ? '#fff' : '#000')} !important;
+ background-color: ${(props) => (props.$isDark ? '#1f1f1f' : '#fff')} !important;
+ border-color: ${(props) => (props.$isDark ? '#434343' : '#d9d9d9')} !important;
+ color: ${(props) => (props.$isDark ? '#fff' : '#000')} !important;
}
`
-const CurrentValueContainer = styled.div<{ isDark?: boolean }>`
+const CurrentValueContainer = styled.div<{ $isDark?: boolean }>`
padding: 12px;
- background: ${(props) => (props.isDark ? '#1f1f1f' : '#f0f0f0')};
+ background: ${(props) => (props.$isDark ? '#1f1f1f' : '#f0f0f0')};
border-radius: 6px;
border-left: 4px solid var(--color-primary);
`
diff --git a/src/renderer/src/windows/dataRefactorTest/components/PreferenceServiceTests.tsx b/src/renderer/src/windows/dataRefactorTest/components/PreferenceServiceTests.tsx
index 095053e618..91db1a45ce 100644
--- a/src/renderer/src/windows/dataRefactorTest/components/PreferenceServiceTests.tsx
+++ b/src/renderer/src/windows/dataRefactorTest/components/PreferenceServiceTests.tsx
@@ -116,7 +116,7 @@ const PreferenceServiceTests: React.FC = () => {
}
return (
-
+
{/* Input Controls */}
@@ -160,7 +160,7 @@ const PreferenceServiceTests: React.FC = () => {
{/* Result Display */}
{getResult !== null && (
-
+
Result:
{typeof getResult === 'object' ? JSON.stringify(getResult, null, 2) : String(getResult)}
@@ -200,26 +200,26 @@ const PreferenceServiceTests: React.FC = () => {
)
}
-const TestContainer = styled.div<{ isDark: boolean }>`
+const TestContainer = styled.div<{ $isDark: boolean }>`
padding: 16px;
- background: ${(props) => (props.isDark ? '#262626' : '#fafafa')};
+ background: ${(props) => (props.$isDark ? '#262626' : '#fafafa')};
border-radius: 8px;
.ant-typography {
- color: ${(props) => (props.isDark ? '#fff' : 'inherit')} !important;
+ color: ${(props) => (props.$isDark ? '#fff' : 'inherit')} !important;
}
.ant-input {
- background-color: ${(props) => (props.isDark ? '#1f1f1f' : '#fff')} !important;
- border-color: ${(props) => (props.isDark ? '#434343' : '#d9d9d9')} !important;
- color: ${(props) => (props.isDark ? '#fff' : '#000')} !important;
+ background-color: ${(props) => (props.$isDark ? '#1f1f1f' : '#fff')} !important;
+ border-color: ${(props) => (props.$isDark ? '#434343' : '#d9d9d9')} !important;
+ color: ${(props) => (props.$isDark ? '#fff' : '#000')} !important;
}
`
-const ResultContainer = styled.div<{ isDark?: boolean }>`
+const ResultContainer = styled.div<{ $isDark?: boolean }>`
margin-top: 16px;
padding: 12px;
- background: ${(props) => (props.isDark ? '#1f1f1f' : '#f0f0f0')};
+ background: ${(props) => (props.$isDark ? '#1f1f1f' : '#f0f0f0')};
border-radius: 6px;
border-left: 4px solid var(--color-primary);
`