From 13311a46ea31c424615f379d0ff2f80cada978cf Mon Sep 17 00:00:00 2001 From: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Date: Fri, 30 Jan 2026 03:38:06 +0100 Subject: [PATCH] Road to No explicit any: Group 8 (part 7) test files (#8459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR removes unsafe type assertions ("as unknown as Type") from test files and improves type safety across the codebase. ### Key Changes #### Type Safety Improvements - Removed improper `as unknown as Type` patterns from 17 test files in Group 8 part 7 - Replaced with proper TypeScript patterns using factory functions and Mock types - Fixed createTestingPinia usage in test files (was incorrectly using createPinia) - Fixed vi.hoisted pattern for mockSetDirty in viewport tests - Fixed vi.doMock lint issues with vi.mock and vi.hoisted pattern - Retained necessary `as unknown as` casts only for complex mock objects where direct type assertions would fail ### Files Changed Test files (Group 8 part 7 - services, stores, utils): - src/services/nodeOrganizationService.test.ts - src/services/providers/algoliaSearchProvider.test.ts - src/services/providers/registrySearchProvider.test.ts - src/stores/comfyRegistryStore.test.ts - src/stores/domWidgetStore.test.ts - src/stores/executionStore.test.ts - src/stores/firebaseAuthStore.test.ts - src/stores/modelToNodeStore.test.ts - src/stores/queueStore.test.ts - src/stores/subgraphNavigationStore.test.ts - src/stores/subgraphNavigationStore.viewport.test.ts - src/stores/subgraphStore.test.ts - src/stores/systemStatsStore.test.ts - src/stores/workspace/nodeHelpStore.test.ts - src/utils/colorUtil.test.ts - src/utils/executableGroupNodeChildDTO.test.ts Source files: - src/stores/modelStore.ts - Improved type handling ### Testing - All TypeScript type checking passes (`pnpm typecheck`) - All affected test files pass (`pnpm test:unit`) - Linting passes without errors (`pnpm lint`) - Code formatting applied (`pnpm format`) Part of the "Road to No Explicit Any" initiative, cleaning up type casting issues from branch `fix/remove-any-types-part8`. ### Previous PRs in this series: - Part 2: #7401 - Part 3: #7935 - Part 4: #7970 - Part 5: #8064 - Part 6: #8083 - Part 7: #8092 - Part 8 Group 1: #8253 - Part 8 Group 2: #8258 - Part 8 Group 3: #8304 - Part 8 Group 4: #8314 - Part 8 Group 5: #8329 - Part 8 Group 6: #8344 - Part 8 Group 7: #8459 (this PR) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8459-Road-to-No-explicit-any-Group-8-part-7-test-files-2f86d73d36508114ad28d82e72a3a5e9) by [Unito](https://www.unito.io) --- src/services/nodeOrganizationService.test.ts | 4 +- .../providers/algoliaSearchProvider.test.ts | 55 ++++++++--- .../providers/registrySearchProvider.test.ts | 31 +++++-- src/stores/comfyRegistryStore.test.ts | 6 +- src/stores/domWidgetStore.test.ts | 7 +- src/stores/executionStore.test.ts | 13 ++- src/stores/firebaseAuthStore.test.ts | 93 ++++++++++--------- src/stores/modelStore.ts | 11 ++- src/stores/modelToNodeStore.test.ts | 12 +-- src/stores/queueStore.test.ts | 8 +- src/stores/subgraphNavigationStore.test.ts | 48 ++++++---- .../subgraphNavigationStore.viewport.test.ts | 31 ++++--- src/stores/subgraphStore.test.ts | 19 +++- src/stores/systemStatsStore.test.ts | 11 ++- src/stores/workspace/nodeHelpStore.test.ts | 9 +- src/utils/colorUtil.test.ts | 8 +- src/utils/executableGroupNodeChildDTO.test.ts | 31 ++++--- 17 files changed, 247 insertions(+), 150 deletions(-) diff --git a/src/services/nodeOrganizationService.test.ts b/src/services/nodeOrganizationService.test.ts index dfc389b14..756440441 100644 --- a/src/services/nodeOrganizationService.test.ts +++ b/src/services/nodeOrganizationService.test.ts @@ -5,7 +5,7 @@ import { ComfyNodeDefImpl } from '@/stores/nodeDefStore' import { NodeSourceType } from '@/types/nodeSource' describe('nodeOrganizationService', () => { - const createMockNodeDef = (overrides: any = {}) => { + const createMockNodeDef = (overrides: Partial = {}) => { const mockNodeDef = { name: 'TestNode', display_name: 'Test Node', @@ -273,7 +273,7 @@ describe('nodeOrganizationService', () => { it('should handle unknown source type', () => { const nodeDef = createMockNodeDef({ nodeSource: { - type: 'unknown' as any, + type: 'unknown' as NodeSourceType, className: 'unknown', displayText: 'Unknown', badgeText: '?' diff --git a/src/services/providers/algoliaSearchProvider.test.ts b/src/services/providers/algoliaSearchProvider.test.ts index 39dfae348..7daffbb5a 100644 --- a/src/services/providers/algoliaSearchProvider.test.ts +++ b/src/services/providers/algoliaSearchProvider.test.ts @@ -1,20 +1,34 @@ +import type { Mock } from 'vitest' import { liteClient as algoliasearch } from 'algoliasearch/dist/lite/builds/browser' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { components } from '@/types/comfyRegistryTypes' import { useAlgoliaSearchProvider } from '@/services/providers/algoliaSearchProvider' import { SortableAlgoliaField } from '@/workbench/extensions/manager/types/comfyManagerTypes' +type RegistryNodePack = components['schemas']['Node'] + +type GlobalWithAlgolia = typeof globalThis & { + __ALGOLIA_APP_ID__: string + __ALGOLIA_API_KEY__: string +} + // Mock global Algolia constants -;(global as any).__ALGOLIA_APP_ID__ = 'test-app-id' -;(global as any).__ALGOLIA_API_KEY__ = 'test-api-key' +const globalWithAlgolia = globalThis as GlobalWithAlgolia +globalWithAlgolia.__ALGOLIA_APP_ID__ = 'test-app-id' +globalWithAlgolia.__ALGOLIA_API_KEY__ = 'test-api-key' // Mock algoliasearch vi.mock('algoliasearch/dist/lite/builds/browser', () => ({ liteClient: vi.fn() })) +interface MockSearchClient { + search: Mock +} + describe('useAlgoliaSearchProvider', () => { - let mockSearchClient: any + let mockSearchClient: MockSearchClient beforeEach(() => { vi.clearAllMocks() @@ -24,7 +38,11 @@ describe('useAlgoliaSearchProvider', () => { search: vi.fn() } - vi.mocked(algoliasearch).mockReturnValue(mockSearchClient) + vi.mocked(algoliasearch).mockReturnValue( + mockSearchClient as Partial< + ReturnType + > as ReturnType + ) }) afterEach(() => { @@ -252,7 +270,7 @@ describe('useAlgoliaSearchProvider', () => { }) describe('getSortValue', () => { - const testPack = { + const testPack: Partial = { id: '1', name: 'Test Pack', downloads: 100, @@ -279,7 +297,10 @@ describe('useAlgoliaSearchProvider', () => { const createdTimestamp = new Date('2024-01-01T10:00:00Z').getTime() expect( - provider.getSortValue(testPack as any, SortableAlgoliaField.Created) + provider.getSortValue( + testPack as RegistryNodePack, + SortableAlgoliaField.Created + ) ).toBe(createdTimestamp) const updatedTimestamp = new Date('2024-01-15T10:00:00Z').getTime() @@ -289,23 +310,35 @@ describe('useAlgoliaSearchProvider', () => { }) it('should handle missing values', () => { - const incompletePack = { id: '1', name: 'Incomplete' } + const incompletePack: Partial = { + id: '1', + name: 'Incomplete' + } const provider = useAlgoliaSearchProvider() expect( - provider.getSortValue(incompletePack, SortableAlgoliaField.Downloads) + provider.getSortValue( + incompletePack as RegistryNodePack, + SortableAlgoliaField.Downloads + ) ).toBe(0) expect( - provider.getSortValue(incompletePack, SortableAlgoliaField.Publisher) + provider.getSortValue( + incompletePack as RegistryNodePack, + SortableAlgoliaField.Publisher + ) ).toBe('') expect( provider.getSortValue( - incompletePack as any, + incompletePack as RegistryNodePack, SortableAlgoliaField.Created ) ).toBe(0) expect( - provider.getSortValue(incompletePack, SortableAlgoliaField.Updated) + provider.getSortValue( + incompletePack as RegistryNodePack, + SortableAlgoliaField.Updated + ) ).toBe(0) }) }) diff --git a/src/services/providers/registrySearchProvider.test.ts b/src/services/providers/registrySearchProvider.test.ts index b564bf7a7..db9c6a6e0 100644 --- a/src/services/providers/registrySearchProvider.test.ts +++ b/src/services/providers/registrySearchProvider.test.ts @@ -14,20 +14,31 @@ describe('useComfyRegistrySearchProvider', () => { const mockListAllPacksCall = vi.fn() const mockListAllPacksClear = vi.fn() + const createMockStore = ( + params: Partial> = {} + ) => { + return { + search: { + call: mockSearchCall, + clear: mockSearchClear, + cancel: vi.fn() + }, + listAllPacks: { + call: mockListAllPacksCall, + clear: mockListAllPacksClear, + cancel: vi.fn() + }, + ...params + } as Partial> as ReturnType< + typeof useComfyRegistryStore + > + } + beforeEach(() => { vi.clearAllMocks() // Setup store mock - vi.mocked(useComfyRegistryStore).mockReturnValue({ - search: { - call: mockSearchCall, - clear: mockSearchClear - }, - listAllPacks: { - call: mockListAllPacksCall, - clear: mockListAllPacksClear - } - } as any) + vi.mocked(useComfyRegistryStore).mockReturnValue(createMockStore()) }) describe('searchPacks', () => { diff --git a/src/stores/comfyRegistryStore.test.ts b/src/stores/comfyRegistryStore.test.ts index f1c1d76cf..6cd1f73d3 100644 --- a/src/stores/comfyRegistryStore.test.ts +++ b/src/stores/comfyRegistryStore.test.ts @@ -127,7 +127,9 @@ describe('useComfyRegistryStore', () => { } vi.mocked(useComfyRegistryService).mockReturnValue( - mockRegistryService as any + mockRegistryService as Partial< + ReturnType + > as ReturnType ) }) @@ -177,7 +179,7 @@ describe('useComfyRegistryStore', () => { const store = useComfyRegistryStore() vi.spyOn(store.getPackById, 'call').mockResolvedValueOnce(null) - const result = await store.getPackById.call(null as any) + const result = await store.getPackById.call(null!) expect(result).toBeNull() expect(mockRegistryService.getPackById).not.toHaveBeenCalled() diff --git a/src/stores/domWidgetStore.test.ts b/src/stores/domWidgetStore.test.ts index 24c2d47e6..100b8fe40 100644 --- a/src/stores/domWidgetStore.test.ts +++ b/src/stores/domWidgetStore.test.ts @@ -1,8 +1,9 @@ -import { createTestingPinia } from '@pinia/testing' import { setActivePinia } from 'pinia' import { beforeEach, describe, expect, it } from 'vitest' +import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { useDomWidgetStore } from '@/stores/domWidgetStore' +import { createTestingPinia } from '@pinia/testing' // Mock DOM widget for testing const createMockDOMWidget = (id: string) => { @@ -15,7 +16,7 @@ const createMockDOMWidget = (id: string) => { title: 'Test Node', pos: [0, 0], size: [200, 100] - } as any, + } as Partial as LGraphNode, name: 'test_widget', type: 'text', value: 'test', @@ -23,7 +24,7 @@ const createMockDOMWidget = (id: string) => { y: 0, margin: 10, isVisible: () => true, - containerNode: undefined as any + containerNode: undefined } } diff --git a/src/stores/executionStore.test.ts b/src/stores/executionStore.test.ts index f15c6a8dc..3850a161b 100644 --- a/src/stores/executionStore.test.ts +++ b/src/stores/executionStore.test.ts @@ -1,7 +1,5 @@ -import { createTestingPinia } from '@pinia/testing' import { setActivePinia } from 'pinia' import { beforeEach, describe, expect, it, vi } from 'vitest' - import { app } from '@/scripts/app' import { useExecutionStore } from '@/stores/executionStore' @@ -11,6 +9,8 @@ const mockNodeIdToNodeLocatorId = vi.fn() const mockNodeLocatorIdToNodeExecutionId = vi.fn() import type * as WorkflowStoreModule from '@/platform/workflow/management/stores/workflowStore' +import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils' +import { createTestingPinia } from '@pinia/testing' // Mock the workflowStore vi.mock('@/platform/workflow/management/stores/workflowStore', async () => { @@ -72,12 +72,11 @@ describe('useExecutionStore - NodeLocatorId conversions', () => { nodes: [] } - const mockNode = { + const mockNode = createMockLGraphNode({ id: 123, isSubgraphNode: () => true, subgraph: mockSubgraph - } as any - + }) // Mock app.rootGraph.getNodeById to return the mock node vi.mocked(app.rootGraph.getNodeById).mockReturnValue(mockNode) @@ -178,11 +177,11 @@ describe('useExecutionStore - Node Error Lookups', () => { nodes: [] } - const mockNode = { + const mockNode = createMockLGraphNode({ id: 123, isSubgraphNode: () => true, subgraph: mockSubgraph - } as any + }) vi.mocked(app.rootGraph.getNodeById).mockReturnValue(mockNode) diff --git a/src/stores/firebaseAuthStore.test.ts b/src/stores/firebaseAuthStore.test.ts index 47ed75c25..34beee00d 100644 --- a/src/stores/firebaseAuthStore.test.ts +++ b/src/stores/firebaseAuthStore.test.ts @@ -1,12 +1,28 @@ import { FirebaseError } from 'firebase/app' +import type { User, UserCredential } from 'firebase/auth' import * as firebaseAuth from 'firebase/auth' -import { createTestingPinia } from '@pinia/testing' import { setActivePinia } from 'pinia' +import type { Mock } from 'vitest' import { beforeEach, describe, expect, it, vi } from 'vitest' import * as vuefire from 'vuefire' import { useDialogService } from '@/services/dialogService' import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore' +import { createTestingPinia } from '@pinia/testing' + +// Hoisted mocks for dynamic imports +const { mockDistributionTypes } = vi.hoisted(() => ({ + mockDistributionTypes: { + isCloud: true, + isDesktop: true + } +})) + +type MockUser = Omit & { + getIdToken: Mock +} + +type MockAuth = Record // Mock fetch const mockFetch = vi.fn() @@ -83,35 +99,20 @@ vi.mock('@/stores/toastStore', () => ({ // Mock useDialogService vi.mock('@/services/dialogService') -const mockDistributionTypes = vi.hoisted(() => ({ - isCloud: false, - isDesktop: false -})) - -vi.mock('@/platform/distribution/types', () => mockDistributionTypes) - -const mockApiKeyStore = vi.hoisted(() => ({ - getAuthHeader: vi.fn().mockReturnValue(null) -})) - -vi.mock('@/stores/apiKeyAuthStore', () => ({ - useApiKeyAuthStore: () => mockApiKeyStore -})) - describe('useFirebaseAuthStore', () => { let store: ReturnType - let authStateCallback: (user: any) => void - let idTokenCallback: (user: any) => void + let authStateCallback: (user: User | null) => void + let idTokenCallback: (user: User | null) => void - const mockAuth = { + const mockAuth: MockAuth = { /* mock Auth object */ } - const mockUser = { + const mockUser: MockUser = { uid: 'test-user-id', email: 'test@example.com', getIdToken: vi.fn().mockResolvedValue('mock-id-token') - } + } as Partial as MockUser beforeEach(() => { vi.resetAllMocks() @@ -123,14 +124,18 @@ describe('useFirebaseAuthStore', () => { }) // Mock useFirebaseAuth to return our mock auth object - vi.mocked(vuefire.useFirebaseAuth).mockReturnValue(mockAuth as any) + vi.mocked(vuefire.useFirebaseAuth).mockReturnValue( + mockAuth as Partial< + ReturnType + > as ReturnType + ) // Mock onAuthStateChanged to capture the callback and simulate initial auth state vi.mocked(firebaseAuth.onAuthStateChanged).mockImplementation( (_, callback) => { - authStateCallback = callback as (user: any) => void + authStateCallback = callback as (user: User | null) => void // Call the callback with our mock user - ;(callback as (user: any) => void)(mockUser) + ;(callback as (user: User | null) => void)(mockUser) // Return an unsubscribe function return vi.fn() } @@ -163,21 +168,26 @@ describe('useFirebaseAuthStore', () => { }) describe('token refresh events', () => { - beforeEach(() => { - mockDistributionTypes.isCloud = true - mockDistributionTypes.isDesktop = true + beforeEach(async () => { + vi.resetModules() + vi.mock('@/platform/distribution/types', () => mockDistributionTypes) vi.mocked(firebaseAuth.onIdTokenChanged).mockImplementation( (_auth, callback) => { - idTokenCallback = callback as (user: any) => void + idTokenCallback = callback as (user: User | null) => void return vi.fn() } ) - vi.mocked(vuefire.useFirebaseAuth).mockReturnValue(mockAuth as any) + vi.mocked(vuefire.useFirebaseAuth).mockReturnValue( + mockAuth as Partial< + ReturnType + > as ReturnType + ) setActivePinia(createTestingPinia({ stubActions: false })) - store = useFirebaseAuthStore() + const storeModule = await import('@/stores/firebaseAuthStore') + store = storeModule.useFirebaseAuthStore() }) it("should not increment tokenRefreshTrigger on the user's first ID token event", () => { @@ -192,14 +202,14 @@ describe('useFirebaseAuthStore', () => { }) it('should not increment when ID token event is for a different user UID', () => { - const otherUser = { uid: 'other-user-id' } + const otherUser = { uid: 'other-user-id' } as Partial as User idTokenCallback?.(mockUser) idTokenCallback?.(otherUser) expect(store.tokenRefreshTrigger).toBe(0) }) it('should increment after switching to a new UID and receiving a second event for that UID', () => { - const otherUser = { uid: 'other-user-id' } + const otherUser = { uid: 'other-user-id' } as Partial as User idTokenCallback?.(mockUser) idTokenCallback?.(otherUser) idTokenCallback?.(otherUser) @@ -238,7 +248,7 @@ describe('useFirebaseAuthStore', () => { // Now, succeed on next attempt vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockResolvedValueOnce({ user: mockUser - } as any) + } as Partial as UserCredential) await store.login('test@example.com', 'correct-password') }) @@ -247,7 +257,7 @@ describe('useFirebaseAuthStore', () => { it('should login with valid credentials', async () => { const mockUserCredential = { user: mockUser } vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockResolvedValue( - mockUserCredential as any + mockUserCredential as Partial as UserCredential ) const result = await store.login('test@example.com', 'password') @@ -283,7 +293,7 @@ describe('useFirebaseAuthStore', () => { // Set up multiple login promises const mockUserCredential = { user: mockUser } vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockResolvedValue( - mockUserCredential as any + mockUserCredential as Partial as UserCredential ) const loginPromise1 = store.login('user1@example.com', 'password1') @@ -301,7 +311,7 @@ describe('useFirebaseAuthStore', () => { it('should register a new user', async () => { const mockUserCredential = { user: mockUser } vi.mocked(firebaseAuth.createUserWithEmailAndPassword).mockResolvedValue( - mockUserCredential as any + mockUserCredential as Partial as UserCredential ) const result = await store.register('new@example.com', 'password') @@ -378,7 +388,7 @@ describe('useFirebaseAuthStore', () => { // Setup mock for login const mockUserCredential = { user: mockUser } vi.mocked(firebaseAuth.signInWithEmailAndPassword).mockResolvedValue( - mockUserCredential as any + mockUserCredential as Partial as UserCredential ) // Login @@ -454,9 +464,6 @@ describe('useFirebaseAuthStore', () => { // This test reproduces the issue where getAuthHeader fails due to network errors // when Firebase Auth tries to refresh tokens offline - // Configure mockApiKeyStore to return null (no API key fallback) - mockApiKeyStore.getAuthHeader.mockReturnValue(null) - // Setup user with network error on token refresh mockUser.getIdToken.mockReset() const networkError = new FirebaseError( @@ -475,7 +482,7 @@ describe('useFirebaseAuthStore', () => { it('should sign in with Google', async () => { const mockUserCredential = { user: mockUser } vi.mocked(firebaseAuth.signInWithPopup).mockResolvedValue( - mockUserCredential as any + mockUserCredential as Partial as UserCredential ) const result = await store.loginWithGoogle() @@ -508,7 +515,7 @@ describe('useFirebaseAuthStore', () => { it('should sign in with Github', async () => { const mockUserCredential = { user: mockUser } vi.mocked(firebaseAuth.signInWithPopup).mockResolvedValue( - mockUserCredential as any + mockUserCredential as Partial as UserCredential ) const result = await store.loginWithGithub() @@ -540,7 +547,7 @@ describe('useFirebaseAuthStore', () => { it('should handle concurrent social login attempts correctly', async () => { const mockUserCredential = { user: mockUser } vi.mocked(firebaseAuth.signInWithPopup).mockResolvedValue( - mockUserCredential as any + mockUserCredential as Partial as UserCredential ) const googleLoginPromise = store.loginWithGoogle() diff --git a/src/stores/modelStore.ts b/src/stores/modelStore.ts index 16015a9d2..32fcec3b6 100644 --- a/src/stores/modelStore.ts +++ b/src/stores/modelStore.ts @@ -7,14 +7,19 @@ import { useSettingStore } from '@/platform/settings/settingStore' import { api } from '@/scripts/api' /** (Internal helper) finds a value in a metadata object from any of a list of keys. */ -function _findInMetadata(metadata: any, ...keys: string[]): string | null { +function _findInMetadata( + metadata: Record, + ...keys: string[] +): string | null { for (const key of keys) { if (key in metadata) { - return metadata[key] + const value = metadata[key] + return value || null } for (const k in metadata) { if (k.endsWith(key)) { - return metadata[k] + const value = metadata[k] + return value || null } } } diff --git a/src/stores/modelToNodeStore.test.ts b/src/stores/modelToNodeStore.test.ts index df93feca5..de57a5aef 100644 --- a/src/stores/modelToNodeStore.test.ts +++ b/src/stores/modelToNodeStore.test.ts @@ -239,7 +239,7 @@ describe('useModelToNodeStore', () => { it('should not register provider when nodeDef is undefined', () => { const modelToNodeStore = useModelToNodeStore() const providerWithoutNodeDef = new ModelNodeProvider( - undefined as any, + undefined!, 'custom_key' ) @@ -507,15 +507,11 @@ describe('useModelToNodeStore', () => { modelToNodeStore.registerDefaults() // These should not throw but return undefined + expect(modelToNodeStore.getCategoryForNodeType(null!)).toBeUndefined() expect( - modelToNodeStore.getCategoryForNodeType(null as any) - ).toBeUndefined() - expect( - modelToNodeStore.getCategoryForNodeType(undefined as any) - ).toBeUndefined() - expect( - modelToNodeStore.getCategoryForNodeType(123 as any) + modelToNodeStore.getCategoryForNodeType(undefined!) ).toBeUndefined() + expect(modelToNodeStore.getCategoryForNodeType('123')).toBeUndefined() }) it('should be case-sensitive for node type matching', () => { diff --git a/src/stores/queueStore.test.ts b/src/stores/queueStore.test.ts index 0f3e8c0f1..a183d3c54 100644 --- a/src/stores/queueStore.test.ts +++ b/src/stores/queueStore.test.ts @@ -38,7 +38,11 @@ function createHistoryJob(createTime: number, id: string): JobListItem { const createTaskOutput = ( nodeId: string = 'node-1', - images: any[] = [] + images: { + type?: 'output' | 'input' | 'temp' + filename?: string + subfolder?: string + }[] = [] ): TaskOutput => ({ [nodeId]: { images @@ -490,7 +494,7 @@ describe('useQueueStore', () => { it('should recreate TaskItemImpl when outputs_count changes', async () => { // Initial load without outputs_count const jobWithoutOutputsCount = createHistoryJob(10, 'job-1') - delete (jobWithoutOutputsCount as any).outputs_count + delete jobWithoutOutputsCount.outputs_count mockGetQueue.mockResolvedValue({ Running: [], Pending: [] }) mockGetHistory.mockResolvedValue([jobWithoutOutputsCount]) diff --git a/src/stores/subgraphNavigationStore.test.ts b/src/stores/subgraphNavigationStore.test.ts index bf38a0324..6741778dc 100644 --- a/src/stores/subgraphNavigationStore.test.ts +++ b/src/stores/subgraphNavigationStore.test.ts @@ -7,7 +7,9 @@ import { useWorkflowStore } from '@/platform/workflow/management/stores/workflow import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore' import { app } from '@/scripts/app' import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore' -import { findSubgraphPathById } from '@/utils/graphTraversalUtil' +import { createMockChangeTracker } from '@/utils/__tests__/litegraphTestUtils' + +import type { Subgraph } from '@/lib/litegraph/src/LGraph' vi.mock('@/scripts/app', () => { const mockCanvas = { @@ -38,7 +40,7 @@ vi.mock('@/scripts/app', () => { vi.mock('@/renderer/core/canvas/canvasStore', () => ({ useCanvasStore: () => ({ - getCanvas: () => (app as any).canvas + getCanvas: () => app.canvas }) })) @@ -63,7 +65,8 @@ describe('useSubgraphNavigationStore', () => { } as ComfyWorkflow // Set the active workflow (cast to bypass TypeScript check in test) - workflowStore.activeWorkflow = mockWorkflow as any + workflowStore.activeWorkflow = + mockWorkflow as typeof workflowStore.activeWorkflow // Simulate being in a subgraph by restoring state navigationStore.restoreState(['subgraph-1', 'subgraph-2']) @@ -72,7 +75,9 @@ describe('useSubgraphNavigationStore', () => { // Simulate a change to the workflow's internal state // (e.g., changeTracker.activeState being reassigned) - mockWorkflow.changeTracker = { activeState: {} } as any + mockWorkflow.changeTracker = { + activeState: {} + } as typeof mockWorkflow.changeTracker // The navigation stack should NOT be cleared because the path hasn't changed expect(navigationStore.exportState()).toHaveLength(2) @@ -87,14 +92,15 @@ describe('useSubgraphNavigationStore', () => { const workflow1 = { path: 'workflow1.json', filename: 'workflow1.json', - changeTracker: { + changeTracker: createMockChangeTracker({ restore: vi.fn(), store: vi.fn() - } - } as unknown as ComfyWorkflow + }) + } as Partial as ComfyWorkflow // Set the active workflow - workflowStore.activeWorkflow = workflow1 as any + workflowStore.activeWorkflow = + workflow1 as typeof workflowStore.activeWorkflow // Simulate the restore process that happens when loading a workflow // Since subgraphState is private, we'll simulate the effect by directly restoring navigation @@ -108,13 +114,14 @@ describe('useSubgraphNavigationStore', () => { const workflow2 = { path: 'workflow2.json', filename: 'workflow2.json', - changeTracker: { + changeTracker: createMockChangeTracker({ restore: vi.fn(), store: vi.fn() - } - } as unknown as ComfyWorkflow + }) + } as Partial as ComfyWorkflow - workflowStore.activeWorkflow = workflow2 as any + workflowStore.activeWorkflow = + workflow2 as typeof workflowStore.activeWorkflow // Simulate the restore process for workflow2 // Since subgraphState is private, we'll simulate the effect by directly restoring navigation @@ -124,7 +131,8 @@ describe('useSubgraphNavigationStore', () => { expect(navigationStore.exportState()).toHaveLength(0) // Switch back to workflow1 - workflowStore.activeWorkflow = workflow1 as any + workflowStore.activeWorkflow = + workflow1 as typeof workflowStore.activeWorkflow // Simulate the restore process for workflow1 again // Since subgraphState is private, we'll simulate the effect by directly restoring navigation @@ -138,17 +146,18 @@ describe('useSubgraphNavigationStore', () => { it('should clear navigation when activeSubgraph becomes undefined', async () => { const navigationStore = useSubgraphNavigationStore() const workflowStore = useWorkflowStore() + const { findSubgraphPathById } = await import('@/utils/graphTraversalUtil') // Create mock subgraph and graph structure const mockSubgraph = { id: 'subgraph-1', - rootGraph: (app as any).graph, + rootGraph: app.graph, _nodes: [], nodes: [] - } + } as Partial as Subgraph // Add the subgraph to the graph's subgraphs map - ;(app as any).graph.subgraphs.set('subgraph-1', mockSubgraph) + app.graph.subgraphs.set('subgraph-1', mockSubgraph) // First set an active workflow const mockWorkflow = { @@ -156,13 +165,14 @@ describe('useSubgraphNavigationStore', () => { filename: 'test-workflow.json' } as ComfyWorkflow - workflowStore.activeWorkflow = mockWorkflow as any + workflowStore.activeWorkflow = + mockWorkflow as typeof workflowStore.activeWorkflow // Mock findSubgraphPathById to return the correct path vi.mocked(findSubgraphPathById).mockReturnValue(['subgraph-1']) // Set canvas.subgraph and trigger update to set activeSubgraph - ;(app as any).canvas.subgraph = mockSubgraph + app.canvas.subgraph = mockSubgraph workflowStore.updateActiveGraph() // Wait for Vue's reactivity to process the change @@ -173,7 +183,7 @@ describe('useSubgraphNavigationStore', () => { expect(navigationStore.exportState()).toEqual(['subgraph-1']) // Clear canvas.subgraph and trigger update (simulating navigating back to root) - ;(app as any).canvas.subgraph = null + app.canvas.subgraph = undefined workflowStore.updateActiveGraph() // Wait for Vue's reactivity to process the change diff --git a/src/stores/subgraphNavigationStore.viewport.test.ts b/src/stores/subgraphNavigationStore.viewport.test.ts index fe52138d3..2e7243f66 100644 --- a/src/stores/subgraphNavigationStore.viewport.test.ts +++ b/src/stores/subgraphNavigationStore.viewport.test.ts @@ -3,11 +3,16 @@ import { setActivePinia } from 'pinia' import { beforeEach, describe, expect, it, vi } from 'vitest' import { nextTick } from 'vue' +import type { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph' import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore' import { app } from '@/scripts/app' import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore' +const { mockSetDirty } = vi.hoisted(() => ({ + mockSetDirty: vi.fn() +})) + vi.mock('@/scripts/app', () => { const mockCanvas = { subgraph: null, @@ -19,7 +24,7 @@ vi.mock('@/scripts/app', () => { offset: [0, 0] } }, - setDirty: vi.fn() + setDirty: mockSetDirty } return { @@ -38,12 +43,12 @@ vi.mock('@/scripts/app', () => { // Mock canvasStore vi.mock('@/renderer/core/canvas/canvasStore', () => ({ useCanvasStore: () => ({ - getCanvas: () => (app as any).canvas + getCanvas: () => app.canvas }) })) // Get reference to mock canvas -const mockCanvas = app.canvas as any +const mockCanvas = app.canvas describe('useSubgraphNavigationStore - Viewport Persistence', () => { beforeEach(() => { @@ -53,7 +58,7 @@ describe('useSubgraphNavigationStore - Viewport Persistence', () => { mockCanvas.ds.offset = [0, 0] mockCanvas.ds.state.scale = 1 mockCanvas.ds.state.offset = [0, 0] - mockCanvas.setDirty.mockClear() + mockSetDirty.mockClear() }) describe('saveViewport', () => { @@ -99,7 +104,7 @@ describe('useSubgraphNavigationStore - Viewport Persistence', () => { // Mock being in a subgraph const mockSubgraph = { id: 'sub-456' } - workflowStore.activeSubgraph = mockSubgraph as any + workflowStore.activeSubgraph = mockSubgraph as Subgraph // Set viewport state mockCanvas.ds.state.scale = 3 @@ -159,7 +164,7 @@ describe('useSubgraphNavigationStore - Viewport Persistence', () => { // Reset canvas mockCanvas.ds.scale = 1 mockCanvas.ds.offset = [0, 0] - mockCanvas.setDirty.mockClear() + mockSetDirty.mockClear() // Try to restore non-existent viewport navigationStore.restoreViewport('non-existent') @@ -167,7 +172,7 @@ describe('useSubgraphNavigationStore - Viewport Persistence', () => { // Canvas should not change expect(mockCanvas.ds.scale).toBe(1) expect(mockCanvas.ds.offset).toEqual([0, 0]) - expect(mockCanvas.setDirty).not.toHaveBeenCalled() + expect(mockSetDirty).not.toHaveBeenCalled() }) }) @@ -182,7 +187,7 @@ describe('useSubgraphNavigationStore - Viewport Persistence', () => { nodes: [], subgraphs: new Map(), getNodeById: vi.fn() - } + } as Partial as LGraph const subgraph1 = { id: 'sub1', rootGraph: mockRootGraph, @@ -195,7 +200,7 @@ describe('useSubgraphNavigationStore - Viewport Persistence', () => { mockCanvas.ds.state.offset = [100, 100] // Navigate to subgraph - workflowStore.activeSubgraph = subgraph1 as any + workflowStore.activeSubgraph = subgraph1 as Partial as Subgraph await nextTick() // Root viewport should have been saved automatically @@ -240,10 +245,14 @@ describe('useSubgraphNavigationStore - Viewport Persistence', () => { const workflow1 = { path: 'workflow1.json' } as ComfyWorkflow const workflow2 = { path: 'workflow2.json' } as ComfyWorkflow - workflowStore.activeWorkflow = workflow1 as any + workflowStore.activeWorkflow = workflow1 as ReturnType< + typeof useWorkflowStore + >['activeWorkflow'] await nextTick() - workflowStore.activeWorkflow = workflow2 as any + workflowStore.activeWorkflow = workflow2 as ReturnType< + typeof useWorkflowStore + >['activeWorkflow'] await nextTick() // Cache should be preserved (LRU will manage memory) diff --git a/src/stores/subgraphStore.test.ts b/src/stores/subgraphStore.test.ts index a8381542b..ce94eca4a 100644 --- a/src/stores/subgraphStore.test.ts +++ b/src/stores/subgraphStore.test.ts @@ -1,9 +1,9 @@ -import { createTestingPinia } from '@pinia/testing' import { setActivePinia } from 'pinia' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema' import type { GlobalSubgraphData } from '@/scripts/api' +import type { ExportedSubgraph } from '@/lib/litegraph/src/types/serialisation' import { api } from '@/scripts/api' import { app as comfyApp } from '@/scripts/app' import { useLitegraphService } from '@/services/litegraphService' @@ -14,6 +14,7 @@ import { createTestSubgraph, createTestSubgraphNode } from '@/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers' +import { createTestingPinia } from '@pinia/testing' // Mock telemetry to break circular dependency (telemetry → workflowStore → app → telemetry) vi.mock('@/platform/telemetry', () => ({ @@ -96,10 +97,18 @@ describe('useSubgraphStore', () => { const graph = subgraphNode.graph graph.add(subgraphNode) vi.mocked(comfyApp.canvas).selectedItems = new Set([subgraphNode]) - vi.mocked(comfyApp.canvas)._serializeItems = vi.fn(() => ({ - nodes: [subgraphNode.serialize()], - subgraphs: [subgraph.serialize() as any] - })) + vi.mocked(comfyApp.canvas)._serializeItems = vi.fn(() => { + const serializedSubgraph = { + ...subgraph.serialize(), + links: [], + groups: [], + version: 1 + } as Partial as ExportedSubgraph + return { + nodes: [subgraphNode.serialize()], + subgraphs: [serializedSubgraph] + } + }) //mock saving of file vi.mocked(api.storeUserData).mockResolvedValue({ status: 200, diff --git a/src/stores/systemStatsStore.test.ts b/src/stores/systemStatsStore.test.ts index a6f070481..1a1db69fa 100644 --- a/src/stores/systemStatsStore.test.ts +++ b/src/stores/systemStatsStore.test.ts @@ -2,6 +2,7 @@ import { createTestingPinia } from '@pinia/testing' import { setActivePinia } from 'pinia' import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { SystemStats } from '@/schemas/apiSchema' import { api } from '@/scripts/api' import { useSystemStatsStore } from '@/stores/systemStatsStore' import { isElectron } from '@/utils/envUtil' @@ -25,7 +26,7 @@ describe('useSystemStatsStore', () => { beforeEach(() => { // Mock API to prevent automatic fetch on store creation - vi.mocked(api.getSystemStats).mockResolvedValue(null as any) + vi.mocked(api.getSystemStats).mockResolvedValue(null!) setActivePinia(createTestingPinia({ stubActions: false })) store = useSystemStatsStore() vi.clearAllMocks() @@ -90,8 +91,8 @@ describe('useSystemStatsStore', () => { }) it('should set loading state correctly', async () => { - let resolvePromise: (value: any) => void = () => {} - const promise = new Promise((resolve) => { + let resolvePromise: (value: SystemStats) => void = () => {} + const promise = new Promise((resolve) => { resolvePromise = resolve }) vi.mocked(api.getSystemStats).mockReturnValue(promise) @@ -99,7 +100,7 @@ describe('useSystemStatsStore', () => { const fetchPromise = store.refetchSystemStats() expect(store.isLoading).toBe(true) - resolvePromise({}) + resolvePromise({} as SystemStats) await fetchPromise expect(store.isLoading).toBe(false) @@ -153,7 +154,7 @@ describe('useSystemStatsStore', () => { argv: [], ram_total: 16000000000, ram_free: 8000000000 - } as any, + } as Partial as SystemStats['system'], devices: [] } diff --git a/src/stores/workspace/nodeHelpStore.test.ts b/src/stores/workspace/nodeHelpStore.test.ts index 365df0b74..edfb078c3 100644 --- a/src/stores/workspace/nodeHelpStore.test.ts +++ b/src/stores/workspace/nodeHelpStore.test.ts @@ -2,6 +2,7 @@ import { createTestingPinia } from '@pinia/testing' import { setActivePinia } from 'pinia' import { beforeEach, describe, expect, it } from 'vitest' +import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore' import { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore' describe('nodeHelpStore', () => { @@ -27,7 +28,9 @@ describe('nodeHelpStore', () => { it('should open help for a node', () => { const nodeHelpStore = useNodeHelpStore() - nodeHelpStore.openHelp(mockCoreNode as any) + nodeHelpStore.openHelp( + mockCoreNode as Partial as ComfyNodeDefImpl + ) expect(nodeHelpStore.currentHelpNode).toStrictEqual(mockCoreNode) expect(nodeHelpStore.isHelpOpen).toBe(true) @@ -36,7 +39,9 @@ describe('nodeHelpStore', () => { it('should close help', () => { const nodeHelpStore = useNodeHelpStore() - nodeHelpStore.openHelp(mockCoreNode as any) + nodeHelpStore.openHelp( + mockCoreNode as Partial as ComfyNodeDefImpl + ) expect(nodeHelpStore.isHelpOpen).toBe(true) nodeHelpStore.closeHelp() diff --git a/src/utils/colorUtil.test.ts b/src/utils/colorUtil.test.ts index 0f21d5d15..36d845897 100644 --- a/src/utils/colorUtil.test.ts +++ b/src/utils/colorUtil.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from 'vitest' +import type { ColorAdjustOptions } from '@/utils/colorUtil' import { adjustColor, hexToRgb, @@ -22,13 +23,16 @@ interface ColorTestCase { type ColorFormat = 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla' vi.mock('es-toolkit/compat', () => ({ - memoize: (fn: any) => fn + memoize: unknown>(fn: T) => fn })) const targetOpacity = 0.5 const targetLightness = 0.5 -const assertColorVariationsMatch = (variations: string[], adjustment: any) => { +const assertColorVariationsMatch = ( + variations: string[], + adjustment: ColorAdjustOptions +) => { for (let i = 0; i < variations.length - 1; i++) { expect(adjustColor(variations[i], adjustment)).toBe( adjustColor(variations[i + 1], adjustment) diff --git a/src/utils/executableGroupNodeChildDTO.test.ts b/src/utils/executableGroupNodeChildDTO.test.ts index 6b2fb3ffa..7665b7b33 100644 --- a/src/utils/executableGroupNodeChildDTO.test.ts +++ b/src/utils/executableGroupNodeChildDTO.test.ts @@ -7,6 +7,7 @@ import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { ExecutableGroupNodeChildDTO } from '@/utils/executableGroupNodeChildDTO' +import { createMockLGraphNode } from './__tests__/litegraphTestUtils' describe('ExecutableGroupNodeChildDTO', () => { let mockNode: LGraphNode @@ -16,18 +17,18 @@ describe('ExecutableGroupNodeChildDTO', () => { beforeEach(() => { // Create mock nodes - mockNode = { + mockNode = createMockLGraphNode({ id: '3', // Simple node ID for most tests graph: {}, getInputNode: vi.fn(), getInputLink: vi.fn(), inputs: [] - } as any + }) - mockInputNode = { + mockInputNode = createMockLGraphNode({ id: '1', graph: {} - } as any + }) // Create the nodesByExecutionId map mockNodesByExecutionId = new Map() @@ -38,7 +39,7 @@ describe('ExecutableGroupNodeChildDTO', () => { describe('resolveInput', () => { it('should resolve input from external node (node outside the group)', () => { // Setup: Group node child with ID '10:3' - const groupNodeChild = { + const groupNodeChild = createMockLGraphNode({ id: '10:3', graph: {}, getInputNode: vi.fn().mockReturnValue(mockInputNode), @@ -46,7 +47,7 @@ describe('ExecutableGroupNodeChildDTO', () => { origin_slot: 0 }), inputs: [] - } as any + }) // External node with ID '1' const externalNodeDto = { @@ -75,19 +76,19 @@ describe('ExecutableGroupNodeChildDTO', () => { it('should resolve input from internal node (node inside the same group)', () => { // Setup: Group node child with ID '10:3' - const groupNodeChild = { + const groupNodeChild = createMockLGraphNode({ id: '10:3', graph: {}, getInputNode: vi.fn(), getInputLink: vi.fn(), inputs: [] - } as any + }) // Internal node with ID '10:2' - const internalInputNode = { + const internalInputNode = createMockLGraphNode({ id: '10:2', graph: {} - } as LGraphNode + }) const internalNodeDto = { id: '2', @@ -97,10 +98,10 @@ describe('ExecutableGroupNodeChildDTO', () => { // Internal nodes are stored with just their index mockNodesByExecutionId.set('2', internalNodeDto) - groupNodeChild.getInputNode.mockReturnValue(internalInputNode) - groupNodeChild.getInputLink.mockReturnValue({ + vi.mocked(groupNodeChild.getInputNode).mockReturnValue(internalInputNode) + vi.mocked(groupNodeChild.getInputLink).mockReturnValue({ origin_slot: 1 - }) + } as ReturnType) const dto = new ExecutableGroupNodeChildDTO( groupNodeChild, @@ -172,7 +173,7 @@ describe('ExecutableGroupNodeChildDTO', () => { it('should throw error for group nodes inside subgraphs (unsupported)', () => { // Setup: Group node child inside a subgraph (execution ID has more than 2 segments) - const nestedGroupNode = { + const nestedGroupNode = createMockLGraphNode({ id: '1:2:3', // subgraph:groupnode:innernode graph: {}, getInputNode: vi.fn().mockReturnValue(mockInputNode), @@ -180,7 +181,7 @@ describe('ExecutableGroupNodeChildDTO', () => { origin_slot: 0 }), inputs: [] - } as any + }) // Create DTO with deeply nested path to simulate group node inside subgraph const dto = new ExecutableGroupNodeChildDTO(