Release Vulpes OS 2.7-preview.24
This commit is contained in:
parent
49572b6b45
commit
97ef99f827
135 changed files with 7717 additions and 2524 deletions
|
|
@ -59,6 +59,20 @@ test('toolbar focus does not select a bar hidden behind the lockscreen or a dial
|
|||
assert.equal(selected,1);
|
||||
});
|
||||
|
||||
test('Up reopens the browser toolbar when the collapsed rocketbar search input kept focus',()=>{
|
||||
let selected=0;
|
||||
const bar={checkVisibility:()=>true,getBoundingClientRect:()=>({x:0,y:20,width:200,height:40}),
|
||||
contains:()=>true};
|
||||
const child=actor('KeypadChild');
|
||||
child.document={location:{hostname:'system.localhost'},activeElement:{id:'rocketbar-input',localName:'input',type:'search'},
|
||||
querySelector:()=>({classList:{contains:()=>false}}),querySelectorAll:()=>[bar],elementFromPoint:()=>bar,
|
||||
documentElement:{dataset:{}}};
|
||||
child.contentWindow={innerWidth:240,innerHeight:320};
|
||||
child.select=()=>selected++;
|
||||
child.receiveMessage({name:'Vulpes:Navigate',data:{key:'ArrowUp'}});
|
||||
assert.equal(selected,1);
|
||||
});
|
||||
|
||||
test('Down leaves the toolbar and routes to its page instead of retaining toolbar focus',()=>{
|
||||
let focused=false;
|
||||
const messages=[];
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import vm from 'node:vm';
|
|||
|
||||
function camera() {
|
||||
const workers = [];
|
||||
const context = {setTimeout, clearTimeout, Subprocess: {async call() {
|
||||
const context = {setTimeout, clearTimeout, Subprocess: {async call(options) {
|
||||
let finish;
|
||||
const done = new Promise(resolve => { finish = resolve; });
|
||||
const worker = {closed:false, writes:[],
|
||||
const worker = {closed:false, writes:[], options,
|
||||
stdin:{async close() {worker.closed=true;finish();}, async write(text) {worker.writes.push(text);}},
|
||||
wait:()=>done, kill:()=>finish()};
|
||||
workers.push(worker); return worker;
|
||||
|
|
@ -16,15 +16,15 @@ function camera() {
|
|||
const source = readFileSync(new URL('../host/NativeCamera.sys.mjs', import.meta.url),'utf8')
|
||||
.replace(/^import .*;\n/gm,'').replace('export class NativeCamera','globalThis.NativeCamera = class NativeCamera');
|
||||
vm.runInNewContext(source,context);
|
||||
const service = new context.NativeCamera();
|
||||
service.packet = async () => ({ready:true,format:'nv21',width:640,height:480});
|
||||
const service = new context.NativeCamera('beatles');
|
||||
service.packet = async () => ({ready:true,format:'nv21',...service.previewSize});
|
||||
return {service,workers};
|
||||
}
|
||||
|
||||
test('a delayed stop or frame from the old camera cannot close its replacement',async()=>{
|
||||
const {service,workers}=camera(), actor={};
|
||||
const old=await service.request(actor,{action:'start',camera:'back'});
|
||||
const next=await service.request(actor,{action:'start',camera:'front'});
|
||||
const next=await service.request(actor,{action:'start',camera:'back'});
|
||||
assert.notEqual(old.session,next.session);assert.equal(workers[0].closed,true);
|
||||
await service.request(actor,{action:'stop',session:old.session});
|
||||
assert.equal(workers[1].closed,false);
|
||||
|
|
@ -36,6 +36,34 @@ test('a delayed stop or frame from the old camera cannot close its replacement',
|
|||
assert.equal(workers[1].closed,true);
|
||||
});
|
||||
|
||||
test('Beatles uses the stock KaiOS 320x240 preview while other devices keep 640x480',async()=>{
|
||||
const {service,workers}=camera(), actor={};
|
||||
const metadata=await service.request(actor,{action:'start',camera:'back'});
|
||||
assert.equal(service.previewSize.width,320);
|
||||
assert.equal(service.previewSize.height,240);
|
||||
assert.equal(metadata.width,320);
|
||||
assert.equal(metadata.height,240);
|
||||
assert.equal(metadata.frameRate,5);
|
||||
assert.deepEqual([...metadata.cameras],['back']);
|
||||
assert.equal(workers[0].options.command,'/usr/bin/python3.11');
|
||||
assert.equal(workers[0].options.arguments[1],'/opt/vulpes/tundra/runtime/beatles-camera-client.py');
|
||||
await service.request(actor,{action:'stop',session:metadata.session});
|
||||
|
||||
const sargo=new service.constructor('sargo');
|
||||
assert.equal(sargo.previewSize.width,640);
|
||||
assert.equal(sargo.previewSize.height,480);
|
||||
sargo.packet=async()=>({ready:true,format:'nv21',...sargo.previewSize});
|
||||
const sargoMetadata=await sargo.request(actor,{action:'start',camera:'back'});
|
||||
assert.equal(sargoMetadata.frameRate,30);
|
||||
await sargo.request(actor,{action:'stop',session:sargoMetadata.session});
|
||||
});
|
||||
|
||||
test('Beatles reports the missing front sensor instead of opening its rear sensor twice',async()=>{
|
||||
const {service,workers}=camera(), actor={};
|
||||
await assert.rejects(service.request(actor,{action:'start',camera:'front'}),/CAMERA_UNAVAILABLE/);
|
||||
assert.equal(workers.length,0);
|
||||
});
|
||||
|
||||
test('a different actor cannot stop or use the current camera',async()=>{
|
||||
const {service,workers}=camera(), owner={}, other={};
|
||||
const meta=await service.request(owner,{action:'start'});
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ sys.stdout.buffer.write(b.getvalue())`,JSON.stringify({extra,manifest})]));
|
|||
}
|
||||
assert.equal(packageURL(source+'&lang=fr'),source);
|
||||
for(const url of ['http://vulpes-os.org/marketplace/index.php?download=42','https://evil.test/marketplace/index.php?download=42','https://vulpes-os.org/x?download=42','https://vulpes-os.org/marketplace/index.php?download=../42'])assert.throws(()=>packageURL(url));
|
||||
const bytes=zip({}, {type:'certified',permissions:{settings:{access:'readwrite'},'webapps-manage':{}}});
|
||||
const bytes=zip({}, {permissions:{settings:{access:'readwrite'},'webapps-manage':{}}});
|
||||
const item=await unpackPackage(bytes,source);
|
||||
assert.equal(item.manifest.type,'web');assert.deepEqual(item.manifest.permissions,{});assert.match(item.id,/^user-[a-f0-9]{24}$/);
|
||||
for (const type of ['privileged','certified'])
|
||||
await assert.rejects(unpackPackage(zip({}, {type}), source), /PRIVILEGED_APP_UNSUPPORTED/);
|
||||
for(const name of ['../escape','/absolute','dir/../../escape','dir\\escape','a:b'])await assert.rejects(unpackPackage(zip({[name]:'bad'}),source));
|
||||
await assert.rejects(unpackPackage(bytes.slice(0,-2),source));
|
||||
const corrupt=bytes.slice();corrupt[100]^=255;await assert.rejects(unpackPackage(corrupt,source));
|
||||
|
|
@ -24,7 +26,7 @@ const service=new Packages({save:async value=>saved=value,changed:async()=>chang
|
|||
const prepared=await service.prepare(source);assert.equal(saved,null);assert.equal(prepared.name,'Test');
|
||||
assert.equal(await service.install(prepared.token),item.id);assert.equal(changes,1);assert.equal(saved.id,item.id);
|
||||
await assert.rejects(service.install(prepared.token),/INSTALL_EXPIRED/);
|
||||
console.log('Package URL policy, ZIP boundaries/CRC, permission stripping and one-use confirmation: OK');
|
||||
console.log('Package URL policy, ZIP boundaries/CRC, permission stripping, privileged-app rejection and one-use confirmation: OK');
|
||||
|
||||
// Cancellation during download must not produce a confirmation token.
|
||||
let finishDownload;
|
||||
|
|
|
|||
Loading…
Reference in a new issue