619 lines
22 KiB
TypeScript
619 lines
22 KiB
TypeScript
import { mobileStopIdentity } from '../src/views/m/pl/plan/mobileStopIdentity'
|
|
import assert from 'node:assert/strict'
|
|
import { existsSync, readFileSync } from 'node:fs'
|
|
import test from 'node:test'
|
|
import { computed, ref } from 'vue'
|
|
import * as vue from 'vue'
|
|
import { renderToString } from 'vue/server-renderer'
|
|
import { compileScript, parse } from '@vue/compiler-sfc'
|
|
import ts from 'typescript'
|
|
|
|
const parentPath = new URL(
|
|
'../src/components/business/pl-plan-form-dialog/index.vue',
|
|
import.meta.url
|
|
)
|
|
const sheetPath = new URL('../src/views/m/pl/plan/ContractLinePickerSheet.vue', import.meta.url)
|
|
const parent = readFileSync(parentPath, 'utf8')
|
|
const sheet = existsSync(sheetPath) ? readFileSync(sheetPath, 'utf8') : ''
|
|
function execute(source: string, names: string[], bindings: Record<string, any>) {
|
|
const ast = ts.createSourceFile(
|
|
'component.ts',
|
|
parse(source).descriptor.scriptSetup?.content || '',
|
|
ts.ScriptTarget.Latest,
|
|
true
|
|
)
|
|
const code = names
|
|
.map((name) => {
|
|
const node = ast.statements.find(
|
|
(node) =>
|
|
(ts.isFunctionDeclaration(node) && node.name?.text === name) ||
|
|
(ts.isVariableStatement(node) &&
|
|
node.declarationList.declarations.some((d) => d.name.getText(ast) === name))
|
|
)
|
|
assert.ok(node, `production declaration ${name} must exist`)
|
|
return node.getText(ast)
|
|
})
|
|
.join('\n')
|
|
return new Function(
|
|
...Object.keys(bindings),
|
|
ts.transpile(code, { target: ts.ScriptTarget.ESNext }) + `;return {${names.join(',')}}`
|
|
)(...Object.values(bindings))
|
|
}
|
|
const rows = [
|
|
{
|
|
id: 'line-a',
|
|
contractId: 'a',
|
|
contractNo: 'HT-A',
|
|
productId: 'same',
|
|
productName: '长名称液体',
|
|
standardProductName: '标准名称',
|
|
productNo: 'P001',
|
|
model: 'M-A',
|
|
brandName: '品牌甲',
|
|
aliasName: '别名甲',
|
|
quantity: 0,
|
|
remainingQty: 0,
|
|
taxRate: 0
|
|
},
|
|
{
|
|
id: 'line-b',
|
|
contractId: 'b',
|
|
contractNo: 'HT-B',
|
|
productId: 'same',
|
|
productName: '液体乙',
|
|
productNo: 'P002',
|
|
productModel: 'M-B'
|
|
}
|
|
]
|
|
function sheetHarness() {
|
|
const events: any[] = []
|
|
const props = { rows, busy: false, loading: false }
|
|
return {
|
|
props,
|
|
events,
|
|
...execute(
|
|
sheet,
|
|
[
|
|
'keyword',
|
|
'selectedRows',
|
|
'filteredRows',
|
|
'rowKey',
|
|
'isSelected',
|
|
'toggleRow',
|
|
'resetSelection',
|
|
'confirmSelection',
|
|
'displayNumber'
|
|
],
|
|
{ ref, computed, props, emit: (...args: any[]) => events.push(args) }
|
|
)
|
|
}
|
|
}
|
|
test('mobile picker replaces desktop dialog only for page variant', () => {
|
|
assert.match(parent, /<ContractLinePickerSheet\s+v-if="isPageVariant"/)
|
|
assert.match(parent, /<el-dialog\s+v-else\s+v-model="linePickerVisible"/)
|
|
assert.doesNotMatch(sheet, /el-table|el-dialog/)
|
|
})
|
|
test('search trims and ignores case across product, contract, model, brand and alias', () => {
|
|
const h = sheetHarness()
|
|
for (const query of [' 标准名称 ', '长名称', ' p001 ', 'm-a', 'ht-a', '品牌甲', '别名甲']) {
|
|
h.keyword.value = query
|
|
assert.deepEqual(h.filteredRows.value, [rows[0]], query)
|
|
}
|
|
h.keyword.value = 'm-b'
|
|
assert.deepEqual(h.filteredRows.value, [rows[1]])
|
|
})
|
|
test('selection survives search and keeps same product across distinct contract lines', () => {
|
|
const h = sheetHarness()
|
|
h.toggleRow(rows[0])
|
|
h.keyword.value = 'HT-B'
|
|
h.toggleRow(rows[1])
|
|
assert.equal(h.selectedRows.value.length, 2)
|
|
assert.notEqual(h.rowKey(rows[0]), h.rowKey(rows[1]))
|
|
h.confirmSelection()
|
|
assert.equal(h.events[0][0], 'confirm')
|
|
assert.deepEqual(h.events[0][1], rows)
|
|
h.resetSelection()
|
|
assert.equal(h.keyword.value, '')
|
|
assert.deepEqual(h.selectedRows.value, [])
|
|
h.confirmSelection()
|
|
assert.equal(h.events.length, 1)
|
|
})
|
|
test('zero quantities and tax are visible, missing numbers are placeholders', () => {
|
|
const h = sheetHarness()
|
|
assert.equal(h.displayNumber(0), '0')
|
|
for (const value of [undefined, null, '', NaN]) assert.equal(h.displayNumber(value), '—')
|
|
})
|
|
const detail = readFileSync(new URL('../src/views/m/pl/plan/detail.vue', import.meta.url), 'utf8')
|
|
function scrollHarness({ y = 600, height = 1200, locked = false, missingLast = false } = {}) {
|
|
const activeTab = ref('hazard')
|
|
const sections: Record<string, number> = { base: -600, hazard: -30, remark: 500 }
|
|
const h = execute(detail, ['syncActiveTabFromScroll'], {
|
|
activeTab,
|
|
scrollLockUntil: locked ? Date.now() + 10000 : 0,
|
|
plan: ref({}),
|
|
scrollMargin: ref(100),
|
|
detailTabs: Object.keys(sections).map((key) => ({ key, anchor: key })),
|
|
window: { innerHeight: 600, scrollY: y },
|
|
document: {
|
|
scrollingElement: { scrollHeight: height },
|
|
getElementById: (key: string) =>
|
|
missingLast && key === 'remark'
|
|
? null
|
|
: { getBoundingClientRect: () => ({ top: sections[key] - (y - 600) }) }
|
|
}
|
|
})
|
|
return { ...h, activeTab }
|
|
}
|
|
test('short final remark remains highlighted at document bottom', () => {
|
|
const h = scrollHarness()
|
|
h.syncActiveTabFromScroll()
|
|
assert.equal(h.activeTab.value, 'remark')
|
|
})
|
|
for (const [name, options, expected] of [
|
|
['Home restores base', { y: 0 }, 'base'],
|
|
['short unscrolled page avoids bottom override', { y: 0, height: 500 }, 'base'],
|
|
['click lock is respected', { locked: true }, 'hazard'],
|
|
['missing last section falls back to top probe', { missingLast: true }, 'hazard']
|
|
] as const)
|
|
test(name, () => {
|
|
const h = scrollHarness(options)
|
|
h.syncActiveTabFromScroll()
|
|
assert.equal(h.activeTab.value, expected)
|
|
})
|
|
function deferred() {
|
|
let resolve!: (value: any) => void
|
|
const promise = new Promise((yes) => {
|
|
resolve = yes
|
|
})
|
|
return { resolve, promise }
|
|
}
|
|
function parentHarness({
|
|
enrich = async (rows: any) => rows,
|
|
fetch = async () => {},
|
|
page = true
|
|
} = {}) {
|
|
const loadForm = ref({
|
|
customerName: '客户甲',
|
|
contracts: [{ contractId: 'a' }],
|
|
products: [] as any[]
|
|
})
|
|
const deliveryForms = ref([
|
|
{ customerName: '客户乙', contracts: [{ contractId: 'b' }], products: [] as any[] },
|
|
{ customerName: '客户丙', contracts: [{ contractId: 'c' }], products: [] as any[] }
|
|
])
|
|
const bindings = {
|
|
mobileStopIdentity,
|
|
initLineBrandMetaFromProduct: () => ({}),
|
|
packageSpecsForProduct: () => [],
|
|
normalizePackageSpec: (value: any) => String(value || '').trim(),
|
|
productPackageKey: (id: any, pkg: any) => `${id}::${pkg || ''}`,
|
|
unionContractProductRows: () => [],
|
|
loadContractProductRows: ref([]),
|
|
contractProductRowsCache: ref({}),
|
|
ref,
|
|
isPageVariant: ref(page),
|
|
loadForm,
|
|
deliveryForms,
|
|
productSelectorTarget: ref({ side: 'load', deliveryIndex: 0 }),
|
|
productSelectorVisible: ref(false),
|
|
isPlInternalCustomerName: () => false,
|
|
collectStopContractIds: (contracts: any[]) => contracts.map((row) => row.contractId),
|
|
fetchContractProductIds: fetch,
|
|
enrichContractLinesForPicker: async (rows: any) => rows,
|
|
enrichPlanProductsHazardFromCrm: enrich,
|
|
enrichPlanProductLinesAlias: async (rows: any) => rows,
|
|
createStopProductsFromPlanProducts: (rows: any) => rows.map((row: any) => ({ ...row })),
|
|
ElMessage: { warning() {}, info() {}, error() {} },
|
|
freightCalc: { resetFreightCalc() {} }
|
|
}
|
|
const h = execute(
|
|
parent,
|
|
[
|
|
'pageReady',
|
|
'pageDisposed',
|
|
'pageLoadGeneration',
|
|
'isPageLoadCurrent',
|
|
'isPageSubmitCurrent',
|
|
'invalidatePageLoad',
|
|
'linePickerVisible',
|
|
'linePickerRows',
|
|
'linePickerSelected',
|
|
'contractLinesCache',
|
|
'mobileLinePickerSession',
|
|
'mobileLinePickerGeneration',
|
|
'mobileLinePickerBusy',
|
|
'mobileLinePickerLoading',
|
|
'mobileLinePickerError',
|
|
'setMobileLinePickerVisible',
|
|
'handleMobileLinePickerConfirm',
|
|
'openProductSelector',
|
|
'handleLinePickerConfirm',
|
|
'handleProductConfirm'
|
|
],
|
|
bindings
|
|
)
|
|
h.pageReady.value = true
|
|
h.contractLinesCache.value = {
|
|
a: [rows[0]],
|
|
b: [rows[1]],
|
|
c: [{ ...rows[1], id: 'line-c', contractId: 'c' }]
|
|
}
|
|
return { ...bindings, ...h }
|
|
}
|
|
test('parent delivers exact selected contract line identities into requested delivery segment', async () => {
|
|
const h = parentHarness()
|
|
await h.openProductSelector('delivery', 1)
|
|
await h.handleMobileLinePickerConfirm([rows[0], rows[1]])
|
|
assert.equal(h.deliveryForms.value[0].products.length, 0)
|
|
assert.deepEqual(
|
|
h.deliveryForms.value[1].products.map((r: any) => [r.contractId, r.contractProductId]),
|
|
[
|
|
['a', 'line-a'],
|
|
['b', 'line-b']
|
|
]
|
|
)
|
|
assert.equal(h.loadForm.value.products.length, 0)
|
|
})
|
|
test('parent busy gate prevents a double confirm while enrichment is pending', async () => {
|
|
const d = deferred()
|
|
let calls = 0
|
|
const h = parentHarness({
|
|
enrich: async (rows: any) => {
|
|
calls++
|
|
await d.promise
|
|
return rows
|
|
}
|
|
})
|
|
await h.openProductSelector('load')
|
|
const first = h.handleMobileLinePickerConfirm([rows[0]])
|
|
assert.equal(h.mobileLinePickerBusy.value, true)
|
|
await h.handleMobileLinePickerConfirm([rows[0]])
|
|
assert.equal(calls, 1)
|
|
d.resolve(null)
|
|
await first
|
|
assert.equal(h.loadForm.value.products.length, 1)
|
|
})
|
|
for (const action of ['close', 'switch', 'unmount'] as const)
|
|
test(`old confirm cannot append after ${action}`, async () => {
|
|
const d = deferred()
|
|
const h = parentHarness({
|
|
enrich: async (rows: any) => {
|
|
await d.promise
|
|
return rows
|
|
}
|
|
})
|
|
await h.openProductSelector('load')
|
|
const pending = h.handleMobileLinePickerConfirm([rows[0]])
|
|
if (action === 'close') {
|
|
h.setMobileLinePickerVisible(false)
|
|
await h.openProductSelector('load')
|
|
}
|
|
if (action === 'switch') await h.openProductSelector('delivery', 1)
|
|
if (action === 'unmount') h.invalidatePageLoad()
|
|
d.resolve(null)
|
|
await pending
|
|
assert.equal(h.loadForm.value.products.length, 0)
|
|
assert.equal(h.deliveryForms.value[1].products.length, 0)
|
|
if (action !== 'unmount') assert.equal(h.linePickerVisible.value, true)
|
|
})
|
|
test('out-of-order opening does not apply old rows to a new segment', async () => {
|
|
const d = deferred()
|
|
const h = parentHarness({
|
|
fetch: async (id?: string) => {
|
|
if (id === 'a') await d.promise
|
|
}
|
|
})
|
|
const old = h.openProductSelector('load')
|
|
await h.openProductSelector('delivery', 1)
|
|
d.resolve(null)
|
|
await old
|
|
assert.equal(h.linePickerRows.value[0].contractId, 'c')
|
|
assert.equal(h.productSelectorTarget.value.deliveryIndex, 1)
|
|
})
|
|
test('desktop confirm remains usable without mobile page readiness or sheet state', async () => {
|
|
const h = parentHarness({ page: false })
|
|
h.pageReady.value = false
|
|
h.linePickerSelected.value = [rows[0]]
|
|
await h.handleLinePickerConfirm()
|
|
assert.equal(h.loadForm.value.products[0].contractProductId, 'line-a')
|
|
})
|
|
|
|
test('mobile confirmation deduplicates by contract plus line id, not line id alone', async () => {
|
|
const h = parentHarness()
|
|
await h.openProductSelector('load')
|
|
await h.handleMobileLinePickerConfirm([rows[0], { ...rows[1], id: rows[0].id }])
|
|
assert.equal(h.loadForm.value.products.length, 2)
|
|
})
|
|
test('failed mobile opening remains visible with retry and clears loading', async () => {
|
|
let fail = true
|
|
const h = parentHarness({
|
|
fetch: async () => {
|
|
if (fail) throw new Error('offline')
|
|
}
|
|
})
|
|
await h.openProductSelector('load')
|
|
assert.equal(h.linePickerVisible.value, true)
|
|
assert.equal(h.mobileLinePickerLoading.value, false)
|
|
assert.match(h.mobileLinePickerError.value, /重新加载/)
|
|
fail = false
|
|
await h.openProductSelector('load')
|
|
assert.equal(h.mobileLinePickerError.value, '')
|
|
assert.equal(h.linePickerRows.value.length, 1)
|
|
})
|
|
test('cancel pending opening cannot reopen or populate the closed sheet', async () => {
|
|
const d = deferred()
|
|
const h = parentHarness({
|
|
fetch: async () => {
|
|
await d.promise
|
|
}
|
|
})
|
|
const pending = h.openProductSelector('load')
|
|
h.setMobileLinePickerVisible(false)
|
|
d.resolve(null)
|
|
await pending
|
|
assert.equal(h.linePickerVisible.value, false)
|
|
assert.equal(h.linePickerRows.value.length, 0)
|
|
})
|
|
test('old confirm finally cannot clear a new session busy flag', async () => {
|
|
const old = deferred()
|
|
const fresh = deferred()
|
|
let count = 0
|
|
const h = parentHarness({
|
|
enrich: async (rows: any) => {
|
|
await (++count === 1 ? old.promise : fresh.promise)
|
|
return rows
|
|
}
|
|
})
|
|
await h.openProductSelector('load')
|
|
const first = h.handleMobileLinePickerConfirm([rows[0]])
|
|
h.setMobileLinePickerVisible(false)
|
|
await h.openProductSelector('delivery', 1)
|
|
const second = h.handleMobileLinePickerConfirm([rows[1]])
|
|
old.resolve(null)
|
|
await first
|
|
assert.equal(h.mobileLinePickerBusy.value, true)
|
|
fresh.resolve(null)
|
|
await second
|
|
assert.equal(h.mobileLinePickerBusy.value, false)
|
|
assert.equal(h.deliveryForms.value[1].products.length, 1)
|
|
})
|
|
|
|
test('sheet session records the page generation and rejects confirmation from an older page', async () => {
|
|
const h = parentHarness()
|
|
await h.openProductSelector('load')
|
|
h.mobileLinePickerGeneration.value -= 1
|
|
await h.handleMobileLinePickerConfirm([rows[0]])
|
|
assert.equal(h.loadForm.value.products.length, 0)
|
|
})
|
|
|
|
function compiledSheet() {
|
|
const script = compileScript(parse(sheet).descriptor, { id: 'picker-test', inlineTemplate: true })
|
|
const code = ts.transpile(script.content, {
|
|
target: ts.ScriptTarget.ESNext,
|
|
module: ts.ModuleKind.ESNext
|
|
})
|
|
const ast = ts.createSourceFile('sheet.js', code, ts.ScriptTarget.Latest, true)
|
|
const bindings: Record<string, any> = Object.fromEntries(
|
|
Object.entries(vue).filter(([name]) => /^[a-zA-Z_$][\w$]*$/.test(name) && name !== 'default')
|
|
)
|
|
const statements: string[] = []
|
|
for (const node of ast.statements) {
|
|
if (ts.isImportDeclaration(node)) {
|
|
const imports = node.importClause?.namedBindings
|
|
if (imports && ts.isNamedImports(imports))
|
|
for (const item of imports.elements) {
|
|
bindings[item.name.text] = (vue as any)[item.propertyName?.text || item.name.text]
|
|
}
|
|
} else statements.push(node.getText(ast))
|
|
}
|
|
return new Function(
|
|
...Object.keys(bindings),
|
|
statements.join('\n').replace('export default', 'return')
|
|
)(...Object.values(bindings))
|
|
}
|
|
test('real sheet renderer keeps all card fields, formatter results, zero values and no desktop table', async () => {
|
|
const app = vue.createSSRApp(compiledSheet(), {
|
|
modelValue: true,
|
|
rows,
|
|
busy: false,
|
|
loading: false,
|
|
error: '',
|
|
formatPackage: () => '字典包装',
|
|
formatBrand: () => '品牌规则结果',
|
|
formatRemaining: (n: any) => (n == null ? '—' : String(n)),
|
|
dictLabel: (_type: string, value: string) => '字典-' + value,
|
|
dictTagType: () => 'success'
|
|
})
|
|
app.component('van-popup', {
|
|
setup:
|
|
(_props: any, { slots }: any) =>
|
|
() =>
|
|
vue.h('section', slots.default?.())
|
|
})
|
|
app.component('el-tag', {
|
|
setup:
|
|
(_props: any, { slots }: any) =>
|
|
() =>
|
|
vue.h('span', slots.default?.())
|
|
})
|
|
const html = await renderToString(app)
|
|
for (const text of [
|
|
'标准名称',
|
|
'HT-A',
|
|
'HT-B',
|
|
'P001',
|
|
'M-A',
|
|
'字典包装',
|
|
'品牌规则结果',
|
|
'别名甲',
|
|
'0%',
|
|
'合同数量',
|
|
'剩余数量',
|
|
'计量单位',
|
|
'状态'
|
|
])
|
|
assert.ok(html.includes(text), text)
|
|
assert.match(html, /<small>合同数量<\/small><span>0<\/span>/)
|
|
assert.match(html, /<small>剩余数量<\/small><span>0<\/span>/)
|
|
assert.match(html, /确认添加/)
|
|
assert.match(html, /disabled/)
|
|
assert.doesNotMatch(html, /<table|el-dialog/)
|
|
})
|
|
test('production visibility watcher resets search and temporary selection on close and reopen', () => {
|
|
const h = sheetHarness()
|
|
const props = vue.reactive({ modelValue: true })
|
|
const ast = ts.createSourceFile(
|
|
'sheet.ts',
|
|
parse(sheet).descriptor.scriptSetup!.content,
|
|
ts.ScriptTarget.Latest,
|
|
true
|
|
)
|
|
const watcher = ast.statements.find(
|
|
(node) => ts.isExpressionStatement(node) && node.getText(ast).startsWith('watch(')
|
|
)
|
|
assert.ok(watcher)
|
|
const scope = vue.effectScope()
|
|
scope.run(() =>
|
|
new Function('watch', 'props', 'resetSelection', ts.transpile(watcher.getText(ast)))(
|
|
vue.watch,
|
|
props,
|
|
h.resetSelection
|
|
)
|
|
)
|
|
h.keyword.value = 'HT-A'
|
|
h.toggleRow(rows[0])
|
|
props.modelValue = false
|
|
assert.equal(h.keyword.value, '')
|
|
assert.equal(h.selectedRows.value.length, 0)
|
|
props.modelValue = true
|
|
assert.equal(h.selectedRows.value.length, 0)
|
|
assert.equal(h.events.length, 0)
|
|
scope.stop()
|
|
})
|
|
|
|
function realFetchHarness(
|
|
read: (id: string) => Promise<any> = async () => {
|
|
throw new Error('offline')
|
|
}
|
|
) {
|
|
const h = parentHarness({ fetch: (...args: any[]) => fetchReal(...args) })
|
|
const pageLoadError = ref('')
|
|
const prod = execute(
|
|
parent,
|
|
['fetchContractProductIds', 'assertPageLoadCurrent', 'failPageLoad', 'pageLoadErrorMessage'],
|
|
{
|
|
...h,
|
|
pageLoadGeneration: 0,
|
|
pageLoadError,
|
|
footerMoreOpen: ref(false),
|
|
props: {},
|
|
contractProductIdsCache: ref({}),
|
|
contractProductRowsCache: ref({}),
|
|
getContractDetailCached: read,
|
|
invalidateContractDetailCache() {},
|
|
collectContractProductRows: (rows: any) => rows,
|
|
collectContractProductIds: (rows: any) => rows.map((row: any) => row.productId)
|
|
}
|
|
)
|
|
const fetchReal = prod.fetchContractProductIds
|
|
return { ...h, pageLoadError }
|
|
}
|
|
test('real contract failure blocks the page, releases loading and closes the sheet for page retry', async () => {
|
|
const h = realFetchHarness()
|
|
await h.openProductSelector('load')
|
|
assert.equal(h.pageReady.value, false)
|
|
assert.ok(h.pageLoadError.value)
|
|
assert.equal(h.mobileLinePickerLoading.value, false)
|
|
assert.match(h.mobileLinePickerError.value, /重新加载/)
|
|
assert.equal(h.linePickerVisible.value, false)
|
|
})
|
|
for (const side of ['load', 'delivery'] as const)
|
|
test(`${side} stop replacement by the contract watcher releases loading and closes only its current sheet`, async () => {
|
|
const d = deferred()
|
|
const h = realFetchHarness(async (id) => {
|
|
await d.promise
|
|
return { id, products: rows }
|
|
})
|
|
const pending = h.openProductSelector(side)
|
|
if (side === 'load') h.loadForm.value = { ...h.loadForm.value, products: [] }
|
|
else h.deliveryForms.value = h.deliveryForms.value.map((stop) => ({ ...stop, products: [] }))
|
|
d.resolve(null)
|
|
await pending
|
|
assert.equal(h.mobileLinePickerLoading.value, false)
|
|
assert.equal(h.linePickerVisible.value, false)
|
|
assert.equal(h.pageReady.value, true, 'obsolete selection must not invalidate the ready page')
|
|
})
|
|
for (const failure of [true, false])
|
|
test(`old real fetch ${failure ? 'failure' : 'success'} cannot fail the page or close/reset a newer loading sheet`, async () => {
|
|
const old = deferred(),
|
|
fresh = deferred()
|
|
const h = realFetchHarness(async (id) => {
|
|
await (id === 'a' ? old.promise : fresh.promise)
|
|
if (id === 'a' && failure) throw new Error('old request failed')
|
|
return { id, products: id === 'a' ? [rows[0]] : [rows[1]] }
|
|
})
|
|
const previous = h.openProductSelector('load')
|
|
const current = h.openProductSelector('delivery')
|
|
old.resolve(null)
|
|
await previous
|
|
assert.equal(h.pageReady.value, true)
|
|
assert.equal(h.linePickerVisible.value, true)
|
|
assert.equal(h.mobileLinePickerLoading.value, true)
|
|
assert.equal(h.mobileLinePickerError.value, '')
|
|
fresh.resolve(null)
|
|
await current
|
|
assert.equal(h.mobileLinePickerLoading.value, false)
|
|
assert.equal(h.linePickerVisible.value, true)
|
|
assert.equal(h.linePickerRows.value[0].contractId, 'b')
|
|
})
|
|
|
|
test('mobile opened line picker follows segment identity through reorder before confirm',async()=>{
|
|
const h=parentHarness()
|
|
await h.openProductSelector('delivery',0)
|
|
const identity=mobileStopIdentity(h.deliveryForms.value[0])
|
|
h.deliveryForms.value=[{...h.deliveryForms.value[1]},{...h.deliveryForms.value[0]}]
|
|
await h.handleMobileLinePickerConfirm([rows[0]])
|
|
assert.equal(h.deliveryForms.value[0].products.length,0)
|
|
assert.equal(mobileStopIdentity(h.deliveryForms.value[1]),identity)
|
|
assert.equal(h.deliveryForms.value[1].products.length,1)
|
|
})
|
|
test('mobile opened line picker discards confirmation after target segment deletion',async()=>{
|
|
const h=parentHarness()
|
|
await h.openProductSelector('delivery',0)
|
|
h.deliveryForms.value=[h.deliveryForms.value[1]]
|
|
await h.handleMobileLinePickerConfirm([rows[0]])
|
|
assert.equal(h.deliveryForms.value[0].products.length,0)
|
|
})
|
|
|
|
test('mobile pending line enrichment follows original segment after reorder',async()=>{
|
|
const pending=deferred()
|
|
const h=parentHarness({enrich:async(input:any)=>{await pending.promise;return input}})
|
|
await h.openProductSelector('delivery',0)
|
|
const task=h.handleMobileLinePickerConfirm([rows[0]])
|
|
h.deliveryForms.value=[{...h.deliveryForms.value[1]},{...h.deliveryForms.value[0]}]
|
|
pending.resolve();await task
|
|
assert.equal(h.deliveryForms.value[0].products.length,0)
|
|
assert.equal(h.deliveryForms.value[1].products.length,1)
|
|
})
|
|
test('mobile pending internal catalog enrichment follows original segment after reorder',async()=>{
|
|
const pending=deferred()
|
|
const h=parentHarness({enrich:async(input:any)=>{await pending.promise;return input}})
|
|
await h.openProductSelector('delivery',0)
|
|
const task=h.handleProductConfirm([{id:'catalog-product',productName:'目录产品'}])
|
|
h.deliveryForms.value=[{...h.deliveryForms.value[1]},{...h.deliveryForms.value[0]}]
|
|
pending.resolve();await task
|
|
assert.equal(h.deliveryForms.value[0].products.length,0)
|
|
assert.equal(h.deliveryForms.value[1].products[0].productId,'catalog-product')
|
|
})
|
|
test('mobile pending contract list opening follows original segment after reorder',async()=>{
|
|
const pending=deferred()
|
|
const h=parentHarness({fetch:async()=>{await pending.promise;return []}})
|
|
const task=h.openProductSelector('delivery',0)
|
|
h.deliveryForms.value=[{...h.deliveryForms.value[1]},{...h.deliveryForms.value[0]}]
|
|
pending.resolve();await task
|
|
assert.equal(h.linePickerVisible.value,true)
|
|
await h.handleMobileLinePickerConfirm([rows[0]])
|
|
assert.equal(h.deliveryForms.value[0].products.length,0)
|
|
assert.equal(h.deliveryForms.value[1].products.length,1)
|
|
})
|