mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-06 16:10:09 +00:00
- Extract all migrateDeprecatedValue logic from individual settings into centralized settingsMigration.ts - Remove migrateDeprecatedValue from SettingParams interface and coreSettings definitions - Simplify settingStore by removing tryMigrateDeprecatedValue function - Ensure migrations run after loadSettingValues() for clean initialization flow - Update tests to reflect new migration approach This refactor centralizes all setting value migrations in one place, making them easier to maintain and avoiding timing issues with settings that don't exist yet.
307 lines
8.5 KiB
TypeScript
307 lines
8.5 KiB
TypeScript
import { createPinia, setActivePinia } from 'pinia'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { api } from '@/scripts/api'
|
|
import { app } from '@/scripts/app'
|
|
import { getSettingInfo, useSettingStore } from '@/stores/settingStore'
|
|
import type { SettingParams } from '@/types/settingTypes'
|
|
|
|
// Mock the api
|
|
vi.mock('@/scripts/api', () => ({
|
|
api: {
|
|
getSettings: vi.fn(),
|
|
storeSetting: vi.fn()
|
|
}
|
|
}))
|
|
|
|
// Mock the app
|
|
vi.mock('@/scripts/app', () => ({
|
|
app: {
|
|
ui: {
|
|
settings: {
|
|
dispatchChange: vi.fn()
|
|
}
|
|
}
|
|
}
|
|
}))
|
|
|
|
describe('useSettingStore', () => {
|
|
let store: ReturnType<typeof useSettingStore>
|
|
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia())
|
|
store = useSettingStore()
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('should initialize with empty settings', () => {
|
|
expect(store.settingValues).toEqual({})
|
|
expect(store.settingsById).toEqual({})
|
|
})
|
|
|
|
describe('loadSettingValues', () => {
|
|
it('should load settings from API', async () => {
|
|
const mockSettings = { 'test.setting': 'value' }
|
|
vi.mocked(api.getSettings).mockResolvedValue(mockSettings as any)
|
|
|
|
await store.loadSettingValues()
|
|
|
|
expect(store.settingValues).toEqual(mockSettings)
|
|
expect(api.getSettings).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should throw error if settings are loaded after registration', async () => {
|
|
const setting: SettingParams = {
|
|
id: 'test.setting',
|
|
name: 'test.setting',
|
|
type: 'text',
|
|
defaultValue: 'default'
|
|
}
|
|
store.addSetting(setting)
|
|
|
|
await expect(store.loadSettingValues()).rejects.toThrow(
|
|
'Setting values must be loaded before any setting is registered.'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('addSetting', () => {
|
|
it('should register a new setting', () => {
|
|
const setting: SettingParams = {
|
|
id: 'test.setting',
|
|
name: 'test.setting',
|
|
type: 'text',
|
|
defaultValue: 'default'
|
|
}
|
|
|
|
store.addSetting(setting)
|
|
|
|
expect(store.settingsById['test.setting']).toEqual(setting)
|
|
})
|
|
|
|
it('should throw error for duplicate setting ID', () => {
|
|
const setting: SettingParams = {
|
|
id: 'test.setting',
|
|
name: 'test.setting',
|
|
type: 'text',
|
|
defaultValue: 'default'
|
|
}
|
|
|
|
store.addSetting(setting)
|
|
expect(() => store.addSetting(setting)).toThrow(
|
|
'Setting test.setting must have a unique ID.'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('get and set', () => {
|
|
it('should get default value when setting not exists', () => {
|
|
const setting: SettingParams = {
|
|
id: 'test.setting',
|
|
name: 'test.setting',
|
|
type: 'text',
|
|
defaultValue: 'default'
|
|
}
|
|
store.addSetting(setting)
|
|
|
|
expect(store.get('test.setting')).toBe('default')
|
|
})
|
|
|
|
it('should set value and trigger onChange', async () => {
|
|
const onChangeMock = vi.fn()
|
|
const dispatchChangeMock = vi.mocked(app.ui.settings.dispatchChange)
|
|
const setting: SettingParams = {
|
|
id: 'test.setting',
|
|
name: 'test.setting',
|
|
type: 'text',
|
|
defaultValue: 'default',
|
|
onChange: onChangeMock
|
|
}
|
|
store.addSetting(setting)
|
|
// Adding the new setting should trigger onChange
|
|
expect(onChangeMock).toHaveBeenCalledTimes(1)
|
|
expect(dispatchChangeMock).toHaveBeenCalledTimes(1)
|
|
|
|
await store.set('test.setting', 'newvalue')
|
|
|
|
expect(store.get('test.setting')).toBe('newvalue')
|
|
expect(onChangeMock).toHaveBeenCalledWith('newvalue', 'default')
|
|
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
expect(dispatchChangeMock).toHaveBeenCalledTimes(2)
|
|
expect(api.storeSetting).toHaveBeenCalledWith('test.setting', 'newvalue')
|
|
|
|
// Set the same value again, it should not trigger onChange
|
|
await store.set('test.setting', 'newvalue')
|
|
expect(onChangeMock).toHaveBeenCalledTimes(2)
|
|
expect(dispatchChangeMock).toHaveBeenCalledTimes(2)
|
|
|
|
// Set a different value, it should trigger onChange
|
|
await store.set('test.setting', 'differentvalue')
|
|
expect(onChangeMock).toHaveBeenCalledWith('differentvalue', 'newvalue')
|
|
expect(onChangeMock).toHaveBeenCalledTimes(3)
|
|
expect(dispatchChangeMock).toHaveBeenCalledTimes(3)
|
|
expect(api.storeSetting).toHaveBeenCalledWith(
|
|
'test.setting',
|
|
'differentvalue'
|
|
)
|
|
})
|
|
|
|
describe('object mutation prevention', () => {
|
|
beforeEach(() => {
|
|
const setting: SettingParams = {
|
|
id: 'test.setting',
|
|
name: 'Test setting',
|
|
type: 'hidden',
|
|
defaultValue: {}
|
|
}
|
|
store.addSetting(setting)
|
|
})
|
|
|
|
it('should prevent mutations of objects after set', async () => {
|
|
const originalObject = { foo: 'bar', nested: { value: 123 } }
|
|
|
|
await store.set('test.setting', originalObject)
|
|
|
|
// Attempt to mutate the original object
|
|
originalObject.foo = 'changed'
|
|
originalObject.nested.value = 456
|
|
|
|
// Get the stored value
|
|
const storedValue = store.get('test.setting')
|
|
|
|
// Verify the stored value wasn't affected by the mutation
|
|
expect(storedValue).toEqual({ foo: 'bar', nested: { value: 123 } })
|
|
})
|
|
|
|
it('should prevent mutations of retrieved objects', async () => {
|
|
const initialValue = { foo: 'bar', nested: { value: 123 } }
|
|
|
|
// Set initial value
|
|
await store.set('test.setting', initialValue)
|
|
|
|
// Get the value and try to mutate it
|
|
const retrievedValue = store.get('test.setting')
|
|
retrievedValue.foo = 'changed'
|
|
if (retrievedValue.nested) {
|
|
retrievedValue.nested.value = 456
|
|
}
|
|
|
|
// Get the value again
|
|
const newRetrievedValue = store.get('test.setting')
|
|
|
|
// Verify the stored value wasn't affected by the mutation
|
|
expect(newRetrievedValue).toEqual({
|
|
foo: 'bar',
|
|
nested: { value: 123 }
|
|
})
|
|
})
|
|
|
|
it('should prevent mutations of arrays after set', async () => {
|
|
const originalArray = [1, 2, { value: 3 }]
|
|
|
|
await store.set('test.setting', originalArray)
|
|
|
|
// Attempt to mutate the original array
|
|
originalArray.push(4)
|
|
if (typeof originalArray[2] === 'object') {
|
|
originalArray[2].value = 999
|
|
}
|
|
|
|
// Get the stored value
|
|
const storedValue = store.get('test.setting')
|
|
|
|
// Verify the stored value wasn't affected by the mutation
|
|
expect(storedValue).toEqual([1, 2, { value: 3 }])
|
|
})
|
|
|
|
it('should prevent mutations of retrieved arrays', async () => {
|
|
const initialArray = [1, 2, { value: 3 }]
|
|
|
|
// Set initial value
|
|
await store.set('test.setting', initialArray)
|
|
|
|
// Get the value and try to mutate it
|
|
const retrievedArray = store.get('test.setting')
|
|
retrievedArray.push(4)
|
|
if (typeof retrievedArray[2] === 'object') {
|
|
retrievedArray[2].value = 999
|
|
}
|
|
|
|
// Get the value again
|
|
const newRetrievedValue = store.get('test.setting')
|
|
|
|
// Verify the stored value wasn't affected by the mutation
|
|
expect(newRetrievedValue).toEqual([1, 2, { value: 3 }])
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('getSettingInfo', () => {
|
|
const baseSetting: SettingParams = {
|
|
id: 'test.setting',
|
|
name: 'test.setting',
|
|
type: 'text',
|
|
defaultValue: 'default'
|
|
}
|
|
|
|
it('should handle settings with explicit category array', () => {
|
|
const setting: SettingParams = {
|
|
...baseSetting,
|
|
id: 'test.setting',
|
|
category: ['Main', 'Sub', 'Detail']
|
|
}
|
|
|
|
const result = getSettingInfo(setting)
|
|
|
|
expect(result).toEqual({
|
|
category: 'Main',
|
|
subCategory: 'Sub'
|
|
})
|
|
})
|
|
|
|
it('should handle settings with id-based categorization', () => {
|
|
const setting: SettingParams = {
|
|
...baseSetting,
|
|
id: 'main.sub.setting.name'
|
|
}
|
|
|
|
const result = getSettingInfo(setting)
|
|
|
|
expect(result).toEqual({
|
|
category: 'main',
|
|
subCategory: 'sub'
|
|
})
|
|
})
|
|
|
|
it('should use "Other" as default subCategory when missing', () => {
|
|
const setting: SettingParams = {
|
|
...baseSetting,
|
|
id: 'single.setting',
|
|
category: ['single']
|
|
}
|
|
|
|
const result = getSettingInfo(setting)
|
|
|
|
expect(result).toEqual({
|
|
category: 'single',
|
|
subCategory: 'Other'
|
|
})
|
|
})
|
|
|
|
it('should use "Other" as default category when missing', () => {
|
|
const setting: SettingParams = {
|
|
...baseSetting,
|
|
id: 'single.setting',
|
|
category: []
|
|
}
|
|
|
|
const result = getSettingInfo(setting)
|
|
|
|
expect(result).toEqual({
|
|
category: 'Other',
|
|
subCategory: 'Other'
|
|
})
|
|
})
|
|
})
|