Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/platform/assets/services/assetService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,16 @@ function createAssetService() {
* @param nodeType - The ComfyUI node comfyClass (e.g., 'CheckpointLoaderSimple', 'LoraLoader')
* @returns true if this input should use asset browser
*/
function isAssetBrowserEligible(nodeType: string = ''): boolean {
function isAssetBrowserEligible(
nodeType: string = '',
widgetName: string
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[quality] medium Priority

Issue: Function signature inconsistency - required parameter appears optional
Context: widgetName parameter is required for the function logic but is not marked with default value or optional typing
Suggestion: Either make widgetName optional with a default value or remove the default empty string from nodeType since both are required

): boolean {
return (
!!nodeType && useModelToNodeStore().getRegisteredNodeTypes().has(nodeType)
(nodeType &&
widgetName &&
useModelToNodeStore().getRegisteredNodeTypes()[nodeType] ===
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[performance] low Priority

Issue: Unnecessary store call on every function invocation
Context: useModelToNodeStore() is called every time the function is invoked, which could be optimized
Suggestion: Consider memoizing the store reference or accepting it as a parameter for better performance in tight loops

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a quick check, this change is out of scope for this PR. Having a single store call breaks mocking used in tests.

widgetName) ||
false
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ const addComboWidget = (
): IBaseWidget => {
const settingStore = useSettingStore()
const isUsingAssetAPI = settingStore.get('Comfy.Assets.UseAssetAPI')
const isEligible = assetService.isAssetBrowserEligible(node.comfyClass)
const isEligible = assetService.isAssetBrowserEligible(
node.comfyClass,
inputSpec.name
)

if (isUsingAssetAPI && isEligible) {
const currentValue = getDefaultValue(inputSpec)
Expand Down
8 changes: 4 additions & 4 deletions src/stores/modelToNodeStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ export const useModelToNodeStore = defineStore('modelToNode', () => {
const haveDefaultsLoaded = ref(false)

/** Internal computed for reactive caching of registered node types */
const registeredNodeTypes = computed(() => {
return new Set(
const registeredNodeTypes = computed<Record<string, string>>(() => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[quality] low Priority

Issue: Inconsistent variable naming between computed property and function
Context: The computed property is named registeredNodeTypes but returns a Record mapping, not just types
Suggestion: Consider renaming to registeredNodeTypeToWidget or nodeTypeWidgetMap for clarity

return Object.fromEntries(
Object.values(modelToNodeMap.value)
.flat()
.filter((provider) => !!provider.nodeDef)
.map((provider) => provider.nodeDef.name)
.map((provider) => [provider.nodeDef.name, provider.key])
)
})

Expand All @@ -51,7 +51,7 @@ export const useModelToNodeStore = defineStore('modelToNode', () => {
})

/** Get set of all registered node types for efficient lookup */
function getRegisteredNodeTypes(): Set<string> {
function getRegisteredNodeTypes(): Record<string, string> {
registerDefaults()
return registeredNodeTypes.value
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ describe('useComboWidget', () => {
)
expect(mockSettingStoreGet).toHaveBeenCalledWith('Comfy.Assets.UseAssetAPI')
expect(vi.mocked(assetService.isAssetBrowserEligible)).toHaveBeenCalledWith(
'CheckpointLoaderSimple'
'CheckpointLoaderSimple',
'ckpt_name'
)
expect(widget).toBe(mockWidget)
})
Expand Down
43 changes: 28 additions & 15 deletions tests-ui/tests/services/assetService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@ const mockGetCategoryForNodeType = vi.fn()

vi.mock('@/stores/modelToNodeStore', () => ({
useModelToNodeStore: vi.fn(() => ({
getRegisteredNodeTypes: vi.fn(
() =>
new Set([
'CheckpointLoaderSimple',
'LoraLoader',
'VAELoader',
'TestNode'
])
),
getRegisteredNodeTypes: vi.fn(() => ({
CheckpointLoaderSimple: 'ckpt_name',
LoraLoader: 'lora_name',
VAELoader: 'vae_name',
TestNode: ''
})),
getCategoryForNodeType: mockGetCategoryForNodeType,
modelToNodeMap: {
checkpoints: [{ nodeDef: { name: 'CheckpointLoaderSimple' } }],
Expand Down Expand Up @@ -193,16 +190,32 @@ describe('assetService', () => {
describe('isAssetBrowserEligible', () => {
it('should return true for registered node types', () => {
expect(
assetService.isAssetBrowserEligible('CheckpointLoaderSimple')
assetService.isAssetBrowserEligible(
'CheckpointLoaderSimple',
'ckpt_name'
)
).toBe(true)
expect(assetService.isAssetBrowserEligible('LoraLoader')).toBe(true)
expect(assetService.isAssetBrowserEligible('VAELoader')).toBe(true)
expect(
assetService.isAssetBrowserEligible('LoraLoader', 'lora_name')
).toBe(true)
expect(assetService.isAssetBrowserEligible('VAELoader', 'vae_name')).toBe(
true
)
})

it('should return false for unregistered node types', () => {
expect(assetService.isAssetBrowserEligible('UnknownNode')).toBe(false)
expect(assetService.isAssetBrowserEligible('NotRegistered')).toBe(false)
expect(assetService.isAssetBrowserEligible('')).toBe(false)
expect(assetService.isAssetBrowserEligible('UnknownNode', 'widget')).toBe(
false
)
expect(
assetService.isAssetBrowserEligible('NotRegistered', 'widget')
).toBe(false)
expect(assetService.isAssetBrowserEligible('', '')).toBe(false)
})
it('should return false for other combo widgets on the registered node', () => {
expect(assetService.isAssetBrowserEligible('ClipLoader', 'type')).toBe(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[quality] low Priority

Issue: Test assertion could be more descriptive
Context: The test name mentions checking for other combo widgets but uses a generic ClipLoader example
Suggestion: Use LoadClip and type widget specifically since that was the original bug, or add a comment explaining the test case

false
)
})
})

Expand Down
18 changes: 9 additions & 9 deletions tests-ui/tests/store/modelToNodeStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ describe('useModelToNodeStore', () => {

// Non-existent nodes are filtered out from registered types
const types = modelToNodeStore.getRegisteredNodeTypes()
expect(types.has('NonExistentLoader')).toBe(false)
expect(types['NonExistentLoader']).toBe(undefined)

expect(
modelToNodeStore.getCategoryForNodeType('NonExistentLoader')
Expand Down Expand Up @@ -347,13 +347,13 @@ describe('useModelToNodeStore', () => {
})

describe('getRegisteredNodeTypes', () => {
it('should return a Set instance', () => {
it('should return an object', () => {
const modelToNodeStore = useModelToNodeStore()
const result = modelToNodeStore.getRegisteredNodeTypes()
expect(result).toBeInstanceOf(Set)
expect(result).toBeTypeOf('object')
})

it('should return empty set when nodeDefStore is empty', () => {
it('should return empty Record when nodeDefStore is empty', () => {
// Create fresh Pinia for this test to avoid state persistence
setActivePinia(createPinia())

Expand All @@ -363,24 +363,24 @@ describe('useModelToNodeStore', () => {
const modelToNodeStore = useModelToNodeStore()

const result = modelToNodeStore.getRegisteredNodeTypes()
expect(result.size).toBe(0)
expect(result).toStrictEqual({})

// Restore original mock for subsequent tests
vi.mocked(useNodeDefStore, { partial: true }).mockReturnValue({
nodeDefsByName: mockNodeDefsByName
})
})

it('should contain node types for efficient Set.has() lookups', () => {
it('should contain node types to resolve widget name', () => {
const modelToNodeStore = useModelToNodeStore()
modelToNodeStore.registerDefaults()

const result = modelToNodeStore.getRegisteredNodeTypes()

// Test Set.has() functionality which assetService depends on
expect(result.has('CheckpointLoaderSimple')).toBe(true)
expect(result.has('LoraLoader')).toBe(true)
expect(result.has('NonExistentNode')).toBe(false)
expect(result['CheckpointLoaderSimple']).toBe('ckpt_name')
expect(result['LoraLoader']).toBe('lora_name')
expect(result['NonExistentNode']).toBe(undefined)
})
})

Expand Down