import { defineComponent, h } from 'vue' import { flushPromises, mount } from '@vue/test-utils' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import OwnerPicker from './OwnerPicker.vue' import type { QueryContext } from '../types' const owners = vi.hoisted(() => vi.fn()) vi.mock('../api/analytics', () => ({ analyticsApi: { owners } })) const context: QueryContext = { salesRunId: '11', inventoryRunId: '12', salesScopeHash: 'sales', inventoryScopeHash: 'stock', queryAsOf: '2026-10-12T01:00:00Z', queryExpiresAt: '2026-10-12T01:30:00Z', businessTimezone: 'Asia/Shanghai', anchorDate: '2026-10-12' } const select = defineComponent({ props: ['remoteMethod'], setup(props, { slots }) { return () => h('div', [h('button', { onClick: () => props.remoteMethod('Ab_') }, '前缀搜索'), slots.default?.()]) } }) const button = defineComponent({ props: ['disabled'], emits: ['click'], setup(props, { slots, emit }) { return () => h('button', { disabled: props.disabled, onClick: () => emit('click') }, slots.default?.()) } }) const option = defineComponent({ props: ['label'], setup(props) { return () => h('span', props.label) } }) const global = { stubs: { 'el-select': select, 'el-button': button, 'el-option': option } } const wrappers: ReturnType[] = [] beforeEach(() => { owners.mockReset(); owners.mockResolvedValue({ context, records: [{ customerCode: 'OWNER-A', customerName: '货主甲' }], total: 25 }) }) afterEach(() => wrappers.splice(0).forEach(wrapper => wrapper.unmount())) describe('fixed-context owner selector', () => { it('uses backend pagination and prefix search without changing the pinned context', async () => { const wrapper = mount(OwnerPicker, { props: { modelValue: '', sourceAccount: 'lingxing-primary', warehouseCode: 'WH-A', context }, global }); wrappers.push(wrapper) await flushPromises() await wrapper.findAll('button').find(node => node.text() === '下一页货主')!.trigger('click'); await flushPromises() expect(owners.mock.calls.at(-1)?.[0]).toMatchObject({ current: 2, size: 20, salesRunId: '11', inventoryRunId: '12', queryAsOf: context.queryAsOf }) await wrapper.findAll('button').find(node => node.text() === '前缀搜索')!.trigger('click'); await flushPromises() expect(owners.mock.calls.at(-1)?.[0]).toMatchObject({ keyword: 'Ab_', current: 1, queryAsOf: context.queryAsOf }) expect(wrapper.text()).toContain('OWNER-A · 货主甲') }) it('ignores late options from the old warehouse and rejects mixed-version choices', async () => { let resolve!: (value: object) => void owners.mockImplementationOnce(() => new Promise(done => { resolve = done })).mockResolvedValue({ context, records: [{ customerCode: 'OWNER-B', customerName: null }], total: 1 }) const wrapper = mount(OwnerPicker, { props: { modelValue: '', sourceAccount: 'lingxing-primary', warehouseCode: 'WH-A', context }, global }); wrappers.push(wrapper) await wrapper.setProps({ warehouseCode: 'WH-B' }); await flushPromises() resolve({ context, records: [{ customerCode: 'OLD-OWNER', customerName: null }], total: 1 }); await flushPromises() expect(wrapper.text()).toContain('OWNER-B') expect(wrapper.text()).not.toContain('OLD-OWNER') expect(owners.mock.calls[0]?.[1].aborted).toBe(true) owners.mockResolvedValue({ context: { ...context, inventoryRunId: '99' }, records: [{ customerCode: 'WRONG-VERSION' }], total: 1 }) await wrapper.findAll('button').find(node => node.text() === '前缀搜索')!.trigger('click'); await flushPromises() expect(wrapper.text()).not.toContain('WRONG-VERSION') expect(wrapper.text()).toContain('货主选项版本已变化') }) })