Files
ARR-2.0-0918/tests/javascript/report_calendar.cjs
T

454 lines
24 KiB
JavaScript

// 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.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','open','loading_summary','failed_summary','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);
});
test('the compact calendar starts closed and its popup and ARR date picker are mutually exclusive',async()=>{
const h=setup(()=>page([row(16)]));
await h.loadReportCalendar();
assert.equal(h.element('#report-calendar-popup').hidden,true);
assert.equal(h.element('#report-calendar-toggle').getAttribute('aria-expanded'),'false');
assert.equal(h.element('#report-calendar-summary').textContent,'report_calendar.coverage');
const reads=h.calls.length;
h.openARRCalendar();
assert.equal(h.arrCalendar.open,true);
h.openReportCalendar();
assert.equal(h.arrCalendar.open,false);
assert.equal(h.state.reportCalendarOpen,true);
assert.equal(h.element('#report-calendar-popup').hidden,false);
assert.equal(h.element('#report-calendar-toggle').getAttribute('aria-expanded'),'true');
assert.match(block(h,`${month}-16`),/report-calendar-day-check/,'a generated day has a non-color check mark');
assert.doesNotMatch(block(h,`${month}-17`),/report-calendar-day-check|report-calendar-day-state/);
h.openARRCalendar();
assert.equal(h.state.reportCalendarOpen,false);
assert.equal(h.element('#report-calendar-popup').hidden,true);
assert.equal(h.arrCalendar.open,true);
assert.equal(h.calls.length,reads,'opening either calendar is a view action');
});
test('Escape and close return focus to the trigger while outside click or keyboard focus preserve the destination',()=>{
const h=setup(()=>{throw new Error('Closing must not request data');});
h.bindReportCalendar();
h.openReportCalendar();
let prevented=false;
h.emitDocumentEvent({type:'keydown',key:'Escape',preventDefault(){prevented=true;}});
assert.equal(prevented,true);
assert.equal(h.state.reportCalendarOpen,false);
assert.equal(h.document.activeElement,h.element('#report-calendar-toggle'));
h.openReportCalendar();
h.element('#report-calendar-close').dispatchEvent({type:'click'});
assert.equal(h.state.reportCalendarOpen,false);
assert.equal(h.document.activeElement,h.element('#report-calendar-toggle'));
for(const type of ['click','focusin']) {
h.openReportCalendar();
const destination=h.element('#xml-file');
destination.focus();
h.emitDocumentEvent({type,target:destination});
assert.equal(h.state.reportCalendarOpen,false);
assert.equal(h.document.activeElement,destination,'dismissal cannot steal focus from the next operation');
}
});
test('successful read-only selection closes the popup but busy or unsaved prices keep it open without discarding input',async()=>{
const date=`${month}-17`;
const h=setup(url=>url===`/api/daily-reports/${date}`?response(200,{business_date:date,current_job:null,latest_job:null}):page());
await h.loadReportCalendar();
h.openReportCalendar();
h.state.uploadInFlight=true;
assert.equal(await h.selectReportCalendarDate(date),false);
assert.equal(h.state.reportCalendarOpen,true);
h.state.uploadInFlight=false;
h.state.dailyPriceReview={case_status:'open',items:[{item_id:1,real_price:null}]};
const input={value:'0',closest:()=>({dataset:{dailyPriceReviewItemId:'1'}})};
h.element('#daily-price-review-body').querySelectorAll=()=>[input];
assert.equal(await h.selectReportCalendarDate(date),false);
assert.equal(h.state.reportCalendarOpen,true);
assert.equal(input.value,'0');
assert.equal(h.element('#arr-download-date').value,`${month}-16`);
h.state.dailyPriceReview=null;
assert.equal(await h.selectReportCalendarDate(date),true);
assert.equal(h.state.reportCalendarOpen,false);
assert.equal(h.element('#report-calendar-popup').hidden,true);
assert.equal(h.document.activeElement,h.element('#report-calendar-toggle'));
assert.equal(h.calls.every(call=>call.method==='GET'),true);
assert.equal(h.calls.some(call=>call.url.includes('/arr-downloads')),false);
});
test('a hidden popup never captures focus during a late month refresh or keyboard-render request',async()=>{
let resolveRead;
const h=setup(()=>new Promise(resolve=>{resolveRead=resolve;}));
h.openReportCalendar();
const pending=h.loadReportCalendar();
const hiddenDay=h.element(`[data-report-calendar-day="${h.state.reportCalendarFocusDate}"]`);
hiddenDay.closest=()=>hiddenDay;
h.closeReportCalendar();
let focusAttempts=0;
hiddenDay.focus=()=>{focusAttempts+=1;};
h.document.activeElement=hiddenDay;
resolveRead(page());
await pending;
h.renderReportCalendar(true);
assert.equal(focusAttempts,0);
assert.equal(h.element('#report-calendar-popup').hidden,true);
});
test('a late day selection cannot close a popup opened again after the original popup was dismissed',async()=>{
let resolveSelection;
const date=`${month}-17`;
const h=setup(url=>url===`/api/daily-reports/${date}`?new Promise(resolve=>{resolveSelection=resolve;}):page());
await h.loadReportCalendar();
h.openReportCalendar();
const selecting=h.selectReportCalendarDate(date);
h.closeReportCalendar();
h.openReportCalendar();
resolveSelection(response(200,{business_date:date,current_job:null,latest_job:null}));
assert.equal(await selecting,true);
assert.equal(h.state.reportCalendarOpen,true);
assert.equal(h.element('#report-calendar-popup').hidden,false);
});
test('a delegated day click stays internal after synchronous rendering removes its target and restores trigger focus on success',async()=>{
let resolveSelection;
const date=`${month}-17`;
const h=setup(url=>url===`/api/daily-reports/${date}`?new Promise(resolve=>{resolveSelection=resolve;}):page());
await h.loadReportCalendar();
h.bindReportCalendar();
h.openReportCalendar();
const originalButton={dataset:{reportCalendarDay:date},closest(){return this;}};
const control=h.element('#report-calendar');
const event={type:'click',target:originalButton,composedPath:()=>[
originalButton,h.element('#report-calendar-days'),h.element('#report-calendar-popup'),control]};
h.element('#report-calendar-days').dispatchEvent(event);
assert.equal(h.state.dailyOverviewDate,date,'the delegated handler has already synchronously rerendered the days');
assert.equal(control.contains(originalButton),false,'the original click target is no longer a current DOM descendant');
h.emitDocumentEvent(event);
assert.equal(h.state.reportCalendarOpen,true,'document must honor the event origin instead of the removed target');
resolveSelection(response(200,{business_date:date,current_job:null,latest_job:null}));
await new Promise(resolve=>setImmediate(resolve));
assert.equal(h.state.reportCalendarOpen,false);
assert.equal(h.document.activeElement,h.element('#report-calendar-toggle'));
assert.equal(h.calls.every(call=>call.method==='GET'),true);
});