fix: clarify explicit zero prices in manual review
This commit is contained in:
1 parent
9487634f83
commit
5acbec75fe
7 files changed
+98
-11
No files matched your search
@@ -0,0 +1,75 @@
|
||||
// Actual UI price saving against a synthetic API, without live business writes.
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const {harness,response} = require('./helpers/arr_ui_harness.cjs');
|
||||
const fixture=(price=null)=>({case_id:'fixture-case',case_status:'open',revision:1,
|
||||
completed_items:price===null?0:1,total_items:1,
|
||||
items:[{item_id:1,company_key:'Fixture Company',rate_code:'FIXTURE',effective_rate_amount:100,
|
||||
real_price:price,affected_records:1,affected_rooms:1,affected_room_nights:2,candidate_prices:[]}],
|
||||
pagination:{offset:0,limit:50,total:1}});
|
||||
|
||||
test('zero is a valid explicit price while blank and negative prices remain invalid',()=>{
|
||||
const h=harness(()=>{throw new Error('unexpected request');});
|
||||
for(const value of [0,'0',' 0 ','1500']) assert.equal(h.strictReviewPrice(value),true);
|
||||
for(const value of [null,undefined,'',' ','-1',-1,'0.00','1.5','abc']) assert.equal(h.strictReviewPrice(value),false);
|
||||
assert.equal(h.reviewPriceInput(0),'0');
|
||||
assert.equal(h.reviewPriceInput('0'),'0');
|
||||
assert.equal(h.reviewPriceInput(null),'');
|
||||
});
|
||||
|
||||
test('zero is rendered as a saved value and permits continuation when all items are saved',()=>{
|
||||
const h=harness(()=>{throw new Error('unexpected request');});
|
||||
h.renderDailyPriceReview(fixture(0));
|
||||
const html=h.element('#daily-price-review-body').innerHTML;
|
||||
assert.match(html,/value="0"/);
|
||||
assert.match(html,/daily-price-review-row is-complete/);
|
||||
assert.match(html,/daily_review.saved/);
|
||||
assert.equal(h.element('#daily-price-review-finalize').disabled,false);
|
||||
h.renderDailyPriceReview(fixture(null));
|
||||
assert.match(h.element('#daily-price-review-body').innerHTML,/value=""/);
|
||||
assert.match(h.element('#daily-price-review-body').innerHTML,/daily_review.price_placeholder/);
|
||||
assert.equal(h.element('#daily-price-review-body').innerHTML.includes('placeholder="0"'),false);
|
||||
assert.equal(h.element('#daily-price-review-finalize').disabled,true);
|
||||
});
|
||||
|
||||
test('explicit zero is saved as zero and survives reloading without becoming empty',async()=>{
|
||||
let current=fixture();
|
||||
const h=harness((url,options)=>{
|
||||
if(options.method==='PATCH') {
|
||||
assert.equal(url,'/api/jobs/fixture-job/review/items/1');
|
||||
const decision=JSON.parse(options.body);
|
||||
assert.deepEqual(decision,{case_id:'fixture-case',revision:1,real_price:'0'});
|
||||
current={...fixture('0'),revision:2};
|
||||
return response(200,{revision:2});
|
||||
}
|
||||
if(url.startsWith('/api/jobs/fixture-job/review?')) return response(200,current);
|
||||
if(url.startsWith('/api/jobs?')) return response(200,[]);
|
||||
throw new Error('unexpected request '+url);
|
||||
});
|
||||
h.state.dailyPriceReview=fixture();
|
||||
h.state.dailyPriceReviewJobId='fixture-job';
|
||||
const input={value:'0',focus(){}};
|
||||
const row={dataset:{dailyPriceReviewItemId:'1'},querySelector:()=>input};
|
||||
await h.saveDailyPriceReviewItem({closest:()=>row});
|
||||
assert.equal(h.calls.filter(c=>c.method==='PATCH').length,1);
|
||||
assert.equal(h.state.dailyPriceReview.items[0].real_price,'0');
|
||||
assert.equal(h.state.dailyPriceReview.completed_items,1);
|
||||
assert.equal(h.dailyPriceReviewCanFinalize(h.state.dailyPriceReview),true);
|
||||
assert.equal(h.element('#daily-price-review-finalize').disabled,false);
|
||||
assert.match(h.element('#daily-price-review-body').innerHTML,/value="0"/);
|
||||
await h.loadDailyPriceReview();
|
||||
assert.equal(h.state.dailyPriceReview.items[0].real_price,'0');
|
||||
assert.equal(h.calls.filter(c=>c.method==='PATCH').length,1,'reload must not submit another decision');
|
||||
});
|
||||
|
||||
test('an empty input cannot be silently saved as zero',async()=>{
|
||||
const h=harness(()=>{throw new Error('unexpected request');});
|
||||
h.state.dailyPriceReview=fixture();
|
||||
h.state.dailyPriceReviewJobId='fixture-job';
|
||||
const input={value:'',focus(){}};
|
||||
const row={dataset:{dailyPriceReviewItemId:'1'},querySelector:()=>input};
|
||||
await h.saveDailyPriceReviewItem({closest:()=>row});
|
||||
assert.equal(h.calls.length,0);
|
||||
assert.equal(h.state.dailyPriceReview.items[0].real_price,null);
|
||||
assert.equal(h.dailyPriceReviewCanFinalize(h.state.dailyPriceReview),false);
|
||||
});
|
||||
@@ -17,12 +17,12 @@ function harness(respond) {
|
||||
};
|
||||
const context=vm.createContext({Headers, console, Date, Intl, Uint8Array,
|
||||
document:{hidden:false,querySelector:element},
|
||||
window:{ARRI18n:{t:key=>key,text:value=>value,errorMessage:code=>code},crypto:webcrypto,
|
||||
window:{ARRI18n:{t:key=>key,text:value=>value,errorMessage:code=>code,formatInteger:value=>String(value)},crypto:webcrypto,
|
||||
setTimeout(){return 1;},clearTimeout(){},location:{replace(){}}},
|
||||
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';
|
||||
const exports='state, api, submitARRDownload, rememberARRIntent, acceptARRDownloadTask, handleARRDownloadDateChange, initARRDownload, loadARRDownloadTask, loadARRDataReview, renderARRDataReview, arrDataReviewCanFinalize, saveARRDataReviewItem, finalizeARRDataReview, trackARRDataReviewDraft, parseARRDataReviewValue, selectARRPendingReview, strictReviewPrice, reviewPriceInput, renderDailyPriceReview, saveDailyPriceReviewItem, loadDailyPriceReview, dailyPriceReviewCanFinalize';
|
||||
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'});
|
||||
|
||||
Reference in new issue
Block a user