import { describe, expect, test } from 'bun:test'; import { approveRemaining, newDraft, submission, summarize, validBox, type ReviewPacket } from './model'; const packet: ReviewPacket = { id:'review-1', revision:'packet-1', title:'Test', round:1, comp:{url:'/comp.png',width:100,height:100}, components:[{id:'art',revision:'art-1',name:'Art',medium:'Raster',note:'',box:{x:0,y:0,w:1,h:1},preview:{kind:'image',url:'/art.png'}},{id:'control',revision:'control-1',name:'Button',medium:'HTML',note:'',box:{x:0,y:0,w:.1,h:.1},preview:{kind:'page',url:'/page.html'}}] }; describe('component review drafts',()=>{ test('bulk approval still requires explicit inventory confirmation',()=>{const draft=approveRemaining(packet,newDraft(packet));expect(summarize(packet,draft).canSubmit).toBe(false);draft.inventoryConfirmed=true;expect(submission(packet,draft).requestId).toBe(packet.id);}); test('approve remaining preserves requests for repair and their feedback',()=>{const draft=newDraft(packet);draft.decisions.art={revision:'art-1',action:'revise',feedback:'Keep the original motif',split:true};const bulk=approveRemaining(packet,draft);expect(bulk.decisions.art).toEqual(draft.decisions.art);expect(summarize(packet,bulk)).toMatchObject({approved:1,revisions:1,canSubmit:true});}); test('stale packet cannot submit',()=>{const draft=approveRemaining(packet,newDraft(packet));draft.inventoryConfirmed=true;expect(()=>submission({...packet,revision:'packet-2'},draft)).toThrow('stale');}); test('changed component version invalidates only that decision',()=>{const draft=approveRemaining(packet,newDraft(packet));draft.inventoryConfirmed=true;const changed={...packet,components:packet.components.map(c=>c.id==='art'?{...c,revision:'art-2'}:c)};expect(summarize(changed,draft)).toMatchObject({approved:1,pending:1,canSubmit:false});}); test('a missing item can be submitted without approving unrelated components',()=>{const draft=newDraft(packet);draft.missing.push({id:'missing-1',name:'Brushwork',box:{x:.2,y:.3,w:.2,h:.2},feedback:'Missing texture'});expect(summarize(packet,draft)).toMatchObject({pending:2,hasFeedback:true,canSubmit:true});draft.missing[0].name=' ';expect(summarize(packet,draft).canSubmit).toBe(false);}); test('rejects invalid regions and does not share mutable submission state',()=>{expect(validBox({x:0,y:0,w:0,h:1})).toBe(false);expect(validBox({x:.9,y:0,w:.5,h:1})).toBe(false);expect(validBox({x:NaN,y:0,w:1,h:1})).toBe(false);const draft=approveRemaining(packet,newDraft(packet));draft.inventoryConfirmed=true;const receipt=submission(packet,draft);draft.decisions.art.feedback='Changed';expect(receipt.decisions.art.feedback).toBe('');}); }); test('draft-only or changed prior decisions never count as carried approval', async () => { const { repairStatus } = await import('./model'); const history: any = { submitted: false, packet: { round: 1 }, draft: { decisions: { art: { action: 'approve' } } }, changes: { art: { kind: 'unchanged', files: [], reasons: [] } }, }; expect(repairStatus('art', history).carried).toBe(false); history.submitted = true; history.changes.art.carried = true; expect(repairStatus('art', history).carried).toBe(true); history.changes.art.kind = 'changed'; expect(repairStatus('art', history).carried).toBe(false); expect(repairStatus('art', history).label).toBe('Review again'); }); test('attention states cannot hide a component behind a stale approval', async () => { const { componentState } = await import('./model'); const draft=approveRemaining(packet,newDraft(packet)); expect(componentState(packet.components[0],draft).kind).toBe('approved'); expect(componentState({...packet.components[0],revision:'new'},draft).kind).toBe('pending'); draft.decisions.art.action='revise'; expect(componentState(packet.components[0],draft)).toMatchObject({kind:'feedback',label:'Feedback ready'}); }); test('captured code keeps its implementation identity without trusting medium as proof', async () => { const { componentPresentation } = await import('./model'); const image = packet.components[0]; expect(componentPresentation({...image, medium:'SVG'}).code).toBe(false); const captured = {...packet.components[1], preview:{kind:'image' as const, sourceKind:'page' as const, url:'/capture.png'}}; expect(componentPresentation(captured)).toMatchObject({code:true,captured:true,label:'HTML',caption:'Rendered component',fileLabel:'Open captured preview'}); expect(componentPresentation(packet.components[1]).caption).toBe('Live component'); });