139 lines
5.8 KiB
JavaScript
139 lines
5.8 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const vm = require('node:vm');
|
|
|
|
const html = fs.readFileSync('workbench.html', 'utf8');
|
|
|
|
function extractFunction(name) {
|
|
const start = html.indexOf(`function ${name}(`);
|
|
assert.notEqual(start, -1, `${name} should exist`);
|
|
const bodyStart = html.indexOf('{', start);
|
|
let depth = 0;
|
|
for (let index = bodyStart; index < html.length; index += 1) {
|
|
if (html[index] === '{') depth += 1;
|
|
if (html[index] === '}') depth -= 1;
|
|
if (depth === 0) return html.slice(start, index + 1);
|
|
}
|
|
throw new Error(`Unable to extract ${name}`);
|
|
}
|
|
|
|
// Regression caught: logistics fields drift back to order-level controls or
|
|
// appear after the SKU instead of being the first two cargo columns.
|
|
const createPage = html.match(/<div class="page-content" id="page-return-relabel-create">([\s\S]*?)<div class="page-content" id="page-return-relabel-detail">/)?.[1] || '';
|
|
const returnInfo = createPage.match(/<div class="return-relabel-section-title">退货信息<\/div>([\s\S]*?)<div class="card return-relabel-section">/)?.[1] || '';
|
|
const cargoHeader = createPage.match(/id="return-relabel-sku-table">\s*<thead><tr>([\s\S]*?)<\/tr><\/thead>/)?.[1] || '';
|
|
assert.ok(returnInfo, '退货信息区域 should exist');
|
|
assert.doesNotMatch(returnInfo, /name="(?:carrier|trackingNo)"/, '物流字段不应再作为整单输入');
|
|
assert.match(
|
|
cargoHeader,
|
|
/^<th>承运商[\s\S]*?<th>运单号[\s\S]*?<th>商品SKU/,
|
|
'承运商、运单号应为商品 SKU 前两列'
|
|
);
|
|
|
|
// Carrier/tracking controls remain available on each SKU row but are optional.
|
|
const appendedRows = [];
|
|
const rowContext = {
|
|
document: {
|
|
getElementById: id => id === 'return-relabel-sku-body' ? { appendChild: row => appendedRows.push(row) } : null,
|
|
createElement: () => ({ innerHTML: '' })
|
|
},
|
|
escapeReturnRelabelHtml: value => String(value == null ? '' : value)
|
|
};
|
|
const addReturnRelabelSkuRow = vm.runInNewContext(
|
|
`(${extractFunction('addReturnRelabelSkuRow')})`,
|
|
rowContext
|
|
);
|
|
addReturnRelabelSkuRow({ carrier: 'UPS', trackingNo: '1Z-001', sku: 'SKU-1' });
|
|
assert.match(
|
|
appendedRows[0].innerHTML,
|
|
/^<td><input[^>]*return-relabel-carrier[^>]*value="UPS"[^>]*><\/td><td><input[^>]*return-relabel-tracking-no[^>]*value="1Z-001"[^>]*><\/td><td><input[^>]*return-relabel-sku/,
|
|
'每条 SKU 行都应先生成可选的承运商和运单号输入框'
|
|
);
|
|
assert.doesNotMatch(appendedRows[0].innerHTML, /return-relabel-carrier[^>]*required/, '承运商不应设置 required');
|
|
assert.doesNotMatch(appendedRows[0].innerHTML, /return-relabel-tracking-no[^>]*required/, '运单号不应设置 required');
|
|
|
|
function control(value, extra = {}) {
|
|
return { value, ...extra };
|
|
}
|
|
|
|
function cargoRow(values) {
|
|
const fields = {
|
|
'.return-relabel-carrier': control(values.carrier),
|
|
'.return-relabel-tracking-no': control(values.trackingNo),
|
|
'.return-relabel-sku': control(values.sku),
|
|
'.return-relabel-name': control(values.name),
|
|
'.return-relabel-fnsku': control(values.fnsku),
|
|
'.return-relabel-new-sku': control(values.newSku || ''),
|
|
'.return-relabel-quantity': control(String(values.quantity))
|
|
};
|
|
return { querySelector: selector => fields[selector] || null };
|
|
}
|
|
|
|
const rows = [
|
|
cargoRow({ carrier: 'UPS', trackingNo: '1Z-001', sku: 'SKU-1', name: '商品一', fnsku: 'FN-1', quantity: 2 }),
|
|
cargoRow({ carrier: 'FedEx', trackingNo: 'FX-002', sku: 'SKU-2', name: '商品二', fnsku: 'FN-2', quantity: 3 })
|
|
];
|
|
const error = { textContent: '' };
|
|
const submitButton = { disabled: false };
|
|
const form = {
|
|
checkValidity: () => true,
|
|
reportValidity() {},
|
|
elements: {
|
|
inboundCode: control('IN-001'),
|
|
sourceFba: control('ONT8'),
|
|
reason: control('客户退货'),
|
|
receivingWarehouse: control('洛杉矶仓'),
|
|
destinationFba: control('LAX9'),
|
|
relabel: control('', { checked: false }),
|
|
labelCase: control('Case1'),
|
|
cartonLabel: control('', { files: [] }),
|
|
skuLabel: control('', { files: [] }),
|
|
address: control('123 Main St'),
|
|
expectedArrival: control('2026-09-10T10:00'),
|
|
returnRemark: control(''),
|
|
tailRemark: control('')
|
|
}
|
|
};
|
|
const saved = [];
|
|
const runtime = {
|
|
document: {
|
|
getElementById: id => ({
|
|
'return-relabel-form': form,
|
|
'return-relabel-error': error,
|
|
'return-relabel-submit': submitButton
|
|
}[id] || null),
|
|
querySelectorAll: selector => selector === '#return-relabel-sku-body tr' ? rows : []
|
|
},
|
|
loadReturnRelabelOrders: () => [],
|
|
saveReturnRelabelOrders: orders => { saved.push(orders); return true; },
|
|
renderReturnRelabelList() {},
|
|
showToast() {},
|
|
openReturnRelabelList() {},
|
|
returnRelabelFilteredOrders: null,
|
|
returnRelabelSubmitting: false
|
|
};
|
|
vm.runInNewContext(
|
|
`${extractFunction('validateReturnRelabelForm')}
|
|
${extractFunction('summarizeReturnRelabelSkuField')}
|
|
${extractFunction('submitReturnRelabelOrder')}`,
|
|
runtime
|
|
);
|
|
|
|
// Optional logistics fields must not block a later SKU from submitting.
|
|
rows[1].querySelector('.return-relabel-tracking-no').value = '';
|
|
assert.equal(runtime.validateReturnRelabelForm(), true);
|
|
assert.equal(error.textContent, '');
|
|
rows[1].querySelector('.return-relabel-tracking-no').value = 'FX-002';
|
|
|
|
// Regression caught: only the first row's logistics data is persisted.
|
|
assert.equal(runtime.submitReturnRelabelOrder({ preventDefault() {} }), false);
|
|
const order = saved[0][0];
|
|
assert.deepEqual(JSON.parse(JSON.stringify(order.skus)), [
|
|
{ carrier: 'UPS', trackingNo: '1Z-001', sku: 'SKU-1', name: '商品一', fnsku: 'FN-1', newSku: '', quantity: 2 },
|
|
{ carrier: 'FedEx', trackingNo: 'FX-002', sku: 'SKU-2', name: '商品二', fnsku: 'FN-2', newSku: '', quantity: 3 }
|
|
]);
|
|
assert.equal(order.carrier, 'UPS、FedEx', '整单兼容字段应汇总去重后的承运商');
|
|
assert.equal(order.trackingNo, '1Z-001、FX-002', '整单兼容字段应汇总去重后的运单号');
|
|
|
|
console.log('return relabel SKU logistics: ok');
|