Files
docs/悦/柏悦物流客户端/tests/verify-return-relabel-consolidation.mjs
T

224 lines
10 KiB
JavaScript
Raw Normal View History

2026-08-12 19:11:56 +08:00
import assert from 'node:assert/strict';
import { existsSync, readFileSync } from 'node:fs';
import { test } from 'node:test';
const prototypePath = new URL('../../官网头程下单/workbench.html', import.meta.url);
const html = existsSync(prototypePath) ? readFileSync(prototypePath, 'utf8') : '';
function extractPageFragment(pageId) {
const idMatch = new RegExp(`id=["']${pageId}["']`).exec(html);
if (!idMatch) return null;
const start = html.lastIndexOf('<div', idMatch.index);
const remainingHtml = html.slice(idMatch.index + idMatch[0].length);
const nextPageMatch = /<div\s+class=["'][^"']*\bpage-content\b[^"']*["'][^>]*>/.exec(remainingHtml);
const end = nextPageMatch
? idMatch.index + idMatch[0].length + nextPageMatch.index
: html.length;
return html.slice(start, end);
}
function extractFirstTableHeaders(pageFragment, message) {
const tableMatch = pageFragment?.match(/<table[^>]*>([\s\S]*?)<\/table>/);
assert.ok(tableMatch, message);
return [...tableMatch[1].matchAll(/<th[^>]*>([\s\S]*?)<\/th>/g)].map((match) =>
match[1].replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim(),
);
}
function extractFunctionFragment(functionName) {
const start = html.indexOf(`function ${functionName}(`);
assert.ok(start >= 0, `${functionName} should exist`);
const next = html.indexOf('\n function ', start + 1);
return html.slice(start, next >= 0 ? next : html.length);
}
test('overseas menu is consolidated to the three approved entries', () => {
assert.equal(existsSync(prototypePath), true, 'workbench prototype should exist');
const warehouseMenu = html.match(/<!-- 海外仓 -->([\s\S]*?)<!-- 订单管理 -->/);
assert.ok(warehouseMenu, 'overseas warehouse menu should exist');
const labels = [...warehouseMenu[1].matchAll(/<a[^>]*class=["'][^"']*dash-nav-link\s+sub[^"']*["'][^>]*>[\s\S]*?<span[^>]*>([^<]+)<\/span>/g)].map((match) =>
match[1].trim(),
);
assert.deepEqual(labels, ['贴标申请', '仓储转运单', '货物废弃', '退货换标']);
assert.doesNotMatch(warehouseMenu[1], /退货入库|退货出库/);
});
test('return relabel list, create, and detail pages exist', () => {
for (const pageId of ['page-return-relabel-list', 'page-return-relabel-create', 'page-return-relabel-detail']) {
assert.ok(new RegExp(`id=["']${pageId}["']`).test(html), `${pageId} should exist`);
}
});
test('return relabel list table has the consolidated order columns', () => {
const listPage = extractPageFragment('page-return-relabel-list');
assert.ok(listPage, 'return relabel list page should exist');
assert.deepEqual(extractFirstTableHeaders(listPage, 'return relabel list table should exist'), [
'退货换标单号',
'来源FBA仓',
'目的FBA仓',
'SKU汇总',
'退件总数',
'是否换标',
'贴标要求',
'后台尾程订单号',
'后台换标单号',
'换标状态',
'尾程订单状态',
'创建时间',
'查看',
]);
for (const filterId of [
'return-relabel-filter-sku',
'return-relabel-filter-enabled',
'return-relabel-filter-tail-status',
]) {
assert.ok(listPage.includes(`id="${filterId}"`), `${filterId} should exist`);
}
});
test('return relabel create form exposes order, multi-SKU, relabel, file, and tail fields', () => {
const createPage = extractPageFragment('page-return-relabel-create');
assert.ok(createPage, 'return relabel create page should exist');
for (const field of [
'来源 FBA 仓库',
'目的 FBA 仓库',
'移除订单 Order ID',
'物流承运商',
'物流跟踪号',
'退货原因',
'上传箱唛',
'上传 SKU 标签',
'派送地址',
'目的仓代码',
'期望到达时间',
]) {
assert.ok(createPage.includes(field), `create page should contain field: ${field}`);
}
assert.deepEqual(extractFirstTableHeaders(createPage, 'return relabel SKU table should exist'), [
'商品SKU',
'中文品名',
'FNSKU',
'退件数量',
'操作',
]);
for (const caseLabel of ['Case1', 'Case2', 'Case3']) {
assert.ok(createPage.includes(caseLabel), `create page should contain ${caseLabel}`);
}
assert.match(createPage, /是否换标/);
for (const section of ['退货信息', '货物明细', '换标设置', '尾程信息']) {
assert.ok(createPage.includes(section), `create page should contain section: ${section}`);
}
for (const meaning of ['只换箱唛', '只换SKU标', '既换箱唛,又换SKU标']) {
assert.ok(createPage.includes(meaning), `create page should explain: ${meaning}`);
}
assert.match(createPage, /name=["']returnRemark["']/);
assert.match(createPage, /name=["']tailRemark["']/);
});
test('return relabel detail keeps backend relations and separate statuses', () => {
const detailPage = extractPageFragment('page-return-relabel-detail');
const detailFunction = extractFunctionFragment('openReturnRelabelDetail');
assert.ok(detailPage, 'return relabel detail page should exist');
for (const label of ['后台尾程订单号', '后台换标单号', '换标状态', '尾程订单状态']) {
assert.ok(detailPage.includes(label), `detail page should contain ${label}`);
}
for (const label of ['是否换标', '贴标要求', '上传箱唛', '上传 SKU 标签', '期望到达时间', '退件总数']) {
assert.ok(detailFunction.includes(label), `detail renderer should contain ${label}`);
}
});
test('return relabel persistence and interaction functions use the approved contract', () => {
assert.ok(
/const\s+RETURN_RELABEL_KEY\s*=\s*["']by_return_relabel_orders_0812["']/.test(html),
'RETURN_RELABEL_KEY should use by_return_relabel_orders_0812',
);
for (const functionName of [
'loadReturnRelabelOrders',
'saveReturnRelabelOrders',
'renderReturnRelabelList',
'filterReturnRelabelOrders',
'resetReturnRelabelFilters',
'addReturnRelabelSkuRow',
'removeReturnRelabelSkuRow',
'toggleReturnRelabel',
'updateFileRequirements',
'validateReturnRelabelForm',
'submitReturnRelabelOrder',
'openReturnRelabelDetail',
]) {
assert.match(html, new RegExp(`function\\s+${functionName}\\s*\\(`), `${functionName} should be defined`);
}
});
test('return relabel submit contract always creates tail order and conditionally creates relabel order', () => {
const submit = extractFunctionFragment('submitReturnRelabelOrder');
assert.match(submit, /submitButton\.disabled\s*=\s*true/);
assert.match(submit, /submitButton\.disabled\s*=\s*false/);
assert.match(submit, /backendTailOrderNo|tailOrderNo|tailOrderId/);
assert.match(submit, /if\s*\([^)]*(?:relabel|换标)[^)]*\)[\s\S]*(?:backendRelabelOrderNo|relabelOrderNo|relabelOrderId)/i);
assert.match(submit, /(?:relabel|换标)[\s\S]*(?:未换标|'-'|"-")/i);
});
test('return relabel Case rules and status labels are represented statically', () => {
const fileRequirements = extractFunctionFragment('updateFileRequirements');
assert.match(fileRequirements, /Case1/);
assert.match(fileRequirements, /Case2/);
assert.match(fileRequirements, /Case3/);
assert.match(fileRequirements, /箱唛/);
assert.match(fileRequirements, /SKU 标签|SKU标/);
for (const status of ['待换标', '换标中', '已完成', '待入库', '已入库待出库', '出库中']) {
assert.ok(html.includes(status), `status label should be present: ${status}`);
}
});
test('return relabel filters, form reset, seed merge, and visible UI follow the full specification', () => {
const listPage = extractPageFragment('page-return-relabel-list');
const createPage = extractPageFragment('page-return-relabel-create');
const detailPage = extractPageFragment('page-return-relabel-detail');
const loader = extractFunctionFragment('loadReturnRelabelOrders');
const opener = extractFunctionFragment('openReturnRelabelCreate');
const filter = extractFunctionFragment('filterReturnRelabelOrders');
const submit = extractFunctionFragment('submitReturnRelabelOrder');
const tailStatus = listPage.match(/id=["']return-relabel-filter-tail-status["'][\s\S]*?<\/select>/)?.[0] || '';
assert.match(tailStatus, />已完成</);
assert.match(filter, /returnRelabelDateValue|normalizeReturnRelabelDate|slice\(0,\s*10\)/);
assert.match(opener, /\.reset\(\)/, 'opening create page should reset the form');
assert.match(opener, /return-relabel-sku-body[\s\S]*(?:innerHTML\s*=\s*["']{2}|replaceChildren)/);
assert.match(loader, /RETURN_RELABEL_SEED[\s\S]*(?:concat|\.\.\.)/);
assert.doesNotMatch(loader, /saveReturnRelabelOrders\(RETURN_RELABEL_SEED\)/);
assert.doesNotMatch(createPage, /return-relabel-section-index/);
assert.doesNotMatch(createPage, /return-relabel-case-copy/);
assert.doesNotMatch(detailPage, /return-relabel-detail-contract/);
assert.match(createPage, /<th[^>]*>操作<\/th>/);
assert.match(submit, /datePart\s*=\s*now\.getFullYear\(\)/);
assert.match(submit, /createdAt:\s*datePart/);
assert.match(submit, /relabel[\s\S]*(?:换标单|backendRelabelOrderNo)/);
});
test('return relabel validation and persistence hardening cover important edge cases', () => {
const loader = extractFunctionFragment('loadReturnRelabelOrders');
const fileRequirements = extractFunctionFragment('updateFileRequirements');
const validator = extractFunctionFragment('validateReturnRelabelForm');
const submit = extractFunctionFragment('submitReturnRelabelOrder');
for (const field of ['orderId', 'carrier', 'trackingNo', 'address', 'destinationCode']) {
assert.ok(validator.includes(`'${field}'`), `${field} should be trimmed and validated`);
}
assert.match(validator, /form\.elements\[field\][\s\S]*\.trim\(\)/);
assert.match(fileRequirements, /Case1[\s\S]*(?:disabled|hidden)/);
assert.match(fileRequirements, /Case2[\s\S]*(?:disabled|hidden)/);
assert.match(loader, /Array\.isArray\(item\.skus\)/);
assert.match(loader, /item\.skus\.every\(s=>s&&typeof s==='object'/);
assert.match(loader, /validLocal\.concat\(RETURN_RELABEL_SEED\)/);
assert.match(submit, /getFullYear\(\)/);
assert.doesNotMatch(submit, /datePart\s*=\s*now\.toISOString/);
const renderer = extractFunctionFragment('renderReturnRelabelList');
assert.doesNotMatch(renderer, /onclick=["'][^"']*openReturnRelabelDetail/);
assert.match(renderer, /data-return-relabel-id/);
});