Files
admin-web/src/modules/inbounds/weight/components/WeightOcrPanel.behavior.test.ts
T

340 lines
12 KiB
TypeScript
Raw Normal View History

2026-08-07 18:44:55 +08:00
import ElementPlus from 'element-plus'
import { createPinia, setActivePinia } from 'pinia'
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useAuthStore } from '@/core/auth/auth'
import {
confirmWeightResult,
correctWeightResult,
getWeightWorkspace,
} from '../api/weightOcr'
import type { WeightResultResource, WeightWorkspaceResource } from '../types'
import WeightOcrPanel from './WeightOcrPanel.vue'
vi.mock('../api/weightOcr', async () => {
const actual = await vi.importActual<typeof import('../api/weightOcr')>('../api/weightOcr')
return {
...actual,
confirmWeightResult: vi.fn(),
correctWeightResult: vi.fn(),
getWeightWorkspace: vi.fn(),
}
})
const pendingWeight: WeightResultResource = {
id: '30001',
fileId: '40001',
ocrProvider: 'FILE_WEIGHT_OCR',
ocrRawText: '285.5',
ocrWeightValue: 285.5,
ocrWeightUnit: 'KG',
confidence: 0.50972,
adjustedWeightValue: null,
adjustedWeightUnit: null,
finalWeightKg: null,
recognitionStatus: 'PENDING_CONFIRM',
adjustmentReason: null,
adjustedBy: null,
adjustedAt: null,
confirmedBy: null,
confirmedAt: null,
version: 0,
}
const workspace: WeightWorkspaceResource = {
inboundId: '20001',
inboundCode: 'RK-20001',
processStatus: 'MEASURING',
actualGrossWeightKg: null,
weightResults: [pendingWeight],
}
enableAutoUnmount(afterEach)
2026-08-07 19:23:43 +08:00
const allWeightPermissions = [
'platform:firstleg:inbound:measure:view',
'platform:firstleg:inbound:weight:edit',
'platform:firstleg:inbound:weight:confirm',
]
function mountPanel(permissions = allWeightPermissions) {
2026-08-07 18:44:55 +08:00
const pinia = createPinia()
setActivePinia(pinia)
const auth = useAuthStore()
auth.token = 'token'
2026-08-07 19:23:43 +08:00
auth.permissions = permissions
2026-08-07 18:44:55 +08:00
useAccessPreviewStore().initializeAfterLogin()
return mount(WeightOcrPanel, {
attachTo: document.body,
props: { customerId: '10001', inboundId: '20001' },
global: { plugins: [pinia, ElementPlus] },
})
}
describe('WeightOcrPanel confirmation behavior', () => {
beforeEach(() => {
vi.clearAllMocks()
document.body.innerHTML = ''
vi.mocked(getWeightWorkspace).mockResolvedValue(workspace)
vi.mocked(confirmWeightResult).mockResolvedValue({
...pendingWeight,
recognitionStatus: 'CONFIRMED',
finalWeightKg: 285.5,
version: 1,
})
})
it('confirms an unchanged OCR value with one request', async () => {
const wrapper = mountPanel()
await flushPromises()
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledTimes(1)
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', { version: 0 })
expect(correctWeightResult).not.toHaveBeenCalled()
})
it('sends the adjusted value and a specific audit reason in the same confirmation request', async () => {
const wrapper = mountPanel()
await flushPromises()
await wrapper.get('input[aria-label="称重图片序号 1 的确认重量"]').setValue('286.125')
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledTimes(1)
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', {
version: 0,
adjustedWeightValue: 286.125,
adjustedWeightUnit: 'KG',
adjustmentReason: 'OCR 原值 285.500 KG,人工确认 286.125 KG',
})
expect(correctWeightResult).not.toHaveBeenCalled()
})
2026-08-07 19:23:43 +08:00
it('sends an LB adjustment atomically after switching units', async () => {
const wrapper = mountPanel()
await flushPromises()
await wrapper.get('input[aria-label="称重图片序号 1 的确认重量"]').setValue('10.125')
await wrapper.findAll('.el-segmented__item').at(1)?.trigger('click')
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', {
version: 0,
adjustedWeightValue: 10.125,
adjustedWeightUnit: 'LB',
adjustmentReason: 'OCR 原值 285.500 KG,人工确认 10.125 LB',
})
expect(correctWeightResult).not.toHaveBeenCalled()
})
it('rejects an LB value that would round to zero kilograms', async () => {
const wrapper = mountPanel()
await flushPromises()
await wrapper.get('input[aria-label="称重图片序号 1 的确认重量"]').setValue('0.001')
await wrapper.findAll('.el-segmented__item').at(1)?.trigger('click')
expect(wrapper.get('.weight-row__editor .el-button--success').attributes('disabled')).toBeDefined()
expect(confirmWeightResult).not.toHaveBeenCalled()
})
it('accepts the smallest LB value that remains representable in kilograms', async () => {
const wrapper = mountPanel()
await flushPromises()
await wrapper.get('input[aria-label="称重图片序号 1 的确认重量"]').setValue('0.002')
await wrapper.findAll('.el-segmented__item').at(1)?.trigger('click')
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', {
version: 0,
adjustedWeightValue: 0.002,
adjustedWeightUnit: 'LB',
adjustmentReason: 'OCR 原值 285.500 KG,人工确认 0.002 LB',
})
})
it('clamps a value outside the safe three-decimal JavaScript range', async () => {
const wrapper = mountPanel()
await flushPromises()
const input = wrapper.get('input[aria-label="称重图片序号 1 的确认重量"]')
await input.setValue('9000000000001')
expect((input.element as HTMLInputElement).value).toBe('9000000000000.000')
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', {
version: 0,
adjustedWeightValue: 9_000_000_000_000,
adjustedWeightUnit: 'KG',
adjustmentReason: 'OCR 原值 285.500 KG,人工确认 9000000000000.000 KG',
})
})
it('rounds an input with more than three decimals before confirmation', async () => {
const wrapper = mountPanel()
await flushPromises()
const input = wrapper.get('input[aria-label="称重图片序号 1 的确认重量"]')
await input.setValue('1.2345')
expect((input.element as HTMLInputElement).value).toBe('1.235')
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', {
version: 0,
adjustedWeightValue: 1.235,
adjustedWeightUnit: 'KG',
adjustmentReason: 'OCR 原值 285.500 KG,人工确认 1.235 KG',
})
})
it('confirms an existing corrected value without creating another correction', async () => {
vi.mocked(getWeightWorkspace).mockResolvedValue({
...workspace,
weightResults: [{
...pendingWeight,
adjustedWeightValue: 286.125,
adjustedWeightUnit: 'KG',
adjustmentReason: '首次修正',
version: 1,
}],
})
const wrapper = mountPanel()
await flushPromises()
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', { version: 1 })
expect(correctWeightResult).not.toHaveBeenCalled()
})
it('allows a confirm-only operator to accept an unchanged OCR value', async () => {
const wrapper = mountPanel([
'platform:firstleg:inbound:measure:view',
'platform:firstleg:inbound:weight:confirm',
])
await flushPromises()
expect(wrapper.get('input[aria-label="称重图片序号 1 的确认重量"]').attributes('disabled')).toBeDefined()
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', { version: 0 })
})
it('does not render confirmation for an edit-only operator', async () => {
const wrapper = mountPanel([
'platform:firstleg:inbound:measure:view',
'platform:firstleg:inbound:weight:edit',
])
await flushPromises()
expect(wrapper.find('.weight-row__editor .el-button--success').exists()).toBe(false)
expect(confirmWeightResult).not.toHaveBeenCalled()
})
it('supports manual entry when OCR did not return a value', async () => {
vi.mocked(getWeightWorkspace).mockResolvedValue({
...workspace,
weightResults: [{
...pendingWeight,
ocrRawText: null,
ocrWeightValue: null,
ocrWeightUnit: null,
recognitionStatus: 'NEEDS_MANUAL',
}],
})
const wrapper = mountPanel()
await flushPromises()
expect(wrapper.get('.weight-row__editor .el-button--success').attributes('disabled')).toBeDefined()
await wrapper.get('input[aria-label="称重图片序号 1 的确认重量"]').setValue('1.500')
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', {
version: 0,
adjustedWeightValue: 1.5,
adjustedWeightUnit: 'KG',
adjustmentReason: 'OCR 原值 —,人工确认 1.500 KG',
})
})
it('normalizes a supported OCR unit alias with surrounding whitespace', async () => {
vi.mocked(getWeightWorkspace).mockResolvedValue({
...workspace,
weightResults: [{ ...pendingWeight, ocrWeightUnit: ' lbs ' }],
})
const wrapper = mountPanel()
await flushPromises()
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', { version: 0 })
})
it('sends an explicit correction when OCR returns an unsupported unit', async () => {
vi.mocked(getWeightWorkspace).mockResolvedValue({
...workspace,
weightResults: [{ ...pendingWeight, ocrWeightUnit: 'G' }],
})
const wrapper = mountPanel()
await flushPromises()
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', {
version: 0,
adjustedWeightValue: 285.5,
adjustedWeightUnit: 'KG',
adjustmentReason: 'OCR 原值 285.500 G,人工确认 285.500 KG',
})
})
it('sends an explicit correction when OCR returns a value without a unit', async () => {
vi.mocked(getWeightWorkspace).mockResolvedValue({
...workspace,
weightResults: [{ ...pendingWeight, ocrWeightUnit: null }],
})
const wrapper = mountPanel()
await flushPromises()
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect(confirmWeightResult).toHaveBeenCalledWith('10001', '20001', '30001', {
version: 0,
adjustedWeightValue: 285.5,
adjustedWeightUnit: 'KG',
adjustmentReason: 'OCR 原值 285.500(单位缺失),人工确认 285.500 KG',
})
})
it('keeps the edited draft available when confirmation fails', async () => {
vi.mocked(confirmWeightResult).mockRejectedValueOnce(new Error('version conflict'))
const wrapper = mountPanel()
await flushPromises()
const input = wrapper.get('input[aria-label="称重图片序号 1 的确认重量"]')
await input.setValue('286.125')
await wrapper.get('.weight-row__editor .el-button--success').trigger('click')
await flushPromises()
expect((input.element as HTMLInputElement).value).toBe('286.125')
expect(wrapper.text()).toContain('version conflict')
expect(confirmWeightResult).toHaveBeenCalledTimes(1)
})
2026-08-07 18:44:55 +08:00
})