316 lines
22 KiB
JavaScript
316 lines
22 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFile } from 'node:fs/promises'
|
|
|
|
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8')
|
|
|
|
test('page configuration starts at login and registers independent PDA routes', async () => {
|
|
const pages = JSON.parse(await read('../pages.json'))
|
|
assert.equal(pages.pages[0].path, 'pages/login/login')
|
|
assert.equal(pages.pages[0].style.navigationStyle, 'custom')
|
|
assert.deepEqual(pages.pages.map((page) => page.path), [
|
|
'pages/login/login', 'pages/home/home', 'pages/module/module', 'pages/operation/operation',
|
|
'pages/tasks/tasks', 'pages/history/history', 'pages/profile/profile', 'pages/scanner-test/scanner-test', 'pages/picking/picking',
|
|
'pages/dropship-palletizing/tasks', 'pages/dropship-palletizing/dropship-palletizing'
|
|
])
|
|
})
|
|
|
|
test('application uses the Vue 3 uni-app entry and no unused Android permissions', async () => {
|
|
const [manifest, main] = await Promise.all([read('../manifest.json'), read('../main.js')])
|
|
assert.match(manifest, /"vueVersion"\s*:\s*"3"/)
|
|
assert.match(manifest, /"uniStatistics"\s*:\s*\{\s*"enable"\s*:\s*false\s*\}/)
|
|
assert.match(main, /createSSRApp/)
|
|
assert.doesNotMatch(main, /new\s+Vue/)
|
|
assert.doesNotMatch(manifest, /android\.permission\.(VIBRATE|ACCESS_NETWORK_STATE)/)
|
|
})
|
|
|
|
test('login uses the real auth endpoint and relaunches to the modular workbench', async () => {
|
|
const [login, auth, session, runtime, vite, manifest] = await Promise.all([
|
|
read('../pages/login/login.vue'), read('../api/auth.js'), read('../stores/session.js'), read('../config/runtime.js'), read('../vite.config.js'), read('../manifest.json')
|
|
])
|
|
for (const text of ['柏悦物流 · PDA作业端', '工号 / 用户名', '登录中']) assert.match(login, new RegExp(text))
|
|
assert.doesNotMatch(login, /DEMO_ACCOUNTS|快捷登录|作业仓库/)
|
|
assert.match(auth, /\/auth\/login/)
|
|
assert.match(auth, /authRequest\('\/auth\/login', \{ loginId, password \}\)/)
|
|
assert.doesNotMatch(auth, /authRequest\('\/auth\/login', \{ username, password \}\)/)
|
|
assert.match(login, /loginRequest\(userId, this\.form\.password\)/)
|
|
assert.match(login, /signInWithTokens\(tokens\)/)
|
|
assert.match(login, /form: \{ userId: '', password: '' \}/)
|
|
assert.doesNotMatch(login, /platform_admin|ChangeMe123!/)
|
|
assert.match(login, /记住账号/)
|
|
assert.match(login, /loadLoginPreference\(\)/)
|
|
assert.match(login, /saveLoginPreference\(this\.rememberAccount, userId\)/)
|
|
assert.match(session, /pda-auth-session/)
|
|
assert.match(session, /pda-login-account-preference/)
|
|
assert.match(session, /claims\.app_code !== 'PLATFORM'/)
|
|
assert.match(runtime, /import\.meta\.env\.VITE_API_BASE_URL/)
|
|
assert.match(runtime, /#ifdef H5[\s\S]*?VITE_H5_API_BASE_URL \|\| '\/gateway-api'/)
|
|
assert.doesNotMatch(runtime, /typeof\s+import\.meta/)
|
|
assert.match(vite, /API_PROXY_TARGET/)
|
|
assert.match(vite, /process\.env\.UNI_INPUT_DIR \|\| process\.cwd\(\)/)
|
|
assert.match(vite, /'\/gateway-api'[\s\S]*?target: proxyTarget[\s\S]*?changeOrigin: true[\s\S]*?replace\(\/\^\\\/gateway-api\/[\s\S]*?'\/api'/)
|
|
assert.match(vite, /removeHeader\('origin'\)/)
|
|
assert.match(vite, /removeHeader\('referer'\)/)
|
|
assert.match(vite, /proxy\.on\('error'/)
|
|
assert.doesNotMatch(manifest, /"proxy"\s*:|pathRewrite|disableHostCheck/)
|
|
assert.match(login, /uni\.reLaunch\(\{ url: '\/pages\/home\/home' \}\)/)
|
|
})
|
|
|
|
test('picking task list uses remote loading orders and never falls back to demo tasks', async () => {
|
|
const [tasks, home, modulePage] = await Promise.all([read('../pages/tasks/tasks.vue'), read('../pages/home/home.vue'), read('../pages/module/module.vue')])
|
|
assert.match(tasks, /listLoadingOrders/)
|
|
assert.match(tasks, /当前网络不可用,请联网后刷新/)
|
|
assert.match(tasks, /进入并开始拣货/)
|
|
assert.match(tasks, /AWAITING_REVIEW[\s\S]*?待上传证据\/确认装柜/)
|
|
assert.match(tasks, /task\.workflowStatus === 'AWAITING_REVIEW' \? '继续装柜取证'/)
|
|
assert.match(tasks, /\['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW', 'COMPLETION_FAILED'\]\.includes\(task\.workflowStatus\)/)
|
|
assert.match(tasks, /\['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW', 'PROCESSING', 'COMPLETION_FAILED'\]\.includes\(task\.workflowStatus\)/)
|
|
assert.match(tasks, /\['AWAITING_REVIEW', 'COMPLETION_FAILED'\]\.includes\(task\.workflowStatus\) \|\| \(task\.assignmentVersion !== null/)
|
|
assert.match(tasks, /暂无待处理的拣货任务/)
|
|
assert.match(tasks, /class="task-code">\{\{ containerReference\(task\) \}\}/)
|
|
assert.doesNotMatch(tasks, /task-identifiers|businessReference\(task\)/)
|
|
assert.match(tasks, /button v-for="task in tasks"[\s\S]*?class="task-card"[\s\S]*?@tap="enterTask\(task\)"/)
|
|
assert.match(tasks, /records\.filter\(\(task\) => this\.isTaskVisible\(task\)\)/)
|
|
assert.match(tasks, /isTaskReady\(task\)[\s\S]*?task\.recordStatus === 'ACTIVE'[\s\S]*?!task\.mergedIntoContainerBookingId/)
|
|
assert.match(tasks, /records\.filter\(\(task\) => this\.isMergedTask\(task\)\)\.forEach\(\(task\) => this\.clearMergedTaskDraft\(task\)\)/)
|
|
assert.match(tasks, /clearMergedTaskDraft\(task\)[\s\S]*?clearPickingDraft\(\{ operatorId, taskId: task\.taskId, loadingPlanId: task\.loadingPlanId, containerBookingId: task\.containerBookingId/)
|
|
assert.match(tasks, /task\.taskId[\s\S]*?task\.loadingPlanId[\s\S]*?task\.containerBookingId[\s\S]*?task\.currentContainerNo[\s\S]*?task\.version[\s\S]*?task\.assignmentVersion/)
|
|
assert.doesNotMatch(tasks, /task\.taskStatus === 'LOADING'/)
|
|
assert.doesNotMatch(tasks, /demo-data|DEMO_/)
|
|
assert.match(home, /operation\.route \|\| '\/pages\/operation\/operation'/)
|
|
assert.match(modulePage, /operation\.route \|\| '\/pages\/operation\/operation'/)
|
|
})
|
|
|
|
test('workbench, module, history and profile are independent sources', async () => {
|
|
const [home, module, history, profile] = await Promise.all([
|
|
read('../pages/home/home.vue'), read('../pages/module/module.vue'), read('../pages/history/history.vue'), read('../pages/profile/profile.vue')
|
|
])
|
|
for (const text of ['首页', '库内作业', 'home-operation-grid', 'home-operation-primary', '开始作业', 'uni-icons']) assert.match(home, new RegExp(text))
|
|
assert.doesNotMatch(home, /getPickingOverview|pendingBoxCount|箱待拣/)
|
|
assert.doesNotMatch(home, /BottomNav|status-strip|task-card/)
|
|
for (const text of ['业务模块', '业务功能', '一件代发清点组托', '集装箱拣货']) assert.match(module, new RegExp(text))
|
|
assert.match(home, /:disabled="!operation\.available"/)
|
|
assert.match(home, /home-operation-disabled/)
|
|
assert.match(home, /home-operation-meta home-operation-pending/)
|
|
assert.match(home, /home-operation-meta home-operation-unavailable/)
|
|
assert.match(home, /openOperation\(operation\) \{ if \(operation\.available\) uni\.navigateTo/)
|
|
assert.doesNotMatch(home, /pages\/operation\/operation\?module=/)
|
|
assert.match(module, /:disabled="!operation\.available"/)
|
|
assert.match(module, /operation-item-disabled/)
|
|
assert.doesNotMatch(module, /pages\/operation\/operation\?module=/)
|
|
assert.match(history, /作业记录/)
|
|
assert.match(profile, /个人中心/)
|
|
assert.match(profile, /signOut\(\)/)
|
|
assert.match(profile, /class="logout-dialog-card"/)
|
|
assert.doesNotMatch([home, module, history, profile].join('\n'), /BottomNav/)
|
|
})
|
|
|
|
test('demo model contains all four modules and picking belongs to outbound', async () => {
|
|
const data = await read('../data/demo-data.js')
|
|
for (const text of ['入库', '出库', '库存', '查询', '集装箱拣货', '一件代发清点组托']) assert.match(data, new RegExp(text))
|
|
assert.match(data, /key: 'outbound'[\s\S]*?name: '集装箱拣货'[\s\S]*?available: true/)
|
|
for (const boxNo of ['CBX-20260709-001', 'CBX-20260709-002', 'CBX-20260709-003']) assert.match(data, new RegExp(boxNo))
|
|
})
|
|
|
|
test('first picking box provides the M001 through M010 scanner demo', async () => {
|
|
const { DEMO_PICKING_ITEMS, createBoxes } = await import('../data/demo-data.js')
|
|
assert.equal(DEMO_PICKING_ITEMS.length, 10)
|
|
assert.deepEqual(DEMO_PICKING_ITEMS.map((item) => item.sku),
|
|
Array.from({ length: 10 }, (_, index) => `M${String(index + 1).padStart(3, '0')}`))
|
|
assert.equal(new Set(DEMO_PICKING_ITEMS.map((item) => item.id)).size, 10)
|
|
assert.ok(DEMO_PICKING_ITEMS.every((item) => item.qty === 1 && item.unit === '件'))
|
|
assert.deepEqual(createBoxes()[0].items.map((item) => item.sku), DEMO_PICKING_ITEMS.map((item) => item.sku))
|
|
})
|
|
|
|
test('non-picking operations remain complete static task pages', async () => {
|
|
const [data, page] = await Promise.all([read('../data/demo-data.js'), read('../pages/operation/operation.vue')])
|
|
for (const text of ['到货收货', '商品上架', '入库质检', '出库复核', '装箱交接', '库存盘点', '商品移库', '库存调整', '商品查询', '库位查询', '任务查询']) assert.match(data, new RegExp(text))
|
|
for (const text of ['静态原型', '支持硬件扫描头连续扫描', '任务列表', '待处理', '进行中', '已完成']) assert.match(page, new RegExp(text))
|
|
})
|
|
|
|
test('picking page keeps scan, draft and risky transition contracts', async () => {
|
|
const [page, scanner] = await Promise.all([read('../pages/picking/picking.vue'), read('../utils/scan-broadcast.js')])
|
|
for (const text of ['确认拣出', '部分拣货', '跳拣', '提交拣货完成', '去拍照', '最近扫码']) assert.match(page, new RegExp(text))
|
|
assert.match(page, /saveCurrentBoxDraft\(\)[\s\S]*?this\.boxDrafts =/)
|
|
assert.match(page, /loadBoxState\(index\)[\s\S]*?const draft = this\.boxDrafts\[index\]/)
|
|
assert.match(page, /requestBoxSwitch\(index\)[\s\S]*?this\.openOverlay\('switchBoxConfirm'\)/)
|
|
assert.match(page, /submitPicking\(\)[\s\S]*?this\.openOverlay\('submitConfirm'\)/)
|
|
assert.match(page, /submitStatusHint\(\)[\s\S]*?提交时需选择原因/)
|
|
assert.match(page, /onBackPress\(\)/)
|
|
assert.doesNotMatch(page, /scanFocused|restoreScanFocus|hideKeyboard|@confirm="handleScan"/)
|
|
assert.match(page, /<ScanTargetInput v-model="scanValue" name="boxCode"[\s\S]*?:active="activeScanField === 'boxCode'"[\s\S]*?@activate="setScanTarget"[\s\S]*?@submit="handleManualSubmit"/)
|
|
assert.match(page, /beforeUnmount\(\)/)
|
|
assert.match(page, /startScanBroadcast/)
|
|
assert.match(page, /onHide\(\) \{[\s\S]*?this\.stopHardwareScanner\(\)[\s\S]*?\}/)
|
|
assert.match(page, /handleHardwareScan\(value\)[\s\S]*?this\.scanValue = value[\s\S]*?this\.processScannedValue\(value, 'broadcast'\)/)
|
|
assert.match(page, /handleManualSubmit\(value\)[\s\S]*?\.trim\(\)\.toUpperCase\(\)[\s\S]*?this\.processScannedValue\(normalizedValue, 'manual'\)/)
|
|
assert.match(page, /recordScanResult\(type, title, boxCode, detail\)/)
|
|
assert.match(page, /lastScanResult\.boxCode/)
|
|
assert.match(page, /boxCode: entry\.boxCode \?\? entry\.sku \?\? ''/)
|
|
assert.doesNotMatch(page, /lastScanResult\.sku|entry\.sku\s*}}/)
|
|
const processScanStart = page.indexOf("processScannedValue(scanValue, source = 'broadcast') {")
|
|
const processScanEnd = page.indexOf('\n\t\t\tevidenceEntries', processScanStart)
|
|
assert.ok(processScanStart >= 0 && processScanEnd > processScanStart)
|
|
assert.doesNotMatch(page.slice(processScanStart, processScanEnd), /this\.scanValue = ''/)
|
|
assert.match(scanner, /android\.intent\.action\.SCANRESULT/)
|
|
assert.match(scanner, /SCAN_BROADCAST_EXTRA = 'value'/)
|
|
assert.match(scanner, /registerReceiver\(receiver, filter\)/)
|
|
assert.match(scanner, /Context\.RECEIVER_EXPORTED/)
|
|
assert.match(scanner, /unregisterReceiver\(receiver\)/)
|
|
assert.match(page, /<view v-if="currentUser" class="page-shell">/)
|
|
assert.match(page, /leavePicking\(\)[\s\S]*?navigateBackOr\('\/pages\/home\/home'\)/)
|
|
})
|
|
|
|
test('picking cabinet selector synchronizes all ready server tasks by stable task id', async () => {
|
|
const page = await read('../pages/picking/picking.vue')
|
|
assert.match(page, /listLoadingOrders/)
|
|
assert.match(page, /async onShow\(\)[\s\S]*?await this\.refreshAvailableBoxes\(\)[\s\S]*?await this\.syncRemoteTask\(\)/)
|
|
assert.match(page, /async refreshAvailableBoxes\(\)[\s\S]*?listLoadingOrders\(\{ page: 1, size: 50 \}\)/)
|
|
assert.match(page, /isSelectableTask\(task\)[\s\S]*?recordStatus === 'ACTIVE'[\s\S]*?\['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW'\]/)
|
|
assert.match(page, /existingByTaskId[\s\S]*?recordByTaskId[\s\S]*?orderedTaskIds[\s\S]*?this\.boxes = nextBoxes/)
|
|
assert.match(page, /selectedTaskId[\s\S]*?findIndex\(\(box\) => String\(box\.taskId \|\| ''\) === selectedTaskId\)/)
|
|
assert.match(page, /selectBox\(index\)[\s\S]*?this\.remoteTaskId = this\.currentBox\.taskId \|\| null[\s\S]*?this\.resetEvidenceState\(\)[\s\S]*?this\.syncRemoteTask\(\)/)
|
|
})
|
|
|
|
test('navigation returns to the real source and prevents duplicate utility pages', async () => {
|
|
const [navigation, header, modulePage, operation, picking] = await Promise.all([
|
|
read('../utils/navigation.js'), read('../components/PortalHeader.vue'), read('../pages/module/module.vue'),
|
|
read('../pages/operation/operation.vue'), read('../pages/picking/picking.vue')
|
|
])
|
|
assert.match(navigation, /pages\.length > 1[\s\S]*?uni\.navigateBack/)
|
|
assert.match(navigation, /uni\.reLaunch\(\{ url: fallbackUrl \}\)/)
|
|
assert.match(navigation, /getCurrentRoute\(\) === route/)
|
|
assert.match(header, /aria-label="返回上一页"/)
|
|
assert.match(header, /navigateBackOr\(this\.fallbackUrl\)/)
|
|
assert.match(header, /navigateToUnique\('\/pages\/profile\/profile'\)/)
|
|
assert.match(header, /@change="handleMenuChange"/)
|
|
assert.match(header, /this\.\$emit\('menu-change', Boolean\(event\.show\)\)/)
|
|
assert.doesNotMatch(header, /goHome\(\)/)
|
|
assert.doesNotMatch([modulePage, operation].join('\n'), /class="inline-back"/)
|
|
assert.match(operation, /:fallback-url="`\/pages\/module\/module\?key=\$\{moduleKey\}`"/)
|
|
assert.match(picking, /navigateBackOr\('\/pages\/home\/home'\)/)
|
|
})
|
|
|
|
test('home consumes Android Back while its utility menu is open', async () => {
|
|
const [home, profile, header] = await Promise.all([
|
|
read('../pages/home/home.vue'), read('../pages/profile/profile.vue'), read('../components/PortalHeader.vue')
|
|
])
|
|
assert.match(home, /@menu-change="menuOpen = \$event"/)
|
|
assert.match(home, /onBackPress\(\)[\s\S]*?if \(!this\.menuOpen\) return false[\s\S]*?this\.\$refs\.portalHeader\.closeMenu\(\)[\s\S]*?return true/)
|
|
assert.match(profile, /:show-profile="false"/)
|
|
assert.match(profile, /openLogout\(\) \{ this\.logoutOpen = true; this\.\$refs\.logoutPopup\.open\(\) \}/)
|
|
assert.match(profile, /closeLogout\(\) \{ this\.logoutOpen = false; this\.\$refs\.logoutPopup\.close\(\) \}/)
|
|
assert.match(header, /v-if="showProfile" class="avatar-button"/)
|
|
assert.match(header, /class="portal-header-spacer"/)
|
|
})
|
|
|
|
test('business dialogs use uni-ui popup components consistently', async () => {
|
|
const [popupHeader, header, profile, picking] = await Promise.all([
|
|
read('../components/PopupHeader.vue'), read('../components/PortalHeader.vue'),
|
|
read('../pages/profile/profile.vue'), read('../pages/picking/picking.vue')
|
|
])
|
|
assert.match(popupHeader, /type="closeempty"/)
|
|
assert.match(popupHeader, /aria-label="`关闭\$\{title\}`"/)
|
|
assert.match(popupHeader, /@tap="\$emit\('close'\)"/)
|
|
assert.match(header, /<uni-popup ref="menuPopup"[\s\S]*?:is-mask-click="true"/)
|
|
assert.match(header, /<PopupHeader title="功能菜单" @close="closeMenu"/)
|
|
assert.doesNotMatch(header, /uni\.showActionSheet/)
|
|
assert.match(profile, /<uni-popup ref="logoutPopup" type="center"[\s\S]*?:is-mask-click="true"[\s\S]*?@change="handleLogoutChange"/)
|
|
assert.match(profile, /<PopupHeader title="确认退出登录?"[\s\S]*?@close="closeLogout"/)
|
|
assert.match(profile, /class="logout-dialog-confirm"/)
|
|
for (const name of ['boxList', 'skip', 'partial', 'switchBoxConfirm', 'submitConfirm', 'complete']) {
|
|
assert.match(picking, new RegExp(`<uni-popup ref="${name}Popup"[^>]*:is-mask-click="true"[^>]*@change="handleOverlayChange\\('${name}', \\$event\\)"`))
|
|
}
|
|
assert.equal((picking.match(/<PopupHeader\b/g) || []).length, 6)
|
|
assert.match(picking, /openOverlay\(name\)[\s\S]*?this\.\$refs\[`\$\{name\}Popup`\]/)
|
|
assert.match(picking, /closeOverlay\(\)[\s\S]*?popup\.close\(\)/)
|
|
assert.match(picking, /handleOverlayChange\(name, event\)[\s\S]*?!event\.show[\s\S]*?this\.resetOverlayState\(\)/)
|
|
assert.match(picking, /resetOverlayState\(\)[\s\S]*?this\.activeOverlay = ''/)
|
|
assert.doesNotMatch([header, profile, picking].join('\n'), /modal-overlay/)
|
|
})
|
|
|
|
test('scanner diagnostic page exposes the confirmed broadcast and cleans up its receiver', async () => {
|
|
const [header, page] = await Promise.all([read('../components/PortalHeader.vue'), read('../pages/scanner-test/scanner-test.vue')])
|
|
assert.doesNotMatch(header, /广播扫码测试|goScannerTest/)
|
|
for (const text of ['接收器已启动', '最近收到', '等待扫描', 'Action', 'Extra 标签', '接收记录']) assert.match(page, new RegExp(text))
|
|
assert.match(page, /startScanBroadcast/)
|
|
assert.match(page, /onHide\(\) \{ this\.stopScanner\(\) \}/)
|
|
assert.match(page, /onUnload\(\) \{ this\.stopScanner\(\) \}/)
|
|
assert.match(page, /this\.history = \[\{ id:[\s\S]*?\.slice\(0, 20\)/)
|
|
})
|
|
|
|
test('picking scan results use the shared success and error audio feedback', async () => {
|
|
const [audio, picking] = await Promise.all([
|
|
read('../utils/audio-feedback.js'), read('../pages/picking/picking.vue')
|
|
])
|
|
assert.match(audio, /success: '\/static\/mp3\/success\.mp3'/)
|
|
assert.match(audio, /error: '\/static\/mp3\/fail\.mp3'/)
|
|
assert.match(audio, /uni\.createInnerAudioContext\(\)/)
|
|
assert.match(audio, /export function playSuccessSound\(\)/)
|
|
assert.match(audio, /export function playErrorSound\(\)/)
|
|
assert.match(picking, /import \{ playErrorSound, playSuccessSound \} from '@\/utils\/audio-feedback\.js'/)
|
|
assert.match(picking, /result\.type === 'unknown'[\s\S]*?playErrorSound\(\)/)
|
|
assert.match(picking, /playSuccessSound\(\)[\s\S]*?this\.pickScannedBox\(result\.item, value\)/)
|
|
})
|
|
|
|
test('reusable scan target input separates field selection from page-level broadcast handling', async () => {
|
|
const [component, picking] = await Promise.all([read('../components/ScanTargetInput.vue'), read('../pages/picking/picking.vue')])
|
|
assert.match(component, /emits: \['activate', 'update:modelValue', 'submit'\]/)
|
|
assert.match(component, /\$emit\('activate', name\)/)
|
|
assert.match(component, /scan-target-input-active/)
|
|
assert.match(component, /<input[\s\S]*?:value="modelValue"[\s\S]*?@input="handleInput"[\s\S]*?@confirm="handleConfirm"/)
|
|
assert.doesNotMatch(component, /<input[\s\S]*?disabled/)
|
|
assert.doesNotMatch(component, /startScanBroadcast|plus\.android/)
|
|
assert.match(picking, /activeScanField: 'boxCode'/)
|
|
assert.match(picking, /handleHardwareScan\(value\)[\s\S]*?activeScanField !== 'boxCode'[\s\S]*?processScannedValue\(value, 'broadcast'\)/)
|
|
})
|
|
|
|
test('picking session persists per task identity and clears on logout', async () => {
|
|
const [store, session, page] = await Promise.all([read('../stores/picking.js'), read('../stores/session.js'), read('../pages/picking/picking.vue')])
|
|
assert.match(store, /getPickingSession/)
|
|
assert.match(store, /savePickingSession/)
|
|
assert.match(store, /resetPickingSession/)
|
|
assert.match(session, /signOut\(\)[\s\S]*?resetPickingSession\(\)/)
|
|
assert.match(page, /onUnload\(\) \{ this\.stopHardwareScanner\(\); this\.persistSession\(\)/)
|
|
})
|
|
|
|
test('shared visual contracts preserve accessible PDA targets', async () => {
|
|
const [app, styles, picking, tasks] = await Promise.all([read('../App.vue'), read('../styles/pda.scss'), read('../pages/picking/picking.vue'), read('../pages/tasks/tasks.vue')])
|
|
assert.match(app, /@import '@\/styles\/pda\.scss'/)
|
|
assert.doesNotMatch(styles, /<\/?style\b/)
|
|
for (const className of ['pick-btn', 'partial-quick-btn']) assert.match(styles, new RegExp(`\\.${className}\\s*\\{[\\s\\S]*?height:\\s*48px;`))
|
|
assert.match(styles, /\.pick-item-actions\s*\{[^}]*grid-template-columns:\s*repeat\(3, minmax\(0, 1fr\)\);[^}]*width:\s*100%;/)
|
|
assert.match(styles, /\.pick-btn\s*\{[^}]*width:\s*100%;[^}]*margin:\s*0;[^}]*align-items:\s*center;[^}]*justify-content:\s*center;/)
|
|
assert.match(styles, /\.pick-btn::after\s*\{[^}]*border:\s*0;/)
|
|
assert.match(styles, /\.processed-actions\s*\{[^}]*grid-template-columns:\s*1fr;/)
|
|
assert.match(styles, /\.pick-btn-undo\s*\{[^}]*border-color:\s*#efb7b7;[^}]*color:\s*#b42323;[^}]*background:\s*#fff5f5;/)
|
|
assert.doesNotMatch(styles, /\.scan-btn\s*\{/)
|
|
assert.match(styles, /\.modal-cancel-btn,[\s\S]*?height:\s*48px;/)
|
|
assert.match(styles, /\.modal-sheet\s*\{[\s\S]*?padding:\s*20px 18px 18px;/)
|
|
assert.doesNotMatch(styles, /\.modal-sheet\s*\{[\s\S]*?padding:[^;]*env\(/)
|
|
assert.match(styles, /\.submit-decision-modal\s*\{[^}]*padding:\s*18px 16px 16px;/)
|
|
assert.match(styles, /\.evidence-card\s*\{[^}]*margin-bottom:\s*8px;/)
|
|
assert.match(styles, /\.evidence-card:last-child\s*\{[^}]*margin-bottom:\s*0;/)
|
|
assert.match(tasks, /\.task-card\s*\{[^}]*margin:\s*0 0 12px;/)
|
|
assert.match(tasks, /\.task-card:last-child\s*\{[^}]*margin-bottom:\s*0;/)
|
|
assert.match(styles, /\.work-content\s*\{[^}]*padding:\s*1px 0 104px;/)
|
|
assert.match(picking, /class="box-select-summary"/)
|
|
assert.match(styles, /\.box-select-summary\s*\{[^}]*min-width:\s*0;[^}]*overflow-wrap:\s*anywhere;[^}]*white-space:\s*normal;/)
|
|
assert.doesNotMatch(styles, /\.box-select-summary\s*\{[^}]*(text-overflow:\s*ellipsis|white-space:\s*nowrap)/)
|
|
assert.match(styles, /\.box-dropdown\s*\{[^}]*top:\s*calc\(100% \+ 5px\);/)
|
|
assert.match(styles, /\.submit-section\s*\{[\s\S]*?bottom:\s*8px;[\s\S]*?width:\s*calc\(100% - 20px\);/)
|
|
assert.match(styles, /\.submit-btn\s*\{[\s\S]*?flex:\s*1;[\s\S]*?height:\s*56px;/)
|
|
assert.match(styles, /\.home-group-mark\s*\{[^}]*margin-right:\s*7px;/)
|
|
assert.match(styles, /\.home-operation-grid\s*\{[^}]*grid-template-columns:\s*repeat\(3, minmax\(0, 1fr\)\);/)
|
|
assert.match(styles, /\.home-operation\s*\{[^}]*width:\s*100%;[^}]*min-height:\s*78px;[^}]*align-self:\s*stretch;/)
|
|
assert.match(styles, /\.home-operation-name\s*\{[^}]*min-height:\s*30px;[^}]*white-space:\s*normal;/)
|
|
assert.doesNotMatch(styles, /\.home-operation-name\s*\{[^}]*(text-overflow:\s*ellipsis|-webkit-line-clamp|white-space:\s*nowrap)/)
|
|
assert.match(styles, /\.home-operation-meta\s*\{[^}]*border-radius:\s*7px;[^}]*white-space:\s*nowrap;/)
|
|
assert.match(styles, /@media \(max-width:\s*340px\)\s*\{[\s\S]*?\.home-operation-grid\s*\{[^}]*grid-template-columns:\s*repeat\(2, minmax\(0, 1fr\)\);/)
|
|
assert.match(styles, /\.header-menu-item\s*\{[^}]*width:\s*calc\(100% - 32px\);[^}]*margin:\s*0 16px;/)
|
|
assert.match(styles, /\.header-menu-close\s*\{[^}]*width:\s*calc\(100% - 32px\);/)
|
|
assert.match(picking, /aria-modal="true"/)
|
|
assert.match(picking, /role="status"/)
|
|
assert.match(picking, /transform:\s*'scaleX\('/)
|
|
})
|