87 lines
4.7 KiB
JavaScript
87 lines
4.7 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import vm from 'node:vm';
|
|
import {readFileSync} from 'node:fs';
|
|
|
|
const source=readFileSync(new URL('../host/gaia-input.js',import.meta.url),'utf8');
|
|
function keyboard() {
|
|
const listeners=new Map();let reply;
|
|
const parent={postMessage(){}};
|
|
const context={VulpesNativeInput:true,location:{hostname:'keyboard.localhost'},
|
|
VulpesCompat:{call:()=>new Promise(resolve=>reply=resolve)},
|
|
navigator:{},document:{readyState:'loading'},parent,console,Event,EventTarget,
|
|
CustomEvent:class extends Event {constructor(name,options){super(name);this.detail=options.detail;}},
|
|
addEventListener(name,fn){listeners.set(name,fn);},dispatchEvent(){}};
|
|
context.window=context;
|
|
vm.runInNewContext(source,context);
|
|
return {
|
|
ime:context.navigator.mozInputMethod,
|
|
state:value=>listeners.get('vulpes-service-event')({detail:{type:'input-focus',data:value}}),
|
|
reply:value=>reply(value),
|
|
viewport:()=>listeners.get('message')({source:parent,origin:'http://system.localhost:8765',
|
|
data:{type:'vulpes-keyboard-viewport',width:360,height:740}})
|
|
};
|
|
}
|
|
test('an old empty snapshot cannot clear a newly focused keyboard',async()=>{
|
|
const k=keyboard();k.viewport();k.state({id:2,text:'current'});k.reply(null);
|
|
await new Promise(resolve=>setImmediate(resolve));
|
|
assert.equal(k.ime.inputcontext.text,'current');
|
|
});
|
|
test('an old active snapshot cannot reopen a dismissed keyboard',async()=>{
|
|
const k=keyboard();k.viewport();k.state(null);k.reply({id:1,text:'old'});
|
|
await new Promise(resolve=>setImmediate(resolve));
|
|
assert.equal(k.ime.inputcontext,null);
|
|
});
|
|
test('focus received before viewport measurement survives a stale initial reply',async()=>{
|
|
const k=keyboard();k.state({id:3,text:'pending'});k.reply(null);
|
|
await new Promise(resolve=>setImmediate(resolve));
|
|
k.viewport();
|
|
assert.equal(k.ime.inputcontext.text,'pending');
|
|
});
|
|
|
|
function systemKeyboard({legacy=true}={}) {
|
|
const events=new EventTarget(), intervals=[], calls=[], states=new Map();
|
|
const frame={style:{},hidden:true,contentWindow:{postMessage(){}},addEventListener(){}};
|
|
const context={VulpesNativeInput:true,location:{hostname:'system.localhost'},
|
|
VulpesCompat:{call:(method,params)=>{calls.push({method,params});return Promise.resolve();}},
|
|
document:{documentElement:{lang:'fr'},createElement:()=>frame,body:{append(){}}},navigator:{language:'fr'},
|
|
inputWindowManager:legacy?{}:undefined,Service:{registerState:(name,provider)=>states.set(name,provider),register:(name,provider)=>states.set(name,provider)},
|
|
console,Event,EventTarget,CustomEvent:class extends Event {constructor(name,options={}){super(name);this.detail=options.detail;}},
|
|
setInterval:fn=>{intervals.push(fn);return intervals.length;},clearInterval(){},
|
|
addEventListener:events.addEventListener.bind(events),dispatchEvent:events.dispatchEvent.bind(events)};
|
|
context.window=context;vm.runInNewContext(source,context);intervals.forEach(fn=>fn());
|
|
const focus=value=>events.dispatchEvent(new context.CustomEvent('vulpes-service-event',{detail:{type:'input-focus',data:value}}));
|
|
return {events,context,frame,calls,focus,states};
|
|
}
|
|
test('Gaia hide commands target the host keyboard',async()=>{
|
|
const s=systemKeyboard();
|
|
await s.context.inputWindowManager.hideInputWindow();
|
|
await s.context.inputWindowManager.hideInputWindowImmediately();
|
|
assert.equal(s.calls.length,2);
|
|
assert.ok(s.calls.every(c=>c.method==='input.request' && c.params.operation==='hide'));
|
|
});
|
|
test('keyboardhidden follows layout completion so app switching can resume',async()=>{
|
|
const s=systemKeyboard(), seen=[];let finish;
|
|
s.events.addEventListener('keyboardhide',e=>{seen.push('hide');e.detail.waitUntil(new Promise(r=>finish=r));});
|
|
s.events.addEventListener('keyboardhidden',()=>seen.push('hidden'));
|
|
s.focus({id:1});s.focus(null);
|
|
assert.equal(s.frame.hidden,true);assert.deepEqual(seen,['hide']);
|
|
finish();await new Promise(r=>setImmediate(r));
|
|
assert.deepEqual(seen,['hide','hidden']);
|
|
});
|
|
test('refocus cancels stale keyboardhidden completion',async()=>{
|
|
const s=systemKeyboard();let finish,hidden=0;
|
|
s.events.addEventListener('keyboardhide',e=>e.detail.waitUntil(new Promise(r=>finish=r)));
|
|
s.events.addEventListener('keyboardhidden',()=>hidden++);
|
|
s.focus({id:1});s.focus(null);s.focus({id:2});finish();
|
|
await new Promise(r=>setImmediate(r));
|
|
assert.equal(hidden,0);assert.equal(s.frame.hidden,false);
|
|
});
|
|
|
|
test('standalone host registers hide services without the legacy KeyboardManager',async()=>{
|
|
const s=systemKeyboard({legacy:false});
|
|
for(const name of ['hideInputWindow','hideInputWindowImmediately'])
|
|
await s.states.get(name)[name]();
|
|
assert.equal(s.calls.length,2);
|
|
s.focus({id:1});assert.equal(s.states.get('getHeight').getHeight(),260);
|
|
});
|