Private
Public Access
Release 0.11.0 with guided calibration, source transitions and root repair
This commit is contained in:
1 parent
e55d37772a
commit
f74a658097
33 files changed
+855
-75
No files matched your search
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
const assert=require('assert'),fs=require('fs'),vm=require('vm');
|
||||
const c={};vm.createContext(c);vm.runInContext(fs.readFileSync('app/calibration.js','utf8'),c);
|
||||
const baseline=Array(16).fill(10);
|
||||
const patches=Array.from({length:32},(_,i)=>Array.from({length:16},(_,r)=>{
|
||||
const center=r*2+.5,distance=Math.min(Math.abs(i-center),32-Math.abs(i-center));
|
||||
return 10+80*Math.max(0,1-distance/2);
|
||||
}));
|
||||
const fit=c.calibrationFit(baseline,patches,'OTHER-TV');assert.equal(fit.points.length,16);
|
||||
fit.points.forEach((p,i)=>{assert.equal(p.region,i);assert(Math.abs(p.position-(i*2+1)/8)<.05);});
|
||||
assert.throws(()=>c.calibrationFit(baseline,Array.from({length:32},()=>Array(16).fill(90)),'OTHER-TV'),/reliable spatial/);
|
||||
assert.throws(()=>c.calibrationFit(baseline,Array.from({length:32},()=>baseline),'OTHER-TV'),/reliable spatial/);
|
||||
assert.throws(()=>c.calibrationFit(baseline,patches.slice(1),'OTHER-TV'),/Incomplete/);
|
||||
assert.throws(()=>c.calibrationAverage([baseline,baseline]),/fresh/);
|
||||
const nested=patches.map(p=>p.map((v,r)=>r===15?p[0]:v));
|
||||
const reduced=c.calibrationFit(baseline,nested,'NESTED-TV');assert.equal(reduced.points.length,15);assert(!reduced.points.some(p=>p.region===15));
|
||||
const duplicate=patches.map(p=>Array(16).fill(p[0]));assert.throws(()=>c.calibrationFit(baseline,duplicate,'DUPLICATE-TV'),/reliable spatial/);
|
||||
assert.deepEqual(Array.from(c.calibrationAverage([baseline,baseline,baseline])),baseline);
|
||||
console.log('PASS: measured edge positions, global/absent response rejection and fresh sample minimum');
|
||||
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
const assert=require('assert'),fs=require('fs'),vm=require('vm');
|
||||
const events={},windowEvents={},elements={};let inputFocuses=0;
|
||||
const doc={activeElement:null,getElementById(id){return elements[id];},addEventListener(k,f){(events[k]||(events[k]=[])).push(f);}};
|
||||
function element(id,input){return {id,tagName:input?'INPUT':'DIV',type:'text',readOnly:true,disabled:false,style:{},attributes:{},handlers:{},
|
||||
addEventListener(k,f){this.handlers[k]=f;},setAttribute(k,v){this.attributes[k]=v;},
|
||||
focus(){if(doc.activeElement && doc.activeElement!==this)doc.activeElement.blur();doc.activeElement=this;if(input)inputFocuses++;},
|
||||
blur(){if(this.handlers.blur)this.handlers.blur();if(doc.activeElement===this)doc.activeElement=null;}
|
||||
};}
|
||||
['host','port','piccapPort','token','customProfile'].forEach(id=>{elements[id]=element(id,true);elements[id+'Field']=element(id+'Field',false);});
|
||||
['overview','receiver','calibration'].forEach(key=>{elements[key+'Panel']=element(key+'Panel');elements[key+'Tab']=element(key+'Tab');});
|
||||
['start','receiver','calibrate','calibrationShortcut'].forEach(id=>elements[id]=element(id));
|
||||
const win={addEventListener(k,f){windowEvents[k]=f;}};
|
||||
const c={document:doc,window:win};vm.createContext(c);vm.runInContext(fs.readFileSync('app/navigation.js','utf8'),c);
|
||||
elements.receiverTab.onclick();assert.equal(elements.receiverPanel.style.display,'block');assert.equal(elements.overviewPanel.style.display,'none');assert.equal(doc.activeElement.id,'receiver');
|
||||
elements.hostField.focus();assert.equal(inputFocuses,0);assert(elements.host.readOnly);
|
||||
elements.hostField.onclick();assert.equal(inputFocuses,1);assert(!elements.host.readOnly);assert.equal(doc.activeElement.id,'host');
|
||||
events.keyboardStateChange[0]({detail:{visibility:false}});assert(elements.host.readOnly);assert.equal(doc.activeElement.id,'hostField');
|
||||
elements.portField.focus();assert.equal(inputFocuses,1);elements.portField.onclick();assert.equal(inputFocuses,2);
|
||||
let prevented=false;events.keydown[0]({keyCode:461,preventDefault(){prevented=true;}});assert(prevented && elements.port.readOnly && doc.activeElement.id==='portField');
|
||||
elements.token.disabled=true;elements.tokenField.onclick();assert.equal(inputFocuses,2);
|
||||
elements.calibrationShortcut.onclick();assert.equal(doc.activeElement.id,'calibrate');assert.equal(elements.calibrationPanel.style.display,'block');assert.equal(elements.receiverPanel.style.display,'none');
|
||||
elements.customProfileField.onclick();assert.equal(inputFocuses,3);elements.start.focus();assert(elements.customProfile.readOnly);
|
||||
console.log('PASS: three sections, calibration shortcut, highlight without input focus, explicit editing, keyboard close/Back and disabled input');
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
const assert=require('assert'),fs=require('fs'),vm=require('vm');
|
||||
const ids=['host','port','piccapPort','receiver','token','clearToken','endpoint','handover','autostart','error','status','regions','start','stop','refresh','saveTarget','testConnection','testResult','diagnostics','profile','customEditor','customProfile','saveProfile','profileStatus'];
|
||||
const ids=['host','port','piccapPort','receiver','token','clearToken','endpoint','handover','autostart','error','status','regions','start','stop','refresh','saveTarget','testConnection','testResult','diagnostics','profile','customEditor','customProfile','saveProfile','profileStatus','calibrate','validateCalibration','cancelCalibration','editProfile','customProfileField','overviewTab','receiverTab','calibrationTab','calibrationShortcut','hostField','portField','piccapPortField','tokenField','grantRoot','rootHint'];
|
||||
const elements={};
|
||||
ids.forEach(id=>elements[id]={value:'',textContent:'',disabled:false,checked:false,attributes:{},style:{},addEventListener(){},focus(){},appendChild(){},setAttribute(k,v){this.attributes[k]=v;},getAttribute(k){return this.attributes[k];}});
|
||||
const requests=[];
|
||||
@@ -14,7 +14,7 @@ const context={PalmServiceBridge:Bridge,document:{getElementById:id=>elements[id
|
||||
vm.runInNewContext(fs.readFileSync('app/app.js','utf8'),context);
|
||||
assert(elements.testResult.textContent.includes('120 LEDs'));
|
||||
assert(elements.diagnostics.textContent.includes('Root service'));
|
||||
assert(controls.includes(elements.piccapPort)&&controls.includes(elements.testConnection));
|
||||
assert(controls.includes(elements.piccapPortField)&&controls.includes(elements.testConnection));
|
||||
context.targetDirty=true;elements.testConnection.onclick();assert(elements.error.textContent.includes('Save receiver'));
|
||||
elements.piccapPort.value='0';elements.saveTarget.onclick();assert(elements.error.textContent.includes('FlatBuffer port'));
|
||||
elements.piccapPort.value='19446';elements.host.value='10.1.0.100';elements.token.value='';elements.saveTarget.onclick();
|
||||
|
||||
@@ -29,11 +29,11 @@ buttons[4].disabled=false;buttons[3].focus();key(39);assert.equal(doc.activeElem
|
||||
key(39);assert.equal(doc.activeElement.id,'start');key(37);assert.equal(doc.activeElement.id,'handover');
|
||||
buttons[2].tagName='INPUT';buttons[2].type='text';buttons[2].focus();
|
||||
assert(!key(37));assert(!key(39));assert(!key(13));assert.equal(doc.activeElement.id,'refresh');
|
||||
key(40);assert.equal(doc.activeElement.id,'autostart');key(38);assert.equal(doc.activeElement.id,'refresh');
|
||||
assert(!key(40));assert(!key(38));assert.equal(doc.activeElement.id,'refresh');
|
||||
buttons[2].tagName='TEXTAREA';buttons[2].focus();
|
||||
assert(!key(37));assert(!key(39));assert(!key(13));
|
||||
key(40);assert.equal(doc.activeElement.id,'autostart');
|
||||
key(38);assert.equal(doc.activeElement.id,'refresh');
|
||||
assert(!key(40));assert(!key(38));assert.equal(doc.activeElement.id,'refresh');
|
||||
buttons[2].readOnly=true;assert(key(40));assert.equal(doc.activeElement.id,'autostart');
|
||||
buttons[2].tagName='BUTTON';
|
||||
buttons.forEach(button=>button.disabled=true);assert(key(39));
|
||||
console.log('PASS: arrow navigation, wraparound, disabled controls, OK once per press, pointer handoff, focus recovery, Back preserved');
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
const assert=require('assert'),fs=require('fs'),vm=require('vm');
|
||||
const elements={},requests=[],timers=new Map();let next=0,uid=1000,enabled=false,deny=false,delayRoot=false,pendingRoot=null;
|
||||
for(const match of fs.readFileSync('app/index.html','utf8').matchAll(/id="([^"]+)"/g))elements[match[1]]={style:{},value:'',textContent:'',attributes:{},disabled:false,addEventListener(){},focus(){},appendChild(){},setAttribute(k,v){this.attributes[k]=v;},getAttribute(k){return this.attributes[k];}};
|
||||
class Bridge {
|
||||
call(uri,raw){
|
||||
const data=JSON.parse(raw);requests.push({uri,data});let r={returnValue:true};
|
||||
if(uri.endsWith('/status'))r={returnValue:true,uid,autostart:enabled,piccapInstalled:true,connectionTest:{state:'Not tested'},calibrationProfile:'auto',effectiveCalibration:'g4',customPoints:[],tvModel:'OLED65G42LW.DEUQLJP'};
|
||||
if(uri.endsWith('/autostart')){if(uid)r={returnValue:false,rootRequired:true};else enabled=data.enabled;}
|
||||
if(uri.includes('hbchannel')){if(delayRoot){pendingRoot=this;return;}if(deny)r={returnValue:false,errorText:'Denied by Homebrew Channel'};}
|
||||
if(uri.endsWith('/restartService'))uid=0;
|
||||
this.onservicecallback(JSON.stringify(r));
|
||||
}
|
||||
cancel(){}
|
||||
}
|
||||
const c={document:{getElementById(id){assert(elements[id],id);return elements[id];},createElement(){return {};}},window:{},PalmServiceBridge:Bridge,setupRemoteNavigation(){},setInterval(){},setTimeout(fn){const id=++next;timers.set(id,fn);return id;},clearTimeout(id){timers.delete(id);}};
|
||||
vm.createContext(c);vm.runInContext(fs.readFileSync('app/app.js','utf8'),c);
|
||||
function flush(){for(let i=0;timers.size && i<20;i++){const [id,fn]=timers.entries().next().value;timers.delete(id);fn();}assert(!timers.size);}
|
||||
assert.equal(elements.grantRoot.style.display,'inline-block');elements.autostart.onclick();flush();
|
||||
assert(enabled && !elements.autostart.disabled && uid===0);assert.equal(elements.grantRoot.style.display,'none');
|
||||
const grant=requests.find(r=>r.uri.includes('hbchannel'));assert.deepEqual(grant.data,{id:'org.webosbrew.histogramhyperion.service'});assert(requests.some(r=>r.uri.endsWith('/restartService')));
|
||||
const grantCount=requests.filter(r=>r.uri.includes('hbchannel')).length;
|
||||
elements.autostart.onclick();flush();assert(!enabled);assert.equal(requests.filter(r=>r.uri.includes('hbchannel')).length,grantCount);
|
||||
uid=1000;deny=true;elements.autostart.onclick();flush();assert(!enabled && !elements.autostart.disabled && !elements.grantRoot.disabled);assert(elements.error.textContent.includes('could not grant root'));
|
||||
deny=false;delayRoot=true;elements.grantRoot.onclick();elements.autostart.onclick();assert(pendingRoot && c.rootWaiters.length===2);
|
||||
delayRoot=false;pendingRoot.onservicecallback(JSON.stringify({returnValue:true}));flush();assert(enabled && !elements.autostart.disabled && !c.rootWaiters.length);
|
||||
console.log('PASS: root-required autostart repairs only own service, confirms root after restart, retries setting, avoids unnecessary elevation and reports denial');
|
||||
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
const assert=require('assert'),fs=require('fs'),vm=require('vm');
|
||||
const elements={},events={},requests=[];let active=false,sequence=0,globalResponse=false;
|
||||
['calibrationVideo','cancelCalibration','calibrate','validateCalibration','wizardStatus','customProfile'].forEach(id=>elements[id]={style:{},disabled:false,currentTime:0,textContent:'',value:'',events:{},focus(){},pause(){},play(){return Promise.resolve();},addEventListener(k,f){this.events[k]=f;}});
|
||||
const c={profile:'auto',profileDirty:false,targetDirty:false,showProfile(){},display(){},setInterval(){return 1;},clearInterval(){},setTimeout(){},Date,Array,JSON,
|
||||
window:{addEventListener(k,f){events[k]=f;}},document:{getElementById(id){return elements[id];},addEventListener(k,f){events[k]=f;}},
|
||||
call(method,done,data){
|
||||
requests.push({method,data});if(method==='calibrationSession')active=data.enabled;
|
||||
const t=elements.calibrationVideo.currentTime,step=t<4?-1:Math.floor((t-4)/2);
|
||||
const regions=Array.from({length:16},(_,r)=>{
|
||||
const center=r*2+.5,d=Math.min(Math.abs(step-center),32-Math.abs(step-center));
|
||||
const value=step<0?10:10+80*(globalResponse?1:Math.max(0,1-d/2));return [value,value,value];
|
||||
});
|
||||
done(method==='status'?{returnValue:true,running:true,activeSource:'HyperDRM',tvModel:'OTHER-TV',colorValid:true,calibrating:active,sampleSequence:++sequence,sampleAgeMs:10,regions}:{returnValue:true});
|
||||
}
|
||||
};vm.createContext(c);
|
||||
vm.runInContext(fs.readFileSync('app/calibration.js','utf8'),c);vm.runInContext(fs.readFileSync('app/wizard.js','utf8'),c);
|
||||
function measure(){
|
||||
elements.calibrate.onclick();assert(active);assert.deepEqual(c.window.remoteNavigationScope,['cancelCalibration']);
|
||||
[1,1.5,2].forEach(t=>{elements.calibrationVideo.currentTime=t;c.wizardSample();});
|
||||
for(let step=0;step<32;step++)[.8,1.1,1.4].forEach(t=>{elements.calibrationVideo.currentTime=4+step*2+t;c.wizardSample();});
|
||||
elements.calibrationVideo.currentTime=68;c.wizardSample();
|
||||
assert(!active && c.wizard===null && c.window.remoteNavigationScope===null);
|
||||
}
|
||||
measure();assert.equal(c.profile,'custom');assert(c.profileDirty);
|
||||
const proposal=JSON.parse(elements.customProfile.value);assert.equal(proposal.points.length,16);
|
||||
assert(requests.some(r=>r.method==='calibrationDraft'));assert(!requests.some(r=>r.method==='calibration'));
|
||||
c.profileDirty=false;globalResponse=true;measure();assert(elements.wizardStatus.textContent.includes('reliable spatial'));assert.equal(elements.customProfile.value,JSON.stringify(proposal,null,2));
|
||||
globalResponse=false;elements.calibrate.onclick();elements.cancelCalibration.onclick();assert(!active && !c.wizard);
|
||||
elements.validateCalibration.onclick();assert.equal(elements.calibrationVideo.currentTime,68);elements.calibrationVideo.events.ended();assert(!active && elements.wizardStatus.textContent.includes('Top-left red'));
|
||||
elements.calibrate.onclick();let prevented=false;events.keydown({keyCode:461,preventDefault(){prevented=true;}});assert(prevented && !active);
|
||||
console.log('PASS: guided measurement, proposal without save, invalid-response preservation, validation, cancel/Back and scoped remote navigation');
|
||||
Reference in new issue
Block a user