From 2a1ea249fa4486c7fa546061e45485f0d01425af Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Tue, 27 Oct 2026 19:12:29 -0700 Subject: [PATCH] test(history): compare the History media inspector's category chip against the concept (#332) D145: with an own category set on the baseline's newest output, the Recorded run viewer's chip is compared with the concept viewer's category chip laid out with the D146 origin and the own category at runtime; D21 maps its hidden "Image category: " prefix. Unmapped, the two differ only in the text, the prefix, the width and span.chip-source. --- tests/browser/history-concept.spec.mjs | 58 +++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/tests/browser/history-concept.spec.mjs b/tests/browser/history-concept.spec.mjs index 353b25d4..b58499e1 100644 --- a/tests/browser/history-concept.spec.mjs +++ b/tests/browser/history-concept.spec.mjs @@ -1,7 +1,7 @@ import {writeFile} from 'node:fs/promises'; import {test,expect} from './fixtures.mjs'; import {captureCanonicalState} from './history-canonical-comparison.mjs'; -import {SIZES,THEMES,MOTIONS,seedConcept,openConcept,openProduction,inventoryOf,diffInventory,mutate,stripExact} from './concept-inventory.mjs'; +import {SIZES,THEMES,MOTIONS,seedConcept,openConcept,openProduction,inventoryOf,diffInventory,mutate,stripExact,measureInspector,withChipSource} from './concept-inventory.mjs'; // Task F5 of #249: the production Prompt history (#/library/) against the immutable concept's (#prompt/alpine, // app.js renderHistory a587–622, the dialogs a1282–1304, style.css 1059–1179 and 1546–1567), on the concept-shaped real @@ -272,6 +272,62 @@ for(const [width,height] of SIZES)for(const theme of THEMES)test(`history geomet } finally {await host.evaluate(()=>window.conceptHost.dispose());await host.close();} }); +/** + * D145 (#332): the History media inspector (a recorded run's current file, `recorded-run.mjs`) shows the file's own + * category with the chip rule of the Images side panel and viewer (D146). The concept has no recorded-run inspector, so + * its chip is the concept viewer's own category chip (app.js a402, imageDetails(i, true)) with the recorded D146 origin + * and the own category added at runtime (`withChipSource`). The chip's own place depends on what precedes it in either + * inspector, so its box is compared by size only. + */ +const OWN_CATEGORY='Portraits'; +const chipOnly=chip=>{const {left,top,source,...rest}=chip;return {...rest,source:source&&(({left,top,...own})=>own)(source)};}; +const categoryChipOf=async(page,root)=>(await page.evaluate(measureInspector,root)).badges.chips.find(chip=>chip.source); + +async function conceptViewerChip(page,theme) { + await openConcept(page,'images',theme);await page.mouse.move(0,0); + await page.locator('.gallery-body > .image-grid > .media-card').first().locator('.image-open').click(); + await expect(page.locator('#viewer-dialog[open] #viewer-details .panel-heading h2')).toHaveText(TITLE); + const restore=await withChipSource(page,'#viewer-details','this image',OWN_CATEGORY);await settle(page); + const chip=await categoryChipOf(page,'#viewer-details'); + await restore(); + return chip; +} + +/** Recorded run for the baseline's newest run, its one reported output opened in the viewer. */ +async function productionRunChip(page,theme,positiveId) { + await openProductionHistory(page,theme,'no-preference',positiveId); + await page.getByRole('button',{name:'Show repeat run for Quality baseline',exact:true}).click(); + const run=page.getByRole('dialog',{name:'Recorded run',exact:true}); + await expect(run).toBeVisible(); + const view=run.getByRole('button',{name:'View current file',exact:true}); + await expect(view).toBeEnabled();await view.click(); + const details=page.locator('#viewer-dialog[open] #viewer-details'); + await expect(details.locator('.chip-source')).toHaveText(' · this image'); + await page.mouse.move(0,0);await settle(page); + return categoryChipOf(page,'#viewer-dialog[open] #viewer-details'); +} + +async function setOwnCategory(request,path,category) { + const listing=await (await request.get('/prompt_manager/images/output?mode=workspace&limit=40')).json(); + const root_scope=listing.images.find(item=>item.root_index===0).root_scope; + const response=await request.put('/prompt_manager/images/category',{data:{items:[{root_index:0,root_scope,path}],category}}); + await expect(response).toBeOK(); +} + +for(const [width,height] of SIZES)for(const theme of THEMES)test(`history media inspector category chip matches the concept ${width}x${height} ${theme} (D145)`,async({page,request},testInfo)=>{ + await page.setViewportSize({width,height}); + const {manifest}=testInfo; + // The baseline's newest image: the output of the run its dot opens (the fixture records one run per image). + const baseline=manifest.images.find(image=>image.prompt==='alpine'&&image.variant===0); + await setOwnCategory(request,baseline.path,OWN_CATEGORY); + const reference=await conceptViewerChip(page,theme); + const actual=await productionRunChip(page,theme,manifest.prompts[0].positive_id); + await captureCanonicalState(page,testInfo,`production-history-run-chip-${width}-${theme}`,'#viewer-dialog[open]'); + expect.soft(reference,'concept chip').toMatchObject({tag:'span',className:'chip',text:`${OWN_CATEGORY} · this image`,hidden:null}); + // D21: the hidden prefix names the scope; D146 as above. + expect.soft(diffInventory({...chipOnly(reference),hidden:'Image category: '},chipOnly(actual)),'chip differences after D21, D145, D146').toEqual([]); +}); + // The comparison is only worth something if it notices a change (spec §7 mutation check): one CSS mutation (the variant // dot's width) and one DOM mutation (Compare negatives removed), each applied after an exact match. test('history comparison detects mutations',async({page},testInfo)=>{