55 lines
5.5 KiB
JavaScript
55 lines
5.5 KiB
JavaScript
import test from 'node:test';
|
|||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import {createRequire} from 'node:module';
|
||
|
|
const {createWindowControls}=createRequire(import.meta.url)('../apps/browser/window-controls.cjs');
|
||
|
|
function fixture(options={}){
|
||
|
|
let destroyed=false,current=true,window,context;
|
||
|
|
const calls=[],state={visible:true,focused:false,minimized:false,maximized:false,fullScreen:false};
|
||
|
|
const contents={isDestroyed:()=>false};
|
||
|
|
window={webContents:contents,isDestroyed:()=>destroyed,getBounds:()=>({x:-500,y:90,width:1280,height:800,title:'secret'}),...Object.fromEntries(Object.entries(state).map(([key])=>['is'+key[0].toUpperCase()+key.slice(1),()=>state[key]])),minimize(){calls.push('minimize');state.minimized=true;},maximize(){calls.push('maximize');state.maximized=true;},restore(){calls.push('restore');state.minimized=false;state.maximized=false;},focus(){calls.push('focus');state.focused=true;}};
|
||
|
|
context={ready:true,policy:{version:1,agents:{enabled:true,scopes:['window:state','window:control']}}};
|
||
|
|
const controls=createWindowControls({getWindow:()=>window,getContext:()=>context,isCurrent:()=>current,...options});
|
||
|
|
return {controls,calls,state,get window(){return window;},get context(){return context;},replace:()=>window={...window},invalidate:()=>current=false,destroy:()=>destroyed=true};
|
||
|
|
}
|
||
|
|
test('window state exports only bounded dimensions and booleans',async()=>{
|
||
|
|
const f=fixture();assert.deepEqual(await f.controls.state(),{width:1280,height:800,visible:true,focused:false,minimized:false,maximized:false,fullScreen:false});
|
||
|
|
f.window.getBounds=()=>({width:Infinity,height:1});await assert.rejects(f.controls.state(),/dimensions/);
|
||
|
|
f.window.getBounds=()=>({width:1,height:1});f.window.isVisible=()=> 'secret';await assert.rejects(f.controls.state(),/state/);
|
||
|
|
});
|
||
|
|
test('the four allowed commands execute once and return fresh state',async()=>{
|
||
|
|
const f=fixture();for(const command of ['minimize','maximize','restore','focus'])assert.equal((await f.controls.control({command})).completed,true);
|
||
|
|
assert.deepEqual(f.calls,['minimize','maximize','restore','focus']);assert.equal((await f.controls.state()).focused,true);
|
||
|
|
});
|
||
|
|
test('state and control scopes are independent and global disable denies both',async()=>{
|
||
|
|
const f=fixture();f.context.policy.agents.scopes=['window:state'];await f.controls.state();await assert.rejects(f.controls.control({command:'focus'}),/scope/);
|
||
|
|
f.context.policy.agents.scopes=['window:control'];await assert.rejects(f.controls.state(),/scope/);await f.controls.control({command:'focus'});
|
||
|
|
f.context.policy.agents.enabled=false;await assert.rejects(f.controls.control({command:'focus'}),/scope/);assert.deepEqual(f.calls,['focus']);
|
||
|
|
});
|
||
|
|
test('no close, resize, arbitrary OS command, extra input or prototype access',async()=>{
|
||
|
|
const f=fixture();for(const args of [{command:'close'},{command:'setBounds'},{command:'__proto__'},{command:'focus',path:'secret'},null,Object.create({command:'focus'})])await assert.rejects(f.controls.control(args),/Invalid/);
|
||
|
|
await assert.rejects(f.controls.state({command:'focus'}),/Invalid/);assert.deepEqual(f.calls,[]);
|
||
|
|
});
|
||
|
|
test('expired and malformed policy, stale identity and destroyed native window deny effects',async()=>{
|
||
|
|
for(const mutate of [f=>f.context.policy.expiresAt='2000-01-01',f=>f.context.policy.expiresAt='invalid',f=>f.context.policy.agents.scopes='window:control',f=>f.invalidate(),f=>f.destroy(),f=>f.context.ready=false]){
|
||
|
|
const f=fixture();mutate(f);await assert.rejects(f.controls.control({command:'focus'}));assert.deepEqual(f.calls,[]);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
test('asynchronous authorization cannot release a stale policy, native window or cancellation',async()=>{
|
||
|
|
for(const mutation of ['policy','window','cancel','grant']){
|
||
|
|
const controller=new AbortController();let f,counter=0;f=fixture({authorize:async()=>{if(++counter===1){if(mutation==='policy')f.context.policy={...f.context.policy};if(mutation==='window')f.replace();if(mutation==='cancel')controller.abort();if(mutation==='grant')f.invalidate();}return true;}});
|
||
|
|
await assert.rejects(f.controls.control({command:'focus'},{signal:controller.signal}));assert.deepEqual(f.calls,[]);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
test('an effect is not repeated when it revokes policy or cancellation suppresses output',async()=>{
|
||
|
|
for(const mutation of ['policy','cancel']){
|
||
|
|
const f=fixture(),controller=new AbortController();f.window.focus=()=>{f.calls.push('focus');if(mutation==='policy')f.context.policy.agents.enabled=false;else controller.abort();};
|
||
|
|
await assert.rejects(f.controls.control({command:'focus'},{signal:controller.signal}));assert.deepEqual(f.calls,['focus']);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
test('fresh grant checks apply before the effect and native state getters cannot export after revocation',async()=>{
|
||
|
|
let checks=0;const f=fixture({authorize:async()=>++checks<2});await assert.rejects(f.controls.control({command:'focus'}),/authorization/);assert.deepEqual(f.calls,[]);
|
||
|
|
const g=fixture();g.window.getBounds=()=>{g.invalidate();return {width:1,height:1};};await assert.rejects(g.controls.state(),/changed/);
|
||
|
|
});
|
||
|
|
test('operation tracing wraps execution and failed native effects are never retried',async()=>{
|
||
|
|
const names=[],f=fixture({traceOperation:async(name,fn)=>{names.push(name);return fn();}});await f.controls.state();f.window.focus=()=>{f.calls.push('focus');throw Error('sensitive native failure');};await assert.rejects(f.controls.control({command:'focus'}),/^Error: Managed window operation failed$/);assert.deepEqual(names,['window:state','window:control']);assert.deepEqual(f.calls,['focus']);
|
||
|
|
});
|