Add daily report calendar to the header
This commit is contained in:
1 parent
2a54838d59
commit
4356160cc9
11 files changed
+735
-28
No files matched your search
@@ -30,7 +30,6 @@ test('overview follows the selected incomplete date and never borrows another su
|
||||
assert.equal(h.element('#metric-arrival').textContent,date);
|
||||
assert.equal(h.element('#metric-arrival-note').textContent,'daily.overview_review');
|
||||
assert.equal(h.element('#metric-rooms').textContent,'—');
|
||||
assert.equal(h.element('#metric-duration').textContent,'—');
|
||||
assert.equal(h.element('#metric-current-version').hidden,true);
|
||||
assert.equal(h.calls.length,0);
|
||||
});
|
||||
@@ -49,7 +48,6 @@ test('a same-date pending replacement preserves the accepted day and hides ungen
|
||||
assert.doesNotMatch(html,/job_id=new/);
|
||||
assert.equal(h.element('#metric-arrival').textContent,date);
|
||||
assert.equal(h.element('#metric-rooms').textContent,'—');
|
||||
assert.equal(h.element('#metric-duration').textContent,'—');
|
||||
assert.equal(h.element('#metric-current-version').hidden,false);
|
||||
assert.doesNotMatch(html,/<tr[^>]+(?:tabindex|onclick|role="button")/);
|
||||
});
|
||||
@@ -59,7 +57,6 @@ test('a successful replacement displays only the accepted new report for the sam
|
||||
h.state.dailyReports=[day(job('new',{no_of_rooms:43}))];
|
||||
h.renderJobs();
|
||||
assert.equal(h.element('#metric-rooms').textContent,'43');
|
||||
assert.equal(h.element('#metric-duration').textContent,'52s');
|
||||
assert.equal(h.element('#metric-current-version').hidden,true);
|
||||
assert.match(h.element('#jobs-body').innerHTML,/job_id=new/);
|
||||
assert.doesNotMatch(h.element('#jobs-body').innerHTML,/job_id=old/);
|
||||
@@ -88,7 +85,6 @@ test('deleting the current report leaves older successful versions explicitly hi
|
||||
assert.match(html,/data-daily-delete="superseded"[^>]+data-daily-current="false"/);
|
||||
assert.equal(h.element('#metric-arrival-note').textContent,'daily.no_current');
|
||||
assert.equal(h.element('#metric-rooms').textContent,'—');
|
||||
assert.equal(h.element('#metric-duration').textContent,'—');
|
||||
});
|
||||
|
||||
test('an out-of-page date reads its own overview and ignores stale and cross-context replies',async()=>{
|
||||
|
||||
@@ -20,11 +20,11 @@ function harness(respond) {
|
||||
return enabled;
|
||||
},contains:value=>classes.has(value)},setAttribute:(key,value)=>attributes.set(key,String(value)),
|
||||
removeAttribute:key=>attributes.delete(key),getAttribute:key=>attributes.get(key)??null,
|
||||
querySelectorAll(){return [];},scrollIntoView(){},focus(){}});
|
||||
querySelector:selector=>element(selector),querySelectorAll(){return [];},scrollIntoView(){},focus(){context.document.activeElement=this;}});
|
||||
}
|
||||
return elements.get(selector);
|
||||
};
|
||||
const context=vm.createContext({Headers, console, Date, Intl, Uint8Array,
|
||||
const context=vm.createContext({Headers, console, Date, Intl, Uint8Array, TextEncoder, btoa,
|
||||
document:{hidden:false,querySelector:element},
|
||||
window:{ARRI18n:{t:(key,values)=>key==='manual_review.context'?`${values.date} · ${values.type}`:key,text:value=>value,errorMessage:code=>code,formatInteger:value=>String(value),formatMonth:value=>value,formatDate:value=>value,formatDurationSeconds:value=>`${value}s`},crypto:webcrypto,
|
||||
setTimeout(callback,delay){const timer={id:timers.length+1,callback,delay,cancelled:false};timers.push(timer);return timer.id;},
|
||||
@@ -32,7 +32,7 @@ function harness(respond) {
|
||||
localStorage:{setItem:(k,v)=>storage.set(k,v),getItem:k=>{storageReads.push(k);return storage.get(k);},removeItem:k=>storage.delete(k)},
|
||||
fetch:async (url, options) => {calls.push({url,method:options.method||'GET',body:options.body,csrf:options.headers.get('X-ARR-CSRF')});return respond(url,options,calls);},
|
||||
});
|
||||
const exports='state, api, submitARRDownload, rememberARRIntent, acceptARRDownloadTask, handleARRDownloadDateChange, initARRDownload, loadARRDownloadTask, loadARRDataReview, renderARRDataReview, arrDataReviewCanFinalize, saveARRDataReviewItem, finalizeARRDataReview, trackARRDataReviewDraft, parseARRDataReviewValue, selectARRPendingReview, strictReviewPrice, reviewPriceInput, renderDailyPriceReview, saveDailyPriceReviewItem, loadDailyPriceReview, openDailyPriceReview, dailyPriceReviewCanFinalize, dailyPriceReviewHasUnsavedChanges, finalizeDailyPriceReview, confirmDailyPriceReviewCancel, renderJobs, renderDailyOverview, syncDailyOverviewDate, loadDailyOverview, selectDailyReportDate, loadJobs, loadDailyHistory, openDailyHistory, closeDailyHistory, openDailyDelete, closeDailyDelete, confirmDailyDelete';
|
||||
const exports='state, api, submitARRDownload, rememberARRIntent, acceptARRDownloadTask, handleARRDownloadDateChange, initARRDownload, loadARRDownloadTask, loadARRDataReview, renderARRDataReview, arrDataReviewCanFinalize, saveARRDataReviewItem, finalizeARRDataReview, trackARRDataReviewDraft, parseARRDataReviewValue, selectARRPendingReview, strictReviewPrice, reviewPriceInput, renderDailyPriceReview, saveDailyPriceReviewItem, loadDailyPriceReview, openDailyPriceReview, dailyPriceReviewCanFinalize, dailyPriceReviewHasUnsavedChanges, finalizeDailyPriceReview, confirmDailyPriceReviewCancel, handleUpload, renderJobs, renderDailyOverview, syncDailyOverviewDate, loadDailyOverview, selectDailyReportDate, loadJobs, loadDailyHistory, openDailyHistory, closeDailyHistory, openDailyDelete, closeDailyDelete, confirmDailyDelete, localMonth, localDate, shiftMonth, renderReportCalendar, loadReportCalendar, changeReportCalendarMonth, selectReportCalendarDate, moveReportCalendarFocus, reportCalendarReady, currentReportGenerated';
|
||||
vm.runInContext(source.replace(boot,` globalThis.subject = {${exports}};\n})();`),context);
|
||||
const subject=context.subject;
|
||||
Object.assign(subject.state,{arrDownloadReady:true,arrDownloadLoaded:true,arrDownloadStorageKey:'test',arrDownloadUsername:'operator',csrf:'fixture'});
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
// Read-only calendar coverage and date selection through actual UI functions.
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
const {harness,response}=require('./helpers/arr_ui_harness.cjs');
|
||||
const month='2026-09';
|
||||
const page=(items=[],total=items.length)=>({status:200,ok:true,json:async()=>({ok:true,data:items,pagination:{total,limit:200,offset:0}})});
|
||||
const row=(day,current={})=>({business_date:`${month}-${String(day).padStart(2,'0')}`,current_job:current===null?null:{
|
||||
job_id:`fixture-${day}`,status:'succeeded',is_current:true,is_retired:false,no_of_rooms:0,...current},latest_job:null});
|
||||
const block=(h,date)=>h.element('#report-calendar-days').innerHTML.match(new RegExp(`<button[^>]+data-report-calendar-day="${date}"[^>]*>[\\s\\S]*?<\\/button>`))?.[0];
|
||||
const priceReview=()=>({case_id:'xml-price-case',case_status:'open',revision:1,business_date:`${month}-16`,
|
||||
completed_items:1,total_items:1,items:[{item_id:1,company_key:'Fixture XML',rate_code:'FIXTURE',
|
||||
effective_rate_amount:0,real_price:0,affected_records:1,affected_rooms:1,affected_room_nights:1,candidate_prices:[]}],
|
||||
pagination:{offset:0,limit:50,total:1}});
|
||||
function setup(respond=()=>page()) {
|
||||
const h=harness(respond);
|
||||
h.state.reportCalendarInitialized=true;
|
||||
h.state.reportCalendarMonth=month;
|
||||
h.state.arrDownloadContextId='hotel-fixture';
|
||||
h.state.jobsMonth='2026-10';
|
||||
h.state.selectedJobId='fixture-selected';
|
||||
return h;
|
||||
}
|
||||
|
||||
test('calendar defaults to the business current month and reads the whole month independently of daily pagination',async()=>{
|
||||
const h=setup((url)=>{
|
||||
assert.equal(url,`/api/daily-reports?month=${h.localMonth()}&limit=200&offset=0`);
|
||||
return page();
|
||||
});
|
||||
h.state.reportCalendarMonth='';
|
||||
h.state.jobsMonth='2026-01';
|
||||
h.state.jobsOffset=100;
|
||||
h.renderReportCalendar();
|
||||
assert.equal(h.state.reportCalendarMonth,h.localMonth());
|
||||
await h.loadReportCalendar();
|
||||
assert.equal(h.state.reportCalendarDataMonth,h.localMonth());
|
||||
assert.equal(h.state.jobsMonth,'2026-01');
|
||||
assert.equal(h.state.jobsOffset,100);
|
||||
assert.equal(h.calls.every(c=>c.method==='GET'),true);
|
||||
});
|
||||
|
||||
test('only effective current successful reports are generated, including zero-room and pending-replacement days',async()=>{
|
||||
const data=[row(1),{...row(2),latest_job:{status:'needs_review',is_current:false}},row(3,null),
|
||||
{...row(4,null),latest_job:{status:'succeeded',is_current:false}},row(5,{is_retired:true}),
|
||||
row(6,{is_current:false}),row(7,{status:'failed'}),row(8,{status:'needs_review'}),row(9,{is_current:undefined})];
|
||||
const h=setup(()=>page(data));
|
||||
h.state.dailyReports=[row(20)];
|
||||
await h.loadReportCalendar();
|
||||
for(const number of [1,2]) assert.match(block(h,`${month}-0${number}`),/is-generated/);
|
||||
for(const number of [3,4,5,6,7,8,9]) assert.match(block(h,`${month}-0${number}`),/is-ungenerated/);
|
||||
assert.match(block(h,`${month}-20`),/is-ungenerated/,'the current daily-list page cannot supply calendar coverage');
|
||||
assert.equal(h.element('#report-calendar-status').textContent,'report_calendar.coverage');
|
||||
});
|
||||
|
||||
test('a complete empty month marks every actual day ungenerated and renders no other-month buttons',async()=>{
|
||||
const h=setup(()=>page());
|
||||
await h.changeReportCalendarMonth('2026-02');
|
||||
const html=h.element('#report-calendar-days').innerHTML;
|
||||
assert.equal((html.match(/data-report-calendar-day=/g)||[]).length,28);
|
||||
assert.equal((html.match(/is-ungenerated/g)||[]).length,28);
|
||||
assert.doesNotMatch(html,/data-report-calendar-day="2026-01|data-report-calendar-day="2026-03/);
|
||||
await h.changeReportCalendarMonth('2028-02');
|
||||
assert.equal((h.element('#report-calendar-days').innerHTML.match(/data-report-calendar-day=/g)||[]).length,29);
|
||||
});
|
||||
|
||||
test('loading and failed reads never label days ungenerated and a retry can restore a confirmed snapshot',async()=>{
|
||||
let resolve;
|
||||
let fail=true;
|
||||
const h=setup(()=>fail?new Promise(r=>{resolve=r;}):page());
|
||||
const reading=h.loadReportCalendar();
|
||||
assert.match(block(h,`${month}-01`),/is-unknown/);
|
||||
assert.doesNotMatch(h.element('#report-calendar-days').innerHTML,/is-ungenerated/);
|
||||
assert.equal(h.element('#report-calendar').getAttribute('aria-busy'),'true');
|
||||
resolve(response(503,null,'DATABASE_QUERY_FAILED'));
|
||||
await reading;
|
||||
assert.equal(h.state.reportCalendarError,true);
|
||||
assert.match(block(h,`${month}-01`),/report_calendar.day_unknown/);
|
||||
assert.doesNotMatch(h.element('#report-calendar-days').innerHTML,/is-ungenerated/);
|
||||
assert.equal(h.element('#report-calendar-retry').hidden,false);
|
||||
fail=false;
|
||||
await h.loadReportCalendar();
|
||||
assert.equal(h.state.reportCalendarError,false);
|
||||
assert.match(block(h,`${month}-01`),/is-ungenerated/);
|
||||
assert.equal(h.element('#report-calendar-retry').hidden,true);
|
||||
});
|
||||
|
||||
for(const data of [{data:[row(1)],pagination:{total:2}},
|
||||
{data:[row(1),row(1)],pagination:{total:2}},
|
||||
{data:[{business_date:'2026-10-01',current_job:null}],pagination:{total:1}},
|
||||
{data:[{business_date:'2026-09-01'}],pagination:{total:1}},
|
||||
{data:{},pagination:{total:0}}]) {
|
||||
test(`an incomplete or invalid month snapshot remains unknown (${JSON.stringify(data).slice(0,58)})`,async()=>{
|
||||
const h=setup(()=>({status:200,ok:true,json:async()=>({ok:true,...data})}));
|
||||
await h.loadReportCalendar();
|
||||
assert.equal(h.reportCalendarReady(),false);
|
||||
assert.equal(h.state.reportCalendarError,true);
|
||||
assert.doesNotMatch(h.element('#report-calendar-days').innerHTML,/is-ungenerated|is-generated/);
|
||||
});
|
||||
}
|
||||
|
||||
test('late previous-month responses and failures cannot overwrite the newer month',async()=>{
|
||||
let resolveOld;
|
||||
const h=setup((url,options,calls)=>calls.length===1?new Promise(resolve=>{resolveOld=resolve;}):page());
|
||||
const old=h.loadReportCalendar();
|
||||
await h.changeReportCalendarMonth('2026-10');
|
||||
resolveOld(response(503,null,'DATABASE_QUERY_FAILED'));
|
||||
await old;
|
||||
assert.equal(h.state.reportCalendarMonth,'2026-10');
|
||||
assert.equal(h.state.reportCalendarDataMonth,'2026-10');
|
||||
assert.equal(h.state.reportCalendarError,false);
|
||||
assert.equal(h.state.reportCalendarLoading,false);
|
||||
assert.equal(h.reportCalendarReady(),true);
|
||||
});
|
||||
|
||||
test('late replies from the former environment cannot replace the new environment snapshot',async()=>{
|
||||
let resolveOld;
|
||||
const h=setup((url,options,calls)=>calls.length===1?new Promise(resolve=>{resolveOld=resolve;}):page([row(2)]));
|
||||
const old=h.loadReportCalendar();
|
||||
h.state.arrDownloadContextId='new-hotel-fixture';
|
||||
await h.loadReportCalendar();
|
||||
resolveOld(page([row(1)]));
|
||||
await old;
|
||||
assert.equal(h.state.reportCalendarContext,'new-hotel-fixture');
|
||||
assert.match(block(h,`${month}-02`),/is-generated/);
|
||||
assert.match(block(h,`${month}-01`),/is-ungenerated/);
|
||||
});
|
||||
|
||||
test('selecting a calendar day only reads its overview and synchronizes the daily viewed month',async()=>{
|
||||
const date=`${month}-17`;
|
||||
const h=setup((url)=>{
|
||||
if(url===`/api/daily-reports/${date}`) return response(200,{business_date:date,current_job:null,latest_job:null});
|
||||
if(url.startsWith('/api/daily-reports?')) return page();
|
||||
throw new Error('Unexpected request '+url);
|
||||
});
|
||||
const savedIntent={request_id:'a'.repeat(32),report_date:'2026-10-07'};
|
||||
h.rememberARRIntent(savedIntent);
|
||||
h.state.arrDataReviewDraftsByRequest={fixture:{room:'ROOM-DRAFT'}};
|
||||
const selected=await h.selectReportCalendarDate(date);
|
||||
assert.equal(selected,true);
|
||||
assert.equal(h.state.dailyOverviewDate,date);
|
||||
assert.equal(h.element('#arr-download-date').value,date);
|
||||
assert.equal(h.state.jobsMonth,month);
|
||||
assert.equal(h.state.jobsOffset,0);
|
||||
assert.equal(h.element('#jobs-history-month').value,month);
|
||||
assert.equal(h.state.arrDownloadIntent.request_id,savedIntent.request_id);
|
||||
assert.equal(h.state.arrDataReviewDraftsByRequest.fixture.room,'ROOM-DRAFT');
|
||||
assert.equal(h.calls.every(c=>c.method==='GET'),true);
|
||||
assert.equal(h.calls.some(c=>c.url.includes('/arr-downloads')),false);
|
||||
assert.match(block(h,date),/aria-pressed="true"/);
|
||||
});
|
||||
|
||||
test('running, submission and price-edit guards preserve the old selected day and human input',async()=>{
|
||||
for(const flag of ['arrDownloadBusy','arrDownloadLoading','arrDataReviewLoading','arrDataReviewMutating','arrDataReviewFinalizing',
|
||||
'dailyPriceReviewLoading','dailyPriceReviewMutating','uploadInFlight','dailyDeleting']) {
|
||||
const h=setup(()=>{throw new Error('No request while '+flag);});
|
||||
h.state[flag]=true;
|
||||
const old=h.element('#arr-download-date').value;
|
||||
assert.equal(await h.selectReportCalendarDate(`${month}-17`),false);
|
||||
assert.equal(h.element('#arr-download-date').value,old);
|
||||
}
|
||||
const h=setup(()=>{throw new Error('No request while running or editing');});
|
||||
h.state.arrDownloadTask={status:'downloading',report_date:'2026-10-07',request_id:'a'.repeat(32)};
|
||||
h.renderReportCalendar();
|
||||
assert.match(block(h,`${month}-17`),/disabled/);
|
||||
assert.equal(await h.selectReportCalendarDate(`${month}-17`),false);
|
||||
h.state.arrDownloadTask=null;
|
||||
h.state.dailyPriceReview={case_status:'open',items:[{item_id:1,real_price:null}]};
|
||||
h.element('#daily-price-review-body').querySelectorAll=()=>[{value:'0',closest:()=>({dataset:{dailyPriceReviewItemId:'1'}})}];
|
||||
assert.equal(await h.selectReportCalendarDate(`${month}-17`),false);
|
||||
assert.equal(h.element('#toast').textContent,'daily_review.save_before_switch');
|
||||
assert.equal(h.calls.length,0);
|
||||
});
|
||||
|
||||
for(const outcome of ['succeeded','needs_review','failed','rejected']) {
|
||||
test(`XML upload ${outcome} restores calendar selection without an OHIP pending task`,async()=>{
|
||||
let resolveUpload;
|
||||
const h=setup((url,options)=>{
|
||||
if(url==='/api/jobs' && options.method==='POST') return new Promise(resolve=>{resolveUpload=resolve;});
|
||||
if(url.startsWith('/api/daily-reports?')) return page();
|
||||
if(url==='/api/jobs/fixture-xml/trace') return response(200,{job:{job_id:'fixture-xml',status:outcome,business_date:`${month}-16`}});
|
||||
if(url.startsWith('/api/jobs/fixture-xml/review?')) return response(200,priceReview());
|
||||
if(url===`/api/daily-reports/${month}-17`) return response(200,{business_date:`${month}-17`,current_job:null,latest_job:null});
|
||||
throw new Error('Unexpected request '+url);
|
||||
});
|
||||
h.state.health={processing_ready:true};
|
||||
h.state.selectedFile={name:'fixture.XML',arrayBuffer:async()=>new Uint8Array([60,47,62]).buffer};
|
||||
await h.loadReportCalendar();
|
||||
assert.doesNotMatch(block(h,`${month}-17`),/disabled/);
|
||||
const uploading=h.handleUpload();
|
||||
await new Promise(resolve=>setImmediate(resolve));
|
||||
assert.equal(h.state.uploadInFlight,true);
|
||||
assert.match(block(h,`${month}-17`),/disabled/);
|
||||
assert.equal(await h.selectReportCalendarDate(`${month}-17`),false);
|
||||
resolveUpload(outcome==='rejected'?response(503,null,'UPLOAD_UNAVAILABLE'):
|
||||
response(200,{job_id:'fixture-xml',business_date:`${month}-16`,status:outcome}));
|
||||
await uploading;
|
||||
assert.equal(h.state.uploadInFlight,false);
|
||||
assert.equal(h.state.dailyPriceReviewLoading,false);
|
||||
assert.equal(h.state.arrDownloadPendingReviews.length,0);
|
||||
assert.doesNotMatch(block(h,`${month}-17`),/disabled/,'the upload finally must refresh disabled day buttons');
|
||||
assert.equal(await h.selectReportCalendarDate(`${month}-17`),true);
|
||||
assert.equal(h.calls.filter(call=>call.method!=='GET').length,1,'only the explicitly started XML upload can write');
|
||||
assert.equal(h.calls.some(call=>call.url.includes('/arr-downloads')),false);
|
||||
});
|
||||
}
|
||||
|
||||
for(const action of ['finalize','cancel']) for(const outcome of action==='finalize'?['succeeded','failed','rejected']:['succeeded','rejected']) {
|
||||
test(`XML price ${action} ${outcome} releases the calendar after review completion`,async()=>{
|
||||
let resolveReview;
|
||||
const h=setup((url,options)=>{
|
||||
if(url===`/api/jobs/fixture-xml/review/${action}` && options.method==='POST') return new Promise(resolve=>{resolveReview=resolve;});
|
||||
if(url.startsWith('/api/jobs/fixture-xml/review?')) return response(200,priceReview());
|
||||
if(url.startsWith('/api/daily-reports?')) return page();
|
||||
if(url===`/api/daily-reports/${month}-17`) return response(200,{business_date:`${month}-17`,current_job:null,latest_job:null});
|
||||
throw new Error('Unexpected request '+url);
|
||||
});
|
||||
h.state.dailyPriceReview=priceReview();
|
||||
h.state.dailyPriceReviewJobId='fixture-xml';
|
||||
await h.loadReportCalendar();
|
||||
assert.doesNotMatch(block(h,`${month}-17`),/disabled/);
|
||||
const mutating=action==='finalize'?h.finalizeDailyPriceReview():h.confirmDailyPriceReviewCancel();
|
||||
assert.equal(h.state.dailyPriceReviewMutating,true);
|
||||
assert.match(block(h,`${month}-17`),/disabled/);
|
||||
assert.equal(await h.selectReportCalendarDate(`${month}-17`),false);
|
||||
resolveReview(outcome==='rejected'?response(503,null,'REVIEW_UNAVAILABLE'):response(200,{status:outcome}));
|
||||
await mutating;
|
||||
assert.equal(h.state.dailyPriceReviewMutating,false);
|
||||
assert.equal(h.state.dailyPriceReviewLoading,false);
|
||||
assert.equal(h.state.arrDownloadPendingReviews.length,0);
|
||||
assert.doesNotMatch(block(h,`${month}-17`),/disabled/,'an XML review has no OHIP task refresh to re-enable the calendar');
|
||||
assert.equal(await h.selectReportCalendarDate(`${month}-17`),true);
|
||||
assert.equal(h.calls.filter(call=>call.method!=='GET').length,1,'calendar selection never replays a review decision');
|
||||
assert.equal(h.calls.some(call=>call.url.includes('/arr-downloads')),false);
|
||||
});
|
||||
}
|
||||
|
||||
test('month browsing crosses years and keyboard focus crosses months without selecting or submitting',async()=>{
|
||||
const h=setup(()=>page());
|
||||
const oldSelection=h.element('#arr-download-date').value;
|
||||
await h.changeReportCalendarMonth('2026-12');
|
||||
await h.changeReportCalendarMonth(h.shiftMonth(h.state.reportCalendarMonth,1));
|
||||
assert.equal(h.state.reportCalendarMonth,'2027-01');
|
||||
await h.moveReportCalendarFocus('PageDown','2027-01-31');
|
||||
assert.equal(h.state.reportCalendarMonth,'2027-02');
|
||||
assert.equal(h.state.reportCalendarFocusDate,'2027-02-28');
|
||||
await h.moveReportCalendarFocus('ArrowRight','2027-02-28');
|
||||
assert.equal(h.state.reportCalendarMonth,'2027-03');
|
||||
assert.equal(h.state.reportCalendarFocusDate,'2027-03-01');
|
||||
await h.moveReportCalendarFocus('Home','2027-03-04');
|
||||
assert.equal(h.state.reportCalendarFocusDate,'2027-03-01');
|
||||
assert.equal(h.element('#arr-download-date').value,oldSelection);
|
||||
assert.equal(h.calls.every(c=>c.method==='GET'),true);
|
||||
assert.equal(h.calls.every(c=>c.url.includes('limit=200')),true);
|
||||
});
|
||||
|
||||
test('refreshing daily results synchronizes effective calendar coverage after a generation or deletion',async()=>{
|
||||
let generated=true;
|
||||
const h=setup(url=>url.includes('limit=200')?page([row(16,generated?{}:null)]):page());
|
||||
h.state.jobsMonth=month;
|
||||
await h.loadJobs(false,false);
|
||||
assert.match(block(h,`${month}-16`),/is-generated/);
|
||||
generated=false;
|
||||
await h.loadJobs(false,false);
|
||||
assert.match(block(h,`${month}-16`),/is-ungenerated/);
|
||||
assert.equal(h.calls.filter(c=>c.url.includes('limit=200')).length,2);
|
||||
assert.equal(h.calls.every(c=>c.method==='GET'),true);
|
||||
});
|
||||
|
||||
test('calendar labels, status legends and read failures are translated in all three locales',()=>{
|
||||
const context=vm.createContext({window:{},document:{readyState:'loading',addEventListener(){}}});
|
||||
vm.runInContext(fs.readFileSync(path.resolve(__dirname,'../../arr_web/static/i18n.js'),'utf8'),context);
|
||||
const catalog=context.window.ARRI18n.CATALOG;
|
||||
for(const key of ['title','widget_aria','generated','not_generated','unknown','loading_day',
|
||||
'day_generated','day_not_generated','day_unknown','day_loading_day','loading','failed','coverage','help','locked','day_group','legend','retry']) {
|
||||
const entry=catalog[`report_calendar.${key}`];
|
||||
assert.equal(entry?.length,3,key);
|
||||
assert.equal(entry.every(Boolean),true,key);
|
||||
assert.match(entry[2],/[\u0e00-\u0e7f]/,key);
|
||||
}
|
||||
});
|
||||
|
||||
test('a slow earlier cross-month selection cannot roll the daily list back after a newer selection completes',async()=>{
|
||||
let resolveOld;
|
||||
const oldDate='2026-09-16',newDate='2026-10-07';
|
||||
const h=setup(url=>{
|
||||
if(url===`/api/daily-reports/${oldDate}`) return new Promise(resolve=>{resolveOld=resolve;});
|
||||
if(url===`/api/daily-reports/${newDate}`) return response(200,{business_date:newDate,current_job:null,latest_job:null});
|
||||
throw new Error('No stale list read expected '+url);
|
||||
});
|
||||
const earlier=h.selectDailyReportDate(oldDate,{syncHistory:true});
|
||||
assert.equal(await h.selectDailyReportDate(newDate,{syncHistory:true}),true);
|
||||
resolveOld(response(200,{business_date:oldDate,current_job:null,latest_job:null}));
|
||||
assert.equal(await earlier,false);
|
||||
assert.equal(h.state.dailyOverviewDate,newDate);
|
||||
assert.equal(h.state.jobsMonth,'2026-10');
|
||||
assert.equal(h.calls.every(c=>c.method==='GET'),true);
|
||||
assert.equal(h.calls.length,2);
|
||||
});
|
||||
|
||||
test('same-date selections across an environment A to B to A transition reject the first A response',async()=>{
|
||||
let resolveOld;
|
||||
const date='2026-09-16';
|
||||
const h=setup((url,options,calls)=>{
|
||||
if(url===`/api/daily-reports/${date}`) {
|
||||
if(calls.length===1) return new Promise(resolve=>{resolveOld=resolve;});
|
||||
return response(200,{business_date:date,current_job:null,latest_job:null});
|
||||
}
|
||||
if(url.startsWith('/api/daily-reports?')) return page();
|
||||
throw new Error('Unexpected request '+url);
|
||||
});
|
||||
h.state.jobsMonth=month;
|
||||
const earlier=h.selectDailyReportDate(date,{syncHistory:true});
|
||||
h.state.arrDownloadContextId='environment-B';
|
||||
h.state.dailyOverview=null;
|
||||
assert.equal(await h.selectDailyReportDate(date,{syncHistory:true}),true);
|
||||
h.state.arrDownloadContextId='hotel-fixture';
|
||||
h.state.dailyOverview=null;
|
||||
assert.equal(await h.selectDailyReportDate(date,{syncHistory:true}),true);
|
||||
resolveOld(response(200,{business_date:date,current_job:row(16).current_job,latest_job:null}));
|
||||
assert.equal(await earlier,false);
|
||||
assert.equal(h.state.dailyOverview.current_job,null);
|
||||
assert.equal(h.state.dailyOverviewDate,date);
|
||||
assert.equal(h.state.jobsMonth,month);
|
||||
});
|
||||
Reference in new issue
Block a user