mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 03:01:27 -03:00
feat(recipes): show a summary modal after rematch runs
Replace the post-run toast cascade and the standalone L4 results modal with a summary modal modeled on the batch download summary: 3-state header, stat cards (matched / needs review / unresolved / errors), an L4 review table with per-entry undo, and a copyable report. Wired into the global, bulk and single-recipe rematch entries; complete no-op runs keep the lightweight toast. Obsolete results-modal code, styles and i18n keys are removed.
This commit is contained in:
@@ -60,13 +60,6 @@ async function cancelRematchOptions() {
|
||||
rematchModalManager.cancelOptions();
|
||||
}
|
||||
|
||||
async function getRematchModalManager() {
|
||||
const { rematchModalManager } = await import(
|
||||
'../../../static/js/managers/RematchModalManager.js'
|
||||
);
|
||||
return rematchModalManager;
|
||||
}
|
||||
|
||||
describe('RecipeContextMenu.rematchRecipe', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -92,8 +85,8 @@ describe('RecipeContextMenu.rematchRecipe', () => {
|
||||
}
|
||||
|
||||
// Oracle R4-F1 pin: branches on `result.rematched > 0` — a blind `repaired`
|
||||
// mirror would fire the skipped toast here.
|
||||
it('posts to the per-recipe rematch endpoint and toasts the rematched count', async () => {
|
||||
// mirror would render 0 matched entries in the summary modal here.
|
||||
it('posts to the per-recipe rematch endpoint and opens the summary modal', async () => {
|
||||
const menu = await createMenu();
|
||||
const card = document.getElementById('card');
|
||||
menu.showMenu(100, 100, card);
|
||||
@@ -125,16 +118,22 @@ describe('RecipeContextMenu.rematchRecipe', () => {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ relaxed: false }),
|
||||
});
|
||||
expect(showToastMock).toHaveBeenCalledWith(
|
||||
// Non-noop runs open the summary modal instead of toasting.
|
||||
expect(showToastMock).not.toHaveBeenCalledWith(
|
||||
'toast.recipes.rematchComplete',
|
||||
{ rematched: 2, skipped: 0, total: 1, entries: 2, recipes: 1, failures: 0 },
|
||||
'success'
|
||||
expect.anything(),
|
||||
expect.anything()
|
||||
);
|
||||
expect(showToastMock).not.toHaveBeenCalledWith(
|
||||
'toast.recipes.rematchSkipped',
|
||||
expect.anything(),
|
||||
expect.anything()
|
||||
);
|
||||
const summaryModal = document.getElementById('rematchSummaryModal');
|
||||
expect(summaryModal).not.toBeNull();
|
||||
expect(summaryModal.querySelector('.summary-header').classList.contains('success')).toBe(true);
|
||||
expect(summaryModal.querySelector('.stat-card-success .stat-card-value').textContent).toBe('2');
|
||||
expect(summaryModal.querySelector('.stat-card-failure .stat-card-value').textContent).toBe('0');
|
||||
expect(global.fetch).toHaveBeenNthCalledWith(2, '/api/lm/recipe/recipe-1');
|
||||
expect(updateSingleItemMock).toHaveBeenCalledWith('/recipes/recipe-1.webp', {
|
||||
id: 'recipe-1',
|
||||
@@ -142,7 +141,7 @@ describe('RecipeContextMenu.rematchRecipe', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('toasts an info message when the entries had no local match', async () => {
|
||||
it('opens the summary modal when the entries had no local match', async () => {
|
||||
const menu = await createMenu();
|
||||
const card = document.getElementById('card');
|
||||
menu.showMenu(100, 100, card);
|
||||
@@ -160,11 +159,11 @@ describe('RecipeContextMenu.rematchRecipe', () => {
|
||||
await confirmRematchOptions();
|
||||
await flushAsyncTasks();
|
||||
|
||||
expect(showToastMock).toHaveBeenCalledWith(
|
||||
'toast.recipes.rematchUnmatched',
|
||||
{ entries: 2, recipes: 1, total: 1 },
|
||||
'info'
|
||||
);
|
||||
const summaryModal = document.getElementById('rematchSummaryModal');
|
||||
expect(summaryModal).not.toBeNull();
|
||||
expect(summaryModal.querySelector('.summary-header').classList.contains('warning')).toBe(true);
|
||||
expect(summaryModal.querySelector('.stat-card-success .stat-card-value').textContent).toBe('0');
|
||||
expect(summaryModal.querySelector('.stat-card-total .stat-card-value').textContent).toBe('2');
|
||||
expect(showToastMock).not.toHaveBeenCalledWith(
|
||||
'toast.recipes.rematchSkipped',
|
||||
expect.anything(),
|
||||
@@ -303,7 +302,7 @@ describe('RecipeContextMenu.rematchRecipe', () => {
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows the results modal when the result carries l4_matches', async () => {
|
||||
it('lists L4 filename matches in the summary modal with undo buttons', async () => {
|
||||
const menu = await createMenu();
|
||||
const card = document.getElementById('card');
|
||||
menu.showMenu(100, 100, card);
|
||||
@@ -321,11 +320,6 @@ describe('RecipeContextMenu.rematchRecipe', () => {
|
||||
json: async () => ({ id: 'recipe-1', title: 'Updated Recipe' }),
|
||||
});
|
||||
|
||||
const rematchModalManager = await getRematchModalManager();
|
||||
const showResultsSpy = vi
|
||||
.spyOn(rematchModalManager, 'showResultsModal')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
document
|
||||
.querySelector('[data-action="rematch"]')
|
||||
.dispatchEvent(new Event('click', { bubbles: true }));
|
||||
@@ -334,7 +328,15 @@ describe('RecipeContextMenu.rematchRecipe', () => {
|
||||
await confirmRematchOptions();
|
||||
await flushAsyncTasks();
|
||||
|
||||
expect(showResultsSpy).toHaveBeenCalledWith(l4Matches);
|
||||
showResultsSpy.mockRestore();
|
||||
const summaryModal = document.getElementById('rematchSummaryModal');
|
||||
expect(summaryModal).not.toBeNull();
|
||||
// L4 matches to review force the warning header
|
||||
expect(summaryModal.querySelector('.summary-header').classList.contains('warning')).toBe(true);
|
||||
expect(summaryModal.querySelector('.stat-card-skipped .stat-card-value').textContent).toBe('1');
|
||||
const rows = summaryModal.querySelectorAll('tr[data-l4-index]');
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0].textContent).toContain('old.safetensors');
|
||||
expect(rows[0].textContent).toContain('new.safetensors');
|
||||
expect(rows[0].querySelector('.rematch-undo-btn[data-action="undo-match"][data-index="0"]')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user