mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-20 18:51:26 -03:00
feat(recipes): show the recipe base model in the modal header
Adds a base model pill at the front of the recipe modal's tags row, showing the full base model name (cards keep the abbreviation since their overlay width is constrained). Falls back to a dimmed Unknown so the header layout does not shift when hydration fills the value in. Hydration now also merges base_model. Translated in all 9 locales.
This commit is contained in:
+3
-1
@@ -920,7 +920,9 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"id": "ID"
|
"id": "ID",
|
||||||
|
"baseModel": "Basismodell",
|
||||||
|
"unknown": "Unbekannt"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"openFileLocation": "Dateispeicherort öffnen",
|
"openFileLocation": "Dateispeicherort öffnen",
|
||||||
|
|||||||
+3
-1
@@ -920,7 +920,9 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"id": "ID"
|
"id": "ID",
|
||||||
|
"baseModel": "Base Model",
|
||||||
|
"unknown": "Unknown"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"openFileLocation": "Open File Location",
|
"openFileLocation": "Open File Location",
|
||||||
|
|||||||
+3
-1
@@ -920,7 +920,9 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"id": "ID"
|
"id": "ID",
|
||||||
|
"baseModel": "Modelo base",
|
||||||
|
"unknown": "Desconocido"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"openFileLocation": "Abrir ubicación del archivo",
|
"openFileLocation": "Abrir ubicación del archivo",
|
||||||
|
|||||||
+3
-1
@@ -920,7 +920,9 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"id": "ID"
|
"id": "ID",
|
||||||
|
"baseModel": "Modèle de base",
|
||||||
|
"unknown": "Inconnu"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"openFileLocation": "Ouvrir l’emplacement du fichier",
|
"openFileLocation": "Ouvrir l’emplacement du fichier",
|
||||||
|
|||||||
+3
-1
@@ -920,7 +920,9 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"id": "ID"
|
"id": "ID",
|
||||||
|
"baseModel": "מודל בסיס",
|
||||||
|
"unknown": "לא ידוע"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"openFileLocation": "פתח מיקום קובץ",
|
"openFileLocation": "פתח מיקום קובץ",
|
||||||
|
|||||||
+3
-1
@@ -920,7 +920,9 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"id": "ID"
|
"id": "ID",
|
||||||
|
"baseModel": "ベースモデル",
|
||||||
|
"unknown": "不明"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"openFileLocation": "ファイルの場所を開く",
|
"openFileLocation": "ファイルの場所を開く",
|
||||||
|
|||||||
+3
-1
@@ -920,7 +920,9 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"id": "ID"
|
"id": "ID",
|
||||||
|
"baseModel": "베이스 모델",
|
||||||
|
"unknown": "알 수 없음"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"openFileLocation": "파일 위치 열기",
|
"openFileLocation": "파일 위치 열기",
|
||||||
|
|||||||
+3
-1
@@ -920,7 +920,9 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"id": "ID"
|
"id": "ID",
|
||||||
|
"baseModel": "Базовая модель",
|
||||||
|
"unknown": "Неизвестно"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"openFileLocation": "Открыть расположение файла",
|
"openFileLocation": "Открыть расположение файла",
|
||||||
|
|||||||
+3
-1
@@ -920,7 +920,9 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"id": "ID"
|
"id": "ID",
|
||||||
|
"baseModel": "基础模型",
|
||||||
|
"unknown": "未知"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"openFileLocation": "打开文件位置",
|
"openFileLocation": "打开文件位置",
|
||||||
|
|||||||
+3
-1
@@ -920,7 +920,9 @@
|
|||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"id": "ID"
|
"id": "ID",
|
||||||
|
"baseModel": "基礎模型",
|
||||||
|
"unknown": "未知"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"openFileLocation": "開啟檔案位置",
|
"openFileLocation": "開啟檔案位置",
|
||||||
|
|||||||
@@ -28,6 +28,38 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tags row: the base model badge shares one line with the compact tags. */
|
||||||
|
.recipe-tags-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-tags-row #recipeTagsContainer {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header base model badge: reuses the card .base-model-label pill shape but
|
||||||
|
swaps the on-image overlay styling (text shadow, backdrop blur) for the
|
||||||
|
accent-tinted chip look used by resource rows in this modal. */
|
||||||
|
.recipe-base-model-badge {
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-width: 160px;
|
||||||
|
text-shadow: none;
|
||||||
|
backdrop-filter: none;
|
||||||
|
background: oklch(var(--lora-accent) / 0.1);
|
||||||
|
color: var(--lora-accent);
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-base-model-badge.is-unknown {
|
||||||
|
background: var(--surface-subtle);
|
||||||
|
color: var(--text-color);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
.recipe-modal-header h2 {
|
.recipe-modal-header h2 {
|
||||||
margin: 0 0 var(--space-1);
|
margin: 0 0 var(--space-1);
|
||||||
padding: var(--space-1);
|
padding: var(--space-1);
|
||||||
|
|||||||
@@ -494,6 +494,7 @@ class RecipeModal {
|
|||||||
this.syncGenerationParams(hydratedRecipe.gen_params);
|
this.syncGenerationParams(hydratedRecipe.gen_params);
|
||||||
this.syncResourcesSection(hydratedRecipe);
|
this.syncResourcesSection(hydratedRecipe);
|
||||||
this.syncHeaderActions();
|
this.syncHeaderActions();
|
||||||
|
this.syncBaseModelBadge();
|
||||||
this.syncMetaFooter();
|
this.syncMetaFooter();
|
||||||
|
|
||||||
// Show the modal
|
// Show the modal
|
||||||
@@ -520,6 +521,32 @@ class RecipeModal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the recipe-level base model badge in the header tags row.
|
||||||
|
* Unlike the width-constrained card overlay (which abbreviates), the
|
||||||
|
* modal has room for the full base model name — matching the model
|
||||||
|
* modal's info grid and this modal's resource rows. Falls back to a
|
||||||
|
* dimmed "Unknown" instead of hiding so the header layout does not
|
||||||
|
* shift when hydration fills the value in.
|
||||||
|
*/
|
||||||
|
syncBaseModelBadge() {
|
||||||
|
const badge = document.getElementById('recipeBaseModelBadge');
|
||||||
|
if (!badge) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawLabel = (this.currentRecipe?.base_model || '').trim();
|
||||||
|
const unknownLabel = translate('recipes.modal.metadata.unknown', {}, 'Unknown');
|
||||||
|
const baseModelLabel = rawLabel || unknownLabel;
|
||||||
|
const fieldLabel = translate('recipes.modal.metadata.baseModel', {}, 'Base Model');
|
||||||
|
|
||||||
|
badge.textContent = baseModelLabel;
|
||||||
|
badge.title = `${fieldLabel}: ${baseModelLabel}`;
|
||||||
|
badge.setAttribute('aria-label', badge.title);
|
||||||
|
badge.classList.toggle('is-unknown', !rawLabel);
|
||||||
|
badge.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the meta footer: clickable file location (opens the recipe JSON
|
* Render the meta footer: clickable file location (opens the recipe JSON
|
||||||
* in the OS file manager) plus the truncated recipe ID with copy button.
|
* in the OS file manager) plus the truncated recipe ID with copy button.
|
||||||
@@ -661,6 +688,10 @@ class RecipeModal {
|
|||||||
nextRecipe.has_workflow = fullRecipe.has_workflow;
|
nextRecipe.has_workflow = fullRecipe.has_workflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fullRecipe.base_model !== undefined) {
|
||||||
|
nextRecipe.base_model = fullRecipe.base_model;
|
||||||
|
}
|
||||||
|
|
||||||
if (fullRecipe.checkpoint !== undefined) {
|
if (fullRecipe.checkpoint !== undefined) {
|
||||||
nextRecipe.checkpoint = fullRecipe.checkpoint;
|
nextRecipe.checkpoint = fullRecipe.checkpoint;
|
||||||
} else {
|
} else {
|
||||||
@@ -718,6 +749,7 @@ class RecipeModal {
|
|||||||
this.updateSourceUrlDisplay(this.currentRecipe.source_path || '');
|
this.updateSourceUrlDisplay(this.currentRecipe.source_path || '');
|
||||||
}
|
}
|
||||||
this.syncHeaderActions();
|
this.syncHeaderActions();
|
||||||
|
this.syncBaseModelBadge();
|
||||||
this.syncMetaFooter();
|
this.syncMetaFooter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,13 @@
|
|||||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Recipe Tags Container (rendered by renderCompactTags) -->
|
<!-- Tags row: the base model badge is an independent sibling of the
|
||||||
<div id="recipeTagsContainer"></div>
|
tags container so renderCompactTags re-renders and tag edit mode
|
||||||
|
never touch it. Badge is populated by RecipeModal.syncBaseModelBadge(). -->
|
||||||
|
<div class="recipe-tags-row">
|
||||||
|
<span id="recipeBaseModelBadge" class="base-model-label recipe-base-model-badge" hidden></span>
|
||||||
|
<div id="recipeTagsContainer"></div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest';
|
||||||
|
|
||||||
|
const showToastMock = vi.fn();
|
||||||
|
const copyToClipboardMock = vi.fn();
|
||||||
|
const translateMock = vi.fn((key, params, fallback) => (typeof fallback === 'string' ? fallback : key));
|
||||||
|
|
||||||
|
const loadingManagerStub = {
|
||||||
|
showSimpleLoading: vi.fn(),
|
||||||
|
hide: vi.fn(),
|
||||||
|
show: vi.fn(),
|
||||||
|
restoreProgressBar: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const recipeItem = {
|
||||||
|
id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||||
|
file_path: '/recipes/a1b2c3d4-e5f6-7890-abcd-ef1234567890.png',
|
||||||
|
title: 'Demo Recipe',
|
||||||
|
tags: [],
|
||||||
|
loras: [],
|
||||||
|
base_model: 'Illustrious',
|
||||||
|
};
|
||||||
|
|
||||||
|
const virtualScrollerStub = {
|
||||||
|
updateSingleItem: vi.fn(),
|
||||||
|
getNavigationState: vi.fn(() => ({
|
||||||
|
index: 0,
|
||||||
|
hasPrev: false,
|
||||||
|
hasNext: false,
|
||||||
|
loadedItems: 1,
|
||||||
|
totalItems: 1,
|
||||||
|
})),
|
||||||
|
getAdjacentItemByFilePath: vi.fn(async () => null),
|
||||||
|
};
|
||||||
|
|
||||||
|
const stateStub = {
|
||||||
|
global: { settings: {}, loadingManager: loadingManagerStub },
|
||||||
|
loadingManager: loadingManagerStub,
|
||||||
|
virtualScroller: virtualScrollerStub,
|
||||||
|
};
|
||||||
|
|
||||||
|
const modalManagerMock = {
|
||||||
|
showModal: vi.fn(),
|
||||||
|
closeModal: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchRecipeDetailsMock = vi.fn(async () => ({}));
|
||||||
|
const updateRecipeMetadataMock = vi.fn(() => Promise.resolve({ success: true }));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/utils/uiHelpers.js', () => ({
|
||||||
|
showToast: showToastMock,
|
||||||
|
copyToClipboard: copyToClipboardMock,
|
||||||
|
sendLoraToWorkflow: vi.fn(),
|
||||||
|
sendModelPathToWorkflow: vi.fn(),
|
||||||
|
openCivitaiByMetadata: vi.fn(),
|
||||||
|
stripLoraTags: vi.fn((text) => text),
|
||||||
|
sendPromptToWorkflow: vi.fn(),
|
||||||
|
sendGenParamsToWorkflow: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({
|
||||||
|
translate: translateMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/state/index.js', () => ({
|
||||||
|
state: stateStub,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/utils/storageHelpers.js', () => ({
|
||||||
|
setSessionItem: vi.fn(),
|
||||||
|
removeSessionItem: vi.fn(),
|
||||||
|
getStorageItem: vi.fn(() => null),
|
||||||
|
setStorageItem: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/api/recipeApi.js', () => ({
|
||||||
|
fetchRecipeDetails: fetchRecipeDetailsMock,
|
||||||
|
updateRecipeMetadata: updateRecipeMetadataMock,
|
||||||
|
sendRecipeWorkflow: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../static/js/api/apiConfig.js', () => ({
|
||||||
|
MODEL_TYPES: {
|
||||||
|
LORA: 'loras',
|
||||||
|
CHECKPOINT: 'checkpoints',
|
||||||
|
EMBEDDING: 'embeddings',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
function recipeModalFixture() {
|
||||||
|
return `
|
||||||
|
<div id="recipeModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<header class="recipe-modal-header">
|
||||||
|
<div class="recipe-modal-header-row">
|
||||||
|
<h2 id="recipeModalTitle">Recipe Details</h2>
|
||||||
|
<div class="modal-nav-controls">
|
||||||
|
<button class="modal-nav-btn" id="recipeNavPrevBtn" disabled></button>
|
||||||
|
<button class="modal-nav-btn" id="recipeNavNextBtn" disabled></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="recipe-header-actions" id="recipeHeaderActions">
|
||||||
|
<button class="modal-send-btn" id="sendRecipeBtn"><i class="fas fa-paper-plane"></i></button>
|
||||||
|
<button class="modal-copy-btn" id="copyRecipeSyntaxBtn"><i class="fas fa-copy"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="recipe-tags-row">
|
||||||
|
<span id="recipeBaseModelBadge" class="base-model-label recipe-base-model-badge" hidden></span>
|
||||||
|
<div id="recipeTagsContainer"></div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="recipe-media-column">
|
||||||
|
<div class="recipe-preview-container" id="recipePreviewContainer">
|
||||||
|
<img id="recipeModalImage" src="" alt="Recipe Preview" class="recipe-preview-media">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-section recipe-gen-params">
|
||||||
|
<div class="gen-params-container">
|
||||||
|
<div class="param-group info-item">
|
||||||
|
<div class="param-content" id="recipePrompt"></div>
|
||||||
|
<div class="param-editor" id="recipePromptEditor">
|
||||||
|
<textarea class="param-textarea" id="recipePromptInput"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="param-group info-item">
|
||||||
|
<div class="param-content" id="recipeNegativePrompt"></div>
|
||||||
|
<div class="param-editor" id="recipeNegativePromptEditor">
|
||||||
|
<textarea class="param-textarea" id="recipeNegativePromptInput"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="other-params" id="recipeOtherParams"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-section recipe-bottom-section">
|
||||||
|
<div class="recipe-section-actions">
|
||||||
|
<span id="recipeLorasCount"></span>
|
||||||
|
<button class="action-btn view-loras-btn" id="viewRecipeLorasBtn"></button>
|
||||||
|
</div>
|
||||||
|
<div class="recipe-loras-list" id="recipeLorasList"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="recipe-meta-footer" id="recipeMetaFooter" hidden></footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flushAsyncTasks() {
|
||||||
|
await Promise.resolve();
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdModals = [];
|
||||||
|
|
||||||
|
async function createRecipeModal() {
|
||||||
|
const { RecipeModal } = await import('../../../static/js/components/RecipeModal.js');
|
||||||
|
const recipeModal = new RecipeModal();
|
||||||
|
createdModals.push(recipeModal);
|
||||||
|
return recipeModal;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('RecipeModal base model badge', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
document.body.innerHTML = recipeModalFixture();
|
||||||
|
global.modalManager = modalManagerMock;
|
||||||
|
global.fetch = vi.fn(async () => ({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({}),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
createdModals.forEach(recipeModal => recipeModal.cleanupNavigationShortcuts());
|
||||||
|
createdModals.length = 0;
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
delete global.modalManager;
|
||||||
|
delete global.fetch;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the full base model name with a labeled tooltip', async () => {
|
||||||
|
const recipeModal = await createRecipeModal();
|
||||||
|
recipeModal.showRecipeDetails(recipeItem);
|
||||||
|
|
||||||
|
const badge = document.getElementById('recipeBaseModelBadge');
|
||||||
|
expect(badge.hidden).toBe(false);
|
||||||
|
expect(badge.textContent).toBe('Illustrious');
|
||||||
|
expect(badge.title).toBe('Base Model: Illustrious');
|
||||||
|
expect(badge.getAttribute('aria-label')).toBe('Base Model: Illustrious');
|
||||||
|
expect(badge.classList.contains('is-unknown')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to a dimmed Unknown badge when no base model is set', async () => {
|
||||||
|
const recipeModal = await createRecipeModal();
|
||||||
|
recipeModal.showRecipeDetails({ ...recipeItem, base_model: '' });
|
||||||
|
|
||||||
|
const badge = document.getElementById('recipeBaseModelBadge');
|
||||||
|
expect(badge.hidden).toBe(false);
|
||||||
|
expect(badge.textContent).toBe('Unknown');
|
||||||
|
expect(badge.title).toBe('Base Model: Unknown');
|
||||||
|
expect(badge.classList.contains('is-unknown')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates the badge once hydration provides the base model', async () => {
|
||||||
|
fetchRecipeDetailsMock.mockResolvedValueOnce({
|
||||||
|
id: recipeItem.id,
|
||||||
|
file_path: recipeItem.file_path,
|
||||||
|
base_model: 'Pony',
|
||||||
|
});
|
||||||
|
|
||||||
|
const recipeModal = await createRecipeModal();
|
||||||
|
recipeModal.showRecipeDetails({ ...recipeItem, base_model: '' });
|
||||||
|
await flushAsyncTasks();
|
||||||
|
|
||||||
|
const badge = document.getElementById('recipeBaseModelBadge');
|
||||||
|
expect(badge.textContent).toBe('Pony');
|
||||||
|
expect(badge.title).toBe('Base Model: Pony');
|
||||||
|
expect(badge.classList.contains('is-unknown')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the list-provided base model when hydration omits it', async () => {
|
||||||
|
fetchRecipeDetailsMock.mockResolvedValueOnce({
|
||||||
|
id: recipeItem.id,
|
||||||
|
file_path: recipeItem.file_path,
|
||||||
|
});
|
||||||
|
|
||||||
|
const recipeModal = await createRecipeModal();
|
||||||
|
recipeModal.showRecipeDetails(recipeItem);
|
||||||
|
await flushAsyncTasks();
|
||||||
|
|
||||||
|
const badge = document.getElementById('recipeBaseModelBadge');
|
||||||
|
expect(badge.textContent).toBe('Illustrious');
|
||||||
|
expect(badge.title).toBe('Base Model: Illustrious');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user