Add permission-based reading of Android contacts, SMS and call history. Include shared Gaia fixes and document device validation limits. Pixel image qualification remains pending.
85 lines
4.7 KiB
JavaScript
85 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';
|
|
function setup(app,path,call){
|
|
const events=new Map(),notices=[];
|
|
const context={console,URL,Map,Set,Number,JSON,Date,Promise,queueMicrotask,setTimeout,clearTimeout,setInterval,clearInterval,
|
|
location:{hostname:app+'.localhost',pathname:path},navigator:{language:'en'},localStorage:{removeItem(){}},
|
|
document:{hidden:false,documentElement:{lang:'en'},readyState:'complete',addEventListener:(type,fn)=>events.set('document-'+type,fn),querySelectorAll(){return[];}},
|
|
MutationObserver:class{observe(){}},addEventListener:(type,fn)=>events.set(type,fn),
|
|
VulpesCompat:{call},VulpesGaiaUI:{notice:value=>notices.push(value)}};
|
|
context.window=context;vm.runInNewContext(readFileSync('android/ui/android-ui.js','utf8'),context);
|
|
return {context,events,notices};
|
|
}
|
|
test('native call rows use Gaia grouping and expose details without changing the local DB',async()=>{
|
|
const s=setup('communications','/dialer/index.html',async(_method,p)=>p.operation==='status'?{calls:true}:[
|
|
{_id:1,date:1000,number:'5550199',duration:42,type:2},
|
|
{_id:2,date:2000,number:'5550199',duration:0,type:2}]);
|
|
let localReads=0;
|
|
s.context.CallLogDBManager={getGroupList(){localReads++;},getGroup(){localReads++;},getGroupAtPosition(){localReads++;},
|
|
_getGroupId:r=>[0,r.number,r.type],_getGroupObject:g=>({...g,id:g.id.join('-'),type:g.id[2]})};
|
|
const db=s.context.CallLogDBManager;
|
|
const rows=await new Promise(resolve=>db.getGroupList(resolve,'lastEntryDate',true,false));
|
|
assert.equal(rows.length,1);assert.equal(rows[0].retryCount,2);assert.equal(rows[0].calls[1].duration,42000);
|
|
assert.equal((await db.getGroup('5550199',1000,'dialing')).calls.length,2);assert.equal(localReads,0);
|
|
const streamed=[];
|
|
await new Promise(resolve=>db.getGroupList(c=>{if(!c.value)return resolve();streamed.push(c.value);c.continue();},'lastEntryDate',true,true));
|
|
assert.equal(streamed.length,1);
|
|
});
|
|
test('SMS provider change refreshes the open conversation without reloading or touching the draft',async()=>{
|
|
const inserted=[];
|
|
const s=setup('sms','/index.html',async()=>[{id:-1,threadId:-2,body:'Received',timestamp:1000}]);
|
|
s.context.Navigation={isCurrentPanel:name=>name==='thread'};
|
|
s.context.ConversationView={activeThread:{id:-2},appendMessage:async message=>inserted.push(message)};
|
|
s.context.Compose={getText:()=> 'Unsent draft'};
|
|
s.context.document.getElementById=()=>null;
|
|
s.context.location.reload=()=>{throw Error('draft lost');};
|
|
s.events.get('vulpes-service-event')({detail:{type:'android-data-changed',data:{kind:'sms'}}});
|
|
await new Promise(resolve=>setImmediate(resolve));
|
|
assert.equal(inserted.length,1);assert.equal(inserted[0].body,'Received');
|
|
assert.equal(s.context.Compose.getText(),'Unsent draft');
|
|
});
|
|
|
|
test('hidden call log refreshes contact names when shown after Android access changes',()=>{
|
|
const s=setup('communications','/dialer/index.html',async()=>({}));
|
|
let updates=0;
|
|
s.context.CallLog={sticky:{},_contactCache:true,updateListWithContactInfo(){updates++;}};
|
|
s.context.document.hidden=true;
|
|
s.events.get('vulpes-service-event')({detail:{type:'android-data-changed',data:{kind:'contacts'}}});
|
|
assert.equal(updates,0);
|
|
s.context.document.hidden=false;
|
|
s.events.get('visibilitychange')();
|
|
assert.equal(updates,1);
|
|
assert.equal(s.context.CallLog._contactCache,false);
|
|
});
|
|
|
|
test('Android contact HTML caches are disabled before Gaia can apply stale names',async()=>{
|
|
const s=setup('communications','/contacts/index.html',async()=>({}));
|
|
try {
|
|
let writes=0;
|
|
s.context.Cache={get enabled(){return true;},get active(){return true;},set firstChunk(value){writes++;},apply:async()=> 'stale names'};
|
|
assert.equal(s.context.Cache.enabled,false);
|
|
assert.equal(s.context.Cache.active,false);
|
|
s.context.Cache.firstChunk={cache:'Android names'};
|
|
assert.equal(writes,0);
|
|
await assert.rejects(s.context.Cache.apply());
|
|
} finally{s.events.get('pagehide')();}
|
|
});
|
|
|
|
test('revoked native contact details close when the hidden app becomes visible',async()=>{
|
|
const s=setup('communications','/contacts/index.html',async()=>[]);
|
|
try {
|
|
let closed=0;
|
|
s.context.MainNavigation={currentView:()=> 'view-contact-details'};
|
|
s.context.Contacts={goBack(){closed++;}};
|
|
s.events.get('document-click')({target:{closest:()=>({dataset:{uuid:'android:1'}})}});
|
|
s.context.document.hidden=true;
|
|
s.events.get('vulpes-service-event')({detail:{type:'android-data-changed',data:{kind:'contacts'}}});
|
|
assert.equal(closed,0);
|
|
s.context.document.hidden=false;
|
|
s.events.get('visibilitychange')();
|
|
await new Promise(resolve=>setImmediate(resolve));
|
|
assert.equal(closed,1);
|
|
} finally{s.events.get('pagehide')();}
|
|
});
|