Release Preview 20 shared desktop, Android and Sargo updates
Restore Gaia settings and media workflows, extend native network adapters, and align the APK payload. Generate French/English changelogs and website entries from the simplified JSON source. Validation: shared JS and Tundra unit tests; desktop workflows and persistence; signed APK payload parity; Sargo RAM and installed startup with boot readback. Android runtime qualification is recorded separately.
This commit is contained in:
parent
be2cdce823
commit
1311bed5e9
170 changed files with 19698 additions and 2495 deletions
53
tests/settings-dialogs.test.mjs
Normal file
53
tests/settings-dialogs.test.mjs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import vm from 'node:vm';
|
||||
import {readFile} from 'node:fs/promises';
|
||||
const load=name=>readFile(new URL('../gaia/apps/settings/js/modules/'+name+'.js',import.meta.url),'utf8');
|
||||
const serviceSource=await load('dialog_service');
|
||||
const managerSource=await load('dialog_manager');
|
||||
const defer=()=>{let resolve,reject;const promise=new Promise((a,b)=>{resolve=a;reject=b;});return {promise,resolve,reject};};
|
||||
const settle=()=>new Promise(resolve=>setImmediate(resolve));
|
||||
function serviceFixture() {
|
||||
let service;const opens=[],closes=[];let rejectClose=false;
|
||||
const panel=Object.assign(new EventTarget(),{classList:{remove(){}},hidden:false});
|
||||
const factory=()=>({panel,cleanup(){}});
|
||||
const manager={open(dialog,options){const d=defer();opens.push({options,...d});return d.promise;},close(dialog,type){closes.push(type);return rejectClose?Promise.reject(Error('invalid')):Promise.resolve('value');}};
|
||||
vm.runInNewContext(serviceSource,{Promise,CustomEvent,document:{getElementById:()=>panel,querySelector:()=>null},define:fn=>{service=fn(name=>name==='settings'?{currentPanel:'#root'}:name==='modules/defer'?defer:name==='modules/dialog_manager'?manager:factory);}});
|
||||
return {service,opens,closes,panel,reject:value=>{rejectClose=value;}};
|
||||
}
|
||||
test('early cancel waits for opening and queued reopen runs after closure',async()=>{
|
||||
const f=serviceFixture();const first=f.service.show('one');const second=f.service.show('two');
|
||||
f.opens[0].options.onWrapCancel();f.opens[0].options.onWrapCancel();
|
||||
await settle();assert.equal(f.closes.length,0);assert.equal(f.opens.length,1);
|
||||
f.opens[0].resolve();assert.equal((await first).type,'cancel');await settle();
|
||||
assert.equal(f.closes.length,1);assert.equal(f.opens.length,2);
|
||||
f.opens[1].resolve();f.opens[1].options.onWrapCancel();await second;assert.equal(f.service._navigating,false);
|
||||
});
|
||||
test('failed validation preserves dialog and permits a later submit',async()=>{
|
||||
const f=serviceFixture();let failures=0;f.panel.addEventListener('dialogvalidationfailed',()=>failures++);
|
||||
const shown=f.service.show('one');f.opens[0].resolve();await settle();
|
||||
f.reject(true);f.opens[0].options.onWrapSubmit();await settle();
|
||||
assert.equal(failures,1);assert.equal(f.service._navigating,true);
|
||||
f.reject(false);f.opens[0].options.onWrapSubmit();assert.equal((await shown).type,'submit');assert.equal(f.service._navigating,false);
|
||||
});
|
||||
function transitionFixture(duration='0.4s') {
|
||||
let manager,next=0;const timers=new Map();const classes=new Set();
|
||||
const panel=Object.assign(new EventTarget(),{hidden:true,classList:{toggle(name,on){on?classes.add(name):classes.delete(name);}}});
|
||||
vm.runInNewContext(managerSource,{Promise,document:{querySelector:()=>({})},window:{getComputedStyle:()=>({transitionDuration:duration,transitionDelay:'0s'})},
|
||||
setTimeout:(fn,ms)=>{timers.set(++next,{fn,ms});return next;},clearTimeout:id=>timers.delete(id),define:fn=>{manager=fn(()=>({}));}});
|
||||
return {manager,panel,classes,timers,step(){const [id,timer]=timers.entries().next().value;timers.delete(id);timer.fn();return timer.ms;}};
|
||||
}
|
||||
test('missing visibility transition and disabled animations both complete',async()=>{
|
||||
for(const duration of ['0.4s','0s']) {
|
||||
const f=transitionFixture(duration);const opened=f.manager._transit('open',{panel:f.panel},{});
|
||||
assert.equal(f.panel.hidden,false);assert.equal(f.step(),150);assert(f.classes.has('current'));
|
||||
assert.equal(f.step(),duration==='0s'?100:500);await opened;
|
||||
const closed=f.manager._transit('close',{panel:f.panel},{});f.step();f.step();await closed;
|
||||
assert.equal(f.panel.hidden,true);assert.equal(f.timers.size,0);
|
||||
}
|
||||
});
|
||||
test('cancelled visibility transition resolves once and removes fallback',async()=>{
|
||||
const f=transitionFixture();const p=f.manager._transit('close',{panel:f.panel},{});f.step();
|
||||
const e=new Event('transitioncancel');Object.defineProperty(e,'propertyName',{value:'visibility'});f.panel.dispatchEvent(e);await p;
|
||||
assert.equal(f.panel.hidden,true);assert.equal(f.timers.size,0);
|
||||
});
|
||||
Loading…
Reference in a new issue