71 lines
4.9 KiB
JavaScript
71 lines
4.9 KiB
JavaScript
'use strict';
|
|
const assert=require('node:assert/strict');
|
|
const {harness,ID,RID}=require('./study-helper.test.cjs');
|
|
let passed=0;
|
|
async function test(name,fn){await fn();passed++;console.log(`PASS ${name}`);}
|
|
function visibility(h,state){h.context.document.visibilityState=state;const event=new h.context.Event('visibilitychange');h.context.document.dispatchEvent(event);return event;}
|
|
function site(h){
|
|
let timer=true,calls=0;
|
|
// Mirrors the observed course component: hidden stops, visible restarts.
|
|
h.context.document.addEventListener('visibilitychange',()=>{calls++;timer=h.context.document.visibilityState!=='hidden';});
|
|
return {get timer(){return timer;},get calls(){return calls;},heartbeat(){if(timer&&!h.video.paused){
|
|
h.api.receive({id:ID,tail:'sessions/session/heartbeat',body:{resourceId:RID}},
|
|
{bizCode:0,data:{resourceCompleted:false,currentPositionSeconds:h.video.currentTime,courseProgressPercent:10}},200);
|
|
}}};
|
|
}
|
|
(async()=>{
|
|
await test('hidden event keeps the website heartbeat alive while real visibility stays hidden',()=>{
|
|
const h=harness(),web=site(h);h.api.begin();h.api.track(h.video);
|
|
const event=visibility(h,'hidden');assert.equal(event.stopped,true);assert.equal(web.timer,true);assert.equal(web.calls,0);
|
|
assert.equal(h.context.document.visibilityState,'hidden');assert.equal(h.video.playbackRate,2);
|
|
h.advance(15000);h.video.currentTime+=30;web.heartbeat();assert.equal(h.api.state.active.saved,true);
|
|
assert.equal(h.api.state.active.serverPosition,150);
|
|
});
|
|
await test('an idle assistant does not intercept website visibility handling',()=>{
|
|
const h=harness(),web=site(h);visibility(h,'hidden');assert.equal(web.timer,false);assert.equal(web.calls,1);
|
|
});
|
|
await test('returning to foreground lets the normal website handler run',()=>{
|
|
const h=harness(),web=site(h);h.api.begin();visibility(h,'hidden');visibility(h,'visible');
|
|
assert.equal(web.calls,1);assert.equal(web.timer,true);
|
|
});
|
|
await test('turning off background mode restores hidden behavior and persists the preference',()=>{
|
|
const h=harness(),web=site(h);h.api.begin();visibility(h,'hidden');h.api.setBackgroundEnabled(false);
|
|
assert.equal(web.timer,false);assert.equal(h.storage.get('icampus.study.v1.background'),'off');
|
|
assert.equal(visibility(h,'hidden').stopped,undefined);
|
|
});
|
|
await test('stop while hidden pauses media and restores website cleanup',()=>{
|
|
const h=harness(),web=site(h);h.api.begin();h.api.track(h.video);visibility(h,'hidden');h.api.stop();
|
|
assert.equal(web.timer,false);assert.equal(h.video.paused,true);assert.equal(h.api.state.running,false);
|
|
});
|
|
await test('pagehide is not suppressed when the tab is closed or reloaded',()=>{
|
|
const h=harness();let cleaned=false;h.window.addEventListener('pagehide',()=>{cleaned=true;});h.api.begin();visibility(h,'hidden');
|
|
h.window.dispatchEvent(new h.context.Event('pagehide'));assert.equal(cleaned,true);assert.equal(h.api.state.running,false);
|
|
});
|
|
await test('a 60-second background timer delay does not expire the active tab lease',()=>{
|
|
const h=harness();h.api.begin();visibility(h,'hidden');h.advance(61000);
|
|
const lease=JSON.parse(h.storage.get('icampus.study.v1.lease'));assert.ok(lease.until>h.context.Date.now());
|
|
});
|
|
await test('background delay allows a real heartbeat to arrive but missing saves still stop',()=>{
|
|
const h=harness();h.api.begin();h.api.track(h.video);visibility(h,'hidden');
|
|
h.advance(80000);h.video.currentTime+=160;h.api.monitor();assert.equal(h.api.state.running,true);
|
|
h.advance(101000);h.video.currentTime+=202;h.api.monitor();assert.equal(h.api.state.running,false);assert.match(h.api.state.status,/180秒/);
|
|
});
|
|
await test('foreground return gives the restarted heartbeat 20 seconds to respond',()=>{
|
|
const h=harness();h.api.begin();h.api.track(h.video);visibility(h,'hidden');h.advance(80000);visibility(h,'visible');
|
|
h.video.currentTime+=160;h.api.monitor();assert.equal(h.api.state.running,true);
|
|
h.advance(21000);h.video.currentTime+=42;h.api.monitor();assert.equal(h.api.state.running,false);
|
|
});
|
|
await test('manual pause is respected in background mode',()=>{
|
|
const h=harness();h.api.begin();h.api.track(h.video);visibility(h,'hidden');h.video.paused=true;h.api.monitor();
|
|
assert.equal(h.api.state.running,false);assert.equal(h.video.playCount,0);
|
|
});
|
|
await test('successful course navigation survives a delayed background tick',async()=>{
|
|
const h=harness();h.api.begin();visibility(h,'hidden');h.api.pendingCourse('123');h.context.location.hash='#/my-course/learn?id=123';
|
|
h.advance(61000);await h.api.tick();assert.equal(h.api.state.running,true);
|
|
});
|
|
await test('non-course routes do not suppress hidden events',()=>{
|
|
const h=harness(),web=site(h);h.api.begin();h.context.location.hash='#/user';visibility(h,'hidden');assert.equal(web.timer,false);
|
|
});
|
|
console.log(`${passed} background playback tests passed.`);
|
|
})().catch(error=>{console.error(error);process.exitCode=1;});
|