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 type { ErrorBoundaryCustomizedProps, CustomFallbackProps }
export type { CustomFallbackProps, ErrorBoundaryCustomizedProps }

View File

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

View File

@ -16,8 +16,11 @@ import React from 'react'
// 创建一个 Icon 工厂函数
export function createIcon(IconComponent: LucideIcon, defaultSize: string | number = '1rem') {
const Icon = React.forwardRef<SVGSVGElement, React.ComponentProps<typeof IconComponent>>(
(props, ref) => <IconComponent ref={ref} size={defaultSize} {...props} />
const Icon = ({
ref,
...props
}: React.ComponentProps<typeof IconComponent> & { ref?: React.RefObject<SVGSVGElement | null> }) => (
<IconComponent ref={ref} size={defaultSize} {...props} />
)
Icon.displayName = `Icon(${IconComponent.displayName || IconComponent.name})`
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 DividerWithText } from './base/DividerWithText'
export { default as EmojiIcon } from './base/EmojiIcon'
export type { CustomFallbackProps, ErrorBoundaryCustomizedProps } 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 Spinner } from './base/Spinner'
export { StatusTag, ErrorTag, SuccessTag, WarnTag, InfoTag } from './base/StatusTag'
export type { StatusType, StatusTagProps } from './base/StatusTag'
export type { StatusTagProps, StatusType } from './base/StatusTag'
export { ErrorTag, InfoTag, StatusTag, SuccessTag, WarnTag } from './base/StatusTag'
export { default as TextBadge } from './base/TextBadge'
// Display Components
@ -26,22 +26,22 @@ export { default as HorizontalScrollContainer } from './layout/HorizontalScrollC
export { default as Scrollbar } from './layout/Scrollbar'
// Icon Components
export { FilePngIcon, FileSvgIcon } from './icons/FileIcons'
export type { LucideIcon, LucideProps } from './icons/Icon'
export {
createIcon,
CopyIcon,
createIcon,
DeleteIcon,
EditIcon,
OcrIcon,
RefreshIcon,
ResetIcon,
ToolIcon,
UnWrapIcon,
VisionIcon,
WebSearchIcon,
WrapIcon,
UnWrapIcon,
OcrIcon
WrapIcon
} 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 SvgSpinners180Ring } from './icons/SvgSpinners180Ring'
export { default as ToolsCallingIcon } from './icons/ToolsCallingIcon'

View File

@ -1,35 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react'
import {
Copy,
Trash,
Pencil,
RefreshCw,
RotateCcw,
Wrench,
Eye,
Search,
WrapText,
AlignLeft,
ScanLine,
Settings,
Download,
Upload,
ChevronRight
} from 'lucide-react'
import { ChevronRight, Download, Settings, Upload } from 'lucide-react'
import {
createIcon,
CopyIcon,
createIcon,
DeleteIcon,
EditIcon,
OcrIcon,
RefreshIcon,
ResetIcon,
ToolIcon,
UnWrapIcon,
VisionIcon,
WebSearchIcon,
WrapIcon,
UnWrapIcon,
OcrIcon
WrapIcon
} from '../../../src/components/icons/Icon'
// Create a dummy component for the story
@ -278,8 +262,7 @@ export const IconGrid: Story = {
{AllIcons.map(({ Icon, name }) => (
<div
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} />
<span className="text-xs">{name}</span>
</div>
@ -287,4 +270,4 @@ export const IconGrid: Story = {
</div>
)
}
}
}

View File

@ -100,7 +100,8 @@ vi.mock('@renderer/utils', () => ({
vi.mock('@shared/config/prompts', () => ({
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', () => ({

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +1,5 @@
import type { ConcreteApiPaths } from '@shared/data/api/apiSchemas'
import type {
ApiClient,
BatchRequest,
BatchResponse,
DataRequest,
DataResponse,
SubscriptionCallback,
SubscriptionOptions,
TransactionRequest
} from '@shared/data/api/apiTypes'
import type { ApiClient, ConcreteApiPaths } from '@shared/data/api/apiSchemas'
import type { DataResponse } from '@shared/data/api/apiTypes'
import { vi } from 'vitest'
/**
@ -18,20 +9,21 @@ import { vi } from 'vitest'
// Mock response utilities
const createMockResponse = <T>(data: T, success = true): DataResponse<T> => ({
success,
id: 'mock-id',
status: success ? 200 : 500,
data,
timestamp: new Date().toISOString(),
...(success ? {} : { error: { code: 'MOCK_ERROR', message: 'Mock error', details: {} } })
...(success ? {} : { error: { code: 'MOCK_ERROR', message: 'Mock error', details: {}, status: 500 } })
})
const createMockError = (message: string): DataResponse<never> => ({
success: false,
id: 'mock-error-id',
status: 500,
error: {
code: 'MOCK_ERROR',
message,
details: {}
},
timestamp: new Date().toISOString()
details: {},
status: 500
}
})
/**
@ -40,80 +32,25 @@ const createMockError = (message: string): DataResponse<never> => ({
export const createMockDataApiService = (customBehavior: Partial<ApiClient> = {}): ApiClient => {
const mockService: ApiClient = {
// HTTP Methods
get: vi.fn(async (path: ConcreteApiPaths, options?: any) => {
// Default mock behavior - return empty data based on path
const mockData = getMockDataForPath(path, 'GET')
return createMockResponse(mockData)
get: vi.fn(async (path: ConcreteApiPaths) => {
// Default mock behavior - return raw data based on path
return getMockDataForPath(path, 'GET') as any
}),
post: vi.fn(async (path: ConcreteApiPaths, options?: any) => {
const mockData = getMockDataForPath(path, 'POST')
return createMockResponse(mockData)
post: vi.fn(async (path: ConcreteApiPaths) => {
return getMockDataForPath(path, 'POST') as any
}),
put: vi.fn(async (path: ConcreteApiPaths, options?: any) => {
const mockData = getMockDataForPath(path, 'PUT')
return createMockResponse(mockData)
put: vi.fn(async (path: ConcreteApiPaths) => {
return getMockDataForPath(path, 'PUT') as any
}),
patch: vi.fn(async (path: ConcreteApiPaths, options?: any) => {
const mockData = getMockDataForPath(path, 'PATCH')
return createMockResponse(mockData)
patch: vi.fn(async (path: ConcreteApiPaths) => {
return getMockDataForPath(path, 'PATCH') as any
}),
delete: vi.fn(async (path: ConcreteApiPaths, options?: any) => {
return createMockResponse({ deleted: true })
}),
// 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() })
delete: vi.fn(async () => {
return { deleted: true } as any
}),
// Apply custom behavior overrides
@ -229,34 +166,6 @@ export const MockDataApiService = {
async delete(path: ConcreteApiPaths, options?: any) {
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
}
@ -269,7 +178,7 @@ export const MockDataApiUtils = {
* Reset all mock function call counts and implementations
*/
resetMocks: () => {
Object.values(mockDataApiService).forEach(method => {
Object.values(mockDataApiService).forEach((method) => {
if (vi.isMockFunction(method)) {
method.mockClear()
}
@ -282,7 +191,7 @@ export const MockDataApiUtils = {
setCustomResponse: (path: ConcreteApiPaths, method: string, response: any) => {
const methodFn = mockDataApiService[method.toLowerCase() as keyof ApiClient] as any
if (vi.isMockFunction(methodFn)) {
methodFn.mockImplementation(async (requestPath: string, options?: any) => {
methodFn.mockImplementation(async (requestPath: string) => {
if (requestPath === path) {
return createMockResponse(response)
}
@ -298,7 +207,7 @@ export const MockDataApiUtils = {
setErrorResponse: (path: ConcreteApiPaths, method: string, errorMessage: string) => {
const methodFn = mockDataApiService[method.toLowerCase() as keyof ApiClient] as any
if (vi.isMockFunction(methodFn)) {
methodFn.mockImplementation(async (requestPath: string, options?: any) => {
methodFn.mockImplementation(async (requestPath: string) => {
if (requestPath === path) {
return createMockError(errorMessage)
}
@ -323,4 +232,4 @@ export const MockDataApiUtils = {
const methodFn = mockDataApiService[method] as any
return vi.isMockFunction(methodFn) ? methodFn.mock.calls : []
}
}
}

View File

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

View File

@ -41,7 +41,7 @@ const mockPersistSubscribers = new Map<RendererPersistCacheKey, Set<() => void>>
const notifyMemorySubscribers = (key: UseCacheKey) => {
const subscribers = mockMemorySubscribers.get(key)
if (subscribers) {
subscribers.forEach(callback => {
subscribers.forEach((callback) => {
try {
callback()
} catch (error) {
@ -54,7 +54,7 @@ const notifyMemorySubscribers = (key: UseCacheKey) => {
const notifySharedSubscribers = (key: UseSharedCacheKey) => {
const subscribers = mockSharedSubscribers.get(key)
if (subscribers) {
subscribers.forEach(callback => {
subscribers.forEach((callback) => {
try {
callback()
} catch (error) {
@ -67,7 +67,7 @@ const notifySharedSubscribers = (key: UseSharedCacheKey) => {
const notifyPersistSubscribers = (key: RendererPersistCacheKey) => {
const subscribers = mockPersistSubscribers.get(key)
if (subscribers) {
subscribers.forEach(callback => {
subscribers.forEach((callback) => {
try {
callback()
} catch (error) {
@ -80,77 +80,83 @@ const notifyPersistSubscribers = (key: RendererPersistCacheKey) => {
/**
* Mock useCache hook (memory cache)
*/
export const mockUseCache = vi.fn(<K extends UseCacheKey>(
key: K,
initValue?: UseCacheSchema[K]
): [UseCacheSchema[K], (value: UseCacheSchema[K]) => void] => {
// Get current value
let currentValue = mockMemoryCache.get(key)
if (currentValue === undefined) {
currentValue = initValue ?? DefaultUseCache[key]
if (currentValue !== undefined) {
mockMemoryCache.set(key, currentValue)
export const mockUseCache = vi.fn(
<K extends UseCacheKey>(
key: K,
initValue?: UseCacheSchema[K]
): [UseCacheSchema[K], (value: UseCacheSchema[K]) => void] => {
// Get current value
let currentValue = mockMemoryCache.get(key)
if (currentValue === undefined) {
currentValue = initValue ?? DefaultUseCache[key]
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)
*/
export const mockUseSharedCache = vi.fn(<K extends UseSharedCacheKey>(
key: K,
initValue?: UseSharedCacheSchema[K]
): [UseSharedCacheSchema[K], (value: UseSharedCacheSchema[K]) => void] => {
// Get current value
let currentValue = mockSharedCache.get(key)
if (currentValue === undefined) {
currentValue = initValue ?? DefaultUseSharedCache[key]
if (currentValue !== undefined) {
mockSharedCache.set(key, currentValue)
export const mockUseSharedCache = vi.fn(
<K extends UseSharedCacheKey>(
key: K,
initValue?: UseSharedCacheSchema[K]
): [UseSharedCacheSchema[K], (value: UseSharedCacheSchema[K]) => void] => {
// Get current value
let currentValue = mockSharedCache.get(key)
if (currentValue === undefined) {
currentValue = initValue ?? DefaultUseSharedCache[key]
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)
*/
export const mockUsePersistCache = vi.fn(<K extends RendererPersistCacheKey>(
key: K,
initValue?: RendererPersistCacheSchema[K]
): [RendererPersistCacheSchema[K], (value: RendererPersistCacheSchema[K]) => void] => {
// Get current value
let currentValue = mockPersistCache.get(key)
if (currentValue === undefined) {
currentValue = initValue ?? DefaultRendererPersistCache[key]
if (currentValue !== undefined) {
mockPersistCache.set(key, currentValue)
export const mockUsePersistCache = vi.fn(
<K extends RendererPersistCacheKey>(
key: K,
initValue?: RendererPersistCacheSchema[K]
): [RendererPersistCacheSchema[K], (value: RendererPersistCacheSchema[K]) => void] => {
// Get current value
let currentValue = mockPersistCache.get(key)
if (currentValue === undefined) {
currentValue = initValue ?? DefaultRendererPersistCache[key]
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
@ -292,18 +298,12 @@ export const MockUseCacheUtils = {
) => {
mockUseCache.mockImplementation((cacheKey, initValue) => {
if (cacheKey === key) {
return [
value,
setValue || vi.fn()
]
return [value, setValue || vi.fn()]
}
// Default behavior for other keys
const defaultValue = mockMemoryCache.get(cacheKey) ?? initValue ?? DefaultUseCache[cacheKey]
return [
defaultValue,
vi.fn()
]
return [defaultValue, vi.fn()]
})
},
@ -317,18 +317,12 @@ export const MockUseCacheUtils = {
) => {
mockUseSharedCache.mockImplementation((cacheKey, initValue) => {
if (cacheKey === key) {
return [
value,
setValue || vi.fn()
]
return [value, setValue || vi.fn()]
}
// Default behavior for other keys
const defaultValue = mockSharedCache.get(cacheKey) ?? initValue ?? DefaultUseSharedCache[cacheKey]
return [
defaultValue,
vi.fn()
]
return [defaultValue, vi.fn()]
})
},
@ -342,18 +336,12 @@ export const MockUseCacheUtils = {
) => {
mockUsePersistCache.mockImplementation((cacheKey, initValue) => {
if (cacheKey === key) {
return [
value,
setValue || vi.fn()
]
return [value, setValue || vi.fn()]
}
// Default behavior for other keys
const defaultValue = mockPersistCache.get(cacheKey) ?? initValue ?? DefaultRendererPersistCache[cacheKey]
return [
defaultValue,
vi.fn()
]
return [defaultValue, vi.fn()]
})
},
@ -425,4 +413,4 @@ export const MockUseCacheUtils = {
shared: Array.from(mockSharedSubscribers.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
*/
export const mockUseQuery = vi.fn(<TPath extends ConcreteApiPaths>(
path: TPath | null,
query?: any,
options?: any
): MockSWRResponse<any> => {
const isLoading = options?.initialLoading ?? false
const hasError = options?.shouldError ?? false
export const mockUseQuery = vi.fn(
<TPath extends ConcreteApiPaths>(path: TPath | null, _query?: any, options?: any): MockSWRResponse<any> => {
const isLoading = options?.initialLoading ?? false
const hasError = options?.shouldError ?? false
if (hasError) {
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 {
data: undefined,
error: new Error(`Mock error for ${path}`),
isLoading: false,
data: mockData,
error: undefined,
isLoading,
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
*/
export const mockUseMutation = vi.fn(<TPath extends ConcreteApiPaths, TMethod extends 'POST' | 'PUT' | 'DELETE' | 'PATCH'>(
path: TPath,
method: TMethod,
options?: any
): MockMutationResponse<any> => {
const isMutating = options?.initialMutating ?? false
const hasError = options?.shouldError ?? false
export const mockUseMutation = vi.fn(
<TPath extends ConcreteApiPaths, TMethod extends 'POST' | 'PUT' | 'DELETE' | 'PATCH'>(
path: TPath,
method: TMethod,
options?: any
): MockMutationResponse<any> => {
const isMutating = options?.initialMutating ?? false
const hasError = options?.shouldError ?? false
const mockTrigger = vi.fn(async (...args: any[]) => {
if (hasError) {
throw new Error(`Mock mutation error for ${method} ${path}`)
const mockTrigger = vi.fn(async (...args: any[]) => {
if (hasError) {
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
*/
export const mockUsePaginatedQuery = vi.fn(<TPath extends ConcreteApiPaths>(
path: TPath | null,
query?: any,
options?: any
): MockPaginatedResponse<any> => {
const isLoading = options?.initialLoading ?? false
const isLoadingMore = options?.initialLoadingMore ?? false
const hasError = options?.shouldError ?? false
export const mockUsePaginatedQuery = vi.fn(
<TPath extends ConcreteApiPaths>(path: TPath | null, _query?: any, options?: any): MockPaginatedResponse<any> => {
const isLoading = options?.initialLoading ?? false
const isLoadingMore = options?.initialLoadingMore ?? false
const hasError = options?.shouldError ?? false
if (hasError) {
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 {
data: undefined,
error: new Error(`Mock paginated error for ${path}`),
isLoading: false,
data: mockData,
error: undefined,
isLoading,
isValidating: false,
mutate: vi.fn().mockResolvedValue(undefined),
mutate: vi.fn().mockResolvedValue(mockData),
loadMore: vi.fn(),
isLoadingMore: false,
hasMore: false,
items: []
isLoadingMore,
hasMore: mockData.hasNext,
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
*/
export const mockUseInvalidateCache = vi.fn(() => {
return {
invalidate: vi.fn(async (path?: ConcreteApiPaths) => {
invalidate: vi.fn(async () => {
// Mock cache invalidation
return Promise.resolve()
}),
@ -211,13 +212,9 @@ export const mockUseInvalidateCache = vi.fn(() => {
/**
* Mock prefetch function
*/
export const mockPrefetch = vi.fn(async <TPath extends ConcreteApiPaths>(
path: TPath,
query?: any,
options?: any
): Promise<any> => {
export const mockPrefetch = vi.fn(async <TPath extends ConcreteApiPaths>(_path: TPath): Promise<any> => {
// Mock prefetch - return mock data
return createMockDataForPath(path)
return createMockDataForPath(_path)
})
/**
@ -261,13 +258,15 @@ export const MockUseDataApiUtils = {
}
}
// Default behavior for other paths
return mockUseQuery.getMockImplementation()?.(queryPath, query, options) || {
data: undefined,
error: undefined,
isLoading: false,
isValidating: false,
mutate: vi.fn().mockResolvedValue(undefined)
}
return (
mockUseQuery.getMockImplementation()?.(queryPath, query, options) || {
data: undefined,
error: undefined,
isLoading: false,
isValidating: false,
mutate: vi.fn().mockResolvedValue(undefined)
}
)
})
},
@ -285,13 +284,15 @@ export const MockUseDataApiUtils = {
mutate: vi.fn().mockResolvedValue(undefined)
}
}
return mockUseQuery.getMockImplementation()?.(queryPath, query, options) || {
data: undefined,
error: undefined,
isLoading: false,
isValidating: false,
mutate: vi.fn().mockResolvedValue(undefined)
}
return (
mockUseQuery.getMockImplementation()?.(queryPath, query, options) || {
data: undefined,
error: undefined,
isLoading: false,
isValidating: false,
mutate: vi.fn().mockResolvedValue(undefined)
}
)
})
},
@ -309,13 +310,15 @@ export const MockUseDataApiUtils = {
mutate: vi.fn().mockResolvedValue(undefined)
}
}
return mockUseQuery.getMockImplementation()?.(queryPath, query, options) || {
data: undefined,
error: undefined,
isLoading: false,
isValidating: false,
mutate: vi.fn().mockResolvedValue(undefined)
}
return (
mockUseQuery.getMockImplementation()?.(queryPath, query, options) || {
data: undefined,
error: undefined,
isLoading: false,
isValidating: false,
mutate: vi.fn().mockResolvedValue(undefined)
}
)
})
},
@ -333,13 +336,15 @@ export const MockUseDataApiUtils = {
reset: vi.fn()
}
}
return mockUseMutation.getMockImplementation()?.(mutationPath, mutationMethod, options) || {
data: undefined,
error: undefined,
isMutating: false,
trigger: vi.fn().mockResolvedValue({}),
reset: vi.fn()
}
return (
mockUseMutation.getMockImplementation()?.(mutationPath, mutationMethod, options) || {
data: undefined,
error: undefined,
isMutating: false,
trigger: vi.fn().mockResolvedValue({}),
reset: vi.fn()
}
)
})
},
@ -357,13 +362,15 @@ export const MockUseDataApiUtils = {
reset: vi.fn()
}
}
return mockUseMutation.getMockImplementation()?.(mutationPath, mutationMethod, options) || {
data: undefined,
error: undefined,
isMutating: false,
trigger: vi.fn().mockResolvedValue({}),
reset: vi.fn()
}
return (
mockUseMutation.getMockImplementation()?.(mutationPath, mutationMethod, options) || {
data: undefined,
error: undefined,
isMutating: false,
trigger: vi.fn().mockResolvedValue({}),
reset: vi.fn()
}
)
})
}
}
}

View File

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