From e8df692f8d7ae6239295d8a76963f825b9db68b7 Mon Sep 17 00:00:00 2001 From: Alexander Brown <448862+DrJKL@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:41:03 -0800 Subject: [PATCH] Add tests for ModelInfoPanel and assetMetadataUtils --- .../modelInfo/ModelInfoPanel.test.ts | 212 ++++++++++++++++++ .../assets/utils/assetMetadataUtils.test.ts | 127 ++++++++++- 2 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 src/platform/assets/components/modelInfo/ModelInfoPanel.test.ts diff --git a/src/platform/assets/components/modelInfo/ModelInfoPanel.test.ts b/src/platform/assets/components/modelInfo/ModelInfoPanel.test.ts new file mode 100644 index 000000000..1863b90b7 --- /dev/null +++ b/src/platform/assets/components/modelInfo/ModelInfoPanel.test.ts @@ -0,0 +1,212 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it, vi } from 'vitest' + +import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser' + +import ModelInfoPanel from './ModelInfoPanel.vue' + +vi.mock('vue-i18n', () => ({ + useI18n: () => ({ + t: (key: string, params?: Record) => + params ? `${key}:${JSON.stringify(params)}` : key + }) +})) + +vi.mock( + '@/components/rightSidePanel/layout/PropertiesAccordionItem.vue', + () => ({ + default: { + name: 'PropertiesAccordionItem', + template: ` +
+
+
+
+ ` + } + }) +) + +vi.mock('./ModelInfoField.vue', () => ({ + default: { + name: 'ModelInfoField', + props: ['label'], + template: ` +
+ {{ label }} + +
+ ` + } +})) + +describe('ModelInfoPanel', () => { + const createMockAsset = ( + overrides: Partial = {} + ): AssetDisplayItem => ({ + id: 'test-id', + name: 'test-model.safetensors', + 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', + description: 'A test model description', + badges: [], + stats: {}, + ...overrides + }) + + const mountPanel = (asset: AssetDisplayItem) => { + return mount(ModelInfoPanel, { + props: { asset }, + global: { + mocks: { + $t: (key: string, params?: Record) => + params ? `${key}:${JSON.stringify(params)}` : key + } + } + }) + } + + describe('Basic Info Section', () => { + it('renders panel title', () => { + const wrapper = mountPanel(createMockAsset()) + expect(wrapper.text()).toContain('assetBrowser.modelInfo.title') + }) + + it('displays asset filename', () => { + const asset = createMockAsset({ name: 'my-model.safetensors' }) + const wrapper = mountPanel(asset) + expect(wrapper.text()).toContain('my-model.safetensors') + }) + + it('displays display_name from user_metadata when present', () => { + const asset = createMockAsset({ + user_metadata: { display_name: 'My Custom Model' } + }) + const wrapper = mountPanel(asset) + expect(wrapper.text()).toContain('My Custom Model') + }) + + it('falls back to asset name when display_name not present', () => { + const asset = createMockAsset({ name: 'fallback-model.safetensors' }) + const wrapper = mountPanel(asset) + expect(wrapper.text()).toContain('fallback-model.safetensors') + }) + + it('renders source link when source_url is present', () => { + const asset = createMockAsset({ + user_metadata: { source_url: 'https://civitai.com/models/123' } + }) + const wrapper = mountPanel(asset) + const link = wrapper.find('a[href="https://civitai.com/models/123"]') + expect(link.exists()).toBe(true) + expect(link.attributes('target')).toBe('_blank') + }) + + it('displays correct source name for Civitai', () => { + const asset = createMockAsset({ + user_metadata: { source_url: 'https://civitai.com/models/123' } + }) + const wrapper = mountPanel(asset) + expect(wrapper.text()).toContain('Civitai') + }) + + it('displays correct source name for Hugging Face', () => { + const asset = createMockAsset({ + user_metadata: { source_url: 'https://huggingface.co/org/model' } + }) + const wrapper = mountPanel(asset) + expect(wrapper.text()).toContain('Hugging Face') + }) + + it('does not render source field when source_url is absent', () => { + const asset = createMockAsset() + const wrapper = mountPanel(asset) + const links = wrapper.findAll('a') + expect(links).toHaveLength(0) + }) + }) + + describe('Model Tagging Section', () => { + it('displays model type from tags', () => { + const asset = createMockAsset({ tags: ['models', 'loras'] }) + const wrapper = mountPanel(asset) + expect(wrapper.text()).toContain('loras') + }) + + it('extracts last segment from nested tag path', () => { + const asset = createMockAsset({ + tags: ['models', 'checkpoints/sd15'] + }) + const wrapper = mountPanel(asset) + expect(wrapper.text()).toContain('sd15') + }) + + it('displays base_model when present', () => { + const asset = createMockAsset({ + user_metadata: { base_model: 'SDXL' } + }) + const wrapper = mountPanel(asset) + expect(wrapper.text()).toContain('SDXL') + }) + + it('renders additional tags as badges', () => { + const asset = createMockAsset({ + user_metadata: { tags: ['anime', 'portrait', 'detailed'] } + }) + const wrapper = mountPanel(asset) + expect(wrapper.text()).toContain('anime') + expect(wrapper.text()).toContain('portrait') + expect(wrapper.text()).toContain('detailed') + }) + }) + + describe('Model Description Section', () => { + it('renders trigger phrases when present', () => { + const asset = createMockAsset({ + user_metadata: { trigger_phrases: ['trigger1', 'trigger2'] } + }) + const wrapper = mountPanel(asset) + expect(wrapper.text()).toContain('trigger1') + expect(wrapper.text()).toContain('trigger2') + }) + + it('renders description when present', () => { + const asset = createMockAsset({ + user_metadata: { description: 'A detailed model description' } + }) + const wrapper = mountPanel(asset) + expect(wrapper.text()).toContain('A detailed model description') + }) + + it('does not render trigger phrases field when empty', () => { + const asset = createMockAsset() + const wrapper = mountPanel(asset) + expect(wrapper.text()).not.toContain( + 'assetBrowser.modelInfo.triggerPhrases' + ) + }) + }) + + describe('Accordion Structure', () => { + it('renders three accordion sections', () => { + const wrapper = mountPanel(createMockAsset()) + const accordions = wrapper.findAll('[data-testid="accordion-item"]') + expect(accordions).toHaveLength(3) + }) + + it('renders correct section labels', () => { + const wrapper = mountPanel(createMockAsset()) + const labels = wrapper.findAll('[data-testid="accordion-label"]') + expect(labels[0].text()).toContain('assetBrowser.modelInfo.basicInfo') + expect(labels[1].text()).toContain('assetBrowser.modelInfo.modelTagging') + expect(labels[2].text()).toContain( + 'assetBrowser.modelInfo.modelDescription' + ) + }) + }) +}) diff --git a/src/platform/assets/utils/assetMetadataUtils.test.ts b/src/platform/assets/utils/assetMetadataUtils.test.ts index 54551f595..32ec8572c 100644 --- a/src/platform/assets/utils/assetMetadataUtils.test.ts +++ b/src/platform/assets/utils/assetMetadataUtils.test.ts @@ -3,7 +3,12 @@ import { describe, expect, it } from 'vitest' import type { AssetItem } from '@/platform/assets/schemas/assetSchema' import { getAssetBaseModel, - getAssetDescription + getAssetDescription, + getAssetDisplayName, + getAssetSourceUrl, + getAssetTags, + getAssetTriggerPhrases, + getSourceName } from '@/platform/assets/utils/assetMetadataUtils' describe('assetMetadataUtils', () => { @@ -62,4 +67,124 @@ describe('assetMetadataUtils', () => { expect(getAssetBaseModel(mockAsset)).toBeNull() }) }) + + describe('getAssetDisplayName', () => { + it('should return display_name when present', () => { + const asset = { + ...mockAsset, + user_metadata: { display_name: 'My Custom Name' } + } + expect(getAssetDisplayName(asset)).toBe('My Custom Name') + }) + + it('should fall back to asset name when display_name is not a string', () => { + const asset = { + ...mockAsset, + user_metadata: { display_name: 123 } + } + expect(getAssetDisplayName(asset)).toBe('test-model') + }) + + it('should fall back to asset name when no metadata', () => { + expect(getAssetDisplayName(mockAsset)).toBe('test-model') + }) + }) + + describe('getAssetSourceUrl', () => { + it('should return source_url when present', () => { + const asset = { + ...mockAsset, + user_metadata: { source_url: 'https://civitai.com/models/123' } + } + expect(getAssetSourceUrl(asset)).toBe('https://civitai.com/models/123') + }) + + it('should return null when source_url is not a string', () => { + const asset = { + ...mockAsset, + user_metadata: { source_url: 123 } + } + expect(getAssetSourceUrl(asset)).toBeNull() + }) + + it('should return null when no metadata', () => { + expect(getAssetSourceUrl(mockAsset)).toBeNull() + }) + }) + + describe('getAssetTriggerPhrases', () => { + it('should return array of trigger phrases when array present', () => { + const asset = { + ...mockAsset, + user_metadata: { trigger_phrases: ['phrase1', 'phrase2'] } + } + expect(getAssetTriggerPhrases(asset)).toEqual(['phrase1', 'phrase2']) + }) + + it('should wrap single string in array', () => { + const asset = { + ...mockAsset, + user_metadata: { trigger_phrases: 'single phrase' } + } + expect(getAssetTriggerPhrases(asset)).toEqual(['single phrase']) + }) + + it('should filter non-string values from array', () => { + const asset = { + ...mockAsset, + user_metadata: { trigger_phrases: ['valid', 123, 'also valid', null] } + } + expect(getAssetTriggerPhrases(asset)).toEqual(['valid', 'also valid']) + }) + + it('should return empty array when no metadata', () => { + expect(getAssetTriggerPhrases(mockAsset)).toEqual([]) + }) + }) + + describe('getAssetTags', () => { + it('should return array of tags when present', () => { + const asset = { + ...mockAsset, + user_metadata: { tags: ['tag1', 'tag2'] } + } + expect(getAssetTags(asset)).toEqual(['tag1', 'tag2']) + }) + + it('should filter non-string values from array', () => { + const asset = { + ...mockAsset, + user_metadata: { tags: ['valid', 123, 'also valid'] } + } + expect(getAssetTags(asset)).toEqual(['valid', 'also valid']) + }) + + it('should return empty array when tags is not an array', () => { + const asset = { + ...mockAsset, + user_metadata: { tags: 'not an array' } + } + expect(getAssetTags(asset)).toEqual([]) + }) + + it('should return empty array when no metadata', () => { + expect(getAssetTags(mockAsset)).toEqual([]) + }) + }) + + describe('getSourceName', () => { + it('should return Civitai for civitai.com URLs', () => { + expect(getSourceName('https://civitai.com/models/123')).toBe('Civitai') + }) + + it('should return Hugging Face for huggingface.co URLs', () => { + expect(getSourceName('https://huggingface.co/org/model')).toBe( + 'Hugging Face' + ) + }) + + it('should return Source for unknown URLs', () => { + expect(getSourceName('https://example.com/model')).toBe('Source') + }) + }) })