feat(admin): 实现排车候选分页并修正报关批次参数
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { ref } from 'vue'
|
||||
import { beforeEach, expect, it, vi } from 'vitest'
|
||||
import type { ApiErrorDetails } from '@/shared/api/http'
|
||||
import { listDispatchCandidates } from './api/dispatches'
|
||||
import { candidate, sharedPalletCandidates } from './dispatchTestFixtures'
|
||||
import type { CandidateList } from './dispatchContracts'
|
||||
import { useDispatchCandidates } from './useDispatchCandidates'
|
||||
|
||||
vi.mock('./api/dispatches', () => ({ listDispatchCandidates: vi.fn() }))
|
||||
beforeEach(() => vi.resetAllMocks())
|
||||
const response = (overrides: Partial<CandidateList> = {}): CandidateList => ({ candidates: [candidate()], snapshotAt: '', complete: false, page: 1, size: 20, total: 41, pages: 3, ...overrides })
|
||||
function deferred() {
|
||||
let resolve!: (value: CandidateList) => void, reject!: (error: Error) => void
|
||||
const promise = new Promise<CandidateList>((yes, no) => { resolve = yes; reject = no })
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
function setup() {
|
||||
const error = ref<ApiErrorDetails | null>(null)
|
||||
return { ...useDispatchCandidates(error), error }
|
||||
}
|
||||
it('deduplicates concurrent identical requests and accepts complete groups on a partial page', async () => {
|
||||
const pending = deferred(), state = setup()
|
||||
vi.mocked(listDispatchCandidates).mockReturnValue(pending.promise)
|
||||
const first = state.load({ warehouseId: '321', page: 2, warehouseTab: 'LAX', orderNo: 'LM' })
|
||||
const second = state.load({ warehouseId: '321', page: 2, warehouseTab: 'LAX', orderNo: 'LM' })
|
||||
expect(first).toBe(second); expect(listDispatchCandidates).toHaveBeenCalledTimes(1)
|
||||
pending.resolve(response({ candidates: [sharedPalletCandidates()[0]!], page: 2 }))
|
||||
await first
|
||||
expect(state.total.value).toBe(41); expect(state.page.value).toBe(2)
|
||||
expect(state.candidates.value[0]!.selectionGroup.sources).toHaveLength(2)
|
||||
expect(() => state.assertCurrent(state.candidates.value)).not.toThrow()
|
||||
})
|
||||
it.each(['success', 'failure'])('ignores stale %s while the latest page is still loading', async outcome => {
|
||||
const old = deferred(), latest = deferred(), state = setup()
|
||||
vi.mocked(listDispatchCandidates).mockReturnValueOnce(old.promise).mockReturnValueOnce(latest.promise)
|
||||
const first = state.load({ warehouseId: '321', page: 1 })
|
||||
const second = state.load({ warehouseId: '321', page: 2 })
|
||||
if (outcome === 'success') old.resolve(response()); else old.reject(new Error('stale failure'))
|
||||
await first
|
||||
expect(state.loading.value).toBe(true); expect(state.error.value).toBeNull(); expect(state.candidates.value).toEqual([])
|
||||
latest.resolve(response({ page: 2 })); await second
|
||||
expect(state.page.value).toBe(2); expect(state.loading.value).toBe(false)
|
||||
})
|
||||
it('ignores an old response after a new warehouse succeeds and invalidates requests on close', async () => {
|
||||
const old = deferred(), state = setup(), other = candidate({ warehouseId: 'other' })
|
||||
vi.mocked(listDispatchCandidates).mockReturnValueOnce(old.promise).mockResolvedValueOnce(response({ candidates: [other] }))
|
||||
const first = state.load({ warehouseId: '321' })
|
||||
await state.load({ warehouseId: 'other' })
|
||||
old.resolve(response()); await first
|
||||
expect(state.candidates.value[0]!.warehouseId).toBe('other')
|
||||
expect(() => state.assertCurrent([candidate()])).toThrow('组成已变化')
|
||||
const closing = deferred()
|
||||
vi.mocked(listDispatchCandidates).mockReturnValueOnce(closing.promise)
|
||||
const request = state.load({ warehouseId: 'other', page: 2 })
|
||||
state.reset(); closing.resolve(response({ page: 2, candidates: [other] })); await request
|
||||
expect(state.candidates.value).toEqual([]); expect(state.total.value).toBe(0); expect(state.loading.value).toBe(false)
|
||||
})
|
||||
it('remembers a group or source version change even after navigating to an unrelated page', async () => {
|
||||
const state = setup(), selected = candidate()
|
||||
const changed = candidate({ selectionGroup: { ...selected.selectionGroup, groupVersion: 'changed' } })
|
||||
vi.mocked(listDispatchCandidates).mockResolvedValueOnce(response()).mockResolvedValueOnce(response({ page: 2, candidates: [changed] })).mockResolvedValueOnce(response({ page: 3, candidates: [] }))
|
||||
await state.load({ warehouseId: '321' }); await state.load({ warehouseId: '321', page: 2 }); await state.load({ warehouseId: '321', page: 3 })
|
||||
expect(() => state.assertCurrent([selected])).toThrow('组成已变化')
|
||||
expect(() => state.assertCurrent([changed])).not.toThrow()
|
||||
})
|
||||
it.each(['warehouse', 'page', 'version'])('rejects a mismatched %s response instead of exposing selectable stale rows', async mismatch => {
|
||||
const state = setup(), row = candidate()
|
||||
if (mismatch === 'warehouse') row.warehouseId = 'other'
|
||||
if (mismatch === 'version') row.sourceVersion = 'changed'
|
||||
vi.mocked(listDispatchCandidates).mockResolvedValue(response({ candidates: [row], page: mismatch === 'page' ? 2 : 1 }))
|
||||
await state.load({ warehouseId: '321' })
|
||||
expect(state.candidates.value).toEqual([]); expect(state.error.value).not.toBeNull()
|
||||
})
|
||||
Reference in New Issue
Block a user