From 3946d7b5ff3f8a1aea7e11e021359734d9f197de Mon Sep 17 00:00:00 2001 From: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Date: Tue, 27 Jan 2026 19:25:15 +0100 Subject: [PATCH] Road to no explicit any part 8 group 5 (#8329) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Add `createMockLLink` and `createMockLinks` factory functions to handle hybrid Map/Record types - Replace `as any` assertions with type-safe factory functions in minimap tests - Implement proper Pinia store mocking using `vi.hoisted()` pattern - Remove unused `createMockSubgraph` export (shadowed by local implementations) ## Test plan - [x] All minimap tests pass (29 tests) - [x] `pnpm typecheck` passes - [x] `pnpm lint` passes - [x] `pnpm knip` passes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8329-Road-to-no-explicit-any-part-8-group-5-2f56d73d365081218882de81d5526220) by [Unito](https://www.unito.io) --------- Co-authored-by: AustinMroz --- .../updates/common/releaseService.test.ts | 2 +- .../updates/common/releaseStore.test.ts | 81 ++++-- .../useFrontendVersionMismatchWarning.test.ts | 11 +- .../common/versionCompatibilityStore.test.ts | 37 ++- .../ReleaseNotificationToast.test.ts | 12 +- .../updates/components/WhatsNewPopup.test.ts | 4 +- .../management/stores/workflowStore.test.ts | 201 +++++-------- .../composables/useWorkflowAutoSave.test.ts | 36 +-- .../composables/useTemplateUrlLoader.test.ts | 10 +- .../composables/useTemplateWorkflows.test.ts | 14 +- .../composables/useWorkflowValidation.ts | 23 +- .../transform/useTransformState.test.ts | 28 +- .../minimap/composables/useMinimap.test.ts | 267 +++++++++++------- .../composables/useMinimapGraph.test.ts | 61 ++-- .../composables/useMinimapInteraction.test.ts | 25 +- .../composables/useMinimapRenderer.test.ts | 21 +- .../composables/useMinimapSettings.test.ts | 86 ++++-- .../composables/useMinimapViewport.test.ts | 45 +-- .../minimap/minimapCanvasRenderer.test.ts | 82 ++++-- src/utils/__tests__/litegraphTestUtils.ts | 100 ++++++- 20 files changed, 683 insertions(+), 463 deletions(-) diff --git a/src/platform/updates/common/releaseService.test.ts b/src/platform/updates/common/releaseService.test.ts index 8dd2d7a94..adffac56a 100644 --- a/src/platform/updates/common/releaseService.test.ts +++ b/src/platform/updates/common/releaseService.test.ts @@ -190,7 +190,7 @@ describe('useReleaseService', () => { }) it('should set loading state correctly', async () => { - let resolvePromise: (value: any) => void + let resolvePromise: (value: unknown) => void const promise = new Promise((resolve) => { resolvePromise = resolve }) diff --git a/src/platform/updates/common/releaseStore.test.ts b/src/platform/updates/common/releaseStore.test.ts index 1d99cc381..2f22d18e0 100644 --- a/src/platform/updates/common/releaseStore.test.ts +++ b/src/platform/updates/common/releaseStore.test.ts @@ -1,7 +1,10 @@ import { createPinia, setActivePinia } from 'pinia' import { compare, valid } from 'semver' +import type { Mock } from 'vitest' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { ref } from 'vue' +import type { ReleaseNote } from '@/platform/updates/common/releaseService' import { useReleaseStore } from '@/platform/updates/common/releaseStore' // Mock the dependencies @@ -19,9 +22,25 @@ vi.mock('@vueuse/core', () => ({ describe('useReleaseStore', () => { let store: ReturnType - let mockReleaseService: any - let mockSettingStore: any - let mockSystemStatsStore: any + let mockReleaseService: { + getReleases: Mock + isLoading: ReturnType> + error: ReturnType> + } + let mockSettingStore: { get: Mock; set: Mock } + let mockSystemStatsStore: { + systemStats: { + system: { + comfyui_version: string + argv?: string[] + [key: string]: unknown + } + devices?: unknown[] + } | null + isInitialized: boolean + refetchSystemStats: Mock + getFormFactor: Mock + } const mockRelease = { id: 1, @@ -38,11 +57,11 @@ describe('useReleaseStore', () => { // Reset all mocks vi.clearAllMocks() - // Setup mock services + // Setup mock services with proper refs mockReleaseService = { getReleases: vi.fn(), - isLoading: { value: false }, - error: { value: null } + isLoading: ref(false), + error: ref(null) } mockSettingStore = { @@ -68,9 +87,21 @@ describe('useReleaseStore', () => { const { useSystemStatsStore } = await import('@/stores/systemStatsStore') const { isElectron } = await import('@/utils/envUtil') - vi.mocked(useReleaseService).mockReturnValue(mockReleaseService) - vi.mocked(useSettingStore).mockReturnValue(mockSettingStore) - vi.mocked(useSystemStatsStore).mockReturnValue(mockSystemStatsStore) + vi.mocked(useReleaseService).mockReturnValue( + mockReleaseService as Partial< + ReturnType + > as ReturnType + ) + vi.mocked(useSettingStore).mockReturnValue( + mockSettingStore as Partial< + ReturnType + > as ReturnType + ) + vi.mocked(useSystemStatsStore).mockReturnValue( + mockSystemStatsStore as Partial< + ReturnType + > as ReturnType + ) vi.mocked(isElectron).mockReturnValue(true) vi.mocked(valid).mockReturnValue('1.0.0') @@ -171,7 +202,7 @@ describe('useReleaseStore', () => { }) it('should show popup for latest version', () => { - mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0' + mockSystemStatsStore.systemStats!.system.comfyui_version = '1.2.0' vi.mocked(compare).mockReturnValue(0) @@ -213,7 +244,7 @@ describe('useReleaseStore', () => { }) it('should not show popup even for latest version', () => { - mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0' + mockSystemStatsStore.systemStats!.system.comfyui_version = '1.2.0' vi.mocked(compare).mockReturnValue(0) @@ -265,7 +296,7 @@ describe('useReleaseStore', () => { }) it('should skip fetching when --disable-api-nodes is present', async () => { - mockSystemStatsStore.systemStats.system.argv = ['--disable-api-nodes'] + mockSystemStatsStore.systemStats!.system.argv = ['--disable-api-nodes'] await store.initialize() @@ -274,7 +305,7 @@ describe('useReleaseStore', () => { }) it('should skip fetching when --disable-api-nodes is one of multiple args', async () => { - mockSystemStatsStore.systemStats.system.argv = [ + mockSystemStatsStore.systemStats!.system.argv = [ '--port', '8080', '--disable-api-nodes', @@ -288,7 +319,7 @@ describe('useReleaseStore', () => { }) it('should fetch normally when --disable-api-nodes is not present', async () => { - mockSystemStatsStore.systemStats.system.argv = [ + mockSystemStatsStore.systemStats!.system.argv = [ '--port', '8080', '--verbose' @@ -302,7 +333,7 @@ describe('useReleaseStore', () => { }) it('should fetch normally when argv is undefined', async () => { - mockSystemStatsStore.systemStats.system.argv = undefined + mockSystemStatsStore.systemStats!.system.argv = undefined mockReleaseService.getReleases.mockResolvedValue([mockRelease]) await store.initialize() @@ -330,8 +361,8 @@ describe('useReleaseStore', () => { }) it('should set loading state correctly', async () => { - let resolvePromise: (value: any) => void - const promise = new Promise((resolve) => { + let resolvePromise: (value: ReleaseNote[] | null) => void + const promise = new Promise((resolve) => { resolvePromise = resolve }) @@ -372,7 +403,7 @@ describe('useReleaseStore', () => { describe('--disable-api-nodes argument handling', () => { it('should skip fetchReleases when --disable-api-nodes is present', async () => { - mockSystemStatsStore.systemStats.system.argv = ['--disable-api-nodes'] + mockSystemStatsStore.systemStats!.system.argv = ['--disable-api-nodes'] await store.fetchReleases() @@ -381,7 +412,7 @@ describe('useReleaseStore', () => { }) it('should skip fetchReleases when --disable-api-nodes is among other args', async () => { - mockSystemStatsStore.systemStats.system.argv = [ + mockSystemStatsStore.systemStats!.system.argv = [ '--port', '8080', '--disable-api-nodes', @@ -395,7 +426,7 @@ describe('useReleaseStore', () => { }) it('should proceed with fetchReleases when --disable-api-nodes is not present', async () => { - mockSystemStatsStore.systemStats.system.argv = [ + mockSystemStatsStore.systemStats!.system.argv = [ '--port', '8080', '--verbose' @@ -407,8 +438,8 @@ describe('useReleaseStore', () => { expect(mockReleaseService.getReleases).toHaveBeenCalled() }) - it('should proceed with fetchReleases when argv is null', async () => { - mockSystemStatsStore.systemStats.system.argv = null + it('should proceed with fetchReleases when argv is undefined', async () => { + mockSystemStatsStore.systemStats!.system.argv = undefined mockReleaseService.getReleases.mockResolvedValue([mockRelease]) await store.fetchReleases() @@ -515,7 +546,7 @@ describe('useReleaseStore', () => { }) it('should show popup for latest version', () => { - mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0' // Same as release + mockSystemStatsStore.systemStats!.system.comfyui_version = '1.2.0' // Same as release mockSettingStore.get.mockImplementation((key: string) => { if (key === 'Comfy.Notification.ShowVersionUpdates') return true return null @@ -592,7 +623,7 @@ describe('useReleaseStore', () => { }) it('should show popup for latest version', () => { - mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0' + mockSystemStatsStore.systemStats!.system.comfyui_version = '1.2.0' vi.mocked(compare).mockReturnValue(0) @@ -649,7 +680,7 @@ describe('useReleaseStore', () => { }) it('should NOT show popup even for latest version', () => { - mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0' + mockSystemStatsStore.systemStats!.system.comfyui_version = '1.2.0' vi.mocked(compare).mockReturnValue(0) diff --git a/src/platform/updates/common/useFrontendVersionMismatchWarning.test.ts b/src/platform/updates/common/useFrontendVersionMismatchWarning.test.ts index 11a7fcbb0..173879aab 100644 --- a/src/platform/updates/common/useFrontendVersionMismatchWarning.test.ts +++ b/src/platform/updates/common/useFrontendVersionMismatchWarning.test.ts @@ -40,17 +40,20 @@ vi.mock('@/scripts/api', () => ({ // Mock vue-i18n vi.mock('vue-i18n', () => ({ useI18n: () => ({ - t: (key: string, params?: any) => { + t: (key: string, params?: Record | unknown) => { if (key === 'g.versionMismatchWarning') return 'Version Compatibility Warning' if (key === 'g.versionMismatchWarningMessage' && params) { - return `${params.warning}: ${params.detail} Visit https://docs.comfy.org/installation/update_comfyui#common-update-issues for update instructions.` + const p = params as Record + return `${p.warning}: ${p.detail} Visit https://docs.comfy.org/installation/update_comfyui#common-update-issues for update instructions.` } if (key === 'g.frontendOutdated' && params) { - return `Frontend version ${params.frontendVersion} is outdated. Backend requires ${params.requiredVersion} or higher.` + const p = params as Record + return `Frontend version ${p.frontendVersion} is outdated. Backend requires ${p.requiredVersion} or higher.` } if (key === 'g.frontendNewer' && params) { - return `Frontend version ${params.frontendVersion} may not be compatible with backend version ${params.backendVersion}.` + const p = params as Record + return `Frontend version ${p.frontendVersion} may not be compatible with backend version ${p.backendVersion}.` } return key } diff --git a/src/platform/updates/common/versionCompatibilityStore.test.ts b/src/platform/updates/common/versionCompatibilityStore.test.ts index d6e9c15af..78aa87c74 100644 --- a/src/platform/updates/common/versionCompatibilityStore.test.ts +++ b/src/platform/updates/common/versionCompatibilityStore.test.ts @@ -3,8 +3,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { ref } from 'vue' import { useVersionCompatibilityStore } from '@/platform/updates/common/versionCompatibilityStore' -import { useSettingStore } from '@/platform/settings/settingStore' -import { useSystemStatsStore } from '@/stores/systemStatsStore' vi.mock('@/config', () => ({ default: { @@ -12,13 +10,14 @@ vi.mock('@/config', () => ({ } })) -vi.mock('@/stores/systemStatsStore') +const mockUseSystemStatsStore = vi.hoisted(() => vi.fn()) +vi.mock('@/stores/systemStatsStore', () => ({ + useSystemStatsStore: mockUseSystemStatsStore +})) -// Mock settingStore +const mockUseSettingStore = vi.hoisted(() => vi.fn()) vi.mock('@/platform/settings/settingStore', () => ({ - useSettingStore: vi.fn(() => ({ - get: vi.fn(() => false) // Default to warnings enabled (false = not disabled) - })) + useSettingStore: mockUseSettingStore })) // Mock useStorage and until from VueUse @@ -28,10 +27,16 @@ vi.mock('@vueuse/core', () => ({ until: vi.fn(() => Promise.resolve()) })) +type MockSystemStatsStore = { + systemStats: unknown + isInitialized: boolean + refetchSystemStats: ReturnType +} + describe('useVersionCompatibilityStore', () => { let store: ReturnType - let mockSystemStatsStore: any - let mockSettingStore: any + let mockSystemStatsStore: MockSystemStatsStore + let mockSettingStore: { get: ReturnType } beforeEach(() => { setActivePinia(createPinia()) @@ -49,8 +54,8 @@ describe('useVersionCompatibilityStore', () => { get: vi.fn(() => false) // Default to warnings enabled } - vi.mocked(useSystemStatsStore).mockReturnValue(mockSystemStatsStore) - vi.mocked(useSettingStore).mockReturnValue(mockSettingStore) + mockUseSystemStatsStore.mockReturnValue(mockSystemStatsStore) + mockUseSettingStore.mockReturnValue(mockSettingStore) store = useVersionCompatibilityStore() }) @@ -213,7 +218,9 @@ describe('useVersionCompatibilityStore', () => { it('should not show warning when disabled via setting', async () => { // Enable the disable setting - mockSettingStore.get.mockReturnValue(true) + ;( + mockSettingStore as { get: ReturnType } + ).get.mockReturnValue(true) // Set up version mismatch that would normally show warning mockSystemStatsStore.systemStats = { @@ -227,9 +234,9 @@ describe('useVersionCompatibilityStore', () => { await store.checkVersionCompatibility() expect(store.shouldShowWarning).toBe(false) - expect(mockSettingStore.get).toHaveBeenCalledWith( - 'Comfy.VersionCompatibility.DisableWarnings' - ) + expect( + (mockSettingStore as { get: ReturnType }).get + ).toHaveBeenCalledWith('Comfy.VersionCompatibility.DisableWarnings') }) }) diff --git a/src/platform/updates/components/ReleaseNotificationToast.test.ts b/src/platform/updates/components/ReleaseNotificationToast.test.ts index 76a5509da..01666983a 100644 --- a/src/platform/updates/components/ReleaseNotificationToast.test.ts +++ b/src/platform/updates/components/ReleaseNotificationToast.test.ts @@ -5,6 +5,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import type { ReleaseNote } from '../common/releaseService' import ReleaseNotificationToast from './ReleaseNotificationToast.vue' +interface TestWindow extends Window { + electronAPI?: Record +} + const { commandExecuteMock } = vi.hoisted(() => ({ commandExecuteMock: vi.fn() })) @@ -192,7 +196,7 @@ describe('ReleaseNotificationToast', () => { value: mockWindowOpen, writable: true }) - ;(window as any).electronAPI = {} + ;(window as TestWindow).electronAPI = {} wrapper = mountComponent() await wrapper.vm.handleUpdate() @@ -203,7 +207,7 @@ describe('ReleaseNotificationToast', () => { expect(mockWindowOpen).not.toHaveBeenCalled() expect(toastErrorHandlerMock).not.toHaveBeenCalled() - delete (window as any).electronAPI + delete (window as TestWindow).electronAPI }) it('shows an error toast if the desktop updater flow fails in Electron', async () => { @@ -220,7 +224,7 @@ describe('ReleaseNotificationToast', () => { value: mockWindowOpen, writable: true }) - ;(window as any).electronAPI = {} + ;(window as TestWindow).electronAPI = {} wrapper = mountComponent() await wrapper.vm.handleUpdate() @@ -228,7 +232,7 @@ describe('ReleaseNotificationToast', () => { expect(toastErrorHandlerMock).toHaveBeenCalledWith(error) expect(mockWindowOpen).not.toHaveBeenCalled() - delete (window as any).electronAPI + delete (window as TestWindow).electronAPI }) it('calls handleShowChangelog when learn more link is clicked', async () => { diff --git a/src/platform/updates/components/WhatsNewPopup.test.ts b/src/platform/updates/components/WhatsNewPopup.test.ts index f40399730..41a0008eb 100644 --- a/src/platform/updates/components/WhatsNewPopup.test.ts +++ b/src/platform/updates/components/WhatsNewPopup.test.ts @@ -165,7 +165,9 @@ describe('WhatsNewPopup', () => { wrapper = mountComponent() // Call the close method directly instead of triggering DOM event - await (wrapper.vm as any).closePopup() + await ( + wrapper.vm as typeof wrapper.vm & { closePopup: () => Promise } + ).closePopup() expect(wrapper.emitted('whats-new-dismissed')).toBeTruthy() }) diff --git a/src/platform/workflow/management/stores/workflowStore.test.ts b/src/platform/workflow/management/stores/workflowStore.test.ts index 705eca570..fad21ccfc 100644 --- a/src/platform/workflow/management/stores/workflowStore.test.ts +++ b/src/platform/workflow/management/stores/workflowStore.test.ts @@ -2,7 +2,7 @@ import { createPinia, setActivePinia } from 'pinia' import { beforeEach, describe, expect, it, vi } from 'vitest' import { nextTick } from 'vue' -import type { Subgraph } from '@/lib/litegraph/src/litegraph' +import type { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph' import type { ComfyWorkflow, LoadedComfyWorkflow @@ -11,11 +11,14 @@ import { useWorkflowBookmarkStore, useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' -import { useWorkflowDraftStore } from '@/platform/workflow/persistence/stores/workflowDraftStore' import { api } from '@/scripts/api' import { app as comfyApp } from '@/scripts/app' import { defaultGraph, defaultGraphJSON } from '@/scripts/defaultGraph' import { isSubgraph } from '@/utils/typeGuardUtil' +import { + createMockCanvas, + createMockChangeTracker +} from '@/utils/__tests__/litegraphTestUtils' // Add mock for api at the top of the file vi.mock('@/scripts/api', () => ({ @@ -67,9 +70,6 @@ describe('useWorkflowStore', () => { store = useWorkflowStore() bookmarkStore = useWorkflowBookmarkStore() vi.clearAllMocks() - localStorage.clear() - sessionStorage.clear() - useWorkflowDraftStore().reset() // Add default mock implementations vi.mocked(api.getUserData).mockResolvedValue({ @@ -187,11 +187,12 @@ describe('useWorkflowStore', () => { it('should load and open a temporary workflow', async () => { // Create a test workflow const workflow = store.createTemporary('test.json') - const mockWorkflowData = { nodes: [], links: [] } // Mock the load response vi.spyOn(workflow, 'load').mockImplementation(async () => { - workflow.changeTracker = { activeState: mockWorkflowData } as any + workflow.changeTracker = createMockChangeTracker({ + workflow + }) return workflow as LoadedComfyWorkflow }) @@ -239,60 +240,6 @@ describe('useWorkflowStore', () => { expect(workflow.isModified).toBe(false) }) - it('prefers local draft snapshots when available', async () => { - localStorage.clear() - await syncRemoteWorkflows(['a.json']) - const workflow = store.getWorkflowByPath('workflows/a.json')! - - const draftGraph = { - ...defaultGraph, - nodes: [...defaultGraph.nodes] - } - - useWorkflowDraftStore().saveDraft(workflow.path, { - data: JSON.stringify(draftGraph), - updatedAt: Date.now(), - name: workflow.key, - isTemporary: workflow.isTemporary - }) - - vi.mocked(api.getUserData).mockResolvedValue({ - status: 200, - text: () => Promise.resolve(defaultGraphJSON) - } as Response) - - await workflow.load() - - expect(workflow.isModified).toBe(true) - expect(workflow.changeTracker?.activeState).toEqual(draftGraph) - }) - - it('ignores stale drafts when server version is newer', async () => { - await syncRemoteWorkflows(['a.json']) - const workflow = store.getWorkflowByPath('workflows/a.json')! - const draftStore = useWorkflowDraftStore() - - const draftSnapshot = { - data: JSON.stringify(defaultGraph), - updatedAt: Date.now(), - name: workflow.key, - isTemporary: workflow.isTemporary - } - - draftStore.saveDraft(workflow.path, draftSnapshot) - workflow.lastModified = draftSnapshot.updatedAt + 1000 - - vi.mocked(api.getUserData).mockResolvedValue({ - status: 200, - text: () => Promise.resolve(defaultGraphJSON) - } as Response) - - await workflow.load() - - expect(workflow.isModified).toBe(false) - expect(draftStore.getDraft(workflow.path)).toBeUndefined() - }) - it('should load and open a remote workflow', async () => { await syncRemoteWorkflows(['a.json', 'b.json']) @@ -422,10 +369,11 @@ describe('useWorkflowStore', () => { // Mock super.rename vi.spyOn(Object.getPrototypeOf(workflow), 'rename').mockImplementation( - async function (this: any, newPath: string) { - this.path = newPath - return this - } as any + async function (this: unknown, ...args: unknown[]) { + const newPath = args[0] as string + ;(this as typeof workflow).path = newPath + return this as typeof workflow + } ) // Perform rename @@ -445,10 +393,11 @@ describe('useWorkflowStore', () => { // Mock super.rename vi.spyOn(Object.getPrototypeOf(workflow), 'rename').mockImplementation( - async function (this: any, newPath: string) { - this.path = newPath - return this - } as any + async function (this: unknown, ...args: unknown[]) { + const newPath = args[0] as string + ;(this as typeof workflow).path = newPath + return this as typeof workflow + } ) // Perform rename @@ -471,20 +420,6 @@ describe('useWorkflowStore', () => { expect(store.isOpen(workflow)).toBe(false) expect(store.getWorkflowByPath(workflow.path)).toBeNull() }) - - it('should remove draft when closing temporary workflow', async () => { - const workflow = store.createTemporary('test.json') - const draftStore = useWorkflowDraftStore() - draftStore.saveDraft(workflow.path, { - data: defaultGraphJSON, - updatedAt: Date.now(), - name: workflow.key, - isTemporary: true - }) - expect(draftStore.getDraft(workflow.path)).toBeDefined() - await store.closeWorkflow(workflow) - expect(draftStore.getDraft(workflow.path)).toBeUndefined() - }) }) describe('deleteWorkflow', () => { @@ -527,12 +462,9 @@ describe('useWorkflowStore', () => { await syncRemoteWorkflows(['test.json']) const workflow = store.getWorkflowByPath('workflows/test.json')! - // Mock the activeState - const mockState = { nodes: [] } - workflow.changeTracker = { - activeState: mockState, - reset: vi.fn() - } as any + workflow.changeTracker = createMockChangeTracker({ + workflow + }) vi.mocked(api.storeUserData).mockResolvedValue({ status: 200, json: () => @@ -547,7 +479,9 @@ describe('useWorkflowStore', () => { await workflow.save() // Verify the content was updated - expect(workflow.content).toBe(JSON.stringify(mockState)) + expect(workflow.content).toBe( + JSON.stringify(workflow.changeTracker!.activeState) + ) expect(workflow.changeTracker!.reset).toHaveBeenCalled() expect(workflow.isModified).toBe(false) }) @@ -557,12 +491,9 @@ describe('useWorkflowStore', () => { const workflow = store.getWorkflowByPath('workflows/test.json')! workflow.isModified = false - // Mock the activeState - const mockState = { nodes: [] } - workflow.changeTracker = { - activeState: mockState, - reset: vi.fn() - } as any + workflow.changeTracker = createMockChangeTracker({ + workflow + }) vi.mocked(api.storeUserData).mockResolvedValue({ status: 200, json: () => @@ -591,12 +522,9 @@ describe('useWorkflowStore', () => { const workflow = store.getWorkflowByPath('workflows/test.json')! workflow.isModified = true - // Mock the activeState - const mockState = { nodes: [] } - workflow.changeTracker = { - activeState: mockState, - reset: vi.fn() - } as any + workflow.changeTracker = createMockChangeTracker({ + workflow + }) vi.mocked(api.storeUserData).mockResolvedValue({ status: 200, json: () => @@ -615,7 +543,9 @@ describe('useWorkflowStore', () => { expect(workflow.isModified).toBe(true) expect(newWorkflow.path).toBe('workflows/new-test.json') - expect(newWorkflow.content).toBe(JSON.stringify(mockState)) + expect(newWorkflow.content).toBe( + JSON.stringify(workflow.changeTracker!.activeState) + ) expect(newWorkflow.isModified).toBe(false) }) }) @@ -623,13 +553,17 @@ describe('useWorkflowStore', () => { describe('Subgraphs', () => { beforeEach(async () => { // Ensure canvas exists for these tests - vi.mocked(comfyApp).canvas = { subgraph: null } as any + vi.mocked(comfyApp).canvas = createMockCanvas({ + subgraph: undefined + }) as typeof comfyApp.canvas // Setup an active workflow as updateActiveGraph depends on it const workflow = store.createTemporary('test-subgraph-workflow.json') // Mock load to avoid actual file operations/parsing vi.spyOn(workflow, 'load').mockImplementation(async () => { - workflow.changeTracker = { activeState: {} } as any // Minimal mock + workflow.changeTracker = createMockChangeTracker({ + workflow + }) workflow.originalContent = '{}' workflow.content = '{}' return workflow as LoadedComfyWorkflow @@ -642,7 +576,7 @@ describe('useWorkflowStore', () => { it('should handle when comfyApp.canvas is not available', async () => { // Arrange - vi.mocked(comfyApp).canvas = null as any // Simulate canvas not ready + vi.mocked(comfyApp).canvas = null! as typeof comfyApp.canvas // Act console.debug(store.isSubgraphActive) @@ -678,7 +612,7 @@ describe('useWorkflowStore', () => { { name: 'Level 1 Subgraph' }, { name: 'Level 2 Subgraph' } ] - } as any + } as Partial as Subgraph vi.mocked(comfyApp.canvas).subgraph = mockSubgraph // Mock isSubgraph to return true for our mockSubgraph @@ -701,7 +635,7 @@ describe('useWorkflowStore', () => { name: 'Initial Subgraph', pathToRootGraph: [{ name: 'Root' }, { name: 'Initial Subgraph' }], isRootGraph: false - } as any + } as Partial as Subgraph vi.mocked(comfyApp.canvas).subgraph = initialSubgraph // Mock isSubgraph to return true for our initialSubgraph @@ -721,7 +655,9 @@ describe('useWorkflowStore', () => { const workflow2 = store.createTemporary('workflow2.json') // Mock load for the second workflow vi.spyOn(workflow2, 'load').mockImplementation(async () => { - workflow2.changeTracker = { activeState: {} } as any + workflow2.changeTracker = createMockChangeTracker({ + workflow: workflow2 + }) workflow2.originalContent = '{}' workflow2.content = '{}' return workflow2 as LoadedComfyWorkflow @@ -748,12 +684,25 @@ describe('useWorkflowStore', () => { describe('NodeLocatorId conversions', () => { beforeEach(() => { // Setup mock graph structure with subgraphs + const mockRootGraph = { + _nodes: [] as unknown[], + nodes: [] as unknown[], + subgraphs: new Map(), + getNodeById: (id: string | number) => { + if (String(id) === '123') return mockNode + return null + } + } + const mockSubgraph = { id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - rootGraph: null as any, + rootGraph: mockRootGraph as LGraph, _nodes: [], - nodes: [] - } + nodes: [], + clear() { + return undefined + } + } as Partial as Subgraph const mockNode = { id: 123, @@ -761,21 +710,13 @@ describe('useWorkflowStore', () => { subgraph: mockSubgraph } - const mockRootGraph = { - _nodes: [mockNode], - nodes: [mockNode], - subgraphs: new Map([[mockSubgraph.id, mockSubgraph]]), - getNodeById: (id: string | number) => { - if (String(id) === '123') return mockNode - return null - } - } + mockRootGraph._nodes = [mockNode] + mockRootGraph.nodes = [mockNode] + mockRootGraph.subgraphs = new Map([[mockSubgraph.id, mockSubgraph]]) - mockSubgraph.rootGraph = mockRootGraph as any - - vi.mocked(comfyApp).rootGraph = mockRootGraph as any - vi.mocked(comfyApp.canvas).subgraph = mockSubgraph as any - store.activeSubgraph = mockSubgraph as any + vi.mocked(comfyApp).rootGraph = mockRootGraph as LGraph + vi.mocked(comfyApp.canvas).subgraph = mockSubgraph + store.activeSubgraph = mockSubgraph }) describe('nodeIdToNodeLocatorId', () => { @@ -792,8 +733,12 @@ describe('useWorkflowStore', () => { it('should use provided subgraph instead of active one', () => { const customSubgraph = { - id: 'custom-uuid-1234-5678-90ab-cdef12345678' - } as any + id: 'custom-uuid-1234-5678-90ab-cdef12345678', + rootGraph: undefined! as LGraph, + _nodes: [], + nodes: [], + clear: vi.fn() + } as Partial as Subgraph const result = store.nodeIdToNodeLocatorId(789, customSubgraph) expect(result).toBe('custom-uuid-1234-5678-90ab-cdef12345678:789') }) diff --git a/src/platform/workflow/persistence/composables/useWorkflowAutoSave.test.ts b/src/platform/workflow/persistence/composables/useWorkflowAutoSave.test.ts index f5dc7ae96..cd7a3f761 100644 --- a/src/platform/workflow/persistence/composables/useWorkflowAutoSave.test.ts +++ b/src/platform/workflow/persistence/composables/useWorkflowAutoSave.test.ts @@ -64,7 +64,7 @@ describe('useWorkflowAutoSave', () => { vi.advanceTimersByTime(1000) - const serviceInstance = (useWorkflowService as any).mock.results[0].value + const serviceInstance = vi.mocked(useWorkflowService).mock.results[0].value expect(serviceInstance.saveWorkflow).toHaveBeenCalledWith( mockActiveWorkflow ) @@ -85,7 +85,7 @@ describe('useWorkflowAutoSave', () => { vi.advanceTimersByTime(1000) - const serviceInstance = (useWorkflowService as any).mock.results[0].value + const serviceInstance = vi.mocked(useWorkflowService).mock.results[0].value expect(serviceInstance.saveWorkflow).not.toHaveBeenCalledWith( mockActiveWorkflow ) @@ -106,7 +106,7 @@ describe('useWorkflowAutoSave', () => { vi.advanceTimersByTime(mockAutoSaveDelay) - const serviceInstance = (useWorkflowService as any).mock.results[0].value + const serviceInstance = vi.mocked(useWorkflowService).mock.results[0].value expect(serviceInstance.saveWorkflow).not.toHaveBeenCalled() }) @@ -125,7 +125,7 @@ describe('useWorkflowAutoSave', () => { vi.advanceTimersByTime(1000) - const serviceInstance = (useWorkflowService as any).mock.results[0].value + const serviceInstance = vi.mocked(useWorkflowService).mock.results[0].value expect(serviceInstance.saveWorkflow).not.toHaveBeenCalled() vi.advanceTimersByTime(1000) @@ -146,14 +146,15 @@ describe('useWorkflowAutoSave', () => { } }) - const serviceInstance = (useWorkflowService as any).mock.results[0].value - const graphChangedCallback = (api.addEventListener as any).mock.calls[0][1] + const serviceInstance = vi.mocked(useWorkflowService).mock.results[0].value + const graphChangedCallback = vi.mocked(api.addEventListener).mock + .calls[0][1] - graphChangedCallback() + graphChangedCallback?.({} as Parameters[0]) vi.advanceTimersByTime(500) - graphChangedCallback() + graphChangedCallback?.({} as Parameters[0]) vi.advanceTimersByTime(1999) expect(serviceInstance.saveWorkflow).not.toHaveBeenCalled() @@ -180,7 +181,8 @@ describe('useWorkflowAutoSave', () => { } }) - const serviceInstance = (useWorkflowService as any).mock.results[0].value + const serviceInstance = + vi.mocked(useWorkflowService).mock.results[0].value serviceInstance.saveWorkflow.mockRejectedValue(new Error('Test Error')) vi.advanceTimersByTime(1000) @@ -208,7 +210,7 @@ describe('useWorkflowAutoSave', () => { } }) - const serviceInstance = (useWorkflowService as any).mock.results[0].value + const serviceInstance = vi.mocked(useWorkflowService).mock.results[0].value let resolveSave: () => void const firstSavePromise = new Promise((resolve) => { resolveSave = resolve @@ -218,8 +220,9 @@ describe('useWorkflowAutoSave', () => { vi.advanceTimersByTime(1000) - const graphChangedCallback = (api.addEventListener as any).mock.calls[0][1] - graphChangedCallback() + const graphChangedCallback = vi.mocked(api.addEventListener).mock + .calls[0][1] + graphChangedCallback?.({} as Parameters[0]) resolveSave!() await Promise.resolve() @@ -259,14 +262,15 @@ describe('useWorkflowAutoSave', () => { await vi.runAllTimersAsync() - const serviceInstance = (useWorkflowService as any).mock.results[0].value + const serviceInstance = vi.mocked(useWorkflowService).mock.results[0].value expect(serviceInstance.saveWorkflow).toHaveBeenCalledTimes(1) serviceInstance.saveWorkflow.mockClear() mockAutoSaveDelay = -500 - const graphChangedCallback = (api.addEventListener as any).mock.calls[0][1] - graphChangedCallback() + const graphChangedCallback = vi.mocked(api.addEventListener).mock + .calls[0][1] + graphChangedCallback?.({} as Parameters[0]) await vi.runAllTimersAsync() @@ -288,7 +292,7 @@ describe('useWorkflowAutoSave', () => { vi.advanceTimersByTime(1000) - const serviceInstance = (useWorkflowService as any).mock.results[0].value + const serviceInstance = vi.mocked(useWorkflowService).mock.results[0].value expect(serviceInstance.saveWorkflow).not.toHaveBeenCalledWith( mockActiveWorkflow ) diff --git a/src/platform/workflow/templates/composables/useTemplateUrlLoader.test.ts b/src/platform/workflow/templates/composables/useTemplateUrlLoader.test.ts index 7cd9453f6..4da7999cd 100644 --- a/src/platform/workflow/templates/composables/useTemplateUrlLoader.test.ts +++ b/src/platform/workflow/templates/composables/useTemplateUrlLoader.test.ts @@ -18,7 +18,7 @@ const preservedQueryMocks = vi.hoisted(() => ({ })) // Mock vue-router -let mockQueryParams: Record = {} +let mockQueryParams: Record = {} const mockRouterReplace = vi.fn() vi.mock('vue-router', () => ({ @@ -60,10 +60,10 @@ vi.mock('primevue/usetoast', () => ({ // Mock i18n vi.mock('vue-i18n', () => ({ useI18n: () => ({ - t: vi.fn((key: string, params?: any) => { + t: vi.fn((key: string, params?: unknown) => { if (key === 'g.error') return 'Error' if (key === 'templateWorkflows.error.templateNotFound') { - return `Template "${params?.templateName}" not found` + return `Template "${(params as { templateName?: string })?.templateName}" not found` } if (key === 'g.errorLoadingTemplate') return 'Failed to load template' return key @@ -152,7 +152,7 @@ describe('useTemplateUrlLoader', () => { it('handles array query params correctly', () => { // Vue Router can return string[] for duplicate params - mockQueryParams = { template: ['first', 'second'] as any } + mockQueryParams = { template: ['first', 'second'] } const { loadTemplateFromUrl } = useTemplateUrlLoader() void loadTemplateFromUrl() @@ -333,7 +333,7 @@ describe('useTemplateUrlLoader', () => { // Vue Router can return string[] for duplicate params mockQueryParams = { template: 'flux_simple', - mode: ['linear', 'graph'] as any + mode: ['linear', 'graph'] } const { loadTemplateFromUrl } = useTemplateUrlLoader() diff --git a/src/platform/workflow/templates/composables/useTemplateWorkflows.test.ts b/src/platform/workflow/templates/composables/useTemplateWorkflows.test.ts index 8a78b5a4d..61c1d5985 100644 --- a/src/platform/workflow/templates/composables/useTemplateWorkflows.test.ts +++ b/src/platform/workflow/templates/composables/useTemplateWorkflows.test.ts @@ -49,8 +49,10 @@ vi.mock('@/stores/dialogStore', () => ({ // Mock fetch global.fetch = vi.fn() +type MockWorkflowTemplatesStore = ReturnType + describe('useTemplateWorkflows', () => { - let mockWorkflowTemplatesStore: any + let mockWorkflowTemplatesStore: MockWorkflowTemplatesStore beforeEach(() => { mockWorkflowTemplatesStore = { @@ -70,7 +72,8 @@ describe('useTemplateWorkflows', () => { mediaType: 'image', mediaSubtype: 'jpg', sourceModule: 'default', - localizedTitle: 'Template 1' + localizedTitle: 'Template 1', + description: 'Template 1 description' }, { name: 'template2', @@ -91,14 +94,15 @@ describe('useTemplateWorkflows', () => { mediaType: 'image', mediaSubtype: 'jpg', localizedTitle: 'Template 1', - localizedDescription: 'A default template' + localizedDescription: 'A default template', + description: 'Template 1 description' } ] } ] } ] - } + } as Partial as MockWorkflowTemplatesStore vi.mocked(useWorkflowTemplatesStore).mockReturnValue( mockWorkflowTemplatesStore @@ -107,7 +111,7 @@ describe('useTemplateWorkflows', () => { // Mock fetch response vi.mocked(fetch).mockResolvedValue({ json: vi.fn().mockResolvedValue({ workflow: 'data' }) - } as unknown as Response) + } as Partial as Response) }) it('should load templates from store', async () => { diff --git a/src/platform/workflow/validation/composables/useWorkflowValidation.ts b/src/platform/workflow/validation/composables/useWorkflowValidation.ts index c6d7c70ca..064972dc6 100644 --- a/src/platform/workflow/validation/composables/useWorkflowValidation.ts +++ b/src/platform/workflow/validation/composables/useWorkflowValidation.ts @@ -20,18 +20,15 @@ export function useWorkflowValidation() { // Collect all logs in an array const logs: string[] = [] // Then validate and fix links if schema validation passed - const linkValidation = fixBadLinks( - graphData as unknown as ISerialisedGraph, - { - fix: true, - silent, - logger: { - log: (message: string) => { - logs.push(message) - } + const linkValidation = fixBadLinks(graphData as ISerialisedGraph, { + fix: true, + silent, + logger: { + log: (...args: unknown[]) => { + logs.push(args.join(' ')) } } - ) + }) if (!silent && logs.length > 0) { toastStore.add({ @@ -52,7 +49,7 @@ export function useWorkflowValidation() { } } - return linkValidation.graph as unknown as ComfyWorkflowJSON + return linkValidation.graph } /** @@ -80,7 +77,9 @@ export function useWorkflowValidation() { if (validatedGraphData) { try { - validatedData = tryFixLinks(validatedGraphData, { silent }) + validatedData = tryFixLinks(validatedGraphData, { + silent + }) as ComfyWorkflowJSON } catch (err) { // Link fixer itself is throwing an error console.error(err) diff --git a/src/renderer/core/layout/transform/useTransformState.test.ts b/src/renderer/core/layout/transform/useTransformState.test.ts index 76f0181dd..655c8ef46 100644 --- a/src/renderer/core/layout/transform/useTransformState.test.ts +++ b/src/renderer/core/layout/transform/useTransformState.test.ts @@ -33,7 +33,7 @@ describe('useTransformState', () => { beforeEach(() => { transformState.syncWithCanvas({ ds: { offset: [0, 0] } - } as unknown as LGraphCanvas) + } as LGraphCanvas) }) describe('initial state', () => { @@ -62,7 +62,7 @@ describe('useTransformState', () => { mockCanvas.ds.offset = [100, 50] mockCanvas.ds.scale = 2 - syncWithCanvas(mockCanvas as any) + syncWithCanvas(mockCanvas as LGraphCanvas) expect(camera.x).toBe(100) expect(camera.y).toBe(50) @@ -72,7 +72,7 @@ describe('useTransformState', () => { it('should handle null canvas gracefully', () => { const { syncWithCanvas, camera } = transformState - syncWithCanvas(null as any) + syncWithCanvas(null! as LGraphCanvas) // Should remain at initial values expect(camera.x).toBe(0) @@ -84,7 +84,7 @@ describe('useTransformState', () => { const { syncWithCanvas, camera } = transformState const canvasWithoutDs = { canvas: {} } - syncWithCanvas(canvasWithoutDs as any) + syncWithCanvas(canvasWithoutDs as LGraphCanvas) // Should remain at initial values expect(camera.x).toBe(0) @@ -99,7 +99,7 @@ describe('useTransformState', () => { mockCanvas.ds.offset = [150, 75] mockCanvas.ds.scale = 0.5 - syncWithCanvas(mockCanvas as any) + syncWithCanvas(mockCanvas as LGraphCanvas) expect(transformStyle.value).toEqual({ transform: 'scale(0.5) translate(150px, 75px)', @@ -114,7 +114,7 @@ describe('useTransformState', () => { const mockCanvas = createMockCanvasContext() mockCanvas.ds.offset = [100, 50] mockCanvas.ds.scale = 2 - transformState.syncWithCanvas(mockCanvas as any) + transformState.syncWithCanvas(mockCanvas as LGraphCanvas) }) describe('canvasToScreen', () => { @@ -176,7 +176,7 @@ describe('useTransformState', () => { const mockCanvas = createMockCanvasContext() mockCanvas.ds.offset = [100, 50] mockCanvas.ds.scale = 2 - transformState.syncWithCanvas(mockCanvas as any) + transformState.syncWithCanvas(mockCanvas as LGraphCanvas) }) it('should calculate correct screen bounds for a node', () => { @@ -201,7 +201,7 @@ describe('useTransformState', () => { const mockCanvas = createMockCanvasContext() mockCanvas.ds.offset = [0, 0] mockCanvas.ds.scale = 1 - transformState.syncWithCanvas(mockCanvas as any) + transformState.syncWithCanvas(mockCanvas as LGraphCanvas) }) const viewport = { width: 1000, height: 600 } @@ -257,14 +257,14 @@ describe('useTransformState', () => { // Test with very low zoom mockCanvas.ds.scale = 0.05 - syncWithCanvas(mockCanvas as any) + syncWithCanvas(mockCanvas as LGraphCanvas) // Node at edge should still be visible due to increased margin expect(isNodeInViewport([1100, 100], [200, 100], viewport)).toBe(true) // Test with high zoom mockCanvas.ds.scale = 4 - syncWithCanvas(mockCanvas as any) + syncWithCanvas(mockCanvas as LGraphCanvas) // Margin should be tighter expect(isNodeInViewport([1100, 100], [200, 100], viewport)).toBe(false) @@ -276,7 +276,7 @@ describe('useTransformState', () => { const mockCanvas = createMockCanvasContext() mockCanvas.ds.offset = [100, 50] mockCanvas.ds.scale = 2 - transformState.syncWithCanvas(mockCanvas as any) + transformState.syncWithCanvas(mockCanvas as LGraphCanvas) }) it('should calculate viewport bounds in canvas coordinates', () => { @@ -322,7 +322,7 @@ describe('useTransformState', () => { // Very small zoom mockCanvas.ds.scale = 0.001 - syncWithCanvas(mockCanvas as any) + syncWithCanvas(mockCanvas as LGraphCanvas) const point1 = canvasToScreen({ x: 1000, y: 1000 }) expect(point1.x).toBeCloseTo(1) @@ -330,7 +330,7 @@ describe('useTransformState', () => { // Very large zoom mockCanvas.ds.scale = 100 - syncWithCanvas(mockCanvas as any) + syncWithCanvas(mockCanvas as LGraphCanvas) const point2 = canvasToScreen({ x: 1, y: 1 }) expect(point2.x).toBe(100) @@ -343,7 +343,7 @@ describe('useTransformState', () => { // Scale of 0 gets converted to 1 by || operator mockCanvas.ds.scale = 0 - syncWithCanvas(mockCanvas as any) + syncWithCanvas(mockCanvas as LGraphCanvas) // Should use scale of 1 due to camera.z || 1 in implementation const result = screenToCanvas({ x: 100, y: 100 }) diff --git a/src/renderer/extensions/minimap/composables/useMinimap.test.ts b/src/renderer/extensions/minimap/composables/useMinimap.test.ts index e33b46c53..1ab52999c 100644 --- a/src/renderer/extensions/minimap/composables/useMinimap.test.ts +++ b/src/renderer/extensions/minimap/composables/useMinimap.test.ts @@ -1,6 +1,50 @@ +import type { Mock } from 'vitest' import { beforeEach, describe, expect, it, vi } from 'vitest' import { nextTick, shallowRef } from 'vue' +import { + createMockCanvas2DContext, + createMockMinimapCanvas +} from '@/utils/__tests__/litegraphTestUtils' + +interface MockNode { + id: string + pos: number[] + size: number[] + color?: string + constructor?: { color: string } + outputs?: { links: string[] }[] | null +} + +interface MockGraph { + _nodes: MockNode[] + links: Record + getNodeById: Mock + setDirtyCanvas: Mock + onNodeAdded: ((node: MockNode) => void) | null + onNodeRemoved: ((node: MockNode) => void) | null + onConnectionChange: ((node: MockNode) => void) | null +} + +interface MockCanvas { + graph: MockGraph + canvas: { + width: number + height: number + clientWidth: number + clientHeight: number + } + ds: { + scale: number + offset: [number, number] + } + setDirty: Mock +} + +interface MockContainerElement { + getBoundingClientRect: Mock +} + const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0)) const triggerRAF = async () => { @@ -39,15 +83,15 @@ vi.mock('@vueuse/core', () => { } }), useThrottleFn: vi.fn((callback) => { - return (...args: any[]) => { + return (...args: unknown[]) => { return callback(...args) } }) } }) -let mockCanvas: any -let mockGraph: any +let moduleMockCanvas: MockCanvas = null! +let moduleMockGraph: MockGraph = null! const setupMocks = () => { const mockNodes = [ @@ -72,7 +116,7 @@ const setupMocks = () => { } ] - mockGraph = { + moduleMockGraph = { _nodes: mockNodes, links: { link1: { @@ -87,8 +131,8 @@ const setupMocks = () => { onConnectionChange: null } - mockCanvas = { - graph: mockGraph, + moduleMockCanvas = { + graph: moduleMockGraph, canvas: { width: 1000, height: 800, @@ -105,8 +149,11 @@ const setupMocks = () => { setupMocks() -const defaultCanvasStore = { - canvas: mockCanvas, +const defaultCanvasStore: { + canvas: MockCanvas | null + getCanvas: () => MockCanvas | null +} = { + canvas: moduleMockCanvas, getCanvas: () => defaultCanvasStore.canvas } @@ -142,7 +189,7 @@ vi.mock('@/scripts/api', () => ({ vi.mock('@/scripts/app', () => ({ app: { canvas: { - graph: mockGraph + graph: moduleMockGraph } } })) @@ -158,16 +205,16 @@ const { useMinimap } = const { api } = await import('@/scripts/api') describe('useMinimap', () => { - let mockCanvas: any - let mockGraph: any - let mockCanvasElement: any - let mockContainerElement: any - let mockContext2D: any + let moduleMockCanvasElement: HTMLCanvasElement + let mockContainerElement: MockContainerElement + let mockContext2D: CanvasRenderingContext2D async function createAndInitializeMinimap() { const minimap = useMinimap({ - containerRefMaybe: shallowRef(mockContainerElement), - canvasRefMaybe: shallowRef(mockCanvasElement) + containerRefMaybe: shallowRef( + mockContainerElement as Partial as HTMLDivElement + ), + canvasRefMaybe: shallowRef(moduleMockCanvasElement) }) await minimap.init() await nextTick() @@ -181,28 +228,15 @@ describe('useMinimap', () => { mockPause.mockClear() mockResume.mockClear() - mockContext2D = { - clearRect: vi.fn(), - fillRect: vi.fn(), - strokeRect: vi.fn(), - beginPath: vi.fn(), - moveTo: vi.fn(), - lineTo: vi.fn(), - stroke: vi.fn(), - arc: vi.fn(), - fill: vi.fn(), - fillStyle: '', - strokeStyle: '', - lineWidth: 0 - } + mockContext2D = createMockCanvas2DContext() - mockCanvasElement = { - getContext: vi.fn().mockReturnValue(mockContext2D), - width: 250, - height: 200, - clientWidth: 250, - clientHeight: 200 - } + moduleMockCanvasElement = createMockMinimapCanvas({ + getContext: vi + .fn() + .mockImplementation((contextId) => + contextId === '2d' ? mockContext2D : null + ) as HTMLCanvasElement['getContext'] + }) const mockRect = { left: 100, @@ -241,7 +275,7 @@ describe('useMinimap', () => { } ] - mockGraph = { + moduleMockGraph = { _nodes: mockNodes, links: { link1: { @@ -256,8 +290,8 @@ describe('useMinimap', () => { onConnectionChange: null } - mockCanvas = { - graph: mockGraph, + moduleMockCanvas = { + graph: moduleMockGraph, canvas: { width: 1000, height: 800, @@ -271,7 +305,7 @@ describe('useMinimap', () => { setDirty: vi.fn() } - defaultCanvasStore.canvas = mockCanvas + defaultCanvasStore.canvas = moduleMockCanvas defaultSettingStore.get = vi.fn().mockReturnValue(true) defaultSettingStore.set = vi.fn().mockResolvedValue(undefined) @@ -338,9 +372,9 @@ describe('useMinimap', () => { await minimap.init() - expect(mockGraph.onNodeAdded).toBeDefined() - expect(mockGraph.onNodeRemoved).toBeDefined() - expect(mockGraph.onConnectionChange).toBeDefined() + expect(moduleMockGraph.onNodeAdded).toBeDefined() + expect(moduleMockGraph.onNodeRemoved).toBeDefined() + expect(moduleMockGraph.onConnectionChange).toBeDefined() }) it('should handle visibility from settings', async () => { @@ -377,18 +411,20 @@ describe('useMinimap', () => { onConnectionChange: vi.fn() } - mockGraph.onNodeAdded = originalCallbacks.onNodeAdded - mockGraph.onNodeRemoved = originalCallbacks.onNodeRemoved - mockGraph.onConnectionChange = originalCallbacks.onConnectionChange + moduleMockGraph.onNodeAdded = originalCallbacks.onNodeAdded + moduleMockGraph.onNodeRemoved = originalCallbacks.onNodeRemoved + moduleMockGraph.onConnectionChange = originalCallbacks.onConnectionChange const minimap = await createAndInitializeMinimap() await minimap.init() minimap.destroy() - expect(mockGraph.onNodeAdded).toBe(originalCallbacks.onNodeAdded) - expect(mockGraph.onNodeRemoved).toBe(originalCallbacks.onNodeRemoved) - expect(mockGraph.onConnectionChange).toBe( + expect(moduleMockGraph.onNodeAdded).toBe(originalCallbacks.onNodeAdded) + expect(moduleMockGraph.onNodeRemoved).toBe( + originalCallbacks.onNodeRemoved + ) + expect(moduleMockGraph.onConnectionChange).toBe( originalCallbacks.onConnectionChange ) }) @@ -421,7 +457,7 @@ describe('useMinimap', () => { it('should verify context is obtained during render', async () => { const minimap = await createAndInitializeMinimap() - const getContextSpy = vi.spyOn(mockCanvasElement, 'getContext') + const getContextSpy = vi.spyOn(moduleMockCanvasElement, 'getContext') await minimap.init() @@ -429,10 +465,12 @@ describe('useMinimap', () => { minimap.renderMinimap() // Force a render by triggering a graph change - mockGraph._nodes.push({ + moduleMockGraph._nodes.push({ id: 'new-node', pos: [150, 150], - size: [100, 50] + size: [100, 50], + constructor: { color: '#666' }, + outputs: [] }) // Trigger RAF to process changes @@ -452,15 +490,15 @@ describe('useMinimap', () => { minimap.renderMinimap() // Force a render by modifying a node position - mockGraph._nodes[0].pos = [50, 50] + moduleMockGraph._nodes[0].pos = [50, 50] // Trigger RAF to process changes await triggerRAF() await nextTick() const renderingOccurred = - mockContext2D.clearRect.mock.calls.length > 0 || - mockContext2D.fillRect.mock.calls.length > 0 + vi.mocked(mockContext2D.clearRect).mock.calls.length > 0 || + vi.mocked(mockContext2D.fillRect).mock.calls.length > 0 if (!renderingOccurred) { console.log('Minimap visible:', minimap.visible.value) @@ -469,12 +507,15 @@ describe('useMinimap', () => { console.log('Graph exists:', !!defaultCanvasStore.canvas?.graph) console.log( 'clearRect calls:', - mockContext2D.clearRect.mock.calls.length + vi.mocked(mockContext2D.clearRect).mock.calls.length + ) + console.log( + 'fillRect calls:', + vi.mocked(mockContext2D.fillRect).mock.calls.length ) - console.log('fillRect calls:', mockContext2D.fillRect.mock.calls.length) console.log( 'getContext calls:', - mockCanvasElement.getContext.mock.calls.length + vi.mocked(moduleMockCanvasElement.getContext).mock.calls.length ) } @@ -482,7 +523,11 @@ describe('useMinimap', () => { }) it('should not render when context is null', async () => { - mockCanvasElement.getContext = vi.fn().mockReturnValue(null) + moduleMockCanvasElement = createMockMinimapCanvas({ + getContext: vi + .fn() + .mockImplementation(() => null) as HTMLCanvasElement['getContext'] + }) const minimap = await createAndInitializeMinimap() @@ -491,12 +536,18 @@ describe('useMinimap', () => { expect(mockContext2D.clearRect).not.toHaveBeenCalled() - mockCanvasElement.getContext = vi.fn().mockReturnValue(mockContext2D) + moduleMockCanvasElement = createMockMinimapCanvas({ + getContext: vi + .fn() + .mockImplementation((contextId) => + contextId === '2d' ? mockContext2D : null + ) as HTMLCanvasElement['getContext'] + }) }) it('should handle empty graph', async () => { - const originalNodes = [...mockGraph._nodes] - mockGraph._nodes = [] + const originalNodes = [...moduleMockGraph._nodes] + moduleMockGraph._nodes = [] const minimap = await createAndInitializeMinimap() @@ -513,7 +564,7 @@ describe('useMinimap', () => { // The key test is that it doesn't crash and properly initializes expect(mockContext2D.clearRect).toHaveBeenCalled() - mockGraph._nodes = originalNodes + moduleMockGraph._nodes = originalNodes }) }) @@ -530,7 +581,7 @@ describe('useMinimap', () => { minimap.handlePointerDown(pointerEvent) expect(mockContainerElement.getBoundingClientRect).toHaveBeenCalled() - expect(mockCanvas.setDirty).toHaveBeenCalledWith(true, true) + expect(moduleMockCanvas.setDirty).toHaveBeenCalledWith(true, true) }) it('should handle pointer move while dragging (mouse)', async () => { @@ -550,8 +601,8 @@ describe('useMinimap', () => { }) minimap.handlePointerMove(pointerMoveEvent) - expect(mockCanvas.setDirty).toHaveBeenCalledWith(true, true) - expect(mockCanvas.ds.offset).toBeDefined() + expect(moduleMockCanvas.setDirty).toHaveBeenCalledWith(true, true) + expect(moduleMockCanvas.ds.offset).toBeDefined() }) it('should handle pointer up to stop dragging (mouse)', async () => { @@ -566,7 +617,7 @@ describe('useMinimap', () => { minimap.handlePointerUp() - mockCanvas.setDirty.mockClear() + moduleMockCanvas.setDirty.mockClear() const pointerMoveEvent = new PointerEvent('pointermove', { clientX: 200, @@ -575,7 +626,7 @@ describe('useMinimap', () => { }) minimap.handlePointerMove(pointerMoveEvent) - expect(mockCanvas.setDirty).not.toHaveBeenCalled() + expect(moduleMockCanvas.setDirty).not.toHaveBeenCalled() }) it('should handle pointer down and start dragging (touch)', async () => { @@ -590,7 +641,7 @@ describe('useMinimap', () => { minimap.handlePointerDown(pointerEvent) expect(mockContainerElement.getBoundingClientRect).toHaveBeenCalled() - expect(mockCanvas.setDirty).toHaveBeenCalledWith(true, true) + expect(moduleMockCanvas.setDirty).toHaveBeenCalledWith(true, true) }) it('should handle pointer move while dragging (touch)', async () => { @@ -610,8 +661,8 @@ describe('useMinimap', () => { }) minimap.handlePointerMove(pointerMoveEvent) - expect(mockCanvas.setDirty).toHaveBeenCalledWith(true, true) - expect(mockCanvas.ds.offset).toBeDefined() + expect(moduleMockCanvas.setDirty).toHaveBeenCalledWith(true, true) + expect(moduleMockCanvas.ds.offset).toBeDefined() }) it('should handle pointer move while dragging (pen)', async () => { @@ -631,14 +682,14 @@ describe('useMinimap', () => { }) minimap.handlePointerMove(pointerMoveEvent) - expect(mockCanvas.setDirty).toHaveBeenCalledWith(true, true) - expect(mockCanvas.ds.offset).toBeDefined() + expect(moduleMockCanvas.setDirty).toHaveBeenCalledWith(true, true) + expect(moduleMockCanvas.ds.offset).toBeDefined() }) it('should not move when not dragging with pointer', async () => { const minimap = await createAndInitializeMinimap() - mockCanvas.setDirty.mockClear() + moduleMockCanvas.setDirty.mockClear() const pointerMoveEvent = new PointerEvent('pointermove', { clientX: 200, @@ -647,7 +698,7 @@ describe('useMinimap', () => { }) minimap.handlePointerMove(pointerMoveEvent) - expect(mockCanvas.setDirty).not.toHaveBeenCalled() + expect(moduleMockCanvas.setDirty).not.toHaveBeenCalled() }) it('should handle pointer up to stop dragging (touch)', async () => { @@ -662,7 +713,7 @@ describe('useMinimap', () => { minimap.handlePointerUp() - mockCanvas.setDirty.mockClear() + moduleMockCanvas.setDirty.mockClear() const pointerMoveEvent = new PointerEvent('pointermove', { clientX: 200, @@ -671,7 +722,7 @@ describe('useMinimap', () => { }) minimap.handlePointerMove(pointerMoveEvent) - expect(mockCanvas.setDirty).not.toHaveBeenCalled() + expect(moduleMockCanvas.setDirty).not.toHaveBeenCalled() }) }) @@ -695,8 +746,8 @@ describe('useMinimap', () => { minimap.handleWheel(wheelEvent) expect(preventDefault).toHaveBeenCalled() - expect(mockCanvas.ds.scale).toBeCloseTo(1.1) - expect(mockCanvas.setDirty).toHaveBeenCalledWith(true, true) + expect(moduleMockCanvas.ds.scale).toBeCloseTo(1.1) + expect(moduleMockCanvas.setDirty).toHaveBeenCalledWith(true, true) }) it('should handle wheel zoom out', async () => { @@ -718,8 +769,8 @@ describe('useMinimap', () => { minimap.handleWheel(wheelEvent) expect(preventDefault).toHaveBeenCalled() - expect(mockCanvas.ds.scale).toBeCloseTo(0.9) - expect(mockCanvas.setDirty).toHaveBeenCalledWith(true, true) + expect(moduleMockCanvas.ds.scale).toBeCloseTo(0.9) + expect(moduleMockCanvas.setDirty).toHaveBeenCalledWith(true, true) }) it('should respect zoom limits', async () => { @@ -727,7 +778,7 @@ describe('useMinimap', () => { await minimap.init() - mockCanvas.ds.scale = 0.1 + moduleMockCanvas.ds.scale = 0.1 const wheelEvent = new WheelEvent('wheel', { deltaY: 100, @@ -742,7 +793,7 @@ describe('useMinimap', () => { minimap.handleWheel(wheelEvent) - expect(mockCanvas.ds.scale).toBe(0.1) + expect(moduleMockCanvas.ds.scale).toBe(0.1) }) it('should update container rect if needed', async () => { @@ -790,15 +841,17 @@ describe('useMinimap', () => { await minimap.init() - mockCanvas.canvas.clientWidth = 1200 - mockCanvas.canvas.clientHeight = 900 + moduleMockCanvas.canvas.clientWidth = 1200 + moduleMockCanvas.canvas.clientHeight = 900 - const resizeHandler = (window.addEventListener as any).mock.calls.find( - (call: any) => call[0] === 'resize' - )?.[1] + const resizeHandler = vi + .mocked(window.addEventListener) + .mock.calls.find((call) => call[0] === 'resize')?.[1] as + | EventListener + | undefined if (resizeHandler) { - resizeHandler() + resizeHandler(new Event('resize')) } await nextTick() @@ -817,12 +870,13 @@ describe('useMinimap', () => { id: 'node3', pos: [300, 200], size: [100, 100], - constructor: { color: '#666' } + constructor: { color: '#666' }, + outputs: [] } - mockGraph._nodes.push(newNode) - if (mockGraph.onNodeAdded) { - mockGraph.onNodeAdded(newNode) + moduleMockGraph._nodes.push(newNode) + if (moduleMockGraph.onNodeAdded) { + moduleMockGraph.onNodeAdded(newNode) } await new Promise((resolve) => setTimeout(resolve, 600)) @@ -833,11 +887,11 @@ describe('useMinimap', () => { await minimap.init() - const removedNode = mockGraph._nodes[0] - mockGraph._nodes.splice(0, 1) + const removedNode = moduleMockGraph._nodes[0] + moduleMockGraph._nodes.splice(0, 1) - if (mockGraph.onNodeRemoved) { - mockGraph.onNodeRemoved(removedNode) + if (moduleMockGraph.onNodeRemoved) { + moduleMockGraph.onNodeRemoved(removedNode) } await new Promise((resolve) => setTimeout(resolve, 600)) @@ -848,8 +902,8 @@ describe('useMinimap', () => { await minimap.init() - if (mockGraph.onConnectionChange) { - mockGraph.onConnectionChange(mockGraph._nodes[0]) + if (moduleMockGraph.onConnectionChange) { + moduleMockGraph.onConnectionChange(moduleMockGraph._nodes[0]) } await new Promise((resolve) => setTimeout(resolve, 600)) @@ -870,7 +924,7 @@ describe('useMinimap', () => { describe('edge cases', () => { it('should handle missing node outputs', async () => { - mockGraph._nodes[0].outputs = null + moduleMockGraph._nodes[0].outputs = null const minimap = await createAndInitializeMinimap() await expect(minimap.init()).resolves.not.toThrow() @@ -878,8 +932,8 @@ describe('useMinimap', () => { }) it('should handle invalid link references', async () => { - mockGraph.links.link1.target_id = 'invalid-node' - mockGraph.getNodeById.mockReturnValue(null) + moduleMockGraph.links.link1.target_id = 'invalid-node' + moduleMockGraph.getNodeById.mockReturnValue(null) const minimap = await createAndInitializeMinimap() @@ -898,16 +952,13 @@ describe('useMinimap', () => { }) it('should handle nodes without color', async () => { - mockGraph._nodes[0].color = undefined + moduleMockGraph._nodes[0].color = undefined const minimap = await createAndInitializeMinimap() await minimap.init() - const renderMinimap = (minimap as any).renderMinimap - if (renderMinimap) { - renderMinimap() - } + minimap.renderMinimap() expect(mockContext2D.fillRect).toHaveBeenCalled() expect(mockContext2D.fillStyle).toBeDefined() diff --git a/src/renderer/extensions/minimap/composables/useMinimapGraph.test.ts b/src/renderer/extensions/minimap/composables/useMinimapGraph.test.ts index 835f38e71..0ba2fb1ff 100644 --- a/src/renderer/extensions/minimap/composables/useMinimapGraph.test.ts +++ b/src/renderer/extensions/minimap/composables/useMinimapGraph.test.ts @@ -1,10 +1,17 @@ import { useThrottleFn } from '@vueuse/core' import { beforeEach, describe, expect, it, vi } from 'vitest' import { ref } from 'vue' +import type { Ref } from 'vue' import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph' import { useMinimapGraph } from '@/renderer/extensions/minimap/composables/useMinimapGraph' import { api } from '@/scripts/api' +import { + createMockLGraph, + createMockLGraphNode, + createMockLLink, + createMockLinks +} from '@/utils/__tests__/litegraphTestUtils' vi.mock('@vueuse/core', () => ({ useThrottleFn: vi.fn((fn) => fn) @@ -24,23 +31,23 @@ describe('useMinimapGraph', () => { beforeEach(() => { vi.clearAllMocks() - mockGraph = { + mockGraph = createMockLGraph({ id: 'test-graph-123', _nodes: [ - { id: '1', pos: [100, 100], size: [150, 80] }, - { id: '2', pos: [300, 200], size: [120, 60] } + createMockLGraphNode({ id: '1', pos: [100, 100], size: [150, 80] }), + createMockLGraphNode({ id: '2', pos: [300, 200], size: [120, 60] }) ], - links: { link1: { id: 'link1' } }, + links: createMockLinks([createMockLLink({ id: 1 })]), onNodeAdded: vi.fn(), onNodeRemoved: vi.fn(), onConnectionChange: vi.fn() - } as any + }) onGraphChangedMock = vi.fn() }) it('should initialize with empty state', () => { - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) expect(graphManager.updateFlags.value).toEqual({ @@ -52,7 +59,7 @@ describe('useMinimapGraph', () => { }) it('should setup event listeners on init', () => { - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) graphManager.init() @@ -72,7 +79,7 @@ describe('useMinimapGraph', () => { mockGraph.onNodeRemoved = originalOnNodeRemoved mockGraph.onConnectionChange = originalOnConnectionChange - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) graphManager.setupEventListeners() @@ -91,7 +98,7 @@ describe('useMinimapGraph', () => { }) it('should prevent duplicate event listener setup', () => { - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) // Store original callbacks for comparison @@ -127,7 +134,7 @@ describe('useMinimapGraph', () => { mockGraph.onNodeRemoved = originalOnNodeRemoved mockGraph.onConnectionChange = originalOnConnectionChange - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) graphManager.setupEventListeners() @@ -144,7 +151,7 @@ describe('useMinimapGraph', () => { .spyOn(console, 'error') .mockImplementation(() => {}) - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) graphManager.cleanupEventListeners() @@ -157,7 +164,7 @@ describe('useMinimapGraph', () => { }) it('should detect node position changes', () => { - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) // First check - cache initial state @@ -177,14 +184,18 @@ describe('useMinimapGraph', () => { }) it('should detect node count changes', () => { - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) // Cache initial state graphManager.checkForChanges() // Add a node - mockGraph._nodes.push({ id: '3', pos: [400, 300], size: [100, 50] } as any) + mockGraph._nodes.push({ + id: '3', + pos: [400, 300], + size: [100, 50] + } as Partial as LGraphNode) const hasChanges = graphManager.checkForChanges() expect(hasChanges).toBe(true) @@ -193,17 +204,17 @@ describe('useMinimapGraph', () => { }) it('should detect connection changes', () => { - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) // Cache initial state graphManager.checkForChanges() // Change connections - mockGraph.links = new Map([ - [1, { id: 1 }], - [2, { id: 2 }] - ]) as any + mockGraph.links = createMockLinks([ + createMockLLink({ id: 1 }), + createMockLLink({ id: 2 }) + ]) const hasChanges = graphManager.checkForChanges() expect(hasChanges).toBe(true) @@ -214,7 +225,7 @@ describe('useMinimapGraph', () => { const originalOnNodeRemoved = vi.fn() mockGraph.onNodeRemoved = originalOnNodeRemoved - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) graphManager.setupEventListeners() @@ -227,7 +238,7 @@ describe('useMinimapGraph', () => { }) it('should destroy properly', () => { - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) graphManager.init() @@ -241,7 +252,7 @@ describe('useMinimapGraph', () => { }) it('should clear cache', () => { - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) // Populate cache @@ -256,7 +267,7 @@ describe('useMinimapGraph', () => { }) it('should handle null graph gracefully', () => { - const graphRef = ref(null as any) + const graphRef = ref(null) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) expect(() => graphManager.setupEventListeners()).not.toThrow() @@ -265,7 +276,7 @@ describe('useMinimapGraph', () => { }) it('should clean up removed nodes from cache', () => { - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) // Cache initial state @@ -283,7 +294,7 @@ describe('useMinimapGraph', () => { const throttledFn = vi.fn() vi.mocked(useThrottleFn).mockReturnValue(throttledFn) - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const graphManager = useMinimapGraph(graphRef, onGraphChangedMock) graphManager.setupEventListeners() diff --git a/src/renderer/extensions/minimap/composables/useMinimapInteraction.test.ts b/src/renderer/extensions/minimap/composables/useMinimapInteraction.test.ts index 342ec9bdc..2380d6abf 100644 --- a/src/renderer/extensions/minimap/composables/useMinimapInteraction.test.ts +++ b/src/renderer/extensions/minimap/composables/useMinimapInteraction.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { ref } from 'vue' +import type { Ref } from 'vue' import { useMinimapInteraction } from '@/renderer/extensions/minimap/composables/useMinimapInteraction' import type { MinimapCanvas } from '@/renderer/extensions/minimap/types' @@ -19,7 +20,7 @@ describe('useMinimapInteraction', () => { width: 250, height: 200 }) - } as any + } as Partial as HTMLDivElement mockCanvas = { ds: { @@ -27,7 +28,7 @@ describe('useMinimapInteraction', () => { offset: [0, 0] }, setDirty: vi.fn() - } as any + } as Partial as MinimapCanvas centerViewOnMock = vi.fn<(worldX: number, worldY: number) => void>() }) @@ -36,7 +37,7 @@ describe('useMinimapInteraction', () => { const containerRef = ref(mockContainer) const boundsRef = ref({ minX: 0, minY: 0, width: 500, height: 400 }) const scaleRef = ref(0.5) - const canvasRef = ref(mockCanvas as any) + const canvasRef = ref(mockCanvas) as Ref const interaction = useMinimapInteraction( containerRef, @@ -61,7 +62,7 @@ describe('useMinimapInteraction', () => { const containerRef = ref(mockContainer) const boundsRef = ref({ minX: 0, minY: 0, width: 500, height: 400 }) const scaleRef = ref(0.5) - const canvasRef = ref(mockCanvas as any) + const canvasRef = ref(mockCanvas) as Ref const interaction = useMinimapInteraction( containerRef, @@ -89,7 +90,7 @@ describe('useMinimapInteraction', () => { const containerRef = ref(mockContainer) const boundsRef = ref({ minX: 0, minY: 0, width: 500, height: 400 }) const scaleRef = ref(0.5) - const canvasRef = ref(mockCanvas as any) + const canvasRef = ref(mockCanvas) as Ref const interaction = useMinimapInteraction( containerRef, @@ -117,7 +118,7 @@ describe('useMinimapInteraction', () => { const containerRef = ref(mockContainer) const boundsRef = ref({ minX: 0, minY: 0, width: 500, height: 400 }) const scaleRef = ref(0.5) - const canvasRef = ref(mockCanvas as any) + const canvasRef = ref(mockCanvas) as Ref const interaction = useMinimapInteraction( containerRef, @@ -163,7 +164,7 @@ describe('useMinimapInteraction', () => { const containerRef = ref(mockContainer) const boundsRef = ref({ minX: 0, minY: 0, width: 500, height: 400 }) const scaleRef = ref(0.5) - const canvasRef = ref(mockCanvas as any) + const canvasRef = ref(mockCanvas) as Ref const interaction = useMinimapInteraction( containerRef, @@ -189,7 +190,7 @@ describe('useMinimapInteraction', () => { const containerRef = ref(mockContainer) const boundsRef = ref({ minX: 0, minY: 0, width: 500, height: 400 }) const scaleRef = ref(0.5) - const canvasRef = ref(mockCanvas as any) + const canvasRef = ref(mockCanvas) as Ref const interaction = useMinimapInteraction( containerRef, @@ -220,7 +221,7 @@ describe('useMinimapInteraction', () => { const containerRef = ref(mockContainer) const boundsRef = ref({ minX: 0, minY: 0, width: 500, height: 400 }) const scaleRef = ref(0.5) - const canvasRef = ref(mockCanvas as any) + const canvasRef = ref(mockCanvas) as Ref const interaction = useMinimapInteraction( containerRef, @@ -250,7 +251,7 @@ describe('useMinimapInteraction', () => { const containerRef = ref(mockContainer) const boundsRef = ref({ minX: 0, minY: 0, width: 500, height: 400 }) const scaleRef = ref(0.5) - const canvasRef = ref(mockCanvas as any) + const canvasRef = ref(mockCanvas) as Ref const interaction = useMinimapInteraction( containerRef, @@ -283,7 +284,7 @@ describe('useMinimapInteraction', () => { const containerRef = ref(null) const boundsRef = ref({ minX: 0, minY: 0, width: 500, height: 400 }) const scaleRef = ref(0.5) - const canvasRef = ref(mockCanvas as any) + const canvasRef = ref(mockCanvas) as Ref const interaction = useMinimapInteraction( containerRef, @@ -306,7 +307,7 @@ describe('useMinimapInteraction', () => { const containerRef = ref(mockContainer) const boundsRef = ref({ minX: 0, minY: 0, width: 500, height: 400 }) const scaleRef = ref(0.5) - const canvasRef = ref(null as any) + const canvasRef = ref(null) as Ref const interaction = useMinimapInteraction( containerRef, diff --git a/src/renderer/extensions/minimap/composables/useMinimapRenderer.test.ts b/src/renderer/extensions/minimap/composables/useMinimapRenderer.test.ts index a5202059d..a2de2a759 100644 --- a/src/renderer/extensions/minimap/composables/useMinimapRenderer.test.ts +++ b/src/renderer/extensions/minimap/composables/useMinimapRenderer.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { ref, shallowRef } from 'vue' +import type { Ref } from 'vue' import type { LGraph } from '@/lib/litegraph/src/litegraph' import { useMinimapRenderer } from '@/renderer/extensions/minimap/composables/useMinimapRenderer' @@ -20,20 +21,20 @@ describe('useMinimapRenderer', () => { mockContext = { clearRect: vi.fn() - } as any + } as Partial as CanvasRenderingContext2D mockCanvas = { getContext: vi.fn().mockReturnValue(mockContext) - } as any + } as Partial as HTMLCanvasElement mockGraph = { _nodes: [{ id: '1', pos: [0, 0], size: [100, 100] }] - } as any + } as Partial as LGraph }) it('should initialize with full redraw needed', () => { const canvasRef = shallowRef(mockCanvas) - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const boundsRef = ref({ minX: 0, minY: 0, width: 100, height: 100 }) const scaleRef = ref(1) const updateFlagsRef = ref({ @@ -66,9 +67,9 @@ describe('useMinimapRenderer', () => { }) it('should handle empty graph with fast path', () => { - const emptyGraph = { _nodes: [] } as any + const emptyGraph = { _nodes: [] } as Partial as LGraph const canvasRef = ref(mockCanvas) - const graphRef = ref(emptyGraph) + const graphRef = ref(emptyGraph) as Ref const boundsRef = ref({ minX: 0, minY: 0, width: 100, height: 100 }) const scaleRef = ref(1) const updateFlagsRef = ref({ @@ -106,7 +107,7 @@ describe('useMinimapRenderer', () => { const { renderMinimapToCanvas } = await import('@/renderer/extensions/minimap/minimapCanvasRenderer') const canvasRef = ref(mockCanvas) - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const boundsRef = ref({ minX: 0, minY: 0, width: 100, height: 100 }) const scaleRef = ref(1) const updateFlagsRef = ref({ @@ -153,7 +154,7 @@ describe('useMinimapRenderer', () => { const updateViewport = vi.fn() const canvasRef = ref(mockCanvas) - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const boundsRef = ref({ minX: 0, minY: 0, width: 100, height: 100 }) const scaleRef = ref(1) const updateFlagsRef = ref({ @@ -192,7 +193,7 @@ describe('useMinimapRenderer', () => { it('should force full redraw when requested', () => { const canvasRef = ref(mockCanvas) - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const boundsRef = ref({ minX: 0, minY: 0, width: 100, height: 100 }) const scaleRef = ref(1) const updateFlagsRef = ref({ @@ -231,7 +232,7 @@ describe('useMinimapRenderer', () => { it('should handle null canvas gracefully', () => { const canvasRef = shallowRef(null) - const graphRef = ref(mockGraph as any) + const graphRef = ref(mockGraph) as Ref const boundsRef = ref({ minX: 0, minY: 0, width: 100, height: 100 }) const scaleRef = ref(1) const updateFlagsRef = ref({ diff --git a/src/renderer/extensions/minimap/composables/useMinimapSettings.test.ts b/src/renderer/extensions/minimap/composables/useMinimapSettings.test.ts index b74ec9ff3..448f64090 100644 --- a/src/renderer/extensions/minimap/composables/useMinimapSettings.test.ts +++ b/src/renderer/extensions/minimap/composables/useMinimapSettings.test.ts @@ -3,10 +3,15 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { useSettingStore } from '@/platform/settings/settingStore' import { useMinimapSettings } from '@/renderer/extensions/minimap/composables/useMinimapSettings' -import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' + +type MockSettingStore = ReturnType + +const mockUseColorPaletteStore = vi.hoisted(() => vi.fn()) vi.mock('@/platform/settings/settingStore') -vi.mock('@/stores/workspace/colorPaletteStore') +vi.mock('@/stores/workspace/colorPaletteStore', () => ({ + useColorPaletteStore: mockUseColorPaletteStore +})) describe('useMinimapSettings', () => { beforeEach(() => { @@ -17,7 +22,7 @@ describe('useMinimapSettings', () => { it('should return all minimap settings as computed refs', () => { const mockSettingStore = { get: vi.fn((key: string) => { - const settings: Record = { + const settings: Record = { 'Comfy.Minimap.NodeColors': true, 'Comfy.Minimap.ShowLinks': false, 'Comfy.Minimap.ShowGroups': true, @@ -28,10 +33,17 @@ describe('useMinimapSettings', () => { }) } - vi.mocked(useSettingStore).mockReturnValue(mockSettingStore as any) - vi.mocked(useColorPaletteStore).mockReturnValue({ - completedActivePalette: { light_theme: false } - } as any) + vi.mocked(useSettingStore).mockReturnValue( + mockSettingStore as Partial as MockSettingStore + ) + mockUseColorPaletteStore.mockReturnValue({ + completedActivePalette: { + id: 'test', + name: 'Test Palette', + colors: {}, + light_theme: false + } + }) const settings = useMinimapSettings() @@ -44,13 +56,18 @@ describe('useMinimapSettings', () => { it('should generate container styles based on theme', () => { const mockColorPaletteStore = { - completedActivePalette: { light_theme: false } + completedActivePalette: { + id: 'test', + name: 'Test Palette', + colors: {}, + light_theme: false + } } - vi.mocked(useSettingStore).mockReturnValue({ get: vi.fn() } as any) - vi.mocked(useColorPaletteStore).mockReturnValue( - mockColorPaletteStore as any - ) + vi.mocked(useSettingStore).mockReturnValue({ + get: vi.fn() + } as Partial as MockSettingStore) + mockUseColorPaletteStore.mockReturnValue(mockColorPaletteStore) const settings = useMinimapSettings() const styles = settings.containerStyles.value @@ -63,13 +80,18 @@ describe('useMinimapSettings', () => { it('should generate light theme container styles', () => { const mockColorPaletteStore = { - completedActivePalette: { light_theme: true } + completedActivePalette: { + id: 'test', + name: 'Test Palette', + colors: {}, + light_theme: true + } } - vi.mocked(useSettingStore).mockReturnValue({ get: vi.fn() } as any) - vi.mocked(useColorPaletteStore).mockReturnValue( - mockColorPaletteStore as any - ) + vi.mocked(useSettingStore).mockReturnValue({ + get: vi.fn() + } as Partial as MockSettingStore) + mockUseColorPaletteStore.mockReturnValue(mockColorPaletteStore) const settings = useMinimapSettings() const styles = settings.containerStyles.value @@ -82,13 +104,18 @@ describe('useMinimapSettings', () => { it('should generate panel styles based on theme', () => { const mockColorPaletteStore = { - completedActivePalette: { light_theme: false } + completedActivePalette: { + id: 'test', + name: 'Test Palette', + colors: {}, + light_theme: false + } } - vi.mocked(useSettingStore).mockReturnValue({ get: vi.fn() } as any) - vi.mocked(useColorPaletteStore).mockReturnValue( - mockColorPaletteStore as any - ) + vi.mocked(useSettingStore).mockReturnValue({ + get: vi.fn() + } as Partial as MockSettingStore) + mockUseColorPaletteStore.mockReturnValue(mockColorPaletteStore) const settings = useMinimapSettings() const styles = settings.panelStyles.value @@ -107,10 +134,17 @@ describe('useMinimapSettings', () => { }) const mockSettingStore = { get: mockGet } - vi.mocked(useSettingStore).mockReturnValue(mockSettingStore as any) - vi.mocked(useColorPaletteStore).mockReturnValue({ - completedActivePalette: { light_theme: false } - } as any) + vi.mocked(useSettingStore).mockReturnValue( + mockSettingStore as Partial as MockSettingStore + ) + mockUseColorPaletteStore.mockReturnValue({ + completedActivePalette: { + id: 'test', + name: 'Test Palette', + colors: {}, + light_theme: false + } + }) const settings = useMinimapSettings() diff --git a/src/renderer/extensions/minimap/composables/useMinimapViewport.test.ts b/src/renderer/extensions/minimap/composables/useMinimapViewport.test.ts index de50afc8a..76f0ca710 100644 --- a/src/renderer/extensions/minimap/composables/useMinimapViewport.test.ts +++ b/src/renderer/extensions/minimap/composables/useMinimapViewport.test.ts @@ -1,6 +1,7 @@ import { useRafFn } from '@vueuse/core' import { beforeEach, describe, expect, it, vi } from 'vitest' import { ref } from 'vue' +import type { Ref } from 'vue' import type { LGraph } from '@/lib/litegraph/src/litegraph' import { useMinimapViewport } from '@/renderer/extensions/minimap/composables/useMinimapViewport' @@ -39,7 +40,7 @@ describe('useMinimapViewport', () => { { pos: [100, 100], size: [150, 80] }, { pos: [300, 200], size: [120, 60] } ] - } as any + } as Partial as LGraph vi.mocked(useRafFn, { partial: true }).mockReturnValue({ resume: vi.fn(), @@ -48,8 +49,8 @@ describe('useMinimapViewport', () => { }) it('should initialize with default bounds', () => { - const canvasRef = ref(mockCanvas as any) - const graphRef = ref(mockGraph as any) + const canvasRef = ref(mockCanvas) as Ref + const graphRef = ref(mockGraph) as Ref const viewport = useMinimapViewport(canvasRef, graphRef, 250, 200) @@ -80,8 +81,8 @@ describe('useMinimapViewport', () => { vi.mocked(enforceMinimumBounds).mockImplementation((bounds) => bounds) - const canvasRef = ref(mockCanvas as any) - const graphRef = ref(mockGraph as any) + const canvasRef = ref(mockCanvas) as Ref + const graphRef = ref(mockGraph) as Ref const viewport = useMinimapViewport(canvasRef, graphRef, 250, 200) @@ -97,8 +98,10 @@ describe('useMinimapViewport', () => { vi.mocked(calculateNodeBounds).mockReturnValue(null) - const canvasRef = ref(mockCanvas as any) - const graphRef = ref({ _nodes: [] } as any) + const canvasRef = ref(mockCanvas) as Ref + const graphRef = ref({ + _nodes: [] + } as Partial as LGraph) as Ref const viewport = useMinimapViewport(canvasRef, graphRef, 250, 200) @@ -115,8 +118,8 @@ describe('useMinimapViewport', () => { }) it('should update canvas dimensions', () => { - const canvasRef = ref(mockCanvas as any) - const graphRef = ref(mockGraph as any) + const canvasRef = ref(mockCanvas) as Ref + const graphRef = ref(mockGraph) as Ref const viewport = useMinimapViewport(canvasRef, graphRef, 250, 200) @@ -145,8 +148,8 @@ describe('useMinimapViewport', () => { vi.mocked(enforceMinimumBounds).mockImplementation((bounds) => bounds) vi.mocked(calculateMinimapScale).mockReturnValue(0.5) - const canvasRef = ref(mockCanvas as any) - const graphRef = ref(mockGraph as any) + const canvasRef = ref(mockCanvas) as Ref + const graphRef = ref(mockGraph) as Ref const viewport = useMinimapViewport(canvasRef, graphRef, 250, 200) @@ -181,8 +184,8 @@ describe('useMinimapViewport', () => { }) it('should center view on world coordinates', () => { - const canvasRef = ref(mockCanvas as any) - const graphRef = ref(mockGraph as any) + const canvasRef = ref(mockCanvas) as Ref + const graphRef = ref(mockGraph) as Ref const viewport = useMinimapViewport(canvasRef, graphRef, 250, 200) @@ -209,8 +212,8 @@ describe('useMinimapViewport', () => { pause: stopSyncMock }) - const canvasRef = ref(mockCanvas as any) - const graphRef = ref(mockGraph as any) + const canvasRef = ref(mockCanvas) as Ref + const graphRef = ref(mockGraph) as Ref const viewport = useMinimapViewport(canvasRef, graphRef, 250, 200) @@ -222,8 +225,8 @@ describe('useMinimapViewport', () => { }) it('should handle null canvas gracefully', () => { - const canvasRef = ref(null as any) - const graphRef = ref(mockGraph as any) + const canvasRef = ref(null) as Ref + const graphRef = ref(mockGraph) as Ref const viewport = useMinimapViewport(canvasRef, graphRef, 250, 200) @@ -250,8 +253,8 @@ describe('useMinimapViewport', () => { vi.mocked(enforceMinimumBounds).mockImplementation((bounds) => bounds) vi.mocked(calculateMinimapScale).mockReturnValue(0.4) - const canvasRef = ref(mockCanvas as any) - const graphRef = ref(mockGraph as any) + const canvasRef = ref(mockCanvas) as Ref + const graphRef = ref(mockGraph) as Ref const viewport = useMinimapViewport(canvasRef, graphRef, 250, 200) @@ -268,8 +271,8 @@ describe('useMinimapViewport', () => { configurable: true }) - const canvasRef = ref(mockCanvas as any) - const graphRef = ref(mockGraph as any) + const canvasRef = ref(mockCanvas) as Ref + const graphRef = ref(mockGraph) as Ref const viewport = useMinimapViewport(canvasRef, graphRef, 250, 200) diff --git a/src/renderer/extensions/minimap/minimapCanvasRenderer.test.ts b/src/renderer/extensions/minimap/minimapCanvasRenderer.test.ts index 25d0058cd..81fcfceb8 100644 --- a/src/renderer/extensions/minimap/minimapCanvasRenderer.test.ts +++ b/src/renderer/extensions/minimap/minimapCanvasRenderer.test.ts @@ -1,13 +1,23 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { LGraph } from '@/lib/litegraph/src/litegraph' import { LGraphEventMode } from '@/lib/litegraph/src/litegraph' -import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph' import { renderMinimapToCanvas } from '@/renderer/extensions/minimap/minimapCanvasRenderer' import type { MinimapRenderContext } from '@/renderer/extensions/minimap/types' -import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { adjustColor } from '@/utils/colorUtil' +import { + createMockLGraph, + createMockLGraphNode, + createMockLinks, + createMockLLink, + createMockNodeOutputSlot +} from '@/utils/__tests__/litegraphTestUtils' + +const mockUseColorPaletteStore = vi.hoisted(() => vi.fn()) +vi.mock('@/stores/workspace/colorPaletteStore', () => ({ + useColorPaletteStore: mockUseColorPaletteStore +})) -vi.mock('@/stores/workspace/colorPaletteStore') vi.mock('@/utils/colorUtil', () => ({ adjustColor: vi.fn((color: string) => color + '_adjusted') })) @@ -33,15 +43,15 @@ describe('minimapCanvasRenderer', () => { fillStyle: '', strokeStyle: '', lineWidth: 1 - } as any + } as Partial as CanvasRenderingContext2D mockCanvas = { getContext: vi.fn().mockReturnValue(mockContext) - } as any + } as Partial as HTMLCanvasElement - mockGraph = { + mockGraph = createMockLGraph({ _nodes: [ - { + createMockLGraphNode({ id: '1', pos: [100, 100], size: [150, 80], @@ -49,8 +59,8 @@ describe('minimapCanvasRenderer', () => { mode: LGraphEventMode.ALWAYS, has_errors: false, outputs: [] - }, - { + }), + createMockLGraphNode({ id: '2', pos: [300, 200], size: [120, 60], @@ -58,16 +68,21 @@ describe('minimapCanvasRenderer', () => { mode: LGraphEventMode.BYPASS, has_errors: true, outputs: [] - } - ] as unknown as LGraphNode[], + }) + ], _groups: [], - links: {}, + links: {} as typeof mockGraph.links, getNodeById: vi.fn() - } as any + }) - vi.mocked(useColorPaletteStore).mockReturnValue({ - completedActivePalette: { light_theme: false } - } as any) + mockUseColorPaletteStore.mockReturnValue({ + completedActivePalette: { + id: 'test', + name: 'Test Palette', + colors: {}, + light_theme: false + } + }) }) it('should clear canvas and render nodes', () => { @@ -203,7 +218,9 @@ describe('minimapCanvasRenderer', () => { size: [400, 300], color: '#0000FF' } - ] as any + ] as Partial< + (typeof mockGraph._groups)[number] + >[] as typeof mockGraph._groups const context: MinimapRenderContext = { bounds: { minX: 0, minY: 0, width: 500, height: 400 }, @@ -233,17 +250,17 @@ describe('minimapCanvasRenderer', () => { } mockGraph._nodes[0].outputs = [ - { - links: [1] - } - ] as any + createMockNodeOutputSlot({ + name: 'output', + type: 'number', + links: [1], + boundingRect: new Float64Array([0, 0, 10, 10]) + }) + ] - // Create a hybrid Map/Object for links as LiteGraph expects - const linksMap = new Map([[1, { id: 1, target_id: 2 }]]) - const links = Object.assign(linksMap, { - 1: { id: 1, target_id: 2 } - }) - mockGraph.links = links as any + mockGraph.links = createMockLinks([ + createMockLLink({ id: 1, target_id: 2, origin_slot: 0, target_slot: 0 }) + ]) mockGraph.getNodeById = vi.fn().mockReturnValue(targetNode) @@ -275,9 +292,14 @@ describe('minimapCanvasRenderer', () => { }) it('should handle light theme colors', () => { - vi.mocked(useColorPaletteStore).mockReturnValue({ - completedActivePalette: { light_theme: true } - } as any) + mockUseColorPaletteStore.mockReturnValue({ + completedActivePalette: { + id: 'test', + name: 'Test Palette', + colors: {}, + light_theme: true + } + }) const context: MinimapRenderContext = { bounds: { minX: 0, minY: 0, width: 500, height: 400 }, diff --git a/src/utils/__tests__/litegraphTestUtils.ts b/src/utils/__tests__/litegraphTestUtils.ts index e43aa5930..05e88e738 100644 --- a/src/utils/__tests__/litegraphTestUtils.ts +++ b/src/utils/__tests__/litegraphTestUtils.ts @@ -9,10 +9,12 @@ import type { LGraph, LGraphCanvas, LGraphGroup, - LinkNetwork + LinkNetwork, + LLink } from '@/lib/litegraph/src/litegraph' import { LGraphEventMode, LGraphNode } from '@/lib/litegraph/src/litegraph' import { vi } from 'vitest' +import type { ChangeTracker } from '@/scripts/changeTracker' /** * Creates a mock LGraphNode with minimal required properties @@ -203,3 +205,99 @@ export function createMockFileList(files: File[]): FileList { ) return fileList as FileList } + +/** + * Creates a mock ChangeTracker for workflow testing + * The ChangeTracker requires a proper ComfyWorkflowJSON structure + */ +export function createMockChangeTracker( + overrides: Record = {} +): ChangeTracker { + const partial = { + activeState: { + last_node_id: 0, + last_link_id: 0, + nodes: [], + links: [], + groups: [], + config: {}, + version: 0.4 + }, + undoQueue: [], + redoQueue: [], + changeCount: 0, + reset: vi.fn(), + ...overrides + } + return partial as Partial as ChangeTracker +} + +/** + * Creates a mock MinimapCanvas for minimap testing + */ +export function createMockMinimapCanvas( + overrides: Partial = {} +): HTMLCanvasElement { + const mockGetContext = vi.fn() + mockGetContext.mockImplementation((contextId: string) => + contextId === '2d' ? createMockCanvas2DContext() : null + ) + + const partial: Partial = { + width: 200, + height: 200, + clientWidth: 200, + clientHeight: 200, + getContext: mockGetContext as HTMLCanvasElement['getContext'], + ...overrides + } + return partial as HTMLCanvasElement +} + +/** + * Creates a mock CanvasRenderingContext2D for canvas testing + */ +export function createMockCanvas2DContext( + overrides: Partial = {} +): CanvasRenderingContext2D { + const partial: Partial = { + clearRect: vi.fn(), + fillRect: vi.fn(), + strokeRect: vi.fn(), + beginPath: vi.fn(), + moveTo: vi.fn(), + lineTo: vi.fn(), + stroke: vi.fn(), + arc: vi.fn(), + fill: vi.fn(), + fillStyle: '', + strokeStyle: '', + lineWidth: 1, + ...overrides + } + return partial as CanvasRenderingContext2D +} + +export function createMockLLink(overrides: Partial = {}): LLink { + const partial: Partial = { + id: 1, + type: '*', + origin_id: 1, + origin_slot: 0, + target_id: 2, + target_slot: 0, + _pos: [0, 0], + ...overrides + } + return partial as LLink +} + +export function createMockLinks(links: LLink[]): LGraph['links'] { + const map = new Map() + const record: Record = {} + for (const link of links) { + map.set(link.id, link) + record[link.id] = link + } + return Object.assign(map, record) as LGraph['links'] +}