Merge remote-tracking branch 'upstream/main' into js/async_nodes

This commit is contained in:
Jacob Segal
2025-07-10 15:35:08 -07:00
75 changed files with 15370 additions and 519 deletions

View File

@@ -227,7 +227,7 @@ describe('useNodePricing', () => {
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.02/Run')
expect(price).toBe('$0.020/Run')
})
it('should return $0.018 for 512x512 size', () => {
@@ -255,7 +255,7 @@ describe('useNodePricing', () => {
const node = createMockNode('OpenAIDalle2', [])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.016-0.02/Run (varies with size)')
expect(price).toBe('$0.016-0.02 x n/Run (varies with size & n)')
})
})
@@ -295,19 +295,19 @@ describe('useNodePricing', () => {
const node = createMockNode('OpenAIGPTImage1', [])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.011-0.30/Run (varies with quality)')
expect(price).toBe('$0.011-0.30 x n/Run (varies with quality & n)')
})
})
describe('dynamic pricing - IdeogramV3', () => {
it('should return $0.08 for Quality rendering speed', () => {
it('should return $0.09 for Quality rendering speed', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('IdeogramV3', [
{ name: 'rendering_speed', value: 'Quality' }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.08/Run')
expect(price).toBe('$0.09/Run')
})
it('should return $0.06 for Balanced rendering speed', () => {
@@ -335,7 +335,31 @@ describe('useNodePricing', () => {
const node = createMockNode('IdeogramV3', [])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.03-0.08/Run (varies with rendering speed)')
expect(price).toBe(
'$0.03-0.08 x num_images/Run (varies with rendering speed & num_images)'
)
})
it('should multiply price by num_images for Quality rendering speed', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('IdeogramV3', [
{ name: 'rendering_speed', value: 'Quality' },
{ name: 'num_images', value: 3 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.27/Run') // 0.09 * 3
})
it('should multiply price by num_images for Turbo rendering speed', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('IdeogramV3', [
{ name: 'rendering_speed', value: 'Turbo' },
{ name: 'num_images', value: 5 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.15/Run') // 0.03 * 5
})
})
@@ -742,6 +766,29 @@ describe('useNodePricing', () => {
expect(widgetNames).toEqual([])
})
describe('Ideogram nodes with num_images parameter', () => {
it('should return correct widget names for IdeogramV1', () => {
const { getRelevantWidgetNames } = useNodePricing()
const widgetNames = getRelevantWidgetNames('IdeogramV1')
expect(widgetNames).toEqual(['num_images'])
})
it('should return correct widget names for IdeogramV2', () => {
const { getRelevantWidgetNames } = useNodePricing()
const widgetNames = getRelevantWidgetNames('IdeogramV2')
expect(widgetNames).toEqual(['num_images'])
})
it('should return correct widget names for IdeogramV3', () => {
const { getRelevantWidgetNames } = useNodePricing()
const widgetNames = getRelevantWidgetNames('IdeogramV3')
expect(widgetNames).toEqual(['rendering_speed', 'num_images'])
})
})
describe('Recraft nodes with n parameter', () => {
it('should return correct widget names for RecraftTextToImageNode', () => {
const { getRelevantWidgetNames } = useNodePricing()
@@ -759,6 +806,54 @@ describe('useNodePricing', () => {
})
})
describe('Ideogram nodes dynamic pricing', () => {
it('should calculate dynamic pricing for IdeogramV1 based on num_images value', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('IdeogramV1', [
{ name: 'num_images', value: 3 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.18/Run') // 0.06 * 3
})
it('should calculate dynamic pricing for IdeogramV2 based on num_images value', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('IdeogramV2', [
{ name: 'num_images', value: 4 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.32/Run') // 0.08 * 4
})
it('should fall back to static display when num_images widget is missing for IdeogramV1', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('IdeogramV1', [])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.06 x num_images/Run')
})
it('should fall back to static display when num_images widget is missing for IdeogramV2', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('IdeogramV2', [])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.08 x num_images/Run')
})
it('should handle edge case when num_images value is 1 for IdeogramV1', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('IdeogramV1', [
{ name: 'num_images', value: 1 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.06/Run') // 0.06 * 1
})
})
describe('Recraft nodes dynamic pricing', () => {
it('should calculate dynamic pricing for RecraftTextToImageNode based on n value', () => {
const { getNodeDisplayPrice } = useNodePricing()
@@ -799,4 +894,133 @@ describe('useNodePricing', () => {
})
})
})
describe('OpenAI nodes dynamic pricing with n parameter', () => {
it('should calculate dynamic pricing for OpenAIDalle2 based on size and n', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('OpenAIDalle2', [
{ name: 'size', value: '1024x1024' },
{ name: 'n', value: 3 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.060/Run') // 0.02 * 3
})
it('should calculate dynamic pricing for OpenAIGPTImage1 based on quality and n', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('OpenAIGPTImage1', [
{ name: 'quality', value: 'low' },
{ name: 'n', value: 2 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.011-0.02 x 2/Run')
})
it('should fall back to static display when n widget is missing for OpenAIDalle2', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('OpenAIDalle2', [
{ name: 'size', value: '512x512' }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.018/Run') // n defaults to 1
})
})
describe('KlingImageGenerationNode dynamic pricing with n parameter', () => {
it('should calculate dynamic pricing for text-to-image with kling-v1', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('KlingImageGenerationNode', [
{ name: 'model_name', value: 'kling-v1' },
{ name: 'n', value: 4 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.0140/Run') // 0.0035 * 4
})
it('should calculate dynamic pricing for text-to-image with kling-v1-5', () => {
const { getNodeDisplayPrice } = useNodePricing()
// Mock node without image input (text-to-image mode)
const node = createMockNode('KlingImageGenerationNode', [
{ name: 'model_name', value: 'kling-v1-5' },
{ name: 'n', value: 2 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.0280/Run') // For kling-v1-5 text-to-image: 0.014 * 2
})
it('should fall back to static display when model widget is missing', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('KlingImageGenerationNode', [])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.0035-0.028 x n/Run (varies with modality & model)')
})
})
describe('New Recraft nodes dynamic pricing', () => {
it('should calculate dynamic pricing for RecraftGenerateImageNode', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('RecraftGenerateImageNode', [
{ name: 'n', value: 3 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.12/Run') // 0.04 * 3
})
it('should calculate dynamic pricing for RecraftVectorizeImageNode', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('RecraftVectorizeImageNode', [
{ name: 'n', value: 5 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.05/Run') // 0.01 * 5
})
it('should calculate dynamic pricing for RecraftGenerateVectorImageNode', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('RecraftGenerateVectorImageNode', [
{ name: 'n', value: 2 }
])
const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.16/Run') // 0.08 * 2
})
})
describe('Widget names for reactive updates', () => {
it('should include n parameter for OpenAI nodes', () => {
const { getRelevantWidgetNames } = useNodePricing()
expect(getRelevantWidgetNames('OpenAIDalle2')).toEqual(['size', 'n'])
expect(getRelevantWidgetNames('OpenAIGPTImage1')).toEqual([
'quality',
'n'
])
})
it('should include n parameter for Kling and new Recraft nodes', () => {
const { getRelevantWidgetNames } = useNodePricing()
expect(getRelevantWidgetNames('KlingImageGenerationNode')).toEqual([
'modality',
'model_name',
'n'
])
expect(getRelevantWidgetNames('RecraftVectorizeImageNode')).toEqual(['n'])
expect(getRelevantWidgetNames('RecraftGenerateImageNode')).toEqual(['n'])
expect(getRelevantWidgetNames('RecraftGenerateVectorImageNode')).toEqual([
'n'
])
expect(
getRelevantWidgetNames('RecraftGenerateColorFromImageNode')
).toEqual(['n'])
})
})
})

View File

@@ -0,0 +1,425 @@
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import { useSettingSearch } from '@/composables/setting/useSettingSearch'
import { st } from '@/i18n'
import { getSettingInfo, useSettingStore } from '@/stores/settingStore'
// Mock dependencies
vi.mock('@/i18n', () => ({
st: vi.fn((_: string, fallback: string) => fallback)
}))
vi.mock('@/stores/settingStore', () => ({
useSettingStore: vi.fn(),
getSettingInfo: vi.fn()
}))
describe('useSettingSearch', () => {
let mockSettingStore: any
let mockSettings: any
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
// Mock settings data
mockSettings = {
'Category.Setting1': {
id: 'Category.Setting1',
name: 'Setting One',
type: 'text',
defaultValue: 'default',
category: ['Category', 'Basic']
},
'Category.Setting2': {
id: 'Category.Setting2',
name: 'Setting Two',
type: 'boolean',
defaultValue: false,
category: ['Category', 'Advanced']
},
'Category.HiddenSetting': {
id: 'Category.HiddenSetting',
name: 'Hidden Setting',
type: 'hidden',
defaultValue: 'hidden',
category: ['Category', 'Basic']
},
'Category.DeprecatedSetting': {
id: 'Category.DeprecatedSetting',
name: 'Deprecated Setting',
type: 'text',
defaultValue: 'deprecated',
deprecated: true,
category: ['Category', 'Advanced']
},
'Other.Setting3': {
id: 'Other.Setting3',
name: 'Other Setting',
type: 'select',
defaultValue: 'option1',
category: ['Other', 'SubCategory']
}
}
// Mock setting store
mockSettingStore = {
settingsById: mockSettings
}
vi.mocked(useSettingStore).mockReturnValue(mockSettingStore)
// Mock getSettingInfo function
vi.mocked(getSettingInfo).mockImplementation((setting: any) => {
const parts = setting.category || setting.id.split('.')
return {
category: parts[0] ?? 'Other',
subCategory: parts[1] ?? 'Other'
}
})
// Mock st function to return fallback value
vi.mocked(st).mockImplementation((_: string, fallback: string) => fallback)
})
describe('initialization', () => {
it('initializes with default state', () => {
const search = useSettingSearch()
expect(search.searchQuery.value).toBe('')
expect(search.filteredSettingIds.value).toEqual([])
expect(search.searchInProgress.value).toBe(false)
expect(search.queryIsEmpty.value).toBe(true)
expect(search.inSearch.value).toBe(false)
expect(search.searchResultsCategories.value).toEqual(new Set())
})
})
describe('reactive properties', () => {
it('queryIsEmpty computed property works correctly', () => {
const search = useSettingSearch()
expect(search.queryIsEmpty.value).toBe(true)
search.searchQuery.value = 'test'
expect(search.queryIsEmpty.value).toBe(false)
search.searchQuery.value = ''
expect(search.queryIsEmpty.value).toBe(true)
})
it('inSearch computed property works correctly', () => {
const search = useSettingSearch()
// Empty query, not in search
expect(search.inSearch.value).toBe(false)
// Has query but search in progress
search.searchQuery.value = 'test'
search.searchInProgress.value = true
expect(search.inSearch.value).toBe(false)
// Has query and search complete
search.searchInProgress.value = false
expect(search.inSearch.value).toBe(true)
})
it('searchResultsCategories computed property works correctly', () => {
const search = useSettingSearch()
// No results
expect(search.searchResultsCategories.value).toEqual(new Set())
// Add some filtered results
search.filteredSettingIds.value = ['Category.Setting1', 'Other.Setting3']
expect(search.searchResultsCategories.value).toEqual(
new Set(['Category', 'Other'])
)
})
it('watches searchQuery and sets searchInProgress to true', async () => {
const search = useSettingSearch()
expect(search.searchInProgress.value).toBe(false)
search.searchQuery.value = 'test'
await nextTick()
expect(search.searchInProgress.value).toBe(true)
})
})
describe('handleSearch', () => {
it('clears results when query is empty', () => {
const search = useSettingSearch()
search.filteredSettingIds.value = ['Category.Setting1']
search.handleSearch('')
expect(search.filteredSettingIds.value).toEqual([])
})
it('filters settings by ID (case insensitive)', () => {
const search = useSettingSearch()
search.handleSearch('category.setting1')
expect(search.filteredSettingIds.value).toContain('Category.Setting1')
expect(search.filteredSettingIds.value).not.toContain('Other.Setting3')
})
it('filters settings by name (case insensitive)', () => {
const search = useSettingSearch()
search.handleSearch('setting one')
expect(search.filteredSettingIds.value).toContain('Category.Setting1')
expect(search.filteredSettingIds.value).not.toContain('Category.Setting2')
})
it('filters settings by category', () => {
const search = useSettingSearch()
search.handleSearch('other')
expect(search.filteredSettingIds.value).toContain('Other.Setting3')
expect(search.filteredSettingIds.value).not.toContain('Category.Setting1')
})
it('excludes hidden settings from results', () => {
const search = useSettingSearch()
search.handleSearch('hidden')
expect(search.filteredSettingIds.value).not.toContain(
'Category.HiddenSetting'
)
})
it('excludes deprecated settings from results', () => {
const search = useSettingSearch()
search.handleSearch('deprecated')
expect(search.filteredSettingIds.value).not.toContain(
'Category.DeprecatedSetting'
)
})
it('sets searchInProgress to false after search', () => {
const search = useSettingSearch()
search.searchInProgress.value = true
search.handleSearch('test')
expect(search.searchInProgress.value).toBe(false)
})
it('includes visible settings in results', () => {
const search = useSettingSearch()
search.handleSearch('setting')
expect(search.filteredSettingIds.value).toEqual(
expect.arrayContaining([
'Category.Setting1',
'Category.Setting2',
'Other.Setting3'
])
)
expect(search.filteredSettingIds.value).not.toContain(
'Category.HiddenSetting'
)
expect(search.filteredSettingIds.value).not.toContain(
'Category.DeprecatedSetting'
)
})
it('includes all visible settings in comprehensive search', () => {
const search = useSettingSearch()
// Search for a partial match that should include multiple settings
search.handleSearch('setting')
// Should find all visible settings (not hidden/deprecated)
expect(search.filteredSettingIds.value.length).toBeGreaterThan(0)
expect(search.filteredSettingIds.value).toEqual(
expect.arrayContaining([
'Category.Setting1',
'Category.Setting2',
'Other.Setting3'
])
)
})
it('uses translated categories for search', () => {
const search = useSettingSearch()
// Mock st to return translated category names
vi.mocked(st).mockImplementation((key: string, fallback: string) => {
if (key === 'settingsCategories.Category') {
return 'Translated Category'
}
return fallback
})
search.handleSearch('translated category')
expect(search.filteredSettingIds.value).toEqual(
expect.arrayContaining(['Category.Setting1', 'Category.Setting2'])
)
})
})
describe('getSearchResults', () => {
it('groups results by subcategory', () => {
const search = useSettingSearch()
search.filteredSettingIds.value = [
'Category.Setting1',
'Category.Setting2'
]
const results = search.getSearchResults(null)
expect(results).toEqual([
{
label: 'Basic',
settings: [mockSettings['Category.Setting1']]
},
{
label: 'Advanced',
settings: [mockSettings['Category.Setting2']]
}
])
})
it('filters results by active category', () => {
const search = useSettingSearch()
search.filteredSettingIds.value = ['Category.Setting1', 'Other.Setting3']
const activeCategory = { label: 'Category' } as any
const results = search.getSearchResults(activeCategory)
expect(results).toEqual([
{
label: 'Basic',
settings: [mockSettings['Category.Setting1']]
}
])
})
it('returns all results when no active category', () => {
const search = useSettingSearch()
search.filteredSettingIds.value = ['Category.Setting1', 'Other.Setting3']
const results = search.getSearchResults(null)
expect(results).toEqual([
{
label: 'Basic',
settings: [mockSettings['Category.Setting1']]
},
{
label: 'SubCategory',
settings: [mockSettings['Other.Setting3']]
}
])
})
it('returns empty array when no filtered results', () => {
const search = useSettingSearch()
search.filteredSettingIds.value = []
const results = search.getSearchResults(null)
expect(results).toEqual([])
})
it('handles multiple settings in same subcategory', () => {
const search = useSettingSearch()
// Add another setting to Basic subcategory
mockSettings['Category.Setting4'] = {
id: 'Category.Setting4',
name: 'Setting Four',
type: 'text',
defaultValue: 'default',
category: ['Category', 'Basic']
}
search.filteredSettingIds.value = [
'Category.Setting1',
'Category.Setting4'
]
const results = search.getSearchResults(null)
expect(results).toEqual([
{
label: 'Basic',
settings: [
mockSettings['Category.Setting1'],
mockSettings['Category.Setting4']
]
}
])
})
})
describe('edge cases', () => {
it('handles empty settings store', () => {
mockSettingStore.settingsById = {}
const search = useSettingSearch()
search.handleSearch('test')
expect(search.filteredSettingIds.value).toEqual([])
})
it('handles settings with undefined category', () => {
mockSettings['NoCategorySetting'] = {
id: 'NoCategorySetting',
name: 'No Category',
type: 'text',
defaultValue: 'default'
}
const search = useSettingSearch()
search.handleSearch('category')
expect(search.filteredSettingIds.value).toContain('NoCategorySetting')
})
it('handles special characters in search query', () => {
const search = useSettingSearch()
// Search for part of the ID that contains a dot
search.handleSearch('category.setting')
expect(search.filteredSettingIds.value).toContain('Category.Setting1')
})
it('handles very long search queries', () => {
const search = useSettingSearch()
const longQuery = 'a'.repeat(1000)
search.handleSearch(longQuery)
expect(search.filteredSettingIds.value).toEqual([])
})
it('handles rapid consecutive searches', async () => {
const search = useSettingSearch()
search.handleSearch('setting')
search.handleSearch('other')
search.handleSearch('category')
expect(search.filteredSettingIds.value).toEqual(
expect.arrayContaining(['Category.Setting1', 'Category.Setting2'])
)
})
})
})

View File

@@ -0,0 +1,442 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mockLocalStorage = vi.hoisted(() => ({
getItem: vi.fn(),
setItem: vi.fn(),
removeItem: vi.fn(),
clear: vi.fn()
}))
Object.defineProperty(window, 'localStorage', {
value: mockLocalStorage,
writable: true
})
vi.mock('@/config/version', () => ({
__COMFYUI_FRONTEND_VERSION__: '1.24.0'
}))
//@ts-expect-error Define global for the test
global.__COMFYUI_FRONTEND_VERSION__ = '1.24.0'
describe('newUserService', () => {
let service: ReturnType<
typeof import('@/services/newUserService').newUserService
>
let mockSettingStore: any
let newUserService: typeof import('@/services/newUserService').newUserService
beforeEach(async () => {
vi.clearAllMocks()
vi.resetModules()
const module = await import('@/services/newUserService')
newUserService = module.newUserService
service = newUserService()
mockSettingStore = {
settingValues: {},
get: vi.fn(),
set: vi.fn()
}
mockLocalStorage.getItem.mockReturnValue(null)
})
describe('checkIsNewUser logic', () => {
it('should identify new user when all conditions are met', async () => {
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(service.isNewUser()).toBe(true)
})
it('should identify new user when settings exist but TutorialCompleted is undefined', async () => {
mockSettingStore.settingValues = { 'some.setting': 'value' }
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(service.isNewUser()).toBe(true)
})
it('should identify existing user when tutorial is completed', async () => {
mockSettingStore.settingValues = { 'Comfy.TutorialCompleted': true }
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return true
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(service.isNewUser()).toBe(false)
})
it('should identify existing user when workflow exists', async () => {
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockImplementation((key: string) => {
if (key === 'workflow') return 'some-workflow'
return null
})
await service.initializeIfNewUser(mockSettingStore)
expect(service.isNewUser()).toBe(false)
})
it('should identify existing user when previous workflow exists', async () => {
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockImplementation((key: string) => {
if (key === 'Comfy.PreviousWorkflow') return 'some-previous-workflow'
return null
})
await service.initializeIfNewUser(mockSettingStore)
expect(service.isNewUser()).toBe(false)
})
it('should identify new user when tutorial is explicitly false', async () => {
mockSettingStore.settingValues = { 'Comfy.TutorialCompleted': false }
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return false
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(service.isNewUser()).toBe(true)
})
it('should identify existing user when has both settings and tutorial completed', async () => {
mockSettingStore.settingValues = {
'some.setting': 'value',
'Comfy.TutorialCompleted': true
}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return true
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(service.isNewUser()).toBe(false)
})
it('should identify existing user when only one condition fails', async () => {
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockImplementation((key: string) => {
if (key === 'workflow') return 'some-workflow'
if (key === 'Comfy.PreviousWorkflow') return null
return null
})
await service.initializeIfNewUser(mockSettingStore)
expect(service.isNewUser()).toBe(false)
})
})
describe('registerInitCallback', () => {
it('should execute callback immediately if new user is already determined', async () => {
const mockCallback = vi.fn().mockResolvedValue(undefined)
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(service.isNewUser()).toBe(true)
await service.registerInitCallback(mockCallback)
expect(mockCallback).toHaveBeenCalledTimes(1)
})
it('should queue callbacks when user status is not determined', async () => {
const mockCallback = vi.fn().mockResolvedValue(undefined)
await service.registerInitCallback(mockCallback)
expect(mockCallback).not.toHaveBeenCalled()
expect(service.isNewUser()).toBeNull()
})
it('should handle callback errors gracefully', async () => {
const mockCallback = vi
.fn()
.mockRejectedValue(new Error('Callback error'))
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
await service.registerInitCallback(mockCallback)
expect(consoleSpy).toHaveBeenCalledWith(
'New user initialization callback failed:',
expect.any(Error)
)
consoleSpy.mockRestore()
})
})
describe('initializeIfNewUser', () => {
it('should set installed version for new users', async () => {
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(mockSettingStore.set).toHaveBeenCalledWith(
'Comfy.InstalledVersion',
'1.24.0'
)
})
it('should not set installed version for existing users', async () => {
mockSettingStore.settingValues = { 'some.setting': 'value' }
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return true
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(mockSettingStore.set).not.toHaveBeenCalled()
})
it('should execute pending callbacks for new users', async () => {
const mockCallback1 = vi.fn().mockResolvedValue(undefined)
const mockCallback2 = vi.fn().mockResolvedValue(undefined)
await service.registerInitCallback(mockCallback1)
await service.registerInitCallback(mockCallback2)
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(mockCallback1).toHaveBeenCalledTimes(1)
expect(mockCallback2).toHaveBeenCalledTimes(1)
})
it('should not execute pending callbacks for existing users', async () => {
const mockCallback = vi.fn().mockResolvedValue(undefined)
await service.registerInitCallback(mockCallback)
mockSettingStore.settingValues = { 'some.setting': 'value' }
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return true
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(mockCallback).not.toHaveBeenCalled()
})
it('should handle callback errors during initialization', async () => {
const mockCallback = vi.fn().mockRejectedValue(new Error('Init error'))
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
await service.registerInitCallback(mockCallback)
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(consoleSpy).toHaveBeenCalledWith(
'New user initialization callback failed:',
expect.any(Error)
)
consoleSpy.mockRestore()
})
it('should not reinitialize if already determined', async () => {
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(mockSettingStore.set).toHaveBeenCalledTimes(1)
await service.initializeIfNewUser(mockSettingStore)
expect(mockSettingStore.set).toHaveBeenCalledTimes(1)
})
it('should correctly determine new user status', async () => {
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
// Before initialization, isNewUser should return null
expect(service.isNewUser()).toBeNull()
await service.initializeIfNewUser(mockSettingStore)
// After initialization, isNewUser should return true for a new user
expect(service.isNewUser()).toBe(true)
// Should set the installed version for new users
expect(mockSettingStore.set).toHaveBeenCalledWith(
'Comfy.InstalledVersion',
expect.any(String)
)
})
})
describe('isNewUser', () => {
it('should return null before determination', () => {
expect(service.isNewUser()).toBeNull()
})
it('should return cached result after determination', async () => {
mockSettingStore.settingValues = {}
mockSettingStore.get.mockReturnValue(undefined)
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(service.isNewUser()).toBe(true)
})
})
describe('edge cases', () => {
it('should handle settingStore.get returning false as not completed', async () => {
mockSettingStore.settingValues = { 'Comfy.TutorialCompleted': false }
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return false
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
expect(service.isNewUser()).toBe(true)
})
it('should handle multiple callback registrations after initialization', async () => {
const mockCallback1 = vi.fn().mockResolvedValue(undefined)
const mockCallback2 = vi.fn().mockResolvedValue(undefined)
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service.initializeIfNewUser(mockSettingStore)
await service.registerInitCallback(mockCallback1)
await service.registerInitCallback(mockCallback2)
expect(mockCallback1).toHaveBeenCalledTimes(1)
expect(mockCallback2).toHaveBeenCalledTimes(1)
})
})
describe('state sharing between instances', () => {
it('should share state between multiple service instances', async () => {
const service1 = newUserService()
const service2 = newUserService()
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service1.initializeIfNewUser(mockSettingStore)
expect(service2.isNewUser()).toBe(true)
expect(service1.isNewUser()).toBe(service2.isNewUser())
})
it('should execute callbacks registered on different instances', async () => {
const service1 = newUserService()
const service2 = newUserService()
const mockCallback1 = vi.fn().mockResolvedValue(undefined)
const mockCallback2 = vi.fn().mockResolvedValue(undefined)
await service1.registerInitCallback(mockCallback1)
await service2.registerInitCallback(mockCallback2)
mockSettingStore.settingValues = {}
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.TutorialCompleted') return undefined
return undefined
})
mockLocalStorage.getItem.mockReturnValue(null)
await service1.initializeIfNewUser(mockSettingStore)
expect(mockCallback1).toHaveBeenCalledTimes(1)
expect(mockCallback2).toHaveBeenCalledTimes(1)
})
})
})

View File

@@ -72,6 +72,14 @@ describe('useComfyRegistryStore', () => {
error: ReturnType<typeof ref<string | null>>
listAllPacks: ReturnType<typeof vi.fn>
getPackById: ReturnType<typeof vi.fn>
inferPackFromNodeName: ReturnType<typeof vi.fn>
search: ReturnType<typeof vi.fn>
getPackVersions: ReturnType<typeof vi.fn>
getPackByVersion: ReturnType<typeof vi.fn>
getPublisherById: ReturnType<typeof vi.fn>
listPacksForPublisher: ReturnType<typeof vi.fn>
getNodeDefs: ReturnType<typeof vi.fn>
postPackReview: ReturnType<typeof vi.fn>
}
beforeEach(() => {
@@ -106,7 +114,15 @@ describe('useComfyRegistryStore', () => {
// Otherwise return paginated results
return Promise.resolve(mockListResult)
}),
getPackById: vi.fn().mockResolvedValue(mockNodePack)
getPackById: vi.fn().mockResolvedValue(mockNodePack),
inferPackFromNodeName: vi.fn().mockResolvedValue(mockNodePack),
search: vi.fn().mockResolvedValue(mockListResult),
getPackVersions: vi.fn().mockResolvedValue([]),
getPackByVersion: vi.fn().mockResolvedValue({}),
getPublisherById: vi.fn().mockResolvedValue({}),
listPacksForPublisher: vi.fn().mockResolvedValue([]),
getNodeDefs: vi.fn().mockResolvedValue({}),
postPackReview: vi.fn().mockResolvedValue({})
}
vi.mocked(useComfyRegistryService).mockReturnValue(
@@ -186,4 +202,58 @@ describe('useComfyRegistryStore', () => {
expect.any(Object) // abort signal
)
})
describe('inferPackFromNodeName', () => {
it('should fetch a pack by comfy node name', async () => {
const store = useComfyRegistryStore()
const nodeName = 'KSampler'
const result = await store.inferPackFromNodeName.call(nodeName)
expect(result).toEqual(mockNodePack)
expect(mockRegistryService.inferPackFromNodeName).toHaveBeenCalledWith(
nodeName,
expect.any(Object) // abort signal
)
})
it('should cache results', async () => {
const store = useComfyRegistryStore()
const nodeName = 'KSampler'
// First call
const result1 = await store.inferPackFromNodeName.call(nodeName)
expect(mockRegistryService.inferPackFromNodeName).toHaveBeenCalledTimes(1)
// Second call - should use cache
const result2 = await store.inferPackFromNodeName.call(nodeName)
expect(mockRegistryService.inferPackFromNodeName).toHaveBeenCalledTimes(1)
expect(result2).toEqual(result1)
})
it('should handle null results when node is not found', async () => {
mockRegistryService.inferPackFromNodeName.mockResolvedValueOnce(null)
const store = useComfyRegistryStore()
const result = await store.inferPackFromNodeName.call('NonExistentNode')
expect(result).toBeNull()
})
it('should clear cache when clearCache is called', async () => {
const store = useComfyRegistryStore()
const nodeName = 'KSampler'
// First call to populate cache
await store.inferPackFromNodeName.call(nodeName)
expect(mockRegistryService.inferPackFromNodeName).toHaveBeenCalledTimes(1)
// Clear cache
store.clearCache()
// Call again - should hit the service again
await store.inferPackFromNodeName.call(nodeName)
expect(mockRegistryService.inferPackFromNodeName).toHaveBeenCalledTimes(2)
})
})
})

View File

@@ -61,6 +61,12 @@ describe('useReleaseStore', () => {
vi.mocked(useSettingStore).mockReturnValue(mockSettingStore)
vi.mocked(useSystemStatsStore).mockReturnValue(mockSystemStatsStore)
// Default showVersionUpdates to true
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
})
store = useReleaseStore()
})
@@ -114,6 +120,107 @@ describe('useReleaseStore', () => {
})
})
describe('showVersionUpdates setting', () => {
beforeEach(() => {
store.releases = [mockRelease]
})
describe('when notifications are enabled', () => {
beforeEach(() => {
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
})
})
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 = {
...mockRelease,
id: 2,
attention: 'medium' as const
}
store.releases = [mockRelease, mediumRelease]
expect(store.shouldShowToast).toBe(true)
})
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', async () => {
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0'
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(0)
expect(store.shouldShowPopup).toBe(true)
})
it('should fetch releases during initialization', async () => {
mockReleaseService.getReleases.mockResolvedValue([mockRelease])
await store.initialize()
expect(mockReleaseService.getReleases).toHaveBeenCalledWith({
project: 'comfyui',
current_version: '1.0.0',
form_factor: 'git-windows'
})
})
})
describe('when notifications are disabled', () => {
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', 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', 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', async () => {
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0'
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(0)
expect(store.shouldShowPopup).toBe(false)
})
it('should skip fetching releases during initialization', async () => {
await store.initialize()
expect(mockReleaseService.getReleases).not.toHaveBeenCalled()
})
it('should not fetch releases when calling fetchReleases directly', async () => {
await store.fetchReleases()
expect(mockReleaseService.getReleases).not.toHaveBeenCalled()
expect(store.isLoading).toBe(false)
})
})
})
describe('release initialization', () => {
it('should fetch releases successfully', async () => {
mockReleaseService.getReleases.mockResolvedValue([mockRelease])
@@ -184,6 +291,17 @@ describe('useReleaseStore', () => {
expect(mockSystemStatsStore.fetchSystemStats).toHaveBeenCalled()
})
it('should not set loading state when notifications disabled', async () => {
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return false
return null
})
await store.initialize()
expect(store.isLoading).toBe(false)
})
})
describe('action handlers', () => {
@@ -248,6 +366,7 @@ describe('useReleaseStore', () => {
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Release.Version') return null
if (key === 'Comfy.Release.Status') return null
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
})
@@ -267,7 +386,10 @@ describe('useReleaseStore', () => {
it('should show red dot for new versions', async () => {
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(1)
mockSettingStore.get.mockReturnValue(null)
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
})
store.releases = [mockRelease]
@@ -276,7 +398,10 @@ describe('useReleaseStore', () => {
it('should show popup for latest version', async () => {
mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0' // Same as release
mockSettingStore.get.mockReturnValue(null)
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
return null
})
const { compareVersions } = await import('@/utils/formatUtil')
vi.mocked(compareVersions).mockReturnValue(0) // versions are equal (latest version)
@@ -286,4 +411,37 @@ describe('useReleaseStore', () => {
expect(store.shouldShowPopup).toBe(true)
})
})
describe('edge cases', () => {
it('should handle missing system stats gracefully', async () => {
mockSystemStatsStore.systemStats = null
mockSettingStore.get.mockImplementation((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return false
return null
})
await store.initialize()
// Should not fetch system stats when notifications disabled
expect(mockSystemStatsStore.fetchSystemStats).not.toHaveBeenCalled()
})
it('should handle concurrent fetchReleases calls', async () => {
mockReleaseService.getReleases.mockImplementation(
() =>
new Promise((resolve) =>
setTimeout(() => resolve([mockRelease]), 100)
)
)
// Start two concurrent calls
const promise1 = store.fetchReleases()
const promise2 = store.fetchReleases()
await Promise.all([promise1, promise2])
// Should only call API once due to loading check
expect(mockReleaseService.getReleases).toHaveBeenCalledTimes(1)
})
})
})

View File

@@ -109,6 +109,241 @@ describe('useSettingStore', () => {
})
})
describe('getDefaultValue', () => {
beforeEach(() => {
// Set up installed version for most tests
store.settingValues['Comfy.InstalledVersion'] = '1.30.0'
})
it('should return regular default value when no defaultsByInstallVersion', () => {
const setting: SettingParams = {
id: 'test.setting',
name: 'Test Setting',
type: 'text',
defaultValue: 'regular-default'
}
store.addSetting(setting)
const result = store.getDefaultValue('test.setting')
expect(result).toBe('regular-default')
})
it('should return versioned default when user version matches', () => {
const setting: SettingParams = {
id: 'test.setting',
name: 'Test Setting',
type: 'text',
defaultValue: 'regular-default',
defaultsByInstallVersion: {
'1.21.3': 'version-1.21.3-default',
'1.40.3': 'version-1.40.3-default'
}
}
store.addSetting(setting)
const result = store.getDefaultValue('test.setting')
// installedVersion is 1.30.0, so should get 1.21.3 default
expect(result).toBe('version-1.21.3-default')
})
it('should return latest versioned default when user version is higher', () => {
store.settingValues['Comfy.InstalledVersion'] = '1.50.0'
const setting: SettingParams = {
id: 'test.setting',
name: 'Test Setting',
type: 'text',
defaultValue: 'regular-default',
defaultsByInstallVersion: {
'1.21.3': 'version-1.21.3-default',
'1.40.3': 'version-1.40.3-default'
}
}
store.addSetting(setting)
const result = store.getDefaultValue('test.setting')
// installedVersion is 1.50.0, so should get 1.40.3 default
expect(result).toBe('version-1.40.3-default')
})
it('should return regular default when user version is lower than all versioned defaults', () => {
store.settingValues['Comfy.InstalledVersion'] = '1.10.0'
const setting: SettingParams = {
id: 'test.setting',
name: 'Test Setting',
type: 'text',
defaultValue: 'regular-default',
defaultsByInstallVersion: {
'1.21.3': 'version-1.21.3-default',
'1.40.3': 'version-1.40.3-default'
}
}
store.addSetting(setting)
const result = store.getDefaultValue('test.setting')
// installedVersion is 1.10.0, lower than all versioned defaults
expect(result).toBe('regular-default')
})
it('should return regular default when no installed version (existing users)', () => {
// Clear installed version to simulate existing user
delete store.settingValues['Comfy.InstalledVersion']
const setting: SettingParams = {
id: 'test.setting',
name: 'Test Setting',
type: 'text',
defaultValue: 'regular-default',
defaultsByInstallVersion: {
'1.21.3': 'version-1.21.3-default',
'1.40.3': 'version-1.40.3-default'
}
}
store.addSetting(setting)
const result = store.getDefaultValue('test.setting')
// No installed version, should use backward compatibility
expect(result).toBe('regular-default')
})
it('should handle function-based versioned defaults', () => {
const setting: SettingParams = {
id: 'test.setting',
name: 'Test Setting',
type: 'text',
defaultValue: 'regular-default',
defaultsByInstallVersion: {
'1.21.3': () => 'dynamic-version-1.21.3-default',
'1.40.3': () => 'dynamic-version-1.40.3-default'
}
}
store.addSetting(setting)
const result = store.getDefaultValue('test.setting')
// installedVersion is 1.30.0, so should get 1.21.3 default (executed)
expect(result).toBe('dynamic-version-1.21.3-default')
})
it('should handle function-based regular defaults with versioned defaults', () => {
store.settingValues['Comfy.InstalledVersion'] = '1.10.0'
const setting: SettingParams = {
id: 'test.setting',
name: 'Test Setting',
type: 'text',
defaultValue: () => 'dynamic-regular-default',
defaultsByInstallVersion: {
'1.21.3': 'version-1.21.3-default',
'1.40.3': 'version-1.40.3-default'
}
}
store.addSetting(setting)
const result = store.getDefaultValue('test.setting')
// installedVersion is 1.10.0, should fallback to function-based regular default
expect(result).toBe('dynamic-regular-default')
})
it('should handle complex version comparison correctly', () => {
const setting: SettingParams = {
id: 'test.setting',
name: 'Test Setting',
type: 'text',
defaultValue: 'regular-default',
defaultsByInstallVersion: {
'1.21.3': 'version-1.21.3-default',
'1.21.10': 'version-1.21.10-default',
'1.40.3': 'version-1.40.3-default'
}
}
store.addSetting(setting)
// Test with 1.21.5 - should get 1.21.3 default
store.settingValues['Comfy.InstalledVersion'] = '1.21.5'
expect(store.getDefaultValue('test.setting')).toBe(
'version-1.21.3-default'
)
// Test with 1.21.15 - should get 1.21.10 default
store.settingValues['Comfy.InstalledVersion'] = '1.21.15'
expect(store.getDefaultValue('test.setting')).toBe(
'version-1.21.10-default'
)
// Test with 1.21.3 exactly - should get 1.21.3 default
store.settingValues['Comfy.InstalledVersion'] = '1.21.3'
expect(store.getDefaultValue('test.setting')).toBe(
'version-1.21.3-default'
)
})
it('should work with get() method using versioned defaults', () => {
const setting: SettingParams = {
id: 'test.setting',
name: 'Test Setting',
type: 'text',
defaultValue: 'regular-default',
defaultsByInstallVersion: {
'1.21.3': 'version-1.21.3-default',
'1.40.3': 'version-1.40.3-default'
}
}
store.addSetting(setting)
// get() should use getDefaultValue internally
const result = store.get('test.setting')
expect(result).toBe('version-1.21.3-default')
})
it('should handle mixed function and static versioned defaults', () => {
const setting: SettingParams = {
id: 'test.setting',
name: 'Test Setting',
type: 'text',
defaultValue: 'regular-default',
defaultsByInstallVersion: {
'1.21.3': () => 'dynamic-1.21.3-default',
'1.40.3': 'static-1.40.3-default'
}
}
store.addSetting(setting)
// Test with 1.30.0 - should get dynamic 1.21.3 default
store.settingValues['Comfy.InstalledVersion'] = '1.30.0'
expect(store.getDefaultValue('test.setting')).toBe(
'dynamic-1.21.3-default'
)
// Test with 1.50.0 - should get static 1.40.3 default
store.settingValues['Comfy.InstalledVersion'] = '1.50.0'
expect(store.getDefaultValue('test.setting')).toBe(
'static-1.40.3-default'
)
})
it('should handle version sorting correctly', () => {
const setting: SettingParams = {
id: 'test.setting',
name: 'Test Setting',
type: 'text',
defaultValue: 'regular-default',
defaultsByInstallVersion: {
'1.40.3': 'version-1.40.3-default',
'1.21.3': 'version-1.21.3-default', // Unsorted order
'1.35.0': 'version-1.35.0-default'
}
}
store.addSetting(setting)
// Test with 1.37.0 - should get 1.35.0 default (highest version <= 1.37.0)
store.settingValues['Comfy.InstalledVersion'] = '1.37.0'
expect(store.getDefaultValue('test.setting')).toBe(
'version-1.35.0-default'
)
})
})
describe('get and set', () => {
it('should get default value when setting not exists', () => {
const setting: SettingParams = {