Files
ComfyUI_frontend/src/platform/assets/utils/assetMetadataUtils.test.ts
Alexander Brown adf81fcd73 refactor: centralize display_name || name into getAssetDisplayName (#9641)
## Summary

Centralize the inline `display_name || name` pattern into
`getAssetDisplayName`, adding `display_name` to the existing metadata
fallback chain.

## Changes

- **What**: Update `getAssetDisplayName` fallback chain to
`user_metadata.name → metadata.name → display_name → name`. Replace all
6 inline `asset.display_name || asset.name` call sites with the shared
utility. Remove duplicate local function in `AssetsSidebarListView.vue`.

## Review Focus

The fallback order preserves user_metadata overrides while incorporating
the `display_name` field added in #9626. All callers now go through a
single code path.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9641-refactor-centralize-display_name-name-into-getAssetDisplayName-31e6d73d365081e09e5de85486583443)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
2026-03-12 09:13:20 -07:00

290 lines
8.7 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import {
getAssetAdditionalTags,
getAssetBaseModel,
getAssetBaseModels,
getAssetDescription,
getAssetDisplayName,
getAssetModelType,
getAssetSourceUrl,
getAssetTriggerPhrases,
getAssetUserDescription,
getSourceName
} from '@/platform/assets/utils/assetMetadataUtils'
describe('assetMetadataUtils', () => {
const mockAsset: AssetItem = {
id: 'test-id',
name: 'test-model',
asset_hash: 'hash123',
size: 1024,
mime_type: 'application/octet-stream',
tags: ['models', 'checkpoints'],
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z',
last_access_time: '2024-01-01T00:00:00Z'
}
describe('getAssetDescription', () => {
it.for([
{
name: 'returns string description when present',
description: 'A test model',
expected: 'A test model'
},
{ name: 'returns null for non-string', description: 123, expected: null },
{ name: 'returns null for null', description: null, expected: null }
])('$name', ({ description, expected }) => {
const asset = { ...mockAsset, user_metadata: { description } }
expect(getAssetDescription(asset)).toBe(expected)
})
it('should return null when no metadata', () => {
expect(getAssetDescription(mockAsset)).toBeNull()
})
})
describe('getAssetBaseModel', () => {
it.for([
{
name: 'returns string base_model when present',
base_model: 'SDXL',
expected: 'SDXL'
},
{ name: 'returns null for non-string', base_model: 123, expected: null },
{ name: 'returns null for null', base_model: null, expected: null }
])('$name', ({ base_model, expected }) => {
const asset = { ...mockAsset, user_metadata: { base_model } }
expect(getAssetBaseModel(asset)).toBe(expected)
})
it('should return null when no metadata', () => {
expect(getAssetBaseModel(mockAsset)).toBeNull()
})
})
describe('getAssetDisplayName', () => {
it.for([
{
name: 'returns name from user_metadata when present',
user_metadata: { name: 'My Custom Name' },
display_name: 'ComfyUI_00001_.png',
expected: 'My Custom Name'
},
{
name: 'returns display_name when user_metadata.name is absent',
user_metadata: undefined,
display_name: 'ComfyUI_00001_.png',
expected: 'ComfyUI_00001_.png'
},
{
name: 'falls back to asset name when both are absent',
user_metadata: undefined,
display_name: undefined,
expected: 'test-model'
},
{
name: 'skips non-string user_metadata.name',
user_metadata: { name: 123 },
display_name: 'ComfyUI_00001_.png',
expected: 'ComfyUI_00001_.png'
},
{
name: 'falls back to asset name when display_name is empty',
user_metadata: undefined,
display_name: '',
expected: 'test-model'
}
])('$name', ({ user_metadata, display_name, expected }) => {
const asset = { ...mockAsset, user_metadata, display_name }
expect(getAssetDisplayName(asset)).toBe(expected)
})
})
describe('getAssetSourceUrl', () => {
it.for([
{
name: 'constructs URL from civitai format',
source_arn: 'civitai:model:123:version:456',
expected: 'https://civitai.com/models/123?modelVersionId=456'
},
{ name: 'returns null for non-string', source_arn: 123, expected: null },
{
name: 'returns null for unrecognized format',
source_arn: 'unknown:format',
expected: null
}
])('$name', ({ source_arn, expected }) => {
const asset = { ...mockAsset, user_metadata: { source_arn } }
expect(getAssetSourceUrl(asset)).toBe(expected)
})
it('should return null when no metadata', () => {
expect(getAssetSourceUrl(mockAsset)).toBeNull()
})
})
describe('getAssetTriggerPhrases', () => {
it.for([
{
name: 'returns array when array present',
trained_words: ['phrase1', 'phrase2'],
expected: ['phrase1', 'phrase2']
},
{
name: 'wraps single string in array',
trained_words: 'single phrase',
expected: ['single phrase']
},
{
name: 'filters non-string values from array',
trained_words: ['valid', 123, 'also valid', null],
expected: ['valid', 'also valid']
}
])('$name', ({ trained_words, expected }) => {
const asset = { ...mockAsset, user_metadata: { trained_words } }
expect(getAssetTriggerPhrases(asset)).toEqual(expected)
})
it('should return empty array when no metadata', () => {
expect(getAssetTriggerPhrases(mockAsset)).toEqual([])
})
})
describe('getAssetAdditionalTags', () => {
it.for([
{
name: 'returns array of tags when present',
additional_tags: ['tag1', 'tag2'],
expected: ['tag1', 'tag2']
},
{
name: 'filters non-string values from array',
additional_tags: ['valid', 123, 'also valid'],
expected: ['valid', 'also valid']
},
{
name: 'returns empty array for non-array',
additional_tags: 'not an array',
expected: []
}
])('$name', ({ additional_tags, expected }) => {
const asset = { ...mockAsset, user_metadata: { additional_tags } }
expect(getAssetAdditionalTags(asset)).toEqual(expected)
})
it('should return empty array when no metadata', () => {
expect(getAssetAdditionalTags(mockAsset)).toEqual([])
})
})
describe('getSourceName', () => {
it.for([
{
name: 'returns Civitai for civitai.com',
url: 'https://civitai.com/models/123',
expected: 'Civitai'
},
{
name: 'returns Hugging Face for huggingface.co',
url: 'https://huggingface.co/org/model',
expected: 'Hugging Face'
},
{
name: 'returns Source for unknown URLs',
url: 'https://example.com/model',
expected: 'Source'
}
])('$name', ({ url, expected }) => {
expect(getSourceName(url)).toBe(expected)
})
})
describe('getAssetBaseModels', () => {
it.for([
{
name: 'array of strings',
base_model: ['SDXL', 'SD1.5', 'Flux'],
expected: ['SDXL', 'SD1.5', 'Flux']
},
{
name: 'filters non-string entries',
base_model: ['SDXL', 123, 'SD1.5', null, undefined],
expected: ['SDXL', 'SD1.5']
},
{
name: 'single string wrapped in array',
base_model: 'SDXL',
expected: ['SDXL']
},
{
name: 'non-array/string returns empty',
base_model: 123,
expected: []
},
{ name: 'undefined returns empty', base_model: undefined, expected: [] }
])('$name', ({ base_model, expected }) => {
const asset = { ...mockAsset, user_metadata: { base_model } }
expect(getAssetBaseModels(asset)).toEqual(expected)
})
it('should return empty array when no metadata', () => {
expect(getAssetBaseModels(mockAsset)).toEqual([])
})
})
describe('getAssetModelType', () => {
it.for([
{
name: 'returns model type from tags',
tags: ['models', 'checkpoints'],
expected: 'checkpoints'
},
{
name: 'returns full path for path-style tags',
tags: ['models', 'diffusers/Kolors/text_encoder'],
expected: 'diffusers/Kolors/text_encoder'
},
{
name: 'returns null when only models tag',
tags: ['models'],
expected: null
},
{ name: 'returns null when tags empty', tags: [], expected: null }
])('$name', ({ tags, expected }) => {
const asset = { ...mockAsset, tags }
expect(getAssetModelType(asset)).toBe(expected)
})
})
describe('getAssetUserDescription', () => {
it.for([
{
name: 'returns description when present',
user_description: 'A custom user description',
expected: 'A custom user description'
},
{
name: 'returns empty for non-string',
user_description: 123,
expected: ''
},
{ name: 'returns empty for null', user_description: null, expected: '' },
{
name: 'returns empty for undefined',
user_description: undefined,
expected: ''
}
])('$name', ({ user_description, expected }) => {
const asset = { ...mockAsset, user_metadata: { user_description } }
expect(getAssetUserDescription(asset)).toBe(expected)
})
it('should return empty string when no metadata', () => {
expect(getAssetUserDescription(mockAsset)).toBe('')
})
})
})