UI improvements, and control softening
This commit is contained in:
@@ -5,6 +5,99 @@ const fs=require('node:fs'),vm=require('node:vm'),path=require('node:path');
|
||||
const {present}=require('../web/control-state.js');
|
||||
const armed={connected:true,armed_known:true,armed:true,app_state:'armed',servos:{}};
|
||||
|
||||
test('model viewport fills the available space above battery status',()=>{
|
||||
const css=fs.readFileSync(path.join(__dirname,'../web/styles.css'),'utf8');
|
||||
assert.match(css,/\.rov-stage \{ flex:1; height:auto; min-height:0; width:100%/);
|
||||
assert.match(css,/\.telemetry-zone \.model-card \{ flex: 1; min-height: 0;/);
|
||||
});
|
||||
|
||||
test('white wash stays behind controls; right panel is gray and edges reach 50 percent',()=>{
|
||||
const css=fs.readFileSync(path.join(__dirname,'../web/styles.css'),'utf8');
|
||||
assert.match(css,/\.workspace-light \{[^}]*z-index:-1/);
|
||||
assert.match(css,/\.controls-workspace \{[^}]*isolation:isolate/);
|
||||
assert.match(css,/\.controls-workspace \{[^}]*background:#ededed/);
|
||||
assert.match(css,/\.sidebar-deck \{[^}]*background:linear-gradient\(160deg,#333,#242424\)/);
|
||||
assert.match(css,/\.cursor-light::after \{[^}]*rgba\(255,255,255,\.50\)/);
|
||||
});
|
||||
|
||||
test('keyboard easing publishes intermediate demands to the real Python bridge',async()=>{
|
||||
const h=harness();h.state();h.run('bridgeReady=true');
|
||||
keyEvent(h,'keydown','w');keyEvent(h,'keydown','r');
|
||||
for(let i=0;i<10;i++){h.advance(50);await Promise.resolve();await Promise.resolve();await Promise.resolve();}
|
||||
const sent=h.calls.filter(call=>call[0]==='axes');
|
||||
assert.ok(sent.some(call=>call[1]>0&&call[1]<1),'surge ramp is transmitted, not just drawn');
|
||||
assert.ok(sent.some(call=>call[3]>0&&call[3]<1),'vertical ramp is transmitted, not just drawn');
|
||||
assert.ok(sent.every(call=>call[1]>=0&&call[1]<=1&&call[3]>=0&&call[3]<=1));
|
||||
});
|
||||
|
||||
test('cursor lighting stays decorative and arrows use centered geometry',()=>{
|
||||
const css=fs.readFileSync(path.join(__dirname,'../web/styles.css'),'utf8');
|
||||
const js=fs.readFileSync(path.join(__dirname,'../web/cursor-light.js'),'utf8');
|
||||
assert.doesNotMatch(js,/set_axes|setJoyControl|setHeave|requestControl/);
|
||||
assert.match(js,/workspace\.append\(wash\)/);
|
||||
assert.match(js,/pointerleave',clear/);
|
||||
assert.match(css,/\.cursor-light \{[^}]*pointer-events:none/);
|
||||
assert.match(css,/range-pill button \{[^}]*place-items:center/);
|
||||
assert.match(css,/#engage\.danger \.engage-label \{ color:#777/);
|
||||
});
|
||||
|
||||
test('edge lighting uses angle; only visible pills get blue and sidebar is excluded',()=>{
|
||||
const css=fs.readFileSync(path.join(__dirname,'../web/styles.css'),'utf8');
|
||||
const js=fs.readFileSync(path.join(__dirname,'../web/cursor-light.js'),'utf8');
|
||||
assert.match(js,/Math\.atan2/);
|
||||
assert.match(js,/!host\.closest\('\.sidebar-deck,#engage'\)/);
|
||||
assert.match(js,/host\.matches\(pillSelector\)/);
|
||||
assert.match(css,/\.cursor-light\.pill-light::before \{ background:radial-gradient/);
|
||||
assert.match(css,/\.cursor-light::after \{[^}]*linear-gradient\(var\(--light-angle/);
|
||||
assert.doesNotMatch(css,/\.cursor-light::after \{[^}]*radial-gradient/);
|
||||
assert.match(css,/\.rov-stage \{[^}]*border-radius:31px; overflow:hidden/);
|
||||
});
|
||||
|
||||
test('all interface typography uses Helvetica with inherited control fonts',()=>{
|
||||
const css=fs.readFileSync(path.join(__dirname,'../web/styles.css'),'utf8');
|
||||
const families=[...css.matchAll(/font-family\s*:\s*([^;}]+)/g)].map(match=>match[1].trim());
|
||||
assert.ok(families.includes('"Helvetica Neue", Helvetica, Arial, sans-serif'));
|
||||
assert.ok(families.every(family=>family==='inherit'||family==='"Helvetica Neue", Helvetica, Arial, sans-serif'));
|
||||
assert.match(css,/button, select, input \{ font: inherit; \}/);
|
||||
});
|
||||
|
||||
function keyEvent(h,type,key,repeat=false){h.fire(type,{key,repeat,target:{tagName:'BODY'},preventDefault(){}})}
|
||||
test('keyboard ramps joystick and vertical together, then eases back on release',()=>{
|
||||
const h=harness();h.state();
|
||||
keyEvent(h,'keydown','w');keyEvent(h,'keydown','d');keyEvent(h,'keydown','r');
|
||||
assert.equal(h.run('control.forward+control.turn+control.vertical'),0);
|
||||
h.advance(180);
|
||||
for(const axis of ['forward','turn','vertical'])assert.ok(h.run(`control.${axis}`)>0&&h.run(`control.${axis}`)<1);
|
||||
keyEvent(h,'keydown','w',true);h.advance(240);
|
||||
assert.ok(Math.abs(h.run('control.forward')-Math.SQRT1_2)<1e-6);
|
||||
assert.ok(Math.abs(h.run('control.turn')-Math.SQRT1_2)<1e-6);
|
||||
assert.equal(h.run('control.vertical'),1);
|
||||
keyEvent(h,'keyup','w');keyEvent(h,'keyup','d');keyEvent(h,'keyup','r');
|
||||
h.advance(180);assert.ok(h.run('control.forward')>0);assert.ok(h.run('control.vertical')>0);
|
||||
h.advance(250);assert.equal(h.run('control.forward+control.turn+control.vertical'),0);
|
||||
});
|
||||
test('keyboard reversal is continuous and focus loss cancels its ramp',()=>{
|
||||
const h=harness();h.state();keyEvent(h,'keydown','w');keyEvent(h,'keydown','r');h.advance(420);
|
||||
keyEvent(h,'keyup','w');keyEvent(h,'keyup','r');keyEvent(h,'keydown','s');keyEvent(h,'keydown','f');
|
||||
assert.equal(h.run('control.forward'),1);assert.equal(h.run('control.vertical'),1);
|
||||
h.advance(200);assert.ok(Math.abs(h.run('control.forward'))<.3);assert.ok(Math.abs(h.run('control.vertical'))<.3);
|
||||
h.advance(220);assert.equal(h.run('control.forward'),-1);assert.equal(h.run('control.vertical'),-1);
|
||||
keyEvent(h,'keyup','s');keyEvent(h,'keyup','f');h.fire('blur');h.advance(500);
|
||||
assert.equal(h.run('control.forward+control.vertical'),0);
|
||||
});
|
||||
test('touch takes ownership from a keyboard ramp without later keyboard overwrite',()=>{
|
||||
const h=harness();h.state();keyEvent(h,'keydown','w');keyEvent(h,'keydown','r');h.advance(100);
|
||||
h.el('#joystick-canvas').emit('pointerdown',{clientX:300,clientY:160});
|
||||
h.el('#heave').emit('pointerdown',{pointerId:2,clientY:300});
|
||||
keyEvent(h,'keydown','a');h.advance(450);
|
||||
assert.equal(h.run('control.turn'),1);assert.equal(Math.abs(h.run('control.forward')),0);assert.equal(h.run('control.vertical'),-1);
|
||||
});
|
||||
test('hold fill and pending engagement share the same green and glow',()=>{
|
||||
const css=fs.readFileSync(path.join(__dirname,'../web/styles.css'),'utf8');
|
||||
assert.match(css,/\.engage-progress-label \{ color:var\(--engage-green\).*filter:var\(--engage-glow\)/);
|
||||
assert.match(css,/#engage\.pending \.engage-label \{ color:var\(--engage-green\); filter:var\(--engage-glow\)/);
|
||||
});
|
||||
|
||||
test('native pywebview result wrapper can execute alongside app globals',()=>{
|
||||
const h=harness();
|
||||
// Window.evaluate_js declares these variables in the page's global scope.
|
||||
|
||||
Reference in New Issue
Block a user