mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-24 08:44:06 +00:00
## Summary Improved type safety in test files by eliminating unsafe type assertions and adopting official testing patterns. Reduced unsafe `as unknown as` type assertions and eliminated all `null!` assertions. ## Changes - **Adopted @pinia/testing patterns** - Replaced manual Pinia store mocking with `createTestingPinia()` in `useSelectionState.test.ts` - Eliminated ~120 lines of mock boilerplate - Created `createMockSettingStore()` helper to replace duplicated store mocks in `useCoreCommands.test.ts` - **Eliminated unsafe null assertions** - Created explicit `MockMaskEditorStore` interface with proper nullable types in `useCanvasTools.test.ts` - Replaced `null!` initializations with `null` and used `!` at point of use or `?.` for optional chaining - **Made partial mock intent explicit** - Updated test utilities in `litegraphTestUtils.ts` to use explicit `Partial<T>` typing - Changed cast pattern from `as T` to `as Partial<T> as T` to show incomplete mock intent - Applied to `createMockLGraphNode()`, `createMockPositionable()`, and `createMockLGraphGroup()` - **Created centralized mock utilities** in `src/utils/__tests__/litegraphTestUtils.ts` - `createMockLGraphNode()`, `createMockPositionable()`, `createMockLGraphGroup()`, `createMockSubgraphNode()` - Updated 8+ test files to use centralized utilities - Used union types `Partial<T> | Record<string, unknown>` for flexible mock creation ## Results - ✅ 0 typecheck errors - ✅ 0 lint errors - ✅ All tests passing in modified files - ✅ Eliminated all `null!` assertions - ✅ Reduced unsafe double-cast patterns significantly ## Files Modified (18) - `src/components/graph/SelectionToolbox.test.ts` - `src/components/graph/selectionToolbox/{BypassButton,ColorPickerButton,ExecuteButton}.test.ts` - `src/components/sidebar/tabs/queue/ResultGallery.test.ts` - `src/composables/canvas/useSelectedLiteGraphItems.test.ts` - `src/composables/graph/{useGraphHierarchy,useSelectionState}.test.ts` - `src/composables/maskeditor/{useCanvasHistory,useCanvasManager,useCanvasTools,useCanvasTransform}.test.ts` - `src/composables/node/{useNodePricing,useWatchWidget}.test.ts` - `src/composables/{useBrowserTabTitle,useCoreCommands}.test.ts` - `src/utils/__tests__/litegraphTestUtils.ts` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8258-refactor-eliminate-unsafe-type-assertions-from-Group-2-test-files-2f16d73d365081549c65fd546cc7c765) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: AustinMroz <austin@comfy.org> Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
216 lines
5.4 KiB
TypeScript
216 lines
5.4 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { useImageLoader } from '@/composables/maskeditor/useImageLoader'
|
|
|
|
type MockStore = {
|
|
imgCanvas: HTMLCanvasElement | null
|
|
maskCanvas: HTMLCanvasElement | null
|
|
rgbCanvas: HTMLCanvasElement | null
|
|
imgCtx: CanvasRenderingContext2D | null
|
|
maskCtx: CanvasRenderingContext2D | null
|
|
image: HTMLImageElement | null
|
|
}
|
|
|
|
type MockDataStore = {
|
|
inputData: {
|
|
baseLayer: { image: HTMLImageElement }
|
|
maskLayer: { image: HTMLImageElement }
|
|
paintLayer: { image: HTMLImageElement } | null
|
|
} | null
|
|
}
|
|
|
|
const mockCanvasManager = {
|
|
invalidateCanvas: vi.fn().mockResolvedValue(undefined),
|
|
updateMaskColor: vi.fn().mockResolvedValue(undefined)
|
|
}
|
|
|
|
const mockStore: MockStore = {
|
|
imgCanvas: null,
|
|
maskCanvas: null,
|
|
rgbCanvas: null,
|
|
imgCtx: null,
|
|
maskCtx: null,
|
|
image: null
|
|
}
|
|
|
|
const mockDataStore: MockDataStore = {
|
|
inputData: null
|
|
}
|
|
|
|
vi.mock('@/stores/maskEditorStore', () => ({
|
|
useMaskEditorStore: vi.fn(() => mockStore)
|
|
}))
|
|
|
|
vi.mock('@/stores/maskEditorDataStore', () => ({
|
|
useMaskEditorDataStore: vi.fn(() => mockDataStore)
|
|
}))
|
|
|
|
vi.mock('@/composables/maskeditor/useCanvasManager', () => ({
|
|
useCanvasManager: vi.fn(() => mockCanvasManager)
|
|
}))
|
|
|
|
vi.mock('@vueuse/core', () => ({
|
|
createSharedComposable: <T extends (...args: unknown[]) => unknown>(fn: T) =>
|
|
fn
|
|
}))
|
|
|
|
describe('useImageLoader', () => {
|
|
let mockBaseImage: HTMLImageElement
|
|
let mockMaskImage: HTMLImageElement
|
|
let mockPaintImage: HTMLImageElement
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
|
|
mockBaseImage = {
|
|
width: 512,
|
|
height: 512
|
|
} as HTMLImageElement
|
|
|
|
mockMaskImage = {
|
|
width: 512,
|
|
height: 512
|
|
} as HTMLImageElement
|
|
|
|
mockPaintImage = {
|
|
width: 512,
|
|
height: 512
|
|
} as HTMLImageElement
|
|
|
|
mockStore.imgCtx = {
|
|
clearRect: vi.fn()
|
|
} as Partial<CanvasRenderingContext2D> as CanvasRenderingContext2D
|
|
|
|
mockStore.maskCtx = {
|
|
clearRect: vi.fn()
|
|
} as Partial<CanvasRenderingContext2D> as CanvasRenderingContext2D
|
|
|
|
mockStore.imgCanvas = {
|
|
width: 0,
|
|
height: 0
|
|
} as Partial<HTMLCanvasElement> as HTMLCanvasElement
|
|
|
|
mockStore.maskCanvas = {
|
|
width: 0,
|
|
height: 0
|
|
} as Partial<HTMLCanvasElement> as HTMLCanvasElement
|
|
|
|
mockStore.rgbCanvas = {
|
|
width: 0,
|
|
height: 0
|
|
} as Partial<HTMLCanvasElement> as HTMLCanvasElement
|
|
|
|
mockDataStore.inputData = {
|
|
baseLayer: { image: mockBaseImage },
|
|
maskLayer: { image: mockMaskImage },
|
|
paintLayer: { image: mockPaintImage }
|
|
}
|
|
})
|
|
|
|
describe('loadImages', () => {
|
|
it('should load images successfully', async () => {
|
|
const loader = useImageLoader()
|
|
|
|
const result = await loader.loadImages()
|
|
|
|
expect(result).toBe(mockBaseImage)
|
|
expect(mockStore.image).toBe(mockBaseImage)
|
|
})
|
|
|
|
it('should set canvas dimensions', async () => {
|
|
const loader = useImageLoader()
|
|
|
|
await loader.loadImages()
|
|
|
|
expect(mockStore.maskCanvas?.width).toBe(512)
|
|
expect(mockStore.maskCanvas?.height).toBe(512)
|
|
expect(mockStore.rgbCanvas?.width).toBe(512)
|
|
expect(mockStore.rgbCanvas?.height).toBe(512)
|
|
})
|
|
|
|
it('should clear canvas contexts', async () => {
|
|
const loader = useImageLoader()
|
|
|
|
await loader.loadImages()
|
|
|
|
expect(mockStore.imgCtx?.clearRect).toHaveBeenCalledWith(0, 0, 0, 0)
|
|
expect(mockStore.maskCtx?.clearRect).toHaveBeenCalledWith(0, 0, 0, 0)
|
|
})
|
|
|
|
it('should call canvasManager methods', async () => {
|
|
const loader = useImageLoader()
|
|
|
|
await loader.loadImages()
|
|
|
|
expect(mockCanvasManager.invalidateCanvas).toHaveBeenCalledWith(
|
|
mockBaseImage,
|
|
mockMaskImage,
|
|
mockPaintImage
|
|
)
|
|
expect(mockCanvasManager.updateMaskColor).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should handle missing paintLayer', async () => {
|
|
mockDataStore.inputData = {
|
|
baseLayer: { image: mockBaseImage },
|
|
maskLayer: { image: mockMaskImage },
|
|
paintLayer: null
|
|
}
|
|
|
|
const loader = useImageLoader()
|
|
|
|
await loader.loadImages()
|
|
|
|
expect(mockCanvasManager.invalidateCanvas).toHaveBeenCalledWith(
|
|
mockBaseImage,
|
|
mockMaskImage,
|
|
null
|
|
)
|
|
})
|
|
|
|
it('should throw error when no input data', async () => {
|
|
mockDataStore.inputData = null
|
|
|
|
const loader = useImageLoader()
|
|
|
|
await expect(loader.loadImages()).rejects.toThrow(
|
|
'No input data available in dataStore'
|
|
)
|
|
})
|
|
|
|
it('should throw error when canvas elements missing', async () => {
|
|
mockStore.imgCanvas = null
|
|
|
|
const loader = useImageLoader()
|
|
|
|
await expect(loader.loadImages()).rejects.toThrow(
|
|
'Canvas elements or contexts not available'
|
|
)
|
|
})
|
|
|
|
it('should throw error when contexts missing', async () => {
|
|
mockStore.imgCtx = null
|
|
|
|
const loader = useImageLoader()
|
|
|
|
await expect(loader.loadImages()).rejects.toThrow(
|
|
'Canvas elements or contexts not available'
|
|
)
|
|
})
|
|
|
|
it('should handle different image dimensions', async () => {
|
|
mockBaseImage.width = 1024
|
|
mockBaseImage.height = 768
|
|
|
|
const loader = useImageLoader()
|
|
|
|
await loader.loadImages()
|
|
|
|
expect(mockStore.maskCanvas?.width).toBe(1024)
|
|
expect(mockStore.maskCanvas?.height).toBe(768)
|
|
expect(mockStore.rgbCanvas?.width).toBe(1024)
|
|
expect(mockStore.rgbCanvas?.height).toBe(768)
|
|
})
|
|
})
|
|
})
|