// Actual progress rendering against synthetic task snapshots; no Oracle requests. 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,scopedKey} = require('./helpers/arr_ui_harness.cjs'); const localeContext=vm.createContext({window:{},document:{readyState:'loading',addEventListener(){}}}); vm.runInContext(fs.readFileSync(path.resolve(__dirname,'../../arr_web/static/i18n.js'),'utf8'),localeContext); const catalog=localeContext.window.ARRI18n.CATALOG; const date='2026-09-16'; const task={request_id:'d'.repeat(32),report_date:date,status:'downloading',job_id:null,can_retry:false}; const progress=(phase,completed,total,percent)=>({phase,completed,total,percent,updated_at:'2026-10-08T05:00:00Z'}); function setup(respond=()=>{throw new Error('rendering must not send a request');}) { const h=harness(respond); h.i18n.t=(key,values={})=>(catalog[key]?.[0]||key).replace(/\{(\w+)\}/g,(_,name)=>String(values[name]??'')); return h; } async function render(h,snapshot) { await h.acceptARRDownloadTask(snapshot,{sync:false}); } test('unknown reservation totals remain indeterminate and never invent zero totals or percentages',async()=>{ const h=setup(); for(const phase of ['queued','searching','fetching']) { await render(h,{...task,status:phase==='queued'?'queued':'downloading',acquisition_progress:progress(phase,0,null,null)}); assert.equal(h.element('#arr-acquisition-progress').hidden,false); assert.equal(h.element('#arr-acquisition-track').getAttribute('aria-valuenow'),null); assert.equal(h.element('#arr-acquisition-track').classList.contains('is-indeterminate'),true); assert.equal(h.element('#arr-acquisition-percent').hidden,true); assert.doesNotMatch(h.element('#arr-acquisition-detail').textContent,/0\s*\/\s*0|%/); } assert.equal(h.calls.length,0); }); test('a real 58 of 85 snapshot renders server progress and accessible counts',async()=>{ const h=setup(); await render(h,{...task,acquisition_progress:progress('fetching',58,85,68)}); assert.equal(h.element('#arr-acquisition-status').textContent,'取数中'); assert.equal(h.element('#arr-acquisition-detail').textContent,'已获取 58 / 85 笔'); assert.equal(h.element('#arr-acquisition-percent').textContent,'68%'); assert.equal(h.element('#arr-acquisition-track').getAttribute('aria-valuenow'),'68'); assert.match(h.element('#arr-acquisition-track').getAttribute('aria-valuetext'),/58 \/ 85.*68%/); assert.equal(h.element('#arr-acquisition-track').classList.contains('is-indeterminate'),false); assert.equal(h.element('#arr-acquisition-bar').style.width,'68%'); }); test('all records still show 99 percent while completeness is being verified',async()=>{ const h=setup(); for(const percent of [99,100]) { await render(h,{...task,acquisition_progress:progress('verifying',85,85,percent)}); assert.equal(h.element('#arr-acquisition-status').textContent,'取数中'); assert.equal(h.element('#arr-acquisition-percent').textContent,'99%'); assert.match(h.element('#arr-acquisition-detail').textContent,/85 \/ 85.*正在校验完整性/); assert.equal(h.element('#arr-acquisition-track').getAttribute('aria-valuenow'),'99'); } }); test('a verified empty result is a real completed zero total',async()=>{ const h=setup(); await render(h,{...task,status:'succeeded',acquisition_progress:progress('completed',0,0,100)}); assert.equal(h.element('#arr-acquisition-status').textContent,'已完成'); assert.equal(h.element('#arr-acquisition-detail').textContent,'已获取 0 / 0 笔'); assert.equal(h.element('#arr-acquisition-percent').textContent,'100%'); }); test('completed retrieval remains distinct from pending field and price review',async()=>{ for(const status of ['needs_data_review','needs_review','processing','succeeded']) { const h=setup(url=>{ assert.equal(url,`/api/arr-downloads/${task.request_id}/data-review`); return response(200,{request_id:task.request_id,report_date:date,status:'editing',items:[],pending_count:0,total_count:0}); }); await render(h,{...task,status,job_id:status==='needs_data_review'?null:'fixture-job',acquisition_progress:progress('completed',85,85,100)}); assert.equal(h.element('#arr-acquisition-status').textContent,'已完成'); assert.equal(h.element('#arr-acquisition-percent').textContent,'100%'); assert.equal(h.element('#arr-acquisition-detail').textContent,'已获取 85 / 85 笔'); if(status==='needs_data_review') assert.equal(h.element('#arr-download-data-review').hidden,false); if(status==='needs_review') assert.equal(h.element('#arr-download-review').hidden,false); assert.doesNotMatch(h.element('#arr-acquisition-detail').textContent,/生成|报表已完成/); } }); test('failed and interrupted retrieval never appear as 100 percent complete',async()=>{ const h=setup(); for(const phase of ['failed','interrupted']) { await render(h,{...task,status:phase,acquisition_progress:progress(phase,85,85,100)}); assert.equal(h.element('#arr-acquisition-status').textContent,phase==='failed'?'取数失败':'取数中断'); assert.equal(h.element('#arr-acquisition-progress').classList.contains('is-completed'),false); assert.equal(h.element('#arr-acquisition-progress').classList.contains('is-error'),true); assert.equal(h.element('#arr-acquisition-percent').textContent,'99%'); assert.equal(h.element('#arr-acquisition-track').classList.contains('is-indeterminate'),false); } }); test('legacy metadata absence shows truthful status without fabricated counts',async()=>{ const h=setup(); await render(h,task); assert.equal(h.element('#arr-acquisition-status').textContent,'取数中'); assert.equal(h.element('#arr-acquisition-track').classList.contains('is-indeterminate'),true); assert.equal(h.element('#arr-acquisition-detail').textContent,'正在获取预订数据…'); await render(h,{...task,status:'needs_review',job_id:'fixture-job'}); assert.equal(h.element('#arr-acquisition-status').textContent,'已完成'); assert.equal(h.element('#arr-acquisition-percent').hidden,true); assert.equal(h.element('#arr-acquisition-track').hidden,true); assert.equal(h.element('#arr-acquisition-detail').textContent,'该任务未记录取数笔数。'); for(const status of ['failed','interrupted']) { await render(h,{...task,status}); assert.equal(h.element('#arr-acquisition-percent').hidden,true); assert.equal(h.element('#arr-acquisition-track').hidden,true); assert.notEqual(h.element('#arr-acquisition-status').textContent,'已完成'); } }); test('progress hides for another selected date and returns for the original running task',async()=>{ const h=setup(); await render(h,{...task,acquisition_progress:progress('fetching',58,85,68)}); h.element('#arr-download-date').value='2026-09-17'; h.handleARRDownloadDateChange(); assert.equal(h.element('#arr-acquisition-progress').hidden,true); assert.equal(h.state.arrDownloadTask.report_date,date); assert.match(h.element('#arr-download-status').textContent,/2026-09-16/); h.element('#arr-download-date').value=date; h.handleARRDownloadDateChange(); assert.equal(h.element('#arr-acquisition-progress').hidden,false); assert.equal(h.element('#arr-acquisition-percent').textContent,'68%'); assert.equal(h.calls.length,0); }); test('context changes hide the former task progress and empty selection has none',async()=>{ const h=setup(()=>response(200,{context_id:'new-context',ready:true,default_date:date,pending_reviews:[],latest_task:null})); h.state.arrDownloadContextId='old-context'; h.state.arrDownloadStorageKey=scopedKey('old-context'); await render(h,{...task,acquisition_progress:progress('fetching',58,85,68)}); await h.initARRDownload(); assert.equal(h.state.arrDownloadTask,null); assert.equal(h.element('#arr-acquisition-progress').hidden,true); }); test('acquisition labels, counts and completeness guidance are available in all three languages',()=>{ for(const [key,values] of Object.entries(catalog).filter(([key])=>key.startsWith('arr_acquisition.'))) { assert.equal(values.length,3,key); assert.equal(values.every(value=>typeof value==='string'&&value.length>0),true,key); assert.match(values[2],/[\u0e00-\u0e7f]/,key); } assert.match(catalog['arr_acquisition.verifying_counts'][0],/校验完整性/); });