优化
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
const http=require('http');
|
||||
const fs=require('fs');
|
||||
const path=require('path');
|
||||
http.createServer((req,res)=>{
|
||||
const file=path.join(process.cwd(), req.url==='/'?'index.html':req.url);
|
||||
fs.createReadStream(file).on('error',()=>{res.statusCode=404;res.end('Not found')}).pipe(res);
|
||||
}).listen(3000,'127.0.0.1');
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$html = Get-Content -Raw -LiteralPath (Join-Path $PSScriptRoot '..\index.html')
|
||||
|
||||
$required = @(
|
||||
'totalWeightLb:203.0',
|
||||
'totalWeightLb:99.9',
|
||||
"totalBoxes:'总箱数'",
|
||||
"totalWeight:'总重量'",
|
||||
"totalBoxes:'Total cartons'",
|
||||
"totalWeight:'Total weight'",
|
||||
"totalBoxes:'Total de cajas'",
|
||||
"totalWeight:'Peso total'",
|
||||
'pickTasks.reduce((sum,task)=>sum+Number(task.palletCount||0),0)',
|
||||
'pickTasks.reduce((sum,task)=>sum+Number(task.boxes||0),0)',
|
||||
'pickTasks.reduce((sum,task)=>sum+Number(task.totalWeightLb||0),0)',
|
||||
'${totalWeight.toFixed(1)} lb'
|
||||
)
|
||||
|
||||
$missing = $required | Where-Object { -not $html.Contains($_) }
|
||||
if ($missing) { throw "Missing handover summary contract: $($missing -join ', ')" }
|
||||
|
||||
$handoverSource = [regex]::Match($html, '(?s)function handoverView\(\).*?(?=function handleHandoverPhoto)').Value
|
||||
if ($handoverSource.Contains('<span>${t(''boxes'')}</span>')) {
|
||||
throw 'Handover still uses the generic cartons label'
|
||||
}
|
||||
|
||||
Write-Output 'PASS: handover summary source contract'
|
||||
@@ -0,0 +1,149 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
|
||||
const match = html.match(/<script>([\s\S]*?)<\/script>/);
|
||||
assert.ok(match, 'inline application script is missing');
|
||||
|
||||
const applicationSource = match[1].replace(/\s*render\(\);\s*$/, '');
|
||||
const tests = [];
|
||||
const runtimeTests = String.raw`
|
||||
render=()=>{};
|
||||
showModal=(message,onConfirm)=>{globalThis.__lastModal={message,onConfirm}};
|
||||
closeModal=()=>{};
|
||||
toast=()=>{};
|
||||
|
||||
function test(name,fn){tests.push({name,fn})}
|
||||
|
||||
test('consolidation ignores dropship pallet lookup and clears type inputs',()=>{
|
||||
state.palletOrderType='dropship';
|
||||
state.fbaPalletLabels=['D4444-TC-001'];
|
||||
state.dropshipPalletLabel='D2222-TC-001';
|
||||
state.palletTypeMessage={type:'error',text:'stale'};
|
||||
state.fbaPalletMessage={type:'error',text:'stale'};
|
||||
state.dropshipScanMessage={type:'error',text:'stale'};
|
||||
startConsolidation();
|
||||
assert.deepEqual(state.fbaPalletLabels,[]);
|
||||
assert.equal(state.dropshipPalletLabel,'');
|
||||
assert.equal(state.palletTypeMessage,null);
|
||||
assert.equal(state.fbaPalletMessage,null);
|
||||
assert.equal(state.dropshipScanMessage,null);
|
||||
|
||||
state.pallet={id:'DRAFT',mode:'consolidate',planId:null,targetPallets:null,destinationCode:'',destinationAddress:'',items:[]};
|
||||
document.__input='D2222-TC-001';
|
||||
handlePalletScan();
|
||||
assert.equal(state.pallet.items.length,0,'consolidation must not import an existing pallet');
|
||||
assert.equal(state.dropshipPalletLabel,'');
|
||||
});
|
||||
|
||||
test('starting and selecting jobs reset type-specific inputs',()=>{
|
||||
state.palletMode='palletize';
|
||||
state.selectedPlanId='INB-260819-026';
|
||||
state.fbaPalletLabels=['D4444-TC-001'];
|
||||
state.dropshipPalletLabel='D2222-TC-001';
|
||||
state.fbaPalletMessage={type:'error',text:'stale'};
|
||||
state.dropshipScanMessage={type:'error',text:'stale'};
|
||||
startPallet();
|
||||
assert.deepEqual(state.fbaPalletLabels,[]);
|
||||
assert.equal(state.dropshipPalletLabel,'');
|
||||
assert.equal(state.fbaPalletMessage,null);
|
||||
assert.equal(state.dropshipScanMessage,null);
|
||||
|
||||
state.fbaPalletLabels=['D4444-TC-002'];
|
||||
state.dropshipPalletLabel='D2222-TC-001';
|
||||
selectPallet('D4444-TC-003','INB-260819-026');
|
||||
assert.deepEqual(state.fbaPalletLabels,[]);
|
||||
assert.equal(state.dropshipPalletLabel,'');
|
||||
|
||||
state.fbaPalletLabels=['D4444-TC-004'];
|
||||
state.dropshipPalletLabel='D2222-TC-001';
|
||||
document.__input='BX-88401-01';
|
||||
handlePalletTopScan();
|
||||
assert.deepEqual(state.fbaPalletLabels,[]);
|
||||
assert.equal(state.dropshipPalletLabel,'');
|
||||
|
||||
state.fbaPalletLabels=['D4444-TC-005'];
|
||||
state.dropshipPalletLabel='D2222-TC-001';
|
||||
startNewConsolidation();
|
||||
assert.deepEqual(state.fbaPalletLabels,[]);
|
||||
assert.equal(state.dropshipPalletLabel,'');
|
||||
|
||||
state.fbaPalletLabels=['D4444-TC-006'];
|
||||
state.dropshipPalletLabel='D2222-TC-001';
|
||||
reconsolidatePallet('D2222-TC-019');
|
||||
assert.deepEqual(state.fbaPalletLabels,[]);
|
||||
assert.equal(state.dropshipPalletLabel,'');
|
||||
});
|
||||
|
||||
test('FBA selector disables occupied labels and every label for persisted pallets',()=>{
|
||||
state.palletOrderType='fba';
|
||||
state.pallet={id:'',mode:'palletize',planId:'INB-260819-018',items:[],persisted:false};
|
||||
let markup=fbaPalletLabelSelector(plans[0]);
|
||||
assert.match(markup,/D2222-TC-001[\s\S]*?<\/button>/);
|
||||
const occupiedButton=markup.match(/<button[^>]*>[\s\S]*?D2222-TC-001[\s\S]*?<\/button>/)?.[0]||'';
|
||||
assert.match(occupiedButton,/disabled/);
|
||||
|
||||
state.pallet.persisted=true;
|
||||
markup=fbaPalletLabelSelector(plans[0]);
|
||||
const buttons=[...markup.matchAll(/<button[^>]*class="fba-pallet-choice[^>]*>/g)].map(match=>match[0]);
|
||||
assert.ok(buttons.length>1);
|
||||
assert.ok(buttons.every(button=>button.includes('disabled')));
|
||||
});
|
||||
|
||||
test('FBA and dropship validation messages render in their owning modules',()=>{
|
||||
state.palletMode='palletize';
|
||||
state.selectedPlanId='INB-260819-026';
|
||||
state.pallet={id:'',mode:'palletize',planId:'INB-260819-026',destinationCode:'',destinationAddress:'',items:[]};
|
||||
state.fbaPalletMessage={type:'error',text:'FBA_MESSAGE'};
|
||||
const selector=fbaPalletLabelSelector(plans[2]);
|
||||
assert.match(selector,/fba-pallet-selector[\s\S]*FBA_MESSAGE/);
|
||||
|
||||
state.palletOrderType='dropship';
|
||||
state.fbaPalletMessage=null;
|
||||
state.dropshipScanMessage={type:'error',text:'DROPSHIP_MESSAGE'};
|
||||
const view=palletView();
|
||||
const scanStart=view.indexOf('pallet-type-scan');
|
||||
const scanEnd=view.indexOf('</section>',scanStart);
|
||||
assert.ok(scanStart>=0 && view.slice(scanStart,scanEnd).includes('DROPSHIP_MESSAGE'));
|
||||
});
|
||||
|
||||
test('FBA submission materializes every available selected label exactly once',()=>{
|
||||
const plan=plans[2];
|
||||
const originalPallets=state.pallets.map(pallet=>JSON.parse(JSON.stringify(pallet)));
|
||||
const originalReceived=plan.receivedPallets;
|
||||
const source=cargo.find(item=>item.id==='C-004');
|
||||
source.palletNo='';
|
||||
state.pallets=originalPallets;
|
||||
state.selectedPlanId=plan.id;
|
||||
state.palletMode='palletize';
|
||||
state.palletOrderType='fba';
|
||||
state.fbaPalletLabels=['D4444-TC-001','D4444-TC-002'];
|
||||
state.pallet={id:'',mode:'palletize',planId:plan.id,targetPallets:plan.expectedPallets,destinationCode:source.destinationCode,destinationAddress:source.destinationAddress,items:[{...source}]};
|
||||
submitPallet();
|
||||
|
||||
const created=state.pallets.filter(pallet=>['D4444-TC-001','D4444-TC-002'].includes(pallet.id));
|
||||
assert.equal(created.length,2);
|
||||
assert.equal(new Set(created.map(pallet=>pallet.id)).size,2);
|
||||
assert.equal(created.reduce((count,pallet)=>count+pallet.items.length,0),1,'cargo must be linked once across the selected pallets');
|
||||
assert.equal(source.palletNo,'D4444-TC-001');
|
||||
assert.equal(plan.receivedPallets,Math.min(plan.expectedPallets,originalReceived+2));
|
||||
});
|
||||
|
||||
for(const entry of tests){
|
||||
try{entry.fn();process.stdout.write('PASS: '+entry.name+'\n')}
|
||||
catch(error){process.stderr.write('FAIL: '+entry.name+'\n'+error.stack+'\n');globalThis.__failed=(globalThis.__failed||0)+1}
|
||||
}
|
||||
if(globalThis.__failed)throw new Error(globalThis.__failed+' runtime behavior test(s) failed');
|
||||
`;
|
||||
|
||||
const documentStub = {
|
||||
__input: '',
|
||||
getElementById(id) {
|
||||
if (id === 'pallet-scan' || id === 'pallet-top-scan') return { value: this.__input };
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const run = new Function('assert', 'tests', 'localStorage', 'document', 'window', applicationSource + '\n' + runtimeTests);
|
||||
run(assert, tests, { getItem: () => null, setItem: () => {} }, documentStub, { scrollTo: () => {} });
|
||||
@@ -0,0 +1,87 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$html = Get-Content -Raw -LiteralPath (Join-Path $PSScriptRoot '..\index.html')
|
||||
|
||||
$required = @(
|
||||
'id="pallet-top-scan"',
|
||||
'function handlePalletTopScan()',
|
||||
"state.page='pallet'",
|
||||
"t('palletLabel')",
|
||||
"t('destination')",
|
||||
"t('cartonCount')",
|
||||
"t('palletStatus')",
|
||||
"unknownPalletScan"
|
||||
)
|
||||
|
||||
$missing = $required | Where-Object { -not $html.Contains($_) }
|
||||
if ($missing) { throw "Missing pallet scan/list contract: $($missing -join ', ')" }
|
||||
|
||||
$requiredTypeContracts = @(
|
||||
"palletOrderType:'fba'",
|
||||
'fbaPalletLabels:[]',
|
||||
"dropshipPalletLabel:''",
|
||||
'function setPalletOrderType(type)',
|
||||
'class="pallet-order-tabs"',
|
||||
"setPalletOrderType('fba')",
|
||||
"setPalletOrderType('overseas')",
|
||||
"setPalletOrderType('dropship')"
|
||||
)
|
||||
$missingTypeContracts = $requiredTypeContracts | Where-Object { -not $html.Contains($_) }
|
||||
if ($missingTypeContracts) { throw "Missing pallet order tabs: $($missingTypeContracts -join ', ')" }
|
||||
|
||||
$plansView = [regex]::Match($html, '(?s)function plansPalletView\(\).*?(?=function receivePhotoView)').Value
|
||||
if (-not $plansView.Contains('pallet-top-scan')) { throw 'Pallet list does not expose a top scan field' }
|
||||
if ($plansView.Contains("t('orderList')")) { throw 'Pallet list still presents the order-list label' }
|
||||
if (-not $plansView.Contains("t('palletBoxSummary')")) { throw 'Pallet list summary label is missing' }
|
||||
if (-not $plansView.Contains('const totalBoxes=pallets.length*5')) { throw 'Pallet list total carton count is missing' }
|
||||
if (-not $plansView.Contains('${t(''palletBoxSummary'')}:${pallets.length}/${totalBoxes}')) { throw 'Pallet summary is missing the colon separator' }
|
||||
if (-not $plansView.Contains("t('scanBoxSku')")) { throw 'Top scan does not use carton mark/SKU copy' }
|
||||
if (-not $html.Contains('function handlePalletTopScan()')) { throw 'Top scan handler is missing' }
|
||||
if (-not $html.Contains("c.boxNo===value||c.sku===value")) { throw 'Top scan does not resolve carton mark/SKU' }
|
||||
$scanHandler = [regex]::Match($html, '(?s)function handlePalletTopScan\(\).*?(?=function receivePhotoView)').Value
|
||||
if (-not $scanHandler.Contains("if(!value){state.palletTopMessage=null;state.palletMode='palletize';startPallet();return}")) { throw 'Empty scan-button click does not enter palletizing' }
|
||||
if (-not $plansView.Contains('<table class="table pallet-list-table">')) { throw 'Pallet list is not rendered as a table' }
|
||||
if ($plansView.Contains("t('viewPalletDetails')") -or $plansView.Contains("t('palletizeAction')")) { throw 'Pallet list still exposes a detail/action button' }
|
||||
if ($plansView.Contains('${esc(pallet.plan.id)}') -or $plansView.Contains('${esc(pallet.container)}')) { throw 'Pallet list still exposes order/container information' }
|
||||
$css = [regex]::Match($html, '(?s)<style id="pallet-list-toolbar">.*?</style>').Value
|
||||
if (-not $css.Contains('.pallet-list-table{min-width:0')) { throw 'Pallet table still requires horizontal scrolling' }
|
||||
if (-not $css.Contains('.pallet-list-table th,.pallet-list-table td{padding:8px 6px')) { throw 'Pallet table spacing is not compact' }
|
||||
if (-not $css.Contains('.pallet-list-table tbody td:nth-child(-n+3){font-size:12px}')) { throw 'Primary pallet columns are not enlarged' }
|
||||
if (-not $css.Contains('.pallet-list-table th:nth-child(1),.pallet-list-table td:nth-child(1){width:35%}')) { throw 'Pallet label column width is incorrect' }
|
||||
if (-not $css.Contains('.pallet-list-table th:nth-child(2),.pallet-list-table td:nth-child(2){width:25%}')) { throw 'Destination column width is incorrect' }
|
||||
if (-not $css.Contains('.pallet-list-table th:nth-child(3),.pallet-list-table td:nth-child(3){width:15%;text-align:left}')) { throw 'Carton count column width is incorrect' }
|
||||
if (-not $css.Contains('.pallet-list-table th:nth-child(4),.pallet-list-table td:nth-child(4){width:25%}')) { throw 'Status column width is incorrect' }
|
||||
if ($html.Contains("if(state.page==='plans'||state.page==='relabel')")) { throw 'Pallet page still injects an order count' }
|
||||
|
||||
$palletView = [regex]::Match($html, '(?s)function palletView\(\).*?(?=function startPallet\()').Value
|
||||
$tabsAt = $palletView.IndexOf('${palletOrderTabs()}')
|
||||
$progressAt = $palletView.IndexOf('pallet-progress-card')
|
||||
$cargoAt = $palletView.IndexOf('${cargoInfo}')
|
||||
if ($tabsAt -lt 0 -or $progressAt -lt $tabsAt -or $cargoAt -lt $progressAt) { throw 'Pallet detail module order is incorrect' }
|
||||
if (-not $palletView.Contains("const showDetails=state.palletOrderType==='overseas'")) { throw 'Detail visibility is not type-driven' }
|
||||
if (-not $palletView.Contains("const scanHint=state.palletOrderType==='dropship'?t('scanPalletLabel'):t('scanBoxSku')")) { throw 'Scan hint is not type-driven' }
|
||||
if (-not $palletView.Contains("t('cargoInfo')")) { throw 'Pallet detail title is not cargo information' }
|
||||
if (-not $palletView.Contains("t('inboundCode')")) { throw 'Pallet detail does not show inbound code' }
|
||||
if (-not $palletView.Contains("t('cartonCount')")) { throw 'Pallet detail does not show carton count' }
|
||||
if ($palletView.Contains("t('palletNo')")) { throw 'Pallet detail still shows pallet number' }
|
||||
if ($palletView.Contains("t('destinationAddress')")) { throw 'Pallet detail still shows destination address' }
|
||||
if ($palletView.Contains("t('pieces')")) { throw 'Pallet detail still shows pieces' }
|
||||
|
||||
$fbaContracts = @(
|
||||
'function fbaPalletLabelSelector(plan)',
|
||||
'function toggleFbaPalletLabel(palletId)',
|
||||
'state.fbaPalletLabels.includes(palletId)',
|
||||
"state.palletOrderType==='fba'&&!state.fbaPalletLabels.length"
|
||||
)
|
||||
$missingFba = $fbaContracts | Where-Object { -not $html.Contains($_) }
|
||||
if ($missingFba) { throw "Missing FBA multi-select behavior: $($missingFba -join ', ')" }
|
||||
|
||||
$dropshipContracts = @(
|
||||
"state.palletOrderType==='dropship'",
|
||||
'state.dropshipPalletLabel=pallet.id',
|
||||
"state.palletOrderType==='dropship'&&!state.dropshipPalletLabel",
|
||||
"state.palletOrderType!=='dropship'"
|
||||
)
|
||||
$missingDropship = $dropshipContracts | Where-Object { -not $html.Contains($_) }
|
||||
if ($missingDropship) { throw "Missing dropship scan behavior: $($missingDropship -join ', ')" }
|
||||
|
||||
Write-Output 'PASS: pallet scan/list source contract'
|
||||
@@ -0,0 +1,27 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$html = Get-Content -Raw -LiteralPath (Join-Path $PSScriptRoot '..\index.html')
|
||||
|
||||
$required = @(
|
||||
'selectedRelabelId:null',
|
||||
'relabelPhotos:{}',
|
||||
"state.page='relabel-photo'",
|
||||
'function relabelPhotoView()',
|
||||
'function openRelabelPhoto(planId)',
|
||||
'function handleRelabelPhoto(input)',
|
||||
'function submitRelabelPhoto()',
|
||||
'capture="environment"',
|
||||
"state.page='relabel'"
|
||||
)
|
||||
|
||||
$missing = $required | Where-Object { -not $html.Contains($_) }
|
||||
if ($missing) { throw "Missing relabel photo page contract: $($missing -join ', ')" }
|
||||
|
||||
$relabelView = [regex]::Match($html, '(?s)function relabelView\(\).*?(?=function openRelabelPhoto)').Value
|
||||
if (-not $relabelView.Contains("onclick=`"openRelabelPhoto('")) { throw 'Relabel list does not open the photo page' }
|
||||
if ($relabelView.Contains('capture="environment"')) { throw 'Relabel list still contains an inline camera input' }
|
||||
|
||||
$photoView = [regex]::Match($html, '(?s)function relabelPhotoView\(\).*?(?=function handleRelabelPhoto)').Value
|
||||
if (-not $photoView.Contains("!photo?'disabled':''")) { throw 'Photo submit button is not gated by a captured photo' }
|
||||
if (-not $photoView.Contains("'relabel'")) { throw 'Photo page does not return to the relabel list' }
|
||||
|
||||
Write-Output 'PASS: relabel photo page source contract'
|
||||
Reference in New Issue
Block a user