Files
ComfyAssets-ComfyUI_PromptM…/tests/browser/library-editor-visual.spec.mjs
Vito 8bcde4fbe4 feat(library): native recipe editor UI, pair archive browsing and export (#235)
* feat(library): manage native editor drafts and recovery

* fix(library): fence pair archive retries against restored storage

* feat(library): add native recipe editor dialogs

* fix(library): read archive counts through the editor controller

* feat(library): reconcile editor saves and archived pairs

Wire the concept's heading actions (Library Export and + New prompt, Images New prompt, history ← Prompt library, Edit metadata and pair Send to ComfyUI) and the Negative variants + Add negative and Show archived controls. Saves navigate or reconcile in place through createLibraryDetail.accept, invalidate cached shelf state by write generation, browse and restore archived pairs, and offer an explicit Library reload when storage was restored.

* fix(library): refresh archived pairs and align editor outcomes

* test(library): enforce approved native editor presentation

* test(library): verify editor host and coverage compatibility

Adds the rejected connected-target host assertion, pins the stock host listing at 80 cases, documents the native editor, and keeps Save/Archive focus after a failed write.

* docs(testing): correct host case totals and cover archive focus

* fix(library): fence editor reloads after restore and settle outcome copy
2026-10-19 01:46:33 -07:00

50 lines
3.8 KiB
JavaScript

import {test,expect} from '../../tools/vendor/node_modules/@playwright/test/index.mjs';
import {captureCanonicalState} from './history-canonical-comparison.mjs';
// Artifact-only captures of the editor states that have no concept counterpart (archive confirmation, Show archived,
// busy and error). The four concept-referenced modes are captured as concept/production pairs by
// workspace-library-editor.spec.mjs. There are deliberately no pixel assertions or baselines: the maintainer accepts
// these captures visually before any baseline is added.
test.use({locale:'en-US',timezoneId:'UTC',reducedMotion:'reduce'});
const workspace='/prompt_manager/workspace';
const editor=page=>page.locator('dialog.library-editor');
const variants=page=>page.getByRole('region',{name:'Saved negative variants',exact:true});
const note=page=>editor(page).locator('.warning-note');
test.beforeEach(async({page,request})=>{
const reset=await request.post('/__test__/library-editor/reset',{data:{}});await expect(reset).toBeOK();page.fixture=await reset.json();
});
for(const [width,height,theme] of [[1600,1100,'dark'],[390,844,'light']])test(`editor state captures for maintainer acceptance ${width} ${theme}`,async({page},testInfo)=>{
await page.setViewportSize({width,height});const f=page.fixture;
await page.goto(`${workspace}#/library/${f.positive_id}`);await expect(variants(page)).toBeVisible();
await page.getByRole('combobox',{name:'Appearance',exact:true}).selectOption(theme);
const shot=name=>captureCanonicalState(page,testInfo,`production-${name}-${width}-${theme}`,name.startsWith('editor-archived')?'.library-variants':'dialog.library-editor');
await variants(page).getByRole('button',{name:'Select negative variant 2',exact:true}).click();
await page.getByRole('button',{name:'Edit metadata',exact:true}).click();await expect(editor(page)).toBeVisible();
await expect(editor(page).getByRole('button',{name:'Save',exact:true})).toBeEnabled();
await editor(page).getByRole('button',{name:'Archive…',exact:true}).click();
await expect(editor(page).getByRole('button',{name:'Archive',exact:true})).toBeEnabled();await shot('editor-archive');
let release;const held=new Promise(resolve=>{release=resolve;});
await page.route(`**/prompt_manager/pairs/${f.pair_id}`,async route=>{if(route.request().method()==='PATCH')await held;await route.continue();});
await editor(page).getByRole('button',{name:'Archive',exact:true}).click();
await expect(note(page)).toBeVisible();await shot('editor-busy');
release();await expect(editor(page)).toHaveCount(0);await page.unroute(`**/prompt_manager/pairs/${f.pair_id}`);
await variants(page).getByRole('button',{name:/^Show archived/}).click();
await expect(variants(page).locator('.library-variant-archived').first()).toBeVisible();await shot('editor-archived-library');
await page.goto(`${workspace}#/library`);await page.getByRole('button',{name:'+ New prompt',exact:true}).click();
await expect(editor(page).getByRole('button',{name:'Save',exact:true})).toBeEnabled();
await page.route('**/prompt_manager/library',route=>route.request().method()==='POST'
?route.fulfill({status:400,contentType:'application/json',body:'{"success":false,"error":"Title is too long.","code":null}'}):route.continue(),{times:1});
await editor(page).getByLabel('Title',{exact:true}).fill(`Long title ${'W'.repeat(160)} with a very long tail`);
await editor(page).getByLabel('Positive prompt',{exact:true}).fill('A quiet harbor at dawn');
await editor(page).getByLabel('Notes',{exact:true}).fill(Array.from({length:40},(_,index)=>`Long note line ${index}`).join('\n'));
await editor(page).getByRole('button',{name:'Save',exact:true}).click();
await expect(note(page)).toHaveText('Not saved: Title is too long. Your entries are kept.');
await shot('editor-long-error');
});