import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest';
const {
MODEL_VERSIONS_MODULE,
API_FACTORY_MODULE,
DOWNLOAD_MANAGER_MODULE,
UI_HELPERS_MODULE,
STATE_MODULE,
I18N_HELPERS_MODULE,
UTILS_MODULE,
} = vi.hoisted(() => ({
MODEL_VERSIONS_MODULE: new URL('../../../static/js/components/shared/ModelVersionsTab.js', import.meta.url).pathname,
API_FACTORY_MODULE: new URL('../../../static/js/api/modelApiFactory.js', import.meta.url).pathname,
DOWNLOAD_MANAGER_MODULE: new URL('../../../static/js/managers/DownloadManager.js', import.meta.url).pathname,
UI_HELPERS_MODULE: new URL('../../../static/js/utils/uiHelpers.js', import.meta.url).pathname,
STATE_MODULE: new URL('../../../static/js/state/index.js', import.meta.url).pathname,
I18N_HELPERS_MODULE: new URL('../../../static/js/utils/i18nHelpers.js', import.meta.url).pathname,
UTILS_MODULE: new URL('../../../static/js/components/shared/utils.js', import.meta.url).pathname,
}));
vi.mock(DOWNLOAD_MANAGER_MODULE, () => ({
downloadManager: {
downloadVersionWithDefaults: vi.fn(),
},
}));
vi.mock(UI_HELPERS_MODULE, () => ({
showToast: vi.fn(),
}));
const stateMock = {
global: {
settings: {
autoplay_on_hover: false,
update_flag_strategy: 'any',
},
},
};
vi.mock(STATE_MODULE, () => ({
state: stateMock,
}));
vi.mock(I18N_HELPERS_MODULE, () => ({
translate: vi.fn((_, __, fallback) => fallback ?? ''),
}));
vi.mock(UTILS_MODULE, () => ({
formatFileSize: vi.fn(() => '1 MB'),
}));
vi.mock(API_FACTORY_MODULE, () => ({
getModelApiClient: vi.fn(),
}));
describe('ModelVersionsTab media rendering', () => {
let getModelApiClient;
let fetchModelUpdateVersions;
beforeEach(async () => {
vi.resetModules();
document.body.innerHTML = `
`;
stateMock.global.settings.autoplay_on_hover = false;
stateMock.global.settings.update_flag_strategy = 'any';
({ getModelApiClient } = await import(API_FACTORY_MODULE));
fetchModelUpdateVersions = vi.fn();
getModelApiClient.mockReturnValue({
fetchModelUpdateVersions,
setModelUpdateIgnore: vi.fn(),
setVersionUpdateIgnore: vi.fn(),
deleteModel: vi.fn(),
});
});
afterEach(() => {
document.body.innerHTML = '';
});
it('renders video preview when preview URL references a video file in a query parameter', async () => {
const previewUrl = '/api/lm/previews?path=%2Fhome%2Fexample%2Fvideo-preview.mp4';
fetchModelUpdateVersions.mockResolvedValue({
success: true,
record: {
shouldIgnore: false,
inLibraryVersionIds: [2],
versions: [
{
versionId: 2,
name: 'v1.0',
previewUrl,
baseModel: 'SDXL',
sizeBytes: 1024,
isInLibrary: true,
shouldIgnore: false,
},
],
},
});
const { initVersionsTab } = await import(MODEL_VERSIONS_MODULE);
const controller = initVersionsTab({
modalId: 'model-versions-modal',
modelType: 'loras',
modelId: 123,
currentVersionId: null,
});
await controller.load();
const videoElement = document.querySelector('.version-media video');
expect(videoElement).toBeTruthy();
expect(videoElement?.getAttribute('src')).toBe(previewUrl);
expect(document.querySelector('.version-media img')).toBeFalsy();
});
it('renders image preview when preview URL does not reference a video', async () => {
const previewUrl = '/api/lm/previews?path=%2Fhome%2Fexample%2Fpreview-image.png';
fetchModelUpdateVersions.mockResolvedValue({
success: true,
record: {
shouldIgnore: false,
inLibraryVersionIds: [3],
versions: [
{
versionId: 3,
name: 'v1.1',
previewUrl,
baseModel: 'SDXL',
sizeBytes: 2048,
isInLibrary: true,
shouldIgnore: false,
},
],
},
});
const { initVersionsTab } = await import(MODEL_VERSIONS_MODULE);
const controller = initVersionsTab({
modalId: 'model-versions-modal',
modelType: 'loras',
modelId: 456,
currentVersionId: null,
});
await controller.load();
const imageElement = document.querySelector('.version-media img');
expect(imageElement).toBeTruthy();
expect(imageElement?.getAttribute('src')).toBe(previewUrl);
expect(document.querySelector('.version-media video')).toBeFalsy();
});
it('shows a stable label with a short state indicator', async () => {
stateMock.global.settings.update_flag_strategy = 'any';
fetchModelUpdateVersions.mockResolvedValue({
success: true,
record: {
shouldIgnore: false,
inLibraryVersionIds: [5],
versions: [
{
versionId: 5,
name: 'base',
baseModel: 'SDXL',
previewUrl: '/api/lm/previews/v-base.png',
sizeBytes: 1024,
isInLibrary: true,
shouldIgnore: false,
},
],
},
});
const { initVersionsTab } = await import(MODEL_VERSIONS_MODULE);
const controller = initVersionsTab({
modalId: 'model-versions-modal',
modelType: 'loras',
modelId: 321,
currentVersionId: 5,
});
await controller.load();
const toggleText = document.querySelector('.versions-filter-toggle .sr-only');
expect(toggleText?.textContent?.trim()).toBe('Base filter: All versions');
});
it('filters versions to the current base model when strategy is same_base', async () => {
stateMock.global.settings.update_flag_strategy = 'same_base';
fetchModelUpdateVersions.mockResolvedValue({
success: true,
record: {
shouldIgnore: false,
inLibraryVersionIds: [10],
versions: [
{
versionId: 10,
name: 'v1.0',
baseModel: 'SDXL',
previewUrl: '/api/lm/previews/v1.png',
sizeBytes: 1024,
isInLibrary: true,
shouldIgnore: false,
},
{
versionId: 11,
name: 'v1.1',
baseModel: 'Realistic',
previewUrl: '/api/lm/previews/v1-1.png',
sizeBytes: 2048,
isInLibrary: false,
shouldIgnore: false,
},
],
},
});
const { initVersionsTab } = await import(MODEL_VERSIONS_MODULE);
const controller = initVersionsTab({
modalId: 'model-versions-modal',
modelType: 'loras',
modelId: 789,
currentVersionId: 10,
});
await controller.load();
expect(document.querySelectorAll('.model-version-row').length).toBe(1);
});
it('toggle button can switch to display all versions', async () => {
stateMock.global.settings.update_flag_strategy = 'same_base';
fetchModelUpdateVersions.mockResolvedValue({
success: true,
record: {
shouldIgnore: false,
inLibraryVersionIds: [10],
versions: [
{
versionId: 10,
name: 'v1.0',
baseModel: 'SDXL',
previewUrl: '/api/lm/previews/v1.png',
sizeBytes: 1024,
isInLibrary: true,
shouldIgnore: false,
},
{
versionId: 11,
name: 'v1.1',
baseModel: 'Realistic',
previewUrl: '/api/lm/previews/v1-1.png',
sizeBytes: 2048,
isInLibrary: false,
shouldIgnore: false,
},
],
},
});
const { initVersionsTab } = await import(MODEL_VERSIONS_MODULE);
const controller = initVersionsTab({
modalId: 'model-versions-modal',
modelType: 'loras',
modelId: 987,
currentVersionId: 10,
});
await controller.load();
expect(document.querySelectorAll('.model-version-row').length).toBe(1);
const toggleButton = document.querySelector('[data-versions-action="toggle-version-display-mode"]');
expect(toggleButton).toBeTruthy();
const toggleTextBefore = document.querySelector('.versions-filter-toggle .sr-only');
expect(toggleTextBefore?.textContent?.trim()).toContain('Same base');
toggleButton?.click();
expect(document.querySelectorAll('.model-version-row').length).toBe(2);
const toggleTextAfter = document.querySelector('.versions-filter-toggle .sr-only');
expect(toggleTextAfter?.textContent?.trim()).toContain('All versions');
});
it('shows a newer version badge when viewing same-base results', async () => {
stateMock.global.settings.update_flag_strategy = 'same_base';
fetchModelUpdateVersions.mockResolvedValue({
success: true,
record: {
shouldIgnore: false,
inLibraryVersionIds: [3, 1],
versions: [
{
versionId: 4,
name: 'V4',
baseModel: 'Base IL',
previewUrl: '/api/lm/previews/v4.png',
sizeBytes: 1024,
isInLibrary: false,
shouldIgnore: false,
},
{
versionId: 3,
name: 'V3',
baseModel: 'Base IL',
previewUrl: '/api/lm/previews/v3.png',
sizeBytes: 2048,
isInLibrary: true,
shouldIgnore: false,
},
{
versionId: 2,
name: 'V2',
baseModel: 'Base Flux',
previewUrl: '/api/lm/previews/v2.png',
sizeBytes: 4096,
isInLibrary: false,
shouldIgnore: false,
},
{
versionId: 1,
name: 'V1',
baseModel: 'Base Flux',
previewUrl: '/api/lm/previews/v1.png',
sizeBytes: 8192,
isInLibrary: true,
shouldIgnore: false,
},
],
},
});
const { initVersionsTab } = await import(MODEL_VERSIONS_MODULE);
const controller = initVersionsTab({
modalId: 'model-versions-modal',
modelType: 'loras',
modelId: 333,
currentVersionId: 1,
});
await controller.load();
const rows = document.querySelectorAll('.model-version-row');
expect(rows.length).toBe(2);
const firstBadges = Array.from(rows[0].querySelectorAll('.version-badge')).map(
badge => badge.textContent?.trim()
);
expect(firstBadges).toContain('Newer Version');
});
});