fix: typecheck/test:lint/format:check

feat: add data related test
This commit is contained in:
fullex 2025-09-16 15:26:36 +08:00
parent 8353f331f1
commit d397a43806
17 changed files with 485 additions and 610 deletions

View File

@ -98,4 +98,4 @@ const ErrorContainer = styled.div`
` `
export { ErrorBoundaryCustomized as ErrorBoundary } export { ErrorBoundaryCustomized as ErrorBoundary }
export type { ErrorBoundaryCustomizedProps, CustomFallbackProps } export type { CustomFallbackProps, ErrorBoundaryCustomizedProps }

View File

@ -5,4 +5,4 @@ export function formatErrorMessage(error: Error): string {
return error.message return error.message
} }
return error.toString() return error.toString()
} }

View File

@ -16,8 +16,11 @@ import React from 'react'
// 创建一个 Icon 工厂函数 // 创建一个 Icon 工厂函数
export function createIcon(IconComponent: LucideIcon, defaultSize: string | number = '1rem') { export function createIcon(IconComponent: LucideIcon, defaultSize: string | number = '1rem') {
const Icon = React.forwardRef<SVGSVGElement, React.ComponentProps<typeof IconComponent>>( const Icon = ({
(props, ref) => <IconComponent ref={ref} size={defaultSize} {...props} /> ref,
...props
}: React.ComponentProps<typeof IconComponent> & { ref?: React.RefObject<SVGSVGElement | null> }) => (
<IconComponent ref={ref} size={defaultSize} {...props} />
) )
Icon.displayName = `Icon(${IconComponent.displayName || IconComponent.name})` Icon.displayName = `Icon(${IconComponent.displayName || IconComponent.name})`
return Icon return Icon

View File

@ -4,12 +4,12 @@ export { default as CustomCollapse } from './base/CustomCollapse'
export { default as CustomTag } from './base/CustomTag' export { default as CustomTag } from './base/CustomTag'
export { default as DividerWithText } from './base/DividerWithText' export { default as DividerWithText } from './base/DividerWithText'
export { default as EmojiIcon } from './base/EmojiIcon' export { default as EmojiIcon } from './base/EmojiIcon'
export type { CustomFallbackProps, ErrorBoundaryCustomizedProps } from './base/ErrorBoundary'
export { ErrorBoundary } from './base/ErrorBoundary' export { ErrorBoundary } from './base/ErrorBoundary'
export type { ErrorBoundaryCustomizedProps, CustomFallbackProps } from './base/ErrorBoundary'
export { default as IndicatorLight } from './base/IndicatorLight' export { default as IndicatorLight } from './base/IndicatorLight'
export { default as Spinner } from './base/Spinner' export { default as Spinner } from './base/Spinner'
export { StatusTag, ErrorTag, SuccessTag, WarnTag, InfoTag } from './base/StatusTag' export type { StatusTagProps, StatusType } from './base/StatusTag'
export type { StatusType, StatusTagProps } from './base/StatusTag' export { ErrorTag, InfoTag, StatusTag, SuccessTag, WarnTag } from './base/StatusTag'
export { default as TextBadge } from './base/TextBadge' export { default as TextBadge } from './base/TextBadge'
// Display Components // Display Components
@ -26,22 +26,22 @@ export { default as HorizontalScrollContainer } from './layout/HorizontalScrollC
export { default as Scrollbar } from './layout/Scrollbar' export { default as Scrollbar } from './layout/Scrollbar'
// Icon Components // Icon Components
export { FilePngIcon, FileSvgIcon } from './icons/FileIcons'
export type { LucideIcon, LucideProps } from './icons/Icon'
export { export {
createIcon,
CopyIcon, CopyIcon,
createIcon,
DeleteIcon, DeleteIcon,
EditIcon, EditIcon,
OcrIcon,
RefreshIcon, RefreshIcon,
ResetIcon, ResetIcon,
ToolIcon, ToolIcon,
UnWrapIcon,
VisionIcon, VisionIcon,
WebSearchIcon, WebSearchIcon,
WrapIcon, WrapIcon
UnWrapIcon,
OcrIcon
} from './icons/Icon' } from './icons/Icon'
export type { LucideIcon, LucideProps } from './icons/Icon'
export { FilePngIcon, FileSvgIcon } from './icons/FileIcons'
export { default as ReasoningIcon } from './icons/ReasoningIcon' export { default as ReasoningIcon } from './icons/ReasoningIcon'
export { default as SvgSpinners180Ring } from './icons/SvgSpinners180Ring' export { default as SvgSpinners180Ring } from './icons/SvgSpinners180Ring'
export { default as ToolsCallingIcon } from './icons/ToolsCallingIcon' export { default as ToolsCallingIcon } from './icons/ToolsCallingIcon'

View File

@ -1,35 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react' import type { Meta, StoryObj } from '@storybook/react'
import { import { ChevronRight, Download, Settings, Upload } from 'lucide-react'
Copy,
Trash,
Pencil,
RefreshCw,
RotateCcw,
Wrench,
Eye,
Search,
WrapText,
AlignLeft,
ScanLine,
Settings,
Download,
Upload,
ChevronRight
} from 'lucide-react'
import { import {
createIcon,
CopyIcon, CopyIcon,
createIcon,
DeleteIcon, DeleteIcon,
EditIcon, EditIcon,
OcrIcon,
RefreshIcon, RefreshIcon,
ResetIcon, ResetIcon,
ToolIcon, ToolIcon,
UnWrapIcon,
VisionIcon, VisionIcon,
WebSearchIcon, WebSearchIcon,
WrapIcon, WrapIcon
UnWrapIcon,
OcrIcon
} from '../../../src/components/icons/Icon' } from '../../../src/components/icons/Icon'
// Create a dummy component for the story // Create a dummy component for the story
@ -278,8 +262,7 @@ export const IconGrid: Story = {
{AllIcons.map(({ Icon, name }) => ( {AllIcons.map(({ Icon, name }) => (
<div <div
key={name} key={name}
className="flex flex-col items-center gap-2 rounded-lg border border-gray-200 p-4 hover:border-blue-500" className="flex flex-col items-center gap-2 rounded-lg border border-gray-200 p-4 hover:border-blue-500">
>
<Icon size={24} /> <Icon size={24} />
<span className="text-xs">{name}</span> <span className="text-xs">{name}</span>
</div> </div>
@ -287,4 +270,4 @@ export const IconGrid: Story = {
</div> </div>
) )
} }
} }

View File

@ -100,7 +100,8 @@ vi.mock('@renderer/utils', () => ({
vi.mock('@shared/config/prompts', () => ({ vi.mock('@shared/config/prompts', () => ({
WEB_SEARCH_PROMPT_FOR_OPENROUTER: 'mock-prompt', WEB_SEARCH_PROMPT_FOR_OPENROUTER: 'mock-prompt',
TRANSLATE_PROMPT: 'You are a translation expert. Your only task is to translate text enclosed with <translate_input> from input language to {{target_language}}, provide the translation result directly without any explanation, without `TRANSLATE` and keep original format.' TRANSLATE_PROMPT:
'You are a translation expert. Your only task is to translate text enclosed with <translate_input> from input language to {{target_language}}, provide the translation result directly without any explanation, without `TRANSLATE` and keep original format.'
})) }))
vi.mock('@renderer/config/systemModels', () => ({ vi.mock('@renderer/config/systemModels', () => ({

View File

@ -7,9 +7,9 @@ import { includeKeywords, matchKeywordsInModel, matchKeywordsInProvider, matchKe
vi.mock('@renderer/i18n/label', () => ({ vi.mock('@renderer/i18n/label', () => ({
getProviderLabel: vi.fn((id: string) => { getProviderLabel: vi.fn((id: string) => {
const labelMap: Record<string, string> = { const labelMap: Record<string, string> = {
'dashscope': 'Alibaba Cloud', dashscope: 'Alibaba Cloud',
'openai': 'OpenAI', openai: 'OpenAI',
'anthropic': 'Anthropic' anthropic: 'Anthropic'
} }
return labelMap[id] || id return labelMap[id] || id
}) })

View File

@ -5,9 +5,9 @@ import { describe, expect, it, vi } from 'vitest'
vi.mock('@renderer/i18n/label', () => ({ vi.mock('@renderer/i18n/label', () => ({
getProviderLabel: vi.fn((id: string) => { getProviderLabel: vi.fn((id: string) => {
const labelMap: Record<string, string> = { const labelMap: Record<string, string> = {
'dashscope': 'Alibaba Cloud', dashscope: 'Alibaba Cloud',
'openai': 'OpenAI', openai: 'OpenAI',
'anthropic': 'Anthropic' anthropic: 'Anthropic'
} }
return labelMap[id] || id return labelMap[id] || id
}) })

View File

@ -79,13 +79,7 @@ export class MockMainCacheService {
}) })
// Private methods exposed for testing // Private methods exposed for testing
private broadcastSync = vi.fn((message: CacheSyncMessage, senderWindowId?: number): void => { // These methods are mocked but not exposed to avoid TypeScript unused warnings
mockBroadcastCalls.push({ message, senderWindowId })
})
private setupIpcHandlers = vi.fn((): void => {
// Mock IPC handler setup
})
} }
// Mock singleton instance // Mock singleton instance
@ -108,7 +102,7 @@ export const MockMainCacheServiceUtils = {
*/ */
resetMocks: () => { resetMocks: () => {
// Reset all method mocks // Reset all method mocks
Object.values(mockInstance).forEach(method => { Object.values(mockInstance).forEach((method) => {
if (vi.isMockFunction(method)) { if (vi.isMockFunction(method)) {
method.mockClear() method.mockClear()
} }
@ -234,4 +228,4 @@ export const MockMainCacheServiceUtils = {
delete: mockInstance.delete.mock.calls.length, delete: mockInstance.delete.mock.calls.length,
cleanup: mockInstance.cleanup.mock.calls.length cleanup: mockInstance.cleanup.mock.calls.length
}) })
} }

View File

@ -35,11 +35,11 @@ export class MockMainDataApiService {
private static instance: MockMainDataApiService private static instance: MockMainDataApiService
private initialized = false private initialized = false
private apiServer: MockApiServer private apiServer: MockApiServer
private ipcAdapter: MockIpcAdapter private _ipcAdapter: MockIpcAdapter // Used for mock setup (referenced in constructor)
private constructor() { private constructor() {
this.apiServer = new MockApiServer() this.apiServer = new MockApiServer()
this.ipcAdapter = new MockIpcAdapter() this._ipcAdapter = new MockIpcAdapter()
} }
public static getInstance(): MockMainDataApiService { public static getInstance(): MockMainDataApiService {
@ -82,6 +82,11 @@ export class MockMainDataApiService {
public shutdown = vi.fn(async (): Promise<void> => { public shutdown = vi.fn(async (): Promise<void> => {
this.initialized = false this.initialized = false
}) })
// Getter for testing purposes
public get ipcAdapter() {
return this._ipcAdapter
}
} }
// Mock singleton instance // Mock singleton instance
@ -112,7 +117,7 @@ export const MockMainDataApiServiceUtils = {
*/ */
resetMocks: () => { resetMocks: () => {
// Reset all method mocks // Reset all method mocks
Object.values(mockInstance).forEach(method => { Object.values(mockInstance).forEach((method) => {
if (vi.isMockFunction(method)) { if (vi.isMockFunction(method)) {
method.mockClear() method.mockClear()
} }
@ -139,7 +144,7 @@ export const MockMainDataApiServiceUtils = {
/** /**
* Mock system info for testing * Mock system info for testing
*/ */
mockSystemInfo: (info: Record<string, any>) => { mockSystemInfo: (info: { server: string; version: string; handlers: string[]; middlewares: string[] }) => {
mockInstance.getApiServer().getSystemInfo.mockReturnValue(info) mockInstance.getApiServer().getSystemInfo.mockReturnValue(info)
}, },
@ -166,4 +171,4 @@ export const MockMainDataApiServiceUtils = {
getSystemStatus: mockInstance.getSystemStatus.mock.calls.length, getSystemStatus: mockInstance.getSystemStatus.mock.calls.length,
getApiServer: mockInstance.getApiServer.mock.calls.length getApiServer: mockInstance.getApiServer.mock.calls.length
}) })
} }

View File

@ -1,8 +1,5 @@
import type {
PreferenceDefaultScopeType,
PreferenceKeyType
} from '@shared/data/preference/preferenceTypes'
import { DefaultPreferences } from '@shared/data/preference/preferenceSchemas' import { DefaultPreferences } from '@shared/data/preference/preferenceSchemas'
import type { PreferenceDefaultScopeType, PreferenceKeyType } from '@shared/data/preference/preferenceTypes'
import { vi } from 'vitest' import { vi } from 'vitest'
/** /**
@ -26,7 +23,7 @@ const mockMainSubscribers = new Map<string, Set<(newValue: any, oldValue?: any)
const notifyMainSubscribers = (key: string, newValue: any, oldValue?: any) => { const notifyMainSubscribers = (key: string, newValue: any, oldValue?: any) => {
const subscribers = mockMainSubscribers.get(key) const subscribers = mockMainSubscribers.get(key)
if (subscribers) { if (subscribers) {
subscribers.forEach(callback => { subscribers.forEach((callback) => {
try { try {
callback(newValue, oldValue) callback(newValue, oldValue)
} catch (error) { } catch (error) {
@ -41,7 +38,7 @@ const notifyMainSubscribers = (key: string, newValue: any, oldValue?: any) => {
*/ */
export class MockMainPreferenceService { export class MockMainPreferenceService {
private static instance: MockMainPreferenceService private static instance: MockMainPreferenceService
private initialized = false private _initialized = false // Used in initialize method
private constructor() {} private constructor() {}
@ -54,7 +51,7 @@ export class MockMainPreferenceService {
// Mock initialization // Mock initialization
public initialize = vi.fn(async (): Promise<void> => { public initialize = vi.fn(async (): Promise<void> => {
this.initialized = true this._initialized = true
}) })
// Mock get method // Mock get method
@ -63,19 +60,18 @@ export class MockMainPreferenceService {
}) })
// Mock set method // Mock set method
public set = vi.fn(async <K extends PreferenceKeyType>( public set = vi.fn(
key: K, async <K extends PreferenceKeyType>(key: K, value: PreferenceDefaultScopeType[K]): Promise<void> => {
value: PreferenceDefaultScopeType[K] const oldValue = mockPreferenceState.get(key)
): Promise<void> => { mockPreferenceState.set(key, value)
const oldValue = mockPreferenceState.get(key) notifyMainSubscribers(key, value, oldValue)
mockPreferenceState.set(key, value) }
notifyMainSubscribers(key, value, oldValue) )
})
// Mock getMultiple method // Mock getMultiple method
public getMultiple = vi.fn(<K extends PreferenceKeyType>(keys: K[]) => { public getMultiple = vi.fn(<K extends PreferenceKeyType>(keys: K[]) => {
const result: any = {} const result: any = {}
keys.forEach(key => { keys.forEach((key) => {
result[key] = mockPreferenceState.get(key) ?? DefaultPreferences.default[key] result[key] = mockPreferenceState.get(key) ?? DefaultPreferences.default[key]
}) })
return result return result
@ -98,7 +94,7 @@ export class MockMainPreferenceService {
mockSubscriptions.set(windowId, new Set()) mockSubscriptions.set(windowId, new Set())
} }
const windowKeys = mockSubscriptions.get(windowId)! const windowKeys = mockSubscriptions.get(windowId)!
keys.forEach(key => windowKeys.add(key)) keys.forEach((key) => windowKeys.add(key))
}) })
public unsubscribeForWindow = vi.fn((windowId: number): void => { public unsubscribeForWindow = vi.fn((windowId: number): void => {
@ -106,45 +102,50 @@ export class MockMainPreferenceService {
}) })
// Mock main process subscription methods // Mock main process subscription methods
public subscribeChange = vi.fn(<K extends PreferenceKeyType>( public subscribeChange = vi.fn(
key: K, <K extends PreferenceKeyType>(
callback: (newValue: PreferenceDefaultScopeType[K], oldValue?: PreferenceDefaultScopeType[K]) => void key: K,
): (() => void) => { callback: (newValue: PreferenceDefaultScopeType[K], oldValue?: PreferenceDefaultScopeType[K]) => void
if (!mockMainSubscribers.has(key)) { ): (() => void) => {
mockMainSubscribers.set(key, new Set()) if (!mockMainSubscribers.has(key)) {
} mockMainSubscribers.set(key, new Set())
mockMainSubscribers.get(key)!.add(callback) }
mockMainSubscribers.get(key)!.add(callback)
// Return unsubscribe function // Return unsubscribe function
return () => { return () => {
const subscribers = mockMainSubscribers.get(key) const subscribers = mockMainSubscribers.get(key)
if (subscribers) { if (subscribers) {
subscribers.delete(callback) subscribers.delete(callback)
if (subscribers.size === 0) { if (subscribers.size === 0) {
mockMainSubscribers.delete(key) mockMainSubscribers.delete(key)
}
} }
} }
} }
}) )
public subscribeMultipleChanges = vi.fn(( public subscribeMultipleChanges = vi.fn(
keys: PreferenceKeyType[], (
callback: (key: PreferenceKeyType, newValue: any, oldValue: any) => void keys: PreferenceKeyType[],
): (() => void) => { callback: (key: PreferenceKeyType, newValue: any, oldValue: any) => void
const unsubscribeFunctions = keys.map(key => ): (() => void) => {
this.subscribeChange(key, (newValue, oldValue) => callback(key, newValue, oldValue)) const unsubscribeFunctions = keys.map((key) =>
) this.subscribeChange(key, (newValue, oldValue) => callback(key, newValue, oldValue))
)
return () => { return () => {
unsubscribeFunctions.forEach(unsubscribe => unsubscribe()) unsubscribeFunctions.forEach((unsubscribe) => unsubscribe())
}
} }
}) )
// Mock utility methods // Mock utility methods
public getAll = vi.fn((): PreferenceDefaultScopeType => { public getAll = vi.fn((): PreferenceDefaultScopeType => {
const result: any = {} const result: any = {}
Object.keys(DefaultPreferences.default).forEach(key => { Object.keys(DefaultPreferences.default).forEach((key) => {
result[key] = mockPreferenceState.get(key as PreferenceKeyType) ?? DefaultPreferences.default[key as PreferenceKeyType] result[key] =
mockPreferenceState.get(key as PreferenceKeyType) ?? DefaultPreferences.default[key as PreferenceKeyType]
}) })
return result return result
}) })
@ -157,7 +158,7 @@ export class MockMainPreferenceService {
public getChangeListenerCount = vi.fn((): number => { public getChangeListenerCount = vi.fn((): number => {
let total = 0 let total = 0
mockMainSubscribers.forEach(subscribers => { mockMainSubscribers.forEach((subscribers) => {
total += subscribers.size total += subscribers.size
}) })
return total return total
@ -179,6 +180,11 @@ export class MockMainPreferenceService {
return stats return stats
}) })
// Getter for testing purposes
public get isInitialized() {
return this._initialized
}
// Static methods // Static methods
public static registerIpcHandler = vi.fn((): void => { public static registerIpcHandler = vi.fn((): void => {
// Mock IPC handler registration // Mock IPC handler registration
@ -205,7 +211,7 @@ export const MockMainPreferenceServiceUtils = {
*/ */
resetMocks: () => { resetMocks: () => {
// Reset all method mocks // Reset all method mocks
Object.values(mockInstance).forEach(method => { Object.values(mockInstance).forEach((method) => {
if (vi.isMockFunction(method)) { if (vi.isMockFunction(method)) {
method.mockClear() method.mockClear()
} }
@ -283,4 +289,4 @@ export const MockMainPreferenceServiceUtils = {
windows: Array.from(mockSubscriptions.entries()).map(([windowId, keys]) => [windowId, keys.size]), windows: Array.from(mockSubscriptions.entries()).map(([windowId, keys]) => [windowId, keys.size]),
mainSubscribers: Array.from(mockMainSubscribers.entries()).map(([key, subs]) => [key, subs.size]) mainSubscribers: Array.from(mockMainSubscribers.entries()).map(([key, subs]) => [key, subs.size])
}) })
} }

View File

@ -2,9 +2,7 @@ import type {
RendererPersistCacheKey, RendererPersistCacheKey,
RendererPersistCacheSchema, RendererPersistCacheSchema,
UseCacheKey, UseCacheKey,
UseCacheSchema, UseSharedCacheKey
UseSharedCacheKey,
UseSharedCacheSchema
} from '@shared/data/cache/cacheSchemas' } from '@shared/data/cache/cacheSchemas'
import { DefaultRendererPersistCache, DefaultUseCache, DefaultUseSharedCache } from '@shared/data/cache/cacheSchemas' import { DefaultRendererPersistCache, DefaultUseCache, DefaultUseSharedCache } from '@shared/data/cache/cacheSchemas'
import type { CacheSubscriber } from '@shared/data/cache/cacheTypes' import type { CacheSubscriber } from '@shared/data/cache/cacheTypes'
@ -18,11 +16,13 @@ import { vi } from 'vitest'
/** /**
* Create a mock CacheService with realistic behavior * Create a mock CacheService with realistic behavior
*/ */
export const createMockCacheService = (options: { export const createMockCacheService = (
initialMemoryCache?: Map<string, any> options: {
initialSharedCache?: Map<string, any> initialMemoryCache?: Map<string, any>
initialPersistCache?: Map<RendererPersistCacheKey, any> initialSharedCache?: Map<string, any>
} = {}) => { initialPersistCache?: Map<RendererPersistCacheKey, any>
} = {}
) => {
// Mock cache storage // Mock cache storage
const memoryCache = new Map<string, any>(options.initialMemoryCache || []) const memoryCache = new Map<string, any>(options.initialMemoryCache || [])
const sharedCache = new Map<string, any>(options.initialSharedCache || []) const sharedCache = new Map<string, any>(options.initialSharedCache || [])
@ -32,10 +32,10 @@ export const createMockCacheService = (options: {
const subscribers = new Map<string, Set<CacheSubscriber>>() const subscribers = new Map<string, Set<CacheSubscriber>>()
// Helper function to notify subscribers // Helper function to notify subscribers
const notifySubscribers = (key: string, value: any) => { const notifySubscribers = (key: string) => {
const keySubscribers = subscribers.get(key) const keySubscribers = subscribers.get(key)
if (keySubscribers) { if (keySubscribers) {
keySubscribers.forEach(callback => { keySubscribers.forEach((callback) => {
try { try {
callback() callback()
} catch (error) { } catch (error) {
@ -56,11 +56,11 @@ export const createMockCacheService = (options: {
return defaultValue !== undefined ? defaultValue : null return defaultValue !== undefined ? defaultValue : null
}), }),
set: vi.fn(<T>(key: string, value: T, ttl?: number): void => { set: vi.fn(<T>(key: string, value: T): void => {
const oldValue = memoryCache.get(key) const oldValue = memoryCache.get(key)
memoryCache.set(key, value) memoryCache.set(key, value)
if (oldValue !== value) { if (oldValue !== value) {
notifySubscribers(key, value) notifySubscribers(key)
} }
}), }),
@ -68,7 +68,7 @@ export const createMockCacheService = (options: {
const existed = memoryCache.has(key) const existed = memoryCache.has(key)
memoryCache.delete(key) memoryCache.delete(key)
if (existed) { if (existed) {
notifySubscribers(key, null) notifySubscribers(key)
} }
return existed return existed
}), }),
@ -76,7 +76,7 @@ export const createMockCacheService = (options: {
clear: vi.fn((): void => { clear: vi.fn((): void => {
const keys = Array.from(memoryCache.keys()) const keys = Array.from(memoryCache.keys())
memoryCache.clear() memoryCache.clear()
keys.forEach(key => notifySubscribers(key, null)) keys.forEach((key) => notifySubscribers(key))
}), }),
has: vi.fn((key: string): boolean => { has: vi.fn((key: string): boolean => {
@ -96,11 +96,11 @@ export const createMockCacheService = (options: {
return defaultValue !== undefined ? defaultValue : null return defaultValue !== undefined ? defaultValue : null
}), }),
setShared: vi.fn(<T>(key: string, value: T, ttl?: number): void => { setShared: vi.fn(<T>(key: string, value: T): void => {
const oldValue = sharedCache.get(key) const oldValue = sharedCache.get(key)
sharedCache.set(key, value) sharedCache.set(key, value)
if (oldValue !== value) { if (oldValue !== value) {
notifySubscribers(`shared:${key}`, value) notifySubscribers(`shared:${key}`)
} }
}), }),
@ -108,7 +108,7 @@ export const createMockCacheService = (options: {
const existed = sharedCache.has(key) const existed = sharedCache.has(key)
sharedCache.delete(key) sharedCache.delete(key)
if (existed) { if (existed) {
notifySubscribers(`shared:${key}`, null) notifySubscribers(`shared:${key}`)
} }
return existed return existed
}), }),
@ -116,7 +116,7 @@ export const createMockCacheService = (options: {
clearShared: vi.fn((): void => { clearShared: vi.fn((): void => {
const keys = Array.from(sharedCache.keys()) const keys = Array.from(sharedCache.keys())
sharedCache.clear() sharedCache.clear()
keys.forEach(key => notifySubscribers(`shared:${key}`, null)) keys.forEach((key) => notifySubscribers(`shared:${key}`))
}), }),
// Persist cache methods // Persist cache methods
@ -131,7 +131,7 @@ export const createMockCacheService = (options: {
const oldValue = persistCache.get(key) const oldValue = persistCache.get(key)
persistCache.set(key, value) persistCache.set(key, value)
if (oldValue !== value) { if (oldValue !== value) {
notifySubscribers(`persist:${key}`, value) notifySubscribers(`persist:${key}`)
} }
}), }),
@ -139,7 +139,7 @@ export const createMockCacheService = (options: {
const existed = persistCache.has(key) const existed = persistCache.has(key)
persistCache.delete(key) persistCache.delete(key)
if (existed) { if (existed) {
notifySubscribers(`persist:${key}`, DefaultRendererPersistCache[key]) notifySubscribers(`persist:${key}`)
} }
return existed return existed
}), }),
@ -147,7 +147,7 @@ export const createMockCacheService = (options: {
clearPersist: vi.fn((): void => { clearPersist: vi.fn((): void => {
const keys = Array.from(persistCache.keys()) as RendererPersistCacheKey[] const keys = Array.from(persistCache.keys()) as RendererPersistCacheKey[]
persistCache.clear() persistCache.clear()
keys.forEach(key => notifySubscribers(`persist:${key}`, DefaultRendererPersistCache[key])) keys.forEach((key) => notifySubscribers(`persist:${key}`))
}), }),
// Subscription methods // Subscription methods
@ -184,11 +184,11 @@ export const createMockCacheService = (options: {
}), }),
// Hook reference tracking (for advanced cache management) // Hook reference tracking (for advanced cache management)
addHookReference: vi.fn((key: string): void => { addHookReference: vi.fn((): void => {
// Mock implementation - in real service this prevents cache cleanup // Mock implementation - in real service this prevents cache cleanup
}), }),
removeHookReference: vi.fn((key: string): void => { removeHookReference: vi.fn((): void => {
// Mock implementation // Mock implementation
}), }),
@ -253,11 +253,11 @@ export const MockCacheService = {
// Delegate all methods to the mock // Delegate all methods to the mock
get<T>(key: string): T | null { get<T>(key: string): T | null {
return mockCacheService.get<T>(key) return mockCacheService.get(key) as T | null
} }
set<T>(key: string, value: T, ttl?: number): void { set<T>(key: string, value: T): void {
return mockCacheService.set(key, value, ttl) return mockCacheService.set(key, value)
} }
delete(key: string): boolean { delete(key: string): boolean {
@ -277,11 +277,11 @@ export const MockCacheService = {
} }
getShared<T>(key: string): T | null { getShared<T>(key: string): T | null {
return mockCacheService.getShared<T>(key) return mockCacheService.getShared(key) as T | null
} }
setShared<T>(key: string, value: T, ttl?: number): void { setShared<T>(key: string, value: T): void {
return mockCacheService.setShared(key, value, ttl) return mockCacheService.setShared(key, value)
} }
deleteShared(key: string): boolean { deleteShared(key: string): boolean {
@ -316,12 +316,12 @@ export const MockCacheService = {
return mockCacheService.unsubscribe(key, callback) return mockCacheService.unsubscribe(key, callback)
} }
addHookReference(key: string): void { addHookReference(): void {
return mockCacheService.addHookReference(key) return mockCacheService.addHookReference()
} }
removeHookReference(key: string): void { removeHookReference(): void {
return mockCacheService.removeHookReference(key) return mockCacheService.removeHookReference()
} }
getAllKeys(): string[] { getAllKeys(): string[] {
@ -343,7 +343,7 @@ export const MockCacheUtils = {
* Reset all mock function call counts and state * Reset all mock function call counts and state
*/ */
resetMocks: () => { resetMocks: () => {
Object.values(mockCacheService).forEach(method => { Object.values(mockCacheService).forEach((method) => {
if (vi.isMockFunction(method)) { if (vi.isMockFunction(method)) {
method.mockClear() method.mockClear()
} }
@ -386,4 +386,4 @@ export const MockCacheUtils = {
triggerCacheChange: (key: string, value: any) => { triggerCacheChange: (key: string, value: any) => {
mockCacheService.set(key, value) mockCacheService.set(key, value)
} }
} }

View File

@ -1,14 +1,5 @@
import type { ConcreteApiPaths } from '@shared/data/api/apiSchemas' import type { ApiClient, ConcreteApiPaths } from '@shared/data/api/apiSchemas'
import type { import type { DataResponse } from '@shared/data/api/apiTypes'
ApiClient,
BatchRequest,
BatchResponse,
DataRequest,
DataResponse,
SubscriptionCallback,
SubscriptionOptions,
TransactionRequest
} from '@shared/data/api/apiTypes'
import { vi } from 'vitest' import { vi } from 'vitest'
/** /**
@ -18,20 +9,21 @@ import { vi } from 'vitest'
// Mock response utilities // Mock response utilities
const createMockResponse = <T>(data: T, success = true): DataResponse<T> => ({ const createMockResponse = <T>(data: T, success = true): DataResponse<T> => ({
success, id: 'mock-id',
status: success ? 200 : 500,
data, data,
timestamp: new Date().toISOString(), ...(success ? {} : { error: { code: 'MOCK_ERROR', message: 'Mock error', details: {}, status: 500 } })
...(success ? {} : { error: { code: 'MOCK_ERROR', message: 'Mock error', details: {} } })
}) })
const createMockError = (message: string): DataResponse<never> => ({ const createMockError = (message: string): DataResponse<never> => ({
success: false, id: 'mock-error-id',
status: 500,
error: { error: {
code: 'MOCK_ERROR', code: 'MOCK_ERROR',
message, message,
details: {} details: {},
}, status: 500
timestamp: new Date().toISOString() }
}) })
/** /**
@ -40,80 +32,25 @@ const createMockError = (message: string): DataResponse<never> => ({
export const createMockDataApiService = (customBehavior: Partial<ApiClient> = {}): ApiClient => { export const createMockDataApiService = (customBehavior: Partial<ApiClient> = {}): ApiClient => {
const mockService: ApiClient = { const mockService: ApiClient = {
// HTTP Methods // HTTP Methods
get: vi.fn(async (path: ConcreteApiPaths, options?: any) => { get: vi.fn(async (path: ConcreteApiPaths) => {
// Default mock behavior - return empty data based on path // Default mock behavior - return raw data based on path
const mockData = getMockDataForPath(path, 'GET') return getMockDataForPath(path, 'GET') as any
return createMockResponse(mockData)
}), }),
post: vi.fn(async (path: ConcreteApiPaths, options?: any) => { post: vi.fn(async (path: ConcreteApiPaths) => {
const mockData = getMockDataForPath(path, 'POST') return getMockDataForPath(path, 'POST') as any
return createMockResponse(mockData)
}), }),
put: vi.fn(async (path: ConcreteApiPaths, options?: any) => { put: vi.fn(async (path: ConcreteApiPaths) => {
const mockData = getMockDataForPath(path, 'PUT') return getMockDataForPath(path, 'PUT') as any
return createMockResponse(mockData)
}), }),
patch: vi.fn(async (path: ConcreteApiPaths, options?: any) => { patch: vi.fn(async (path: ConcreteApiPaths) => {
const mockData = getMockDataForPath(path, 'PATCH') return getMockDataForPath(path, 'PATCH') as any
return createMockResponse(mockData)
}), }),
delete: vi.fn(async (path: ConcreteApiPaths, options?: any) => { delete: vi.fn(async () => {
return createMockResponse({ deleted: true }) return { deleted: true } as any
}),
// Batch operations
batch: vi.fn(async (requests: BatchRequest[]): Promise<BatchResponse> => {
const responses = requests.map((request, index) => ({
id: request.id || `batch_${index}`,
success: true,
data: getMockDataForPath(request.path as ConcreteApiPaths, request.method),
timestamp: new Date().toISOString()
}))
return {
success: true,
responses,
timestamp: new Date().toISOString()
}
}),
// Transaction support
transaction: vi.fn(async (operations: TransactionRequest[]): Promise<DataResponse<any[]>> => {
const results = operations.map((op, index) => ({
operation: op.operation,
result: getMockDataForPath(op.path as ConcreteApiPaths, 'POST'),
success: true
}))
return createMockResponse(results)
}),
// Subscription methods
subscribe: vi.fn((path: ConcreteApiPaths, callback: SubscriptionCallback, options?: SubscriptionOptions) => {
// Return a mock unsubscribe function
return vi.fn()
}),
unsubscribe: vi.fn((path: ConcreteApiPaths) => {
// Mock unsubscribe
}),
// Connection management
connect: vi.fn(async () => {
return createMockResponse({ connected: true })
}),
disconnect: vi.fn(async () => {
return createMockResponse({ disconnected: true })
}),
// Health check
ping: vi.fn(async () => {
return createMockResponse({ pong: true, timestamp: new Date().toISOString() })
}), }),
// Apply custom behavior overrides // Apply custom behavior overrides
@ -229,34 +166,6 @@ export const MockDataApiService = {
async delete(path: ConcreteApiPaths, options?: any) { async delete(path: ConcreteApiPaths, options?: any) {
return mockDataApiService.delete(path, options) return mockDataApiService.delete(path, options)
} }
async batch(requests: BatchRequest[]) {
return mockDataApiService.batch(requests)
}
async transaction(operations: TransactionRequest[]) {
return mockDataApiService.transaction(operations)
}
subscribe(path: ConcreteApiPaths, callback: SubscriptionCallback, options?: SubscriptionOptions) {
return mockDataApiService.subscribe(path, callback, options)
}
unsubscribe(path: ConcreteApiPaths) {
return mockDataApiService.unsubscribe(path)
}
async connect() {
return mockDataApiService.connect()
}
async disconnect() {
return mockDataApiService.disconnect()
}
async ping() {
return mockDataApiService.ping()
}
}, },
dataApiService: mockDataApiService dataApiService: mockDataApiService
} }
@ -269,7 +178,7 @@ export const MockDataApiUtils = {
* Reset all mock function call counts and implementations * Reset all mock function call counts and implementations
*/ */
resetMocks: () => { resetMocks: () => {
Object.values(mockDataApiService).forEach(method => { Object.values(mockDataApiService).forEach((method) => {
if (vi.isMockFunction(method)) { if (vi.isMockFunction(method)) {
method.mockClear() method.mockClear()
} }
@ -282,7 +191,7 @@ export const MockDataApiUtils = {
setCustomResponse: (path: ConcreteApiPaths, method: string, response: any) => { setCustomResponse: (path: ConcreteApiPaths, method: string, response: any) => {
const methodFn = mockDataApiService[method.toLowerCase() as keyof ApiClient] as any const methodFn = mockDataApiService[method.toLowerCase() as keyof ApiClient] as any
if (vi.isMockFunction(methodFn)) { if (vi.isMockFunction(methodFn)) {
methodFn.mockImplementation(async (requestPath: string, options?: any) => { methodFn.mockImplementation(async (requestPath: string) => {
if (requestPath === path) { if (requestPath === path) {
return createMockResponse(response) return createMockResponse(response)
} }
@ -298,7 +207,7 @@ export const MockDataApiUtils = {
setErrorResponse: (path: ConcreteApiPaths, method: string, errorMessage: string) => { setErrorResponse: (path: ConcreteApiPaths, method: string, errorMessage: string) => {
const methodFn = mockDataApiService[method.toLowerCase() as keyof ApiClient] as any const methodFn = mockDataApiService[method.toLowerCase() as keyof ApiClient] as any
if (vi.isMockFunction(methodFn)) { if (vi.isMockFunction(methodFn)) {
methodFn.mockImplementation(async (requestPath: string, options?: any) => { methodFn.mockImplementation(async (requestPath: string) => {
if (requestPath === path) { if (requestPath === path) {
return createMockError(errorMessage) return createMockError(errorMessage)
} }
@ -323,4 +232,4 @@ export const MockDataApiUtils = {
const methodFn = mockDataApiService[method] as any const methodFn = mockDataApiService[method] as any
return vi.isMockFunction(methodFn) ? methodFn.mock.calls : [] return vi.isMockFunction(methodFn) ? methodFn.mock.calls : []
} }
} }

View File

@ -34,7 +34,7 @@ export const mockPreferenceDefaults: Record<string, any> = {
// App preferences // App preferences
'app.user.name': 'MockUser', 'app.user.name': 'MockUser',
'app.language': 'zh-CN', 'app.language': 'zh-CN'
// Add more defaults as needed // Add more defaults as needed
} }
@ -76,14 +76,14 @@ export const createMockPreferenceService = (customDefaults: Record<string, any>
}), }),
clear: vi.fn(() => { clear: vi.fn(() => {
Object.keys(mergedDefaults).forEach(key => delete mergedDefaults[key]) Object.keys(mergedDefaults).forEach((key) => delete mergedDefaults[key])
return Promise.resolve() return Promise.resolve()
}), }),
// Internal state access for testing // Internal state access for testing
_getMockState: () => ({ ...mergedDefaults }), _getMockState: () => ({ ...mergedDefaults }),
_resetMockState: () => { _resetMockState: () => {
Object.keys(mergedDefaults).forEach(key => delete mergedDefaults[key]) Object.keys(mergedDefaults).forEach((key) => delete mergedDefaults[key])
Object.assign(mergedDefaults, mockPreferenceDefaults, customDefaults) Object.assign(mergedDefaults, mockPreferenceDefaults, customDefaults)
} }
} }
@ -95,4 +95,4 @@ export const mockPreferenceService = createMockPreferenceService()
// Export for easy mocking in individual tests // Export for easy mocking in individual tests
export const MockPreferenceService = { export const MockPreferenceService = {
preferenceService: mockPreferenceService preferenceService: mockPreferenceService
} }

View File

@ -41,7 +41,7 @@ const mockPersistSubscribers = new Map<RendererPersistCacheKey, Set<() => void>>
const notifyMemorySubscribers = (key: UseCacheKey) => { const notifyMemorySubscribers = (key: UseCacheKey) => {
const subscribers = mockMemorySubscribers.get(key) const subscribers = mockMemorySubscribers.get(key)
if (subscribers) { if (subscribers) {
subscribers.forEach(callback => { subscribers.forEach((callback) => {
try { try {
callback() callback()
} catch (error) { } catch (error) {
@ -54,7 +54,7 @@ const notifyMemorySubscribers = (key: UseCacheKey) => {
const notifySharedSubscribers = (key: UseSharedCacheKey) => { const notifySharedSubscribers = (key: UseSharedCacheKey) => {
const subscribers = mockSharedSubscribers.get(key) const subscribers = mockSharedSubscribers.get(key)
if (subscribers) { if (subscribers) {
subscribers.forEach(callback => { subscribers.forEach((callback) => {
try { try {
callback() callback()
} catch (error) { } catch (error) {
@ -67,7 +67,7 @@ const notifySharedSubscribers = (key: UseSharedCacheKey) => {
const notifyPersistSubscribers = (key: RendererPersistCacheKey) => { const notifyPersistSubscribers = (key: RendererPersistCacheKey) => {
const subscribers = mockPersistSubscribers.get(key) const subscribers = mockPersistSubscribers.get(key)
if (subscribers) { if (subscribers) {
subscribers.forEach(callback => { subscribers.forEach((callback) => {
try { try {
callback() callback()
} catch (error) { } catch (error) {
@ -80,77 +80,83 @@ const notifyPersistSubscribers = (key: RendererPersistCacheKey) => {
/** /**
* Mock useCache hook (memory cache) * Mock useCache hook (memory cache)
*/ */
export const mockUseCache = vi.fn(<K extends UseCacheKey>( export const mockUseCache = vi.fn(
key: K, <K extends UseCacheKey>(
initValue?: UseCacheSchema[K] key: K,
): [UseCacheSchema[K], (value: UseCacheSchema[K]) => void] => { initValue?: UseCacheSchema[K]
// Get current value ): [UseCacheSchema[K], (value: UseCacheSchema[K]) => void] => {
let currentValue = mockMemoryCache.get(key) // Get current value
if (currentValue === undefined) { let currentValue = mockMemoryCache.get(key)
currentValue = initValue ?? DefaultUseCache[key] if (currentValue === undefined) {
if (currentValue !== undefined) { currentValue = initValue ?? DefaultUseCache[key]
mockMemoryCache.set(key, currentValue) if (currentValue !== undefined) {
mockMemoryCache.set(key, currentValue)
}
} }
// Mock setValue function
const setValue = vi.fn((value: UseCacheSchema[K]) => {
mockMemoryCache.set(key, value)
notifyMemorySubscribers(key)
})
return [currentValue, setValue]
} }
)
// Mock setValue function
const setValue = vi.fn((value: UseCacheSchema[K]) => {
mockMemoryCache.set(key, value)
notifyMemorySubscribers(key)
})
return [currentValue, setValue]
})
/** /**
* Mock useSharedCache hook (shared cache) * Mock useSharedCache hook (shared cache)
*/ */
export const mockUseSharedCache = vi.fn(<K extends UseSharedCacheKey>( export const mockUseSharedCache = vi.fn(
key: K, <K extends UseSharedCacheKey>(
initValue?: UseSharedCacheSchema[K] key: K,
): [UseSharedCacheSchema[K], (value: UseSharedCacheSchema[K]) => void] => { initValue?: UseSharedCacheSchema[K]
// Get current value ): [UseSharedCacheSchema[K], (value: UseSharedCacheSchema[K]) => void] => {
let currentValue = mockSharedCache.get(key) // Get current value
if (currentValue === undefined) { let currentValue = mockSharedCache.get(key)
currentValue = initValue ?? DefaultUseSharedCache[key] if (currentValue === undefined) {
if (currentValue !== undefined) { currentValue = initValue ?? DefaultUseSharedCache[key]
mockSharedCache.set(key, currentValue) if (currentValue !== undefined) {
mockSharedCache.set(key, currentValue)
}
} }
// Mock setValue function
const setValue = vi.fn((value: UseSharedCacheSchema[K]) => {
mockSharedCache.set(key, value)
notifySharedSubscribers(key)
})
return [currentValue, setValue]
} }
)
// Mock setValue function
const setValue = vi.fn((value: UseSharedCacheSchema[K]) => {
mockSharedCache.set(key, value)
notifySharedSubscribers(key)
})
return [currentValue, setValue]
})
/** /**
* Mock usePersistCache hook (persistent cache) * Mock usePersistCache hook (persistent cache)
*/ */
export const mockUsePersistCache = vi.fn(<K extends RendererPersistCacheKey>( export const mockUsePersistCache = vi.fn(
key: K, <K extends RendererPersistCacheKey>(
initValue?: RendererPersistCacheSchema[K] key: K,
): [RendererPersistCacheSchema[K], (value: RendererPersistCacheSchema[K]) => void] => { initValue?: RendererPersistCacheSchema[K]
// Get current value ): [RendererPersistCacheSchema[K], (value: RendererPersistCacheSchema[K]) => void] => {
let currentValue = mockPersistCache.get(key) // Get current value
if (currentValue === undefined) { let currentValue = mockPersistCache.get(key)
currentValue = initValue ?? DefaultRendererPersistCache[key] if (currentValue === undefined) {
if (currentValue !== undefined) { currentValue = initValue ?? DefaultRendererPersistCache[key]
mockPersistCache.set(key, currentValue) if (currentValue !== undefined) {
mockPersistCache.set(key, currentValue)
}
} }
// Mock setValue function
const setValue = vi.fn((value: RendererPersistCacheSchema[K]) => {
mockPersistCache.set(key, value)
notifyPersistSubscribers(key)
})
return [currentValue, setValue]
} }
)
// Mock setValue function
const setValue = vi.fn((value: RendererPersistCacheSchema[K]) => {
mockPersistCache.set(key, value)
notifyPersistSubscribers(key)
})
return [currentValue, setValue]
})
/** /**
* Export all mocks as a unified module * Export all mocks as a unified module
@ -292,18 +298,12 @@ export const MockUseCacheUtils = {
) => { ) => {
mockUseCache.mockImplementation((cacheKey, initValue) => { mockUseCache.mockImplementation((cacheKey, initValue) => {
if (cacheKey === key) { if (cacheKey === key) {
return [ return [value, setValue || vi.fn()]
value,
setValue || vi.fn()
]
} }
// Default behavior for other keys // Default behavior for other keys
const defaultValue = mockMemoryCache.get(cacheKey) ?? initValue ?? DefaultUseCache[cacheKey] const defaultValue = mockMemoryCache.get(cacheKey) ?? initValue ?? DefaultUseCache[cacheKey]
return [ return [defaultValue, vi.fn()]
defaultValue,
vi.fn()
]
}) })
}, },
@ -317,18 +317,12 @@ export const MockUseCacheUtils = {
) => { ) => {
mockUseSharedCache.mockImplementation((cacheKey, initValue) => { mockUseSharedCache.mockImplementation((cacheKey, initValue) => {
if (cacheKey === key) { if (cacheKey === key) {
return [ return [value, setValue || vi.fn()]
value,
setValue || vi.fn()
]
} }
// Default behavior for other keys // Default behavior for other keys
const defaultValue = mockSharedCache.get(cacheKey) ?? initValue ?? DefaultUseSharedCache[cacheKey] const defaultValue = mockSharedCache.get(cacheKey) ?? initValue ?? DefaultUseSharedCache[cacheKey]
return [ return [defaultValue, vi.fn()]
defaultValue,
vi.fn()
]
}) })
}, },
@ -342,18 +336,12 @@ export const MockUseCacheUtils = {
) => { ) => {
mockUsePersistCache.mockImplementation((cacheKey, initValue) => { mockUsePersistCache.mockImplementation((cacheKey, initValue) => {
if (cacheKey === key) { if (cacheKey === key) {
return [ return [value, setValue || vi.fn()]
value,
setValue || vi.fn()
]
} }
// Default behavior for other keys // Default behavior for other keys
const defaultValue = mockPersistCache.get(cacheKey) ?? initValue ?? DefaultRendererPersistCache[cacheKey] const defaultValue = mockPersistCache.get(cacheKey) ?? initValue ?? DefaultRendererPersistCache[cacheKey]
return [ return [defaultValue, vi.fn()]
defaultValue,
vi.fn()
]
}) })
}, },
@ -425,4 +413,4 @@ export const MockUseCacheUtils = {
shared: Array.from(mockSharedSubscribers.entries()).map(([key, subs]) => [key, subs.size]), shared: Array.from(mockSharedSubscribers.entries()).map(([key, subs]) => [key, subs.size]),
persist: Array.from(mockPersistSubscribers.entries()).map(([key, subs]) => [key, subs.size]) persist: Array.from(mockPersistSubscribers.entries()).map(([key, subs]) => [key, subs.size])
}) })
} }

View File

@ -71,133 +71,134 @@ function createMockDataForPath(path: ConcreteApiPaths): any {
/** /**
* Mock useQuery hook * Mock useQuery hook
*/ */
export const mockUseQuery = vi.fn(<TPath extends ConcreteApiPaths>( export const mockUseQuery = vi.fn(
path: TPath | null, <TPath extends ConcreteApiPaths>(path: TPath | null, _query?: any, options?: any): MockSWRResponse<any> => {
query?: any, const isLoading = options?.initialLoading ?? false
options?: any const hasError = options?.shouldError ?? false
): MockSWRResponse<any> => {
const isLoading = options?.initialLoading ?? false if (hasError) {
const hasError = options?.shouldError ?? false return {
data: undefined,
error: new Error(`Mock error for ${path}`),
isLoading: false,
isValidating: false,
mutate: vi.fn().mockResolvedValue(undefined)
}
}
const mockData = path ? createMockDataForPath(path) : undefined
if (hasError) {
return { return {
data: undefined, data: mockData,
error: new Error(`Mock error for ${path}`), error: undefined,
isLoading: false, isLoading,
isValidating: false, isValidating: false,
mutate: vi.fn().mockResolvedValue(undefined) mutate: vi.fn().mockResolvedValue(mockData)
} }
} }
)
const mockData = path ? createMockDataForPath(path) : undefined
return {
data: mockData,
error: undefined,
isLoading,
isValidating: false,
mutate: vi.fn().mockResolvedValue(mockData)
}
})
/** /**
* Mock useMutation hook * Mock useMutation hook
*/ */
export const mockUseMutation = vi.fn(<TPath extends ConcreteApiPaths, TMethod extends 'POST' | 'PUT' | 'DELETE' | 'PATCH'>( export const mockUseMutation = vi.fn(
path: TPath, <TPath extends ConcreteApiPaths, TMethod extends 'POST' | 'PUT' | 'DELETE' | 'PATCH'>(
method: TMethod, path: TPath,
options?: any method: TMethod,
): MockMutationResponse<any> => { options?: any
const isMutating = options?.initialMutating ?? false ): MockMutationResponse<any> => {
const hasError = options?.shouldError ?? false const isMutating = options?.initialMutating ?? false
const hasError = options?.shouldError ?? false
const mockTrigger = vi.fn(async (...args: any[]) => { const mockTrigger = vi.fn(async (...args: any[]) => {
if (hasError) { if (hasError) {
throw new Error(`Mock mutation error for ${method} ${path}`) throw new Error(`Mock mutation error for ${method} ${path}`)
}
// Simulate different responses based on method
switch (method) {
case 'POST':
return { id: 'new_item', created: true, ...args[0] }
case 'PUT':
case 'PATCH':
return { id: 'updated_item', updated: true, ...args[0] }
case 'DELETE':
return { deleted: true }
default:
return { success: true }
}
})
return {
data: undefined,
error: undefined,
isMutating,
trigger: mockTrigger,
reset: vi.fn()
} }
// Simulate different responses based on method
switch (method) {
case 'POST':
return { id: 'new_item', created: true, ...args[0] }
case 'PUT':
case 'PATCH':
return { id: 'updated_item', updated: true, ...args[0] }
case 'DELETE':
return { deleted: true }
default:
return { success: true }
}
})
return {
data: undefined,
error: undefined,
isMutating,
trigger: mockTrigger,
reset: vi.fn()
} }
}) )
/** /**
* Mock usePaginatedQuery hook * Mock usePaginatedQuery hook
*/ */
export const mockUsePaginatedQuery = vi.fn(<TPath extends ConcreteApiPaths>( export const mockUsePaginatedQuery = vi.fn(
path: TPath | null, <TPath extends ConcreteApiPaths>(path: TPath | null, _query?: any, options?: any): MockPaginatedResponse<any> => {
query?: any, const isLoading = options?.initialLoading ?? false
options?: any const isLoadingMore = options?.initialLoadingMore ?? false
): MockPaginatedResponse<any> => { const hasError = options?.shouldError ?? false
const isLoading = options?.initialLoading ?? false
const isLoadingMore = options?.initialLoadingMore ?? false if (hasError) {
const hasError = options?.shouldError ?? false return {
data: undefined,
error: new Error(`Mock paginated error for ${path}`),
isLoading: false,
isValidating: false,
mutate: vi.fn().mockResolvedValue(undefined),
loadMore: vi.fn(),
isLoadingMore: false,
hasMore: false,
items: []
}
}
const mockItems = path
? [
{ id: 'item1', name: 'Mock Item 1' },
{ id: 'item2', name: 'Mock Item 2' },
{ id: 'item3', name: 'Mock Item 3' }
]
: []
const mockData: PaginatedResponse<any> = {
items: mockItems,
total: mockItems.length,
page: 1,
pageCount: 1,
hasNext: false,
hasPrev: false
}
if (hasError) {
return { return {
data: undefined, data: mockData,
error: new Error(`Mock paginated error for ${path}`), error: undefined,
isLoading: false, isLoading,
isValidating: false, isValidating: false,
mutate: vi.fn().mockResolvedValue(undefined), mutate: vi.fn().mockResolvedValue(mockData),
loadMore: vi.fn(), loadMore: vi.fn(),
isLoadingMore: false, isLoadingMore,
hasMore: false, hasMore: mockData.hasNext,
items: [] items: mockItems
} }
} }
)
const mockItems = path ? [
{ id: 'item1', name: 'Mock Item 1' },
{ id: 'item2', name: 'Mock Item 2' },
{ id: 'item3', name: 'Mock Item 3' }
] : []
const mockData: PaginatedResponse<any> = {
items: mockItems,
total: mockItems.length,
page: 1,
pageSize: 10,
hasMore: false
}
return {
data: mockData,
error: undefined,
isLoading,
isValidating: false,
mutate: vi.fn().mockResolvedValue(mockData),
loadMore: vi.fn(),
isLoadingMore,
hasMore: mockData.hasMore,
items: mockItems
}
})
/** /**
* Mock useInvalidateCache hook * Mock useInvalidateCache hook
*/ */
export const mockUseInvalidateCache = vi.fn(() => { export const mockUseInvalidateCache = vi.fn(() => {
return { return {
invalidate: vi.fn(async (path?: ConcreteApiPaths) => { invalidate: vi.fn(async () => {
// Mock cache invalidation // Mock cache invalidation
return Promise.resolve() return Promise.resolve()
}), }),
@ -211,13 +212,9 @@ export const mockUseInvalidateCache = vi.fn(() => {
/** /**
* Mock prefetch function * Mock prefetch function
*/ */
export const mockPrefetch = vi.fn(async <TPath extends ConcreteApiPaths>( export const mockPrefetch = vi.fn(async <TPath extends ConcreteApiPaths>(_path: TPath): Promise<any> => {
path: TPath,
query?: any,
options?: any
): Promise<any> => {
// Mock prefetch - return mock data // Mock prefetch - return mock data
return createMockDataForPath(path) return createMockDataForPath(_path)
}) })
/** /**
@ -261,13 +258,15 @@ export const MockUseDataApiUtils = {
} }
} }
// Default behavior for other paths // Default behavior for other paths
return mockUseQuery.getMockImplementation()?.(queryPath, query, options) || { return (
data: undefined, mockUseQuery.getMockImplementation()?.(queryPath, query, options) || {
error: undefined, data: undefined,
isLoading: false, error: undefined,
isValidating: false, isLoading: false,
mutate: vi.fn().mockResolvedValue(undefined) isValidating: false,
} mutate: vi.fn().mockResolvedValue(undefined)
}
)
}) })
}, },
@ -285,13 +284,15 @@ export const MockUseDataApiUtils = {
mutate: vi.fn().mockResolvedValue(undefined) mutate: vi.fn().mockResolvedValue(undefined)
} }
} }
return mockUseQuery.getMockImplementation()?.(queryPath, query, options) || { return (
data: undefined, mockUseQuery.getMockImplementation()?.(queryPath, query, options) || {
error: undefined, data: undefined,
isLoading: false, error: undefined,
isValidating: false, isLoading: false,
mutate: vi.fn().mockResolvedValue(undefined) isValidating: false,
} mutate: vi.fn().mockResolvedValue(undefined)
}
)
}) })
}, },
@ -309,13 +310,15 @@ export const MockUseDataApiUtils = {
mutate: vi.fn().mockResolvedValue(undefined) mutate: vi.fn().mockResolvedValue(undefined)
} }
} }
return mockUseQuery.getMockImplementation()?.(queryPath, query, options) || { return (
data: undefined, mockUseQuery.getMockImplementation()?.(queryPath, query, options) || {
error: undefined, data: undefined,
isLoading: false, error: undefined,
isValidating: false, isLoading: false,
mutate: vi.fn().mockResolvedValue(undefined) isValidating: false,
} mutate: vi.fn().mockResolvedValue(undefined)
}
)
}) })
}, },
@ -333,13 +336,15 @@ export const MockUseDataApiUtils = {
reset: vi.fn() reset: vi.fn()
} }
} }
return mockUseMutation.getMockImplementation()?.(mutationPath, mutationMethod, options) || { return (
data: undefined, mockUseMutation.getMockImplementation()?.(mutationPath, mutationMethod, options) || {
error: undefined, data: undefined,
isMutating: false, error: undefined,
trigger: vi.fn().mockResolvedValue({}), isMutating: false,
reset: vi.fn() trigger: vi.fn().mockResolvedValue({}),
} reset: vi.fn()
}
)
}) })
}, },
@ -357,13 +362,15 @@ export const MockUseDataApiUtils = {
reset: vi.fn() reset: vi.fn()
} }
} }
return mockUseMutation.getMockImplementation()?.(mutationPath, mutationMethod, options) || { return (
data: undefined, mockUseMutation.getMockImplementation()?.(mutationPath, mutationMethod, options) || {
error: undefined, data: undefined,
isMutating: false, error: undefined,
trigger: vi.fn().mockResolvedValue({}), isMutating: false,
reset: vi.fn() trigger: vi.fn().mockResolvedValue({}),
} reset: vi.fn()
}
)
}) })
} }
} }

View File

@ -1,8 +1,4 @@
import type { import type { PreferenceKeyType, PreferenceUpdateOptions } from '@shared/data/preference/preferenceTypes'
PreferenceKeyType,
PreferenceUpdateOptions,
PreferenceValueType
} from '@shared/data/preference/preferenceTypes'
import { vi } from 'vitest' import { vi } from 'vitest'
import { mockPreferenceDefaults } from './PreferenceService' import { mockPreferenceDefaults } from './PreferenceService'
@ -27,7 +23,7 @@ const mockPreferenceSubscribers = new Map<PreferenceKeyType, Set<() => void>>()
const notifyPreferenceSubscribers = (key: PreferenceKeyType) => { const notifyPreferenceSubscribers = (key: PreferenceKeyType) => {
const subscribers = mockPreferenceSubscribers.get(key) const subscribers = mockPreferenceSubscribers.get(key)
if (subscribers) { if (subscribers) {
subscribers.forEach(callback => { subscribers.forEach((callback) => {
try { try {
callback() callback()
} catch (error) { } catch (error) {
@ -40,111 +36,109 @@ const notifyPreferenceSubscribers = (key: PreferenceKeyType) => {
/** /**
* Mock usePreference hook * Mock usePreference hook
*/ */
export const mockUsePreference = vi.fn(<K extends PreferenceKeyType>( export const mockUsePreference = vi.fn(
key: K, <K extends PreferenceKeyType>(key: K, options?: PreferenceUpdateOptions): [any, (value: any) => Promise<void>] => {
options?: PreferenceUpdateOptions // Get current value
): [PreferenceValueType<K>, (value: PreferenceValueType<K>) => Promise<void>] => { const currentValue = mockPreferenceState.get(key) ?? mockPreferenceDefaults[key] ?? null
// Get current value
const currentValue = mockPreferenceState.get(key) ?? mockPreferenceDefaults[key] ?? null
// Mock setValue function // Mock setValue function
const setValue = vi.fn(async (value: PreferenceValueType<K>) => { const setValue = vi.fn(async (value: any) => {
const oldValue = mockPreferenceState.get(key) const oldValue = mockPreferenceState.get(key)
// Simulate optimistic updates (default behavior) // Simulate optimistic updates (default behavior)
if (options?.optimistic !== false) { if (options?.optimistic !== false) {
mockPreferenceState.set(key, value) mockPreferenceState.set(key, value)
notifyPreferenceSubscribers(key)
}
// Simulate async update delay
await new Promise(resolve => setTimeout(resolve, 10))
// For pessimistic updates, update after delay
if (options?.optimistic === false) {
mockPreferenceState.set(key, value)
notifyPreferenceSubscribers(key)
}
// Simulate error scenarios if configured
if (options?.shouldError) {
// Rollback optimistic update on error
if (options.optimistic !== false) {
mockPreferenceState.set(key, oldValue)
notifyPreferenceSubscribers(key) notifyPreferenceSubscribers(key)
} }
throw new Error(`Mock preference error for key: ${key}`)
}
})
return [currentValue, setValue] // Simulate async update delay
}) await new Promise((resolve) => setTimeout(resolve, 10))
// For pessimistic updates, update after delay
if (options?.optimistic === false) {
mockPreferenceState.set(key, value)
notifyPreferenceSubscribers(key)
}
// Simulate error scenarios if configured
if ((options as any)?.shouldError) {
// Rollback optimistic update on error
if (options?.optimistic !== false) {
mockPreferenceState.set(key, oldValue)
notifyPreferenceSubscribers(key)
}
throw new Error(`Mock preference error for key: ${key}`)
}
})
return [currentValue, setValue]
}
)
/** /**
* Mock useMultiplePreferences hook * Mock useMultiplePreferences hook
*/ */
export const mockUseMultiplePreferences = vi.fn(<T extends Record<string, PreferenceKeyType>>( export const mockUseMultiplePreferences = vi.fn(
keys: T, <T extends Record<string, PreferenceKeyType>>(
options?: PreferenceUpdateOptions keys: T,
): [ options?: PreferenceUpdateOptions
{ [K in keyof T]: PreferenceValueType<T[K]> }, ): [{ [K in keyof T]: any }, (values: Partial<{ [K in keyof T]: any }>) => Promise<void>] => {
(values: Partial<{ [K in keyof T]: PreferenceValueType<T[K]> }>) => Promise<void> // Get current values for all keys
] => { const currentValues = {} as { [K in keyof T]: any }
// Get current values for all keys Object.entries(keys).forEach(([alias, key]) => {
const currentValues = {} as { [K in keyof T]: PreferenceValueType<T[K]> } currentValues[alias as keyof T] =
Object.entries(keys).forEach(([alias, key]) => { mockPreferenceState.get(key as PreferenceKeyType) ?? mockPreferenceDefaults[key as string] ?? null
currentValues[alias as keyof T] = mockPreferenceState.get(key as PreferenceKeyType) ?? })
mockPreferenceDefaults[key as string] ?? null
})
// Mock setValues function // Mock setValues function
const setValues = vi.fn(async (values: Partial<{ [K in keyof T]: PreferenceValueType<T[K]> }>) => { const setValues = vi.fn(async (values: Partial<{ [K in keyof T]: any }>) => {
const oldValues = { ...currentValues } const oldValues = { ...currentValues }
// Simulate optimistic updates // Simulate optimistic updates
if (options?.optimistic !== false) { if (options?.optimistic !== false) {
Object.entries(values).forEach(([alias, value]) => { Object.entries(values).forEach(([alias, value]) => {
const key = keys[alias as keyof T] as PreferenceKeyType
if (value !== undefined) {
mockPreferenceState.set(key, value)
currentValues[alias as keyof T] = value as any
notifyPreferenceSubscribers(key)
}
})
}
// Simulate async update delay
await new Promise(resolve => setTimeout(resolve, 10))
// For pessimistic updates, update after delay
if (options?.optimistic === false) {
Object.entries(values).forEach(([alias, value]) => {
const key = keys[alias as keyof T] as PreferenceKeyType
if (value !== undefined) {
mockPreferenceState.set(key, value)
currentValues[alias as keyof T] = value as any
notifyPreferenceSubscribers(key)
}
})
}
// Simulate error scenarios
if (options?.shouldError) {
// Rollback optimistic updates on error
if (options.optimistic !== false) {
Object.entries(oldValues).forEach(([alias, value]) => {
const key = keys[alias as keyof T] as PreferenceKeyType const key = keys[alias as keyof T] as PreferenceKeyType
mockPreferenceState.set(key, value) if (value !== undefined) {
currentValues[alias as keyof T] = value mockPreferenceState.set(key, value)
notifyPreferenceSubscribers(key) currentValues[alias as keyof T] = value as any
notifyPreferenceSubscribers(key)
}
}) })
} }
throw new Error('Mock multiple preferences error')
}
})
return [currentValues, setValues] // Simulate async update delay
}) await new Promise((resolve) => setTimeout(resolve, 10))
// For pessimistic updates, update after delay
if (options?.optimistic === false) {
Object.entries(values).forEach(([alias, value]) => {
const key = keys[alias as keyof T] as PreferenceKeyType
if (value !== undefined) {
mockPreferenceState.set(key, value)
currentValues[alias as keyof T] = value as any
notifyPreferenceSubscribers(key)
}
})
}
// Simulate error scenarios
if ((options as any)?.shouldError) {
// Rollback optimistic updates on error
if (options?.optimistic !== false) {
Object.entries(oldValues).forEach(([alias, value]) => {
const key = keys[alias as keyof T] as PreferenceKeyType
mockPreferenceState.set(key, value)
currentValues[alias as keyof T] = value
notifyPreferenceSubscribers(key)
})
}
throw new Error('Mock multiple preferences error')
}
})
return [currentValues, setValues]
}
)
/** /**
* Export all mocks as a unified module * Export all mocks as a unified module
@ -178,7 +172,7 @@ export const MockUsePreferenceUtils = {
/** /**
* Set a preference value for testing * Set a preference value for testing
*/ */
setPreferenceValue: <K extends PreferenceKeyType>(key: K, value: PreferenceValueType<K>) => { setPreferenceValue: <K extends PreferenceKeyType>(key: K, value: any) => {
mockPreferenceState.set(key, value) mockPreferenceState.set(key, value)
notifyPreferenceSubscribers(key) notifyPreferenceSubscribers(key)
}, },
@ -186,7 +180,7 @@ export const MockUsePreferenceUtils = {
/** /**
* Get current preference value * Get current preference value
*/ */
getPreferenceValue: <K extends PreferenceKeyType>(key: K): PreferenceValueType<K> => { getPreferenceValue: <K extends PreferenceKeyType>(key: K): any => {
return mockPreferenceState.get(key) ?? mockPreferenceDefaults[key] ?? null return mockPreferenceState.get(key) ?? mockPreferenceDefaults[key] ?? null
}, },
@ -214,7 +208,7 @@ export const MockUsePreferenceUtils = {
/** /**
* Simulate preference change from external source * Simulate preference change from external source
*/ */
simulateExternalPreferenceChange: <K extends PreferenceKeyType>(key: K, value: PreferenceValueType<K>) => { simulateExternalPreferenceChange: <K extends PreferenceKeyType>(key: K, value: any) => {
mockPreferenceState.set(key, value) mockPreferenceState.set(key, value)
notifyPreferenceSubscribers(key) notifyPreferenceSubscribers(key)
}, },
@ -222,26 +216,15 @@ export const MockUsePreferenceUtils = {
/** /**
* Mock preference hook to return specific value for a key * Mock preference hook to return specific value for a key
*/ */
mockPreferenceReturn: <K extends PreferenceKeyType>( mockPreferenceReturn: <K extends PreferenceKeyType>(key: K, value: any, setValue?: (value: any) => Promise<void>) => {
key: K, mockUsePreference.mockImplementation((preferenceKey) => {
value: PreferenceValueType<K>,
setValue?: (value: PreferenceValueType<K>) => Promise<void>
) => {
mockUsePreference.mockImplementation((preferenceKey, options) => {
if (preferenceKey === key) { if (preferenceKey === key) {
return [ return [value, setValue || vi.fn().mockResolvedValue(undefined)]
value,
setValue || vi.fn().mockResolvedValue(undefined)
]
} }
// Default behavior for other keys // Default behavior for other keys
const defaultValue = mockPreferenceState.get(preferenceKey) ?? const defaultValue = mockPreferenceState.get(preferenceKey) ?? mockPreferenceDefaults[preferenceKey] ?? null
mockPreferenceDefaults[preferenceKey] ?? null return [defaultValue, vi.fn().mockResolvedValue(undefined)]
return [
defaultValue,
vi.fn().mockResolvedValue(undefined)
]
}) })
}, },
@ -249,7 +232,7 @@ export const MockUsePreferenceUtils = {
* Mock preference hook to simulate error for a key * Mock preference hook to simulate error for a key
*/ */
mockPreferenceError: <K extends PreferenceKeyType>(key: K, error: Error) => { mockPreferenceError: <K extends PreferenceKeyType>(key: K, error: Error) => {
mockUsePreference.mockImplementation((preferenceKey, options) => { mockUsePreference.mockImplementation((preferenceKey) => {
if (preferenceKey === key) { if (preferenceKey === key) {
const setValue = vi.fn().mockRejectedValue(error) const setValue = vi.fn().mockRejectedValue(error)
const currentValue = mockPreferenceState.get(key) ?? mockPreferenceDefaults[key] ?? null const currentValue = mockPreferenceState.get(key) ?? mockPreferenceDefaults[key] ?? null
@ -257,12 +240,8 @@ export const MockUsePreferenceUtils = {
} }
// Default behavior for other keys // Default behavior for other keys
const defaultValue = mockPreferenceState.get(preferenceKey) ?? const defaultValue = mockPreferenceState.get(preferenceKey) ?? mockPreferenceDefaults[preferenceKey] ?? null
mockPreferenceDefaults[preferenceKey] ?? null return [defaultValue, vi.fn().mockResolvedValue(undefined)]
return [
defaultValue,
vi.fn().mockResolvedValue(undefined)
]
}) })
}, },
@ -293,4 +272,4 @@ export const MockUsePreferenceUtils = {
getSubscriberCount: (key: PreferenceKeyType): number => { getSubscriberCount: (key: PreferenceKeyType): number => {
return mockPreferenceSubscribers.get(key)?.size ?? 0 return mockPreferenceSubscribers.get(key)?.size ?? 0
} }
} }