feat: Add more Storybook stories for UI components

This commit is contained in:
snomiao
2025-09-22 21:10:30 +00:00
parent e5d4d07d32
commit ce71c2c529
521 changed files with 8078 additions and 22384 deletions

View File

@@ -2,9 +2,9 @@ import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick, ref } from 'vue'
import { useComfyManagerService } from '@/workbench/extensions/manager/services/comfyManagerService'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
import type { components as ManagerComponents } from '@/workbench/extensions/manager/types/generatedManagerTypes'
import { useComfyManagerService } from '@/services/comfyManagerService'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { components as ManagerComponents } from '@/types/generatedManagerTypes'
type InstalledPacksResponse =
ManagerComponents['schemas']['InstalledPacksResponse']
@@ -13,7 +13,7 @@ type ManagerDatabaseSource =
ManagerComponents['schemas']['ManagerDatabaseSource']
type ManagerPackInstalled = ManagerComponents['schemas']['ManagerPackInstalled']
vi.mock('@/workbench/extensions/manager/services/comfyManagerService', () => ({
vi.mock('@/services/comfyManagerService', () => ({
useComfyManagerService: vi.fn()
}))
@@ -23,7 +23,7 @@ vi.mock('@/services/dialogService', () => ({
})
}))
vi.mock('@/workbench/extensions/manager/composables/useManagerQueue', () => {
vi.mock('@/composables/useManagerQueue', () => {
const enqueueTaskMock = vi.fn()
return {

View File

@@ -58,11 +58,9 @@ vi.mock('firebase/auth', async (importOriginal) => {
onAuthStateChanged: vi.fn(),
signInWithPopup: vi.fn(),
GoogleAuthProvider: class {
addScope = vi.fn()
setCustomParameters = vi.fn()
},
GithubAuthProvider: class {
addScope = vi.fn()
setCustomParameters = vi.fn()
},
setPersistence: vi.fn().mockResolvedValue(undefined)
@@ -150,6 +148,13 @@ describe('useFirebaseAuthStore', () => {
expect(store.loading).toBe(false)
})
it('should set persistence to local storage on initialization', () => {
expect(firebaseAuth.setPersistence).toHaveBeenCalledWith(
mockAuth,
firebaseAuth.browserLocalPersistence
)
})
it('should properly clean up error state between operations', async () => {
// First, cause an error
const mockError = new Error('Invalid password')

View File

@@ -1,8 +1,8 @@
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { ExecutedWsMessage } from '@/schemas/apiSchema'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { ExecutedWsMessage } from '@/schemas/apiSchema'
import { app } from '@/scripts/app'
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
import * as litegraphUtil from '@/utils/litegraphUtil'

View File

@@ -19,7 +19,7 @@ const EXPECTED_DEFAULT_TYPES = [
'gligen'
] as const
type NodeDefStoreType = ReturnType<typeof useNodeDefStore>
type NodeDefStoreType = typeof import('@/stores/nodeDefStore')
// Create minimal but valid ComfyNodeDefImpl for testing
function createMockNodeDef(name: string): ComfyNodeDefImpl {
@@ -343,107 +343,6 @@ describe('useModelToNodeStore', () => {
})
})
describe('getCategoryForNodeType', () => {
it('should return category for known node type', () => {
const modelToNodeStore = useModelToNodeStore()
modelToNodeStore.registerDefaults()
expect(
modelToNodeStore.getCategoryForNodeType('CheckpointLoaderSimple')
).toBe('checkpoints')
expect(modelToNodeStore.getCategoryForNodeType('LoraLoader')).toBe(
'loras'
)
expect(modelToNodeStore.getCategoryForNodeType('VAELoader')).toBe('vae')
})
it('should return undefined for unknown node type', () => {
const modelToNodeStore = useModelToNodeStore()
modelToNodeStore.registerDefaults()
expect(
modelToNodeStore.getCategoryForNodeType('NonExistentNode')
).toBeUndefined()
expect(modelToNodeStore.getCategoryForNodeType('')).toBeUndefined()
})
it('should return first category when node type exists in multiple categories', () => {
const modelToNodeStore = useModelToNodeStore()
// Test with a node that exists in the defaults but add our own first
// Since defaults register 'StyleModelLoader' in 'style_models',
// we verify our custom registrations come after defaults in Object.entries iteration
const result = modelToNodeStore.getCategoryForNodeType('StyleModelLoader')
expect(result).toBe('style_models') // This proves the method works correctly
// Now test that custom registrations after defaults also work
modelToNodeStore.quickRegister(
'unicorn_styles',
'StyleModelLoader',
'param1'
)
const result2 =
modelToNodeStore.getCategoryForNodeType('StyleModelLoader')
// Should still be style_models since it was registered first by defaults
expect(result2).toBe('style_models')
})
it('should trigger lazy registration when called before registerDefaults', () => {
const modelToNodeStore = useModelToNodeStore()
const result = modelToNodeStore.getCategoryForNodeType(
'CheckpointLoaderSimple'
)
expect(result).toBe('checkpoints')
})
it('should be performant for repeated lookups', () => {
const modelToNodeStore = useModelToNodeStore()
modelToNodeStore.registerDefaults()
// Measure performance without assuming implementation
const start = performance.now()
for (let i = 0; i < 1000; i++) {
modelToNodeStore.getCategoryForNodeType('CheckpointLoaderSimple')
}
const end = performance.now()
// Should be fast enough for UI responsiveness
expect(end - start).toBeLessThan(10)
})
it('should handle invalid input types gracefully', () => {
const modelToNodeStore = useModelToNodeStore()
modelToNodeStore.registerDefaults()
// These should not throw but return undefined
expect(
modelToNodeStore.getCategoryForNodeType(null as any)
).toBeUndefined()
expect(
modelToNodeStore.getCategoryForNodeType(undefined as any)
).toBeUndefined()
expect(
modelToNodeStore.getCategoryForNodeType(123 as any)
).toBeUndefined()
})
it('should be case-sensitive for node type matching', () => {
const modelToNodeStore = useModelToNodeStore()
modelToNodeStore.registerDefaults()
expect(
modelToNodeStore.getCategoryForNodeType('checkpointloadersimple')
).toBeUndefined()
expect(
modelToNodeStore.getCategoryForNodeType('CHECKPOINTLOADERSIMPLE')
).toBeUndefined()
expect(
modelToNodeStore.getCategoryForNodeType('CheckpointLoaderSimple')
).toBe('checkpoints')
})
})
describe('edge cases', () => {
it('should handle empty string model type', () => {
const modelToNodeStore = useModelToNodeStore()

View File

@@ -1,11 +1,10 @@
import { createPinia, setActivePinia } from 'pinia'
import { compare as semverCompare } from 'semver'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
// Mock the dependencies
vi.mock('semver')
vi.mock('@/utils/formatUtil')
vi.mock('@/utils/envUtil')
vi.mock('@/platform/updates/common/releaseService')
vi.mock('@/platform/settings/settingStore')
@@ -114,15 +113,17 @@ describe('useReleaseStore', () => {
expect(store.recentReleases).toEqual(releases.slice(0, 3))
})
it('should show update button (shouldShowUpdateButton)', () => {
vi.mocked(semverCompare).mockReturnValue(1) // newer version available
it('should show update button (shouldShowUpdateButton)', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1) // newer version available
store.releases = [mockRelease]
expect(store.shouldShowUpdateButton).toBe(true)
})
it('should not show update button when no new version', () => {
vi.mocked(semverCompare).mockReturnValue(-1) // current version is newer
it('should not show update button when no new version', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(-1) // current version is newer
store.releases = [mockRelease]
expect(store.shouldShowUpdateButton).toBe(false)
@@ -130,20 +131,21 @@ describe('useReleaseStore', () => {
})
describe('showVersionUpdates setting', () => {
beforeEach(async () => {
beforeEach(() => {
store.releases = [mockRelease]
})
describe('when notifications are enabled', () => {
beforeEach(async () => {
beforeEach(() => {
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
})
})
it('should show toast for medium/high attention releases', () => {
vi.mocked(semverCompare).mockReturnValue(1)
it('should show toast for medium/high attention releases', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
// Need multiple releases for hasMediumOrHighAttention to work
const mediumRelease = {
@@ -156,16 +158,17 @@ describe('useReleaseStore', () => {
expect(store.shouldShowToast).toBe(true)
})
it('should show red dot for new versions', () => {
vi.mocked(semverCompare).mockReturnValue(1)
it('should show red dot for new versions', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
expect(store.shouldShowRedDot).toBe(true)
})
it('should show popup for latest version', () => {
it('should show popup for latest version', async () => {
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0'
vi.mocked(semverCompare).mockReturnValue(0)
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(0)
expect(store.shouldShowPopup).toBe(true)
})
@@ -178,36 +181,37 @@ describe('useReleaseStore', () => {
expect(mockReleaseService.getReleases).toHaveBeenCalledWith({
project: 'comfyui',
current_version: '1.0.0',
form_factor: 'git-windows',
locale: 'en'
form_factor: 'git-windows'
})
})
})
describe('when notifications are disabled', () => {
beforeEach(async () => {
beforeEach(() => {
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return false
return null
})
})
it('should not show toast even with new version available', () => {
vi.mocked(semverCompare).mockReturnValue(1)
it('should not show toast even with new version available', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
expect(store.shouldShowToast).toBe(false)
})
it('should not show red dot even with new version available', () => {
vi.mocked(semverCompare).mockReturnValue(1)
it('should not show red dot even with new version available', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
expect(store.shouldShowRedDot).toBe(false)
})
it('should not show popup even for latest version', () => {
it('should not show popup even for latest version', async () => {
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0'
vi.mocked(semverCompare).mockReturnValue(0)
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(0)
expect(store.shouldShowPopup).toBe(false)
})
@@ -236,8 +240,7 @@ describe('useReleaseStore', () => {
expect(mockReleaseService.getReleases).toHaveBeenCalledWith({
project: 'comfyui',
current_version: '1.0.0',
form_factor: 'git-windows',
locale: 'en'
form_factor: 'git-windows'
})
expect(store.releases).toEqual([mockRelease])
})
@@ -251,8 +254,7 @@ describe('useReleaseStore', () => {
expect(mockReleaseService.getReleases).toHaveBeenCalledWith({
project: 'comfyui',
current_version: '1.0.0',
form_factor: 'desktop-mac',
locale: 'en'
form_factor: 'desktop-mac'
})
})
@@ -422,7 +424,7 @@ describe('useReleaseStore', () => {
})
describe('action handlers', () => {
beforeEach(async () => {
beforeEach(() => {
store.releases = [mockRelease]
})
@@ -479,7 +481,7 @@ describe('useReleaseStore', () => {
})
describe('popup visibility', () => {
it('should show toast for medium/high attention releases', () => {
it('should show toast for medium/high attention releases', async () => {
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Release.Version') return null
if (key === 'Comfy.Release.Status') return null
@@ -487,7 +489,8 @@ describe('useReleaseStore', () => {
return null
})
vi.mocked(semverCompare).mockReturnValue(1)
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
const mediumRelease = { ...mockRelease, attention: 'medium' as const }
store.releases = [
@@ -499,8 +502,9 @@ describe('useReleaseStore', () => {
expect(store.shouldShowToast).toBe(true)
})
it('should show red dot for new versions', () => {
vi.mocked(semverCompare).mockReturnValue(1)
it('should show red dot for new versions', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
@@ -511,14 +515,15 @@ describe('useReleaseStore', () => {
expect(store.shouldShowRedDot).toBe(true)
})
it('should show popup for latest version', () => {
it('should show popup for latest version', async () => {
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
})
vi.mocked(semverCompare).mockReturnValue(0) // versions are equal (latest version)
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(0) // versions are equal (latest version)
store.releases = [mockRelease]
@@ -560,7 +565,7 @@ describe('useReleaseStore', () => {
})
describe('isElectron environment checks', () => {
beforeEach(async () => {
beforeEach(() => {
// Set up a new version available
store.releases = [mockRelease]
mockSettingStore.get.mockImplementation((key: string) => {
@@ -575,8 +580,9 @@ describe('useReleaseStore', () => {
vi.mocked(isElectron).mockReturnValue(true)
})
it('should show toast when conditions are met', () => {
vi.mocked(semverCompare).mockReturnValue(1)
it('should show toast when conditions are met', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
// Need multiple releases for hasMediumOrHighAttention
const mediumRelease = {
@@ -589,16 +595,17 @@ describe('useReleaseStore', () => {
expect(store.shouldShowToast).toBe(true)
})
it('should show red dot when new version available', () => {
vi.mocked(semverCompare).mockReturnValue(1)
it('should show red dot when new version available', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
expect(store.shouldShowRedDot).toBe(true)
})
it('should show popup for latest version', () => {
it('should show popup for latest version', async () => {
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0'
vi.mocked(semverCompare).mockReturnValue(0)
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(0)
expect(store.shouldShowPopup).toBe(true)
})
@@ -610,8 +617,9 @@ describe('useReleaseStore', () => {
vi.mocked(isElectron).mockReturnValue(false)
})
it('should NOT show toast even when all other conditions are met', () => {
vi.mocked(semverCompare).mockReturnValue(1)
it('should NOT show toast even when all other conditions are met', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
// Set up all conditions that would normally show toast
const mediumRelease = {
@@ -624,14 +632,16 @@ describe('useReleaseStore', () => {
expect(store.shouldShowToast).toBe(false)
})
it('should NOT show red dot even when new version available', () => {
vi.mocked(semverCompare).mockReturnValue(1)
it('should NOT show red dot even when new version available', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
expect(store.shouldShowRedDot).toBe(false)
})
it('should NOT show toast regardless of attention level', () => {
vi.mocked(semverCompare).mockReturnValue(1)
it('should NOT show toast regardless of attention level', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
// Test with high attention releases
const highRelease = {
@@ -649,18 +659,19 @@ describe('useReleaseStore', () => {
expect(store.shouldShowToast).toBe(false)
})
it('should NOT show red dot even with high attention release', () => {
vi.mocked(semverCompare).mockReturnValue(1)
it('should NOT show red dot even with high attention release', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
store.releases = [{ ...mockRelease, attention: 'high' as const }]
expect(store.shouldShowRedDot).toBe(false)
})
it('should NOT show popup even for latest version', () => {
it('should NOT show popup even for latest version', async () => {
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0'
vi.mocked(semverCompare).mockReturnValue(0)
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(0)
expect(store.shouldShowPopup).toBe(false)
})

View File

@@ -1,7 +1,7 @@
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it } from 'vitest'
import type { ServerConfig } from '@/constants/serverConfig'
import { ServerConfig } from '@/constants/serverConfig'
import type { FormItem } from '@/platform/settings/types'
import { useServerConfigStore } from '@/stores/serverConfigStore'

View File

@@ -3,9 +3,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import type { Subgraph } from '@/lib/litegraph/src/litegraph'
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
import {
type LoadedComfyWorkflow,
ComfyWorkflow,
LoadedComfyWorkflow,
useWorkflowBookmarkStore,
useWorkflowStore
} from '@/platform/workflow/management/stores/workflowStore'