* feat(inspector): expose bounded physical file evidence * fix(inspector): distinguish ambiguous saved associations * feat(inspector): derive conservative generation facts * fix(inspector): guard fact ancestry and inspection state * feat(inspector): share complete file details and exports * fix(inspector): preserve fact states and touch targets * feat(viewer): navigate the loaded media collection Refs #216 * fix(viewer): refit after current stage geometry changes Ref #216. Observe the current media stage after readiness and wrapped title changes without rebuilding focused controls or resetting transforms at unchanged dimensions. * feat(viewer): add lifecycle-safe slideshow and media preferences * fix(viewer): resume gallery continuation after close (#216) * fix(images): guard source recovery and wrap viewer actions Clear stale root and folder intent, cancel old pages, and wait for current source discovery before loading a refreshed collection. Preserve safe retry through failed, superseded, and disposed discovery. Wrap narrow viewer footer actions and wait for measured gallery viewport settling in responsive checks. Refs #216; addresses PR #224 review. * test(viewer): await gallery layout after viewport changes Require actual inner, root client, and document widths to agree after each responsive-case viewport and CSS zoom transition. Keep the existing exact frame, overflow, focus, and coarse target assertions unchanged. Observed PR #224 CI initial mobile resize at537px before pending card relayout settled to356px. Refs #216.
51 lines
4.6 KiB
JavaScript
51 lines
4.6 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
const modulePath='../../web/js/workspace/file-inspector.mjs';
|
|
const load=()=>import(modulePath);
|
|
const rawWorkflow='{ "nodes": [], "seed": 9007199254740993, "cfg": 1.0000000000000000001 }';
|
|
function record() {
|
|
return {identity:{kind:'rooted-file',key:'2:a.png',root_index:2,path:'a.png',root_scope:'a'.repeat(64)},file:{format:'PNG'},prompts:{positive:{status:'resolved',text:'a\n猫'}},generation:{seed:{status:'known',value:'9007199254740993',source:'workflow',evidence:'configuration'}},library:{metadata_scope:'pair',tags:['saved']},download:{filename:'a.png',url:'/prompt_manager/images/serve/a.png?root=2'},metadata:{status:'available',omitted_reason:null,raw:{workflow:rawWorkflow,prompt:'{broken',parameters:'literal <svg> </pre>'},record_formats:{workflow:'original-text',prompt:'original-text',parameters:'original-text'},workflow_graph:{status:'parsed',value:{seed:'9007199254740993'}},prompt_graph:{status:'malformed',value:null},parameters:'literal <svg> </pre>'}};
|
|
}
|
|
|
|
test('metadata export retains literal raw and scoped evidence',async()=>{
|
|
const {inspectionDownloads}=await load(),source=record(),download=inspectionDownloads(source).metadata;
|
|
assert.equal(download.available,true);assert.equal(download.mime,'application/json');assert.equal(download.filename,'a.metadata.json');
|
|
const value=JSON.parse(download.bodyText);assert.equal(value.inspection_version,1);
|
|
for(const key of ['identity','file','prompts','generation','library'])assert.deepEqual(value[key],source[key]);
|
|
assert.deepEqual(value.metadata.raw,source.metadata.raw);assert.deepEqual(value.metadata.record_formats,source.metadata.record_formats);
|
|
assert.equal(value.metadata.prompt_graph,undefined);assert.equal(value.metadata.workflow_graph,undefined);
|
|
});
|
|
test('workflow export never reserializes unsafe numeric literals',async()=>{
|
|
const {inspectionDownloads}=await load();assert.equal(inspectionDownloads(record()).workflow.bodyText,rawWorkflow);
|
|
const source=record();source.metadata.workflow_graph.status='malformed';assert.equal(inspectionDownloads(source).workflow.available,false);
|
|
});
|
|
test('omitted raw forbids partial export',async()=>{
|
|
const {inspectionDownloads}=await load(),source=record();source.metadata.status='limited';source.metadata.omitted_reason='response-size-limit';source.metadata.raw={};
|
|
for(const value of Object.values(inspectionDownloads(source))) {assert.equal(value.available,false);assert.equal(value.bodyText,null);assert.equal(value.reason,'Raw metadata exceeds the response limit. Download the original file to inspect it externally.');}
|
|
assert.ok(source.download.url);
|
|
});
|
|
test('download names cannot introduce path segments',async()=>{
|
|
const {inspectionDownloads}=await load(),source=record();source.download.filename='../../evil\\nested\u0000.png';
|
|
for(const value of Object.values(inspectionDownloads(source)))assert.doesNotMatch(value.filename,/[\\/\x00-\x1f]/);
|
|
});
|
|
test('already disposed inspector creates no DOM URLs or handlers',async()=>{
|
|
const {mountFileInspector,mountPromptInspector}=await load(),parent=new AbortController();parent.abort();
|
|
const forbidden=new Proxy({}, {get(){assert.fail('disposed mount touched DOM/API');}});
|
|
for(const mount of [mountFileInspector,mountPromptInspector]) {
|
|
const view=mount(forbidden,{parentScope:{signal:parent.signal},sender:forbidden,api:forbidden,image:forbidden});
|
|
view.setState?.({record:record()});view.dispose();view.dispose();
|
|
}
|
|
});
|
|
test('reserialized container workflow never exports as original',async()=>{
|
|
const {inspectionDownloads}=await load(),source=record();source.metadata.record_formats.workflow='reserialized-json';
|
|
const workflow=inspectionDownloads(source).workflow;assert.equal(workflow.available,false);assert.equal(workflow.bodyText,null);
|
|
assert.equal(workflow.reason,'Original workflow JSON text is unavailable for this container record. Download metadata or the original file.');
|
|
});
|
|
test('metadata export preserves representation formats and omits private carrier',async()=>{
|
|
const {inspectionDownloads}=await load(),source=record();source.metadata.record_formats.workflow='reserialized-json';source.metadata.raw.private_carrier='SECRET';source.metadata.carrier='SECRET';
|
|
const download=inspectionDownloads(source).metadata,value=JSON.parse(download.bodyText);
|
|
assert.equal(value.metadata?.record_formats?.workflow,'reserialized-json');assert.equal(value.metadata.raw.workflow,rawWorkflow);assert.ok(!download.bodyText.includes('SECRET'));
|
|
assert.equal(value.metadata.representations.workflow,'Reserialized container JSON; numeric spelling/precision may differ');
|
|
});
|