fix: restore recording playback and default to 30-day retention
This commit is contained in:
@@ -4,6 +4,15 @@ test('ranges clip to selected day and merge overlaps without filling gaps',()=>{
|
||||
const items=[{start:date(-10),duration:40},{start:date(20),duration:30},{start:date(60),duration:100},{start:'invalid',duration:20},{start:date(3),duration:-4},{start:date(3),duration:Infinity}];
|
||||
assert.deepEqual(Ranges.normalize(items,date(0),date(100)),[{start:+date(0),end:+date(50)},{start:+date(60),end:+date(100)}]);
|
||||
});
|
||||
test('microsecond recording starts round inward and never seek into the preceding gap',()=>{
|
||||
const raw='2026-10-04T00:00:00.340240Z';
|
||||
const ranges=Ranges.normalize([{start:raw,duration:41.1355}],date(0),date(100));
|
||||
assert.equal(new Date(ranges[0].start).toISOString(),'2026-10-04T00:00:00.341Z');
|
||||
assert.equal(new Date(ranges[0].end).toISOString(),'2026-10-04T00:00:41.475Z');
|
||||
const exact=Ranges.normalize([{start:'2026-10-04T00:00:00.340000Z',duration:10}],date(0),date(100));
|
||||
assert.equal(exact[0].start,Date.parse(raw));
|
||||
assert.deepEqual(Ranges.normalize([{start:raw,duration:0.0001}],date(0),date(1)),[]);
|
||||
});
|
||||
test('long recording continues after each one-hour session',()=>{
|
||||
const ranges=[{start:+date(0),end:+date(8000)}];assert.deepEqual(Ranges.at(ranges,date(0)),{start:+date(0),end:+date(3600)});
|
||||
assert.deepEqual(Ranges.next(ranges,date(3600)),{start:+date(3600),end:+date(7200)});assert.equal(Ranges.next(ranges,date(8000)),null);
|
||||
@@ -20,6 +29,19 @@ test('clip requires valid, positive, fully recorded interval up to one hour',()=
|
||||
function section(file,a,b){const s=fs.readFileSync('web/'+file,'utf8'),i=s.indexOf(a),j=s.indexOf(b,i+a.length);assert.ok(i>=0&&j>i);return s.slice(i,j);}
|
||||
function deferred(){let resolve;const promise=new Promise(r=>resolve=r);return{promise,resolve};}
|
||||
function ui(extra={}){const elements=new Map();const $=s=>{if(!elements.has(s))elements.set(s,{replaceChildren(){},reset(){},value:'',hidden:false});return elements.get(s);};return vm.createContext({$,Number,Date,URLSearchParams,loggedIn:true,activePage:'playback',RecordingRanges:Ranges,...extra});}
|
||||
test('recording health distinguishes configured from writing and never changes the player',()=>{
|
||||
const c=ui({state:{cameras:[{id:'camera',recordingState:'off'}],storage:{retentionDays:30,maxGB:40,reserveGB:8}},isAdmin:()=>true});
|
||||
c.$('#play-camera').value='camera';c.$('#archive-source').value='local';c.$('#recording-configure').dataset={};
|
||||
c.$('#play-video').src='keep-playing';
|
||||
vm.runInContext(section('recording-ui.js','function renderRecordingStatus(','function localDateTime('),c);
|
||||
for(const [status,text] of [['off','未开启'],['waiting','等待文件写入'],['recording','正在录像'],['paused','磁盘保护'],['offline','等待摄像头'],['unavailable','服务未连接'],['disabled','设备连接已停用']]){
|
||||
c.state.cameras[0].recordingState=status;c.renderRecordingStatus();assert.ok(c.$('#recording-health-text').textContent.includes(text));
|
||||
}
|
||||
assert.match(c.$('#recording-policy-note').textContent,/30 天/);assert.equal(c.$('#recording-configure').dataset.editCamera,'camera');
|
||||
assert.equal(c.$('#play-video').src,'keep-playing');
|
||||
c.isAdmin=()=>false;c.state.storage={};c.renderRecordingStatus();assert.equal(c.$('#recording-configure').hidden,true);assert.doesNotMatch(c.$('#recording-policy-note').textContent,/undefined|40 GB/);
|
||||
c.$('#archive-source').value='recorder';c.renderRecordingStatus();assert.equal(c.$('#recording-health').hidden,true);
|
||||
});
|
||||
test('auto-continue preserves user choice, stops short clips and ends at last segment',()=>{
|
||||
const c=ui({playbackRange:{start:0,end:10000},spans:[{start:0,end:10000},{start:20000,end:30000}],playAt:d=>c.played=+d});
|
||||
vm.runInContext(section('recording-ui.js','function continuePlayback(','function makeClip('),c);
|
||||
|
||||
Reference in New Issue
Block a user