feat(history): concept shared gallery
The history page's gallery is the concept's section.history-gallery with its panel heading (Shared gallery, <name> · same positive prompt) and the Images media cards, followed by the recorded executions. A saved variant with no images shows the concept's empty copy. Recorded as D47.
This commit is contained in:
@@ -39,7 +39,7 @@ export async function historyGeometry(page, production) {
|
||||
branch: '.library-history .variant-lane', label: '.library-history .variant-lane > span', dot: '.library-history .variant-dot[aria-pressed="true"]',
|
||||
positiveLabel: '.library-history .positive-lane span',
|
||||
role: '[aria-label="Saved prompt texts"] .prompt-section',
|
||||
prompt: '[aria-label="Saved prompt texts"] .prompt-text', gallery: '.library-shared-gallery',
|
||||
prompt: '[aria-label="Saved prompt texts"] .prompt-text', gallery: '.library-history .history-gallery',
|
||||
heading: '.library-history .timeline-panel h2', intro: '.library-history .timeline-panel .panel-heading .small',
|
||||
footer: '.library-history .timeline-lower', all: '.library-history .timeline-lower button',
|
||||
recordedPositiveLabel: '.history-positive-lane span', recordedBranch: '.history-context-branch',
|
||||
|
||||
@@ -959,3 +959,47 @@ test('dot names never repeat for trimmed titles or titles that read like a fallb
|
||||
expect(new Set(names).size).toBe(names.length);
|
||||
expect(names).toEqual(['Select negative variant 1','Select negative variant 2','Select negative variant 3']);
|
||||
});
|
||||
// #249 F4: the concept's shared gallery (app.js a616-621, style.css 1546-1548) of PR C media cards (C3 activation).
|
||||
test('shared gallery uses concept cards, image opens the viewer, empty copy',async({page,request})=>{
|
||||
const f=page.fixture;await open(page,f.canonical_id);
|
||||
const section=gallery(page),heading=section.locator(':scope > .panel-heading');
|
||||
expect(await section.evaluate(node=>`${node.tagName.toLowerCase()}.${node.className}`)).toBe('section.history-gallery');
|
||||
await expect(heading.locator(':scope > h2')).toHaveText('Shared gallery');
|
||||
// The page opens on the newest lane, a saved variant with no runs: the concept's empty copy.
|
||||
await expect(heading.locator(':scope > span.small')).toHaveText('No negative · same positive prompt');
|
||||
await expect(section.locator('.empty')).toHaveText('Saved variant · no runs yet.');await expect(section.locator('.empty')).toBeVisible();
|
||||
await expect(section.locator('article[data-image-key]')).toHaveCount(0);
|
||||
// As in the concept, the gallery follows the variants panel; recorded executions (#209) stay below it.
|
||||
const order=await page.locator('.library-history').evaluate(node=>[...node.querySelectorAll('section[aria-label]')].map(item=>item.getAttribute('aria-label')).filter(name=>['Saved negative variants','Shared gallery','Recorded executions'].includes(name)));
|
||||
expect(order).toEqual(['Saved negative variants','Shared gallery','Recorded executions']);
|
||||
const dot=variantsPanel(page).getByRole('button',{name:'Select negative Quality baseline',exact:true});await dot.click();await expect(dot).toHaveAttribute('aria-pressed','true');
|
||||
await expect(heading.locator(':scope > span.small')).toHaveText('Quality baseline · same positive prompt');
|
||||
const cards=section.locator('article.media-card[data-image-key]');await expect(cards).toHaveCount(2);
|
||||
await expect(section.locator('.empty')).toBeHidden();
|
||||
const details=await(await request.get(`/prompt_manager/library/${f.canonical_id}`)).json();
|
||||
const pair=details.variants.find(variant=>variant.title==='Quality baseline').id;
|
||||
const listing=await(await request.get(`/prompt_manager/library/${f.canonical_id}/images?pair_id=${pair}`)).json();
|
||||
const first=cards.first(),filename=(await first.locator('.image-open').getAttribute('aria-label')).replace(/^Open /,'');
|
||||
const image=listing.images.find(item=>item.filename===filename);expect(typeof image.favorite).toBe('boolean');
|
||||
// The concept card: art (open button + overlays), then the info row; the star where the read returns favorite and
|
||||
// no checkbox (the selection bar stays Images-only, #259).
|
||||
expect(await first.evaluate(node=>[...node.children].map(child=>child.className))).toEqual(['media-art','media-info']);
|
||||
expect(await first.locator(':scope > .media-art').evaluate(node=>[...node.children].map(child=>`${child.tagName.toLowerCase()}.${child.className}`))).toEqual(['button.image-open','div.image-overlays']);
|
||||
await expect(first.locator('.image-overlays input[type="checkbox"]')).toHaveCount(0);
|
||||
const star=first.locator('.image-overlays > button.star-button');
|
||||
await expect(star).toHaveAccessibleName(`Favorite ${filename}`);await expect(star).toHaveAttribute('aria-pressed',String(image.favorite));
|
||||
await expect(star).toHaveText(image.favorite?'★':'☆');
|
||||
await expect(first.locator('.media-info h3')).toHaveCount(1);
|
||||
await expect(first.locator('.image-card-actions > button.icon-btn')).toHaveAccessibleName(`Inspect ${filename}`);
|
||||
// C3 activation: the image opens the viewer and Escape returns focus to it.
|
||||
const openButton=first.locator('.image-open');await openButton.click();
|
||||
const viewer=page.locator('dialog#viewer-dialog');await expect(viewer).toBeVisible();
|
||||
await page.keyboard.press('Escape');await expect(viewer).toBeHidden();await expect(openButton).toBeFocused();
|
||||
await expect(section.locator(':scope > .panel-heading')).toHaveCSS('justify-content','space-between');
|
||||
await expect(section).toHaveCSS('margin-top','24px');
|
||||
// At 390px the heading stays inside the gallery and the page never scrolls sideways.
|
||||
await page.setViewportSize({width:390,height:844});
|
||||
await expect.poll(()=>page.evaluate(()=>document.documentElement.scrollWidth-document.documentElement.clientWidth)).toBeLessThanOrEqual(1);
|
||||
const [box,text]=await Promise.all([section.boundingBox(),heading.locator(':scope > span.small').boundingBox()]);
|
||||
expect(text.x+text.width).toBeLessThanOrEqual(box.x+box.width+0.5);
|
||||
});
|
||||
|
||||
@@ -563,7 +563,7 @@ test('saved choices retain curved staggered lanes and the full negative immediat
|
||||
const values=await page.locator('.library-history .variant-lane').evaluateAll(nodes=>nodes.map(node=>({offset:parseFloat(getComputedStyle(node).marginInlineStart),radius:getComputedStyle(node,'::before').borderBottomLeftRadius})));
|
||||
expect(values).toEqual([{offset:38,radius:'22px'},{offset:85,radius:'22px'},{offset:132,radius:'22px'},{offset:179,radius:'22px'}]);
|
||||
const order=await page.locator('.library-history').evaluate(node=>[...node.querySelectorAll('section[aria-label]')].filter(section=>['Saved negative variants','Recorded executions','Shared gallery'].includes(section.getAttribute('aria-label'))).map(section=>section.getAttribute('aria-label')));
|
||||
expect(order).toEqual(['Saved negative variants','Recorded executions','Shared gallery']);
|
||||
expect(order).toEqual(['Saved negative variants','Shared gallery','Recorded executions']);
|
||||
const rows=await(await request.get('/prompt_manager/library')).json();const cat=rows.items.find(item=>item.text==='Cat portrait');
|
||||
const details=await(await request.get(`/prompt_manager/library/${cat.id}`)).json();
|
||||
const selected=details.variants[1].negative_text;
|
||||
|
||||
@@ -106,12 +106,12 @@ for (const [width, height] of [[1600, 1100], [390, 844]]) for (const theme of ['
|
||||
// As in the concept, the selected pair's send sits in the history heading, not among the saved texts.
|
||||
await expect(roles.getByRole('button', { name: 'Send prompt pair', exact: true })).toHaveCount(0);
|
||||
await expect(page.locator('#workspace-main > .heading button.send')).toBeEnabled();
|
||||
await expect(page.locator('.library-shared-gallery article[data-image-key]')).toHaveCount(2);
|
||||
await page.locator('.library-shared-gallery .image-card-actions button').first().click();
|
||||
await expect(page.locator('.library-history .history-gallery article[data-image-key]')).toHaveCount(2);
|
||||
await page.locator('.library-history .history-gallery .image-card-actions button').first().click();
|
||||
await expect(page.getByRole('complementary', { name: 'Selected image details', exact: true })).toContainText('canonical-alt.png');
|
||||
await page.locator('.library-shared-gallery .media-card.selected .image-open').click();
|
||||
await page.locator('.library-history .history-gallery .media-card.selected .image-open').click();
|
||||
await expect(page.locator('dialog#viewer-dialog')).toBeVisible();await page.keyboard.press('Escape');
|
||||
for (const [name, selector] of [['saved', '.library-history article.surface'], ['branches', '.library-history .timeline-panel'], ['gallery', '.library-shared-gallery'], ['chronology', '.recorded-history']]) {
|
||||
for (const [name, selector] of [['saved', '.library-history article.surface'], ['branches', '.library-history .timeline-panel'], ['gallery', '.library-history .history-gallery'], ['chronology', '.recorded-history']]) {
|
||||
await captureCanonicalState(page, testInfo, `production-${name}-${width}-${theme}`, selector);
|
||||
}
|
||||
const run = page.locator(`button[data-run-id="${fixture.canonical_run}"][data-anchor-id="10"]`);
|
||||
|
||||
Reference in New Issue
Block a user