fix(appointment): restore overlay panel and compact calendar controls
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { inject, onBeforeUnmount } from 'vue'
|
||||
import type { LeaveGuard } from './useWorkDraft'
|
||||
import AppointmentCalendarView from './AppointmentCalendarView.vue'
|
||||
import { getWorkCalendar, getWorkItems, getWorkSummary } from './workApi'
|
||||
import { listArrivalCalendar } from './api'
|
||||
import { workCalendar, workItem, workSummary, followAction } from './work.test-support'
|
||||
const routing = vi.hoisted(() => ({ query: {} as Record<string, string>, push: vi.fn(), replace: vi.fn(), canReadSource: true }))
|
||||
const routing = vi.hoisted(() => ({ query: {} as Record<string, string>, push: vi.fn(), replace: vi.fn(), canReadSource: true, allowLeave: true }))
|
||||
vi.mock('vue-router', () => ({ useRoute: () => routing, useRouter: () => routing, onBeforeRouteLeave: vi.fn(), onBeforeRouteUpdate: vi.fn() }))
|
||||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: (permission: string) => permission !== 'platform:overseas-warehouse:last-mile-order:list' || routing.canReadSource }) }))
|
||||
vi.mock('./workApi', async original => ({ ...await original<typeof import('./workApi')>(), getWorkCalendar: vi.fn(), getWorkItems: vi.fn(), getWorkSummary: vi.fn() }))
|
||||
@@ -13,8 +15,12 @@ function render() {
|
||||
return mount(AppointmentCalendarView, { global: { stubs: {
|
||||
AdminPage: { template: '<main><slot /></main>' },
|
||||
ElButton: { template: '<button><slot /></button>' },
|
||||
WorkCalendarPanel: { name: 'WorkCalendarPanel', props: ['anchor', 'mode', 'flow', 'types', 'calendar', 'failed', 'reference'], template: '<div data-calendar />' },
|
||||
WorkDetailPanel: { name: 'WorkDetailPanel', props: ['itemKey', 'initialAction', 'canLeave'], template: '<div data-detail>{{ itemKey }}</div>' },
|
||||
WorkCalendarPanel: { name: 'WorkCalendarPanel', props: ['anchor', 'mode', 'flow', 'types', 'calendar', 'failed', 'reference', 'selectedDate', 'selectedType'], template: '<div data-calendar><slot name="inbox" /></div>' },
|
||||
WorkDetailPanel: { name: 'WorkDetailPanel', props: ['itemKey', 'initialAction', 'canLeave'], setup() {
|
||||
const register = inject<(guard: LeaveGuard) => () => void>('appointment-work-leave-guard')!
|
||||
const unregister = register(async () => routing.allowLeave)
|
||||
onBeforeUnmount(unregister)
|
||||
}, template: '<div data-detail>{{ itemKey }}</div>' },
|
||||
SourceAppointmentPanel: { name: 'SourceAppointmentPanel', props: ['source'], template: '<div data-cargo />' },
|
||||
LoadErrorState: { props: ['title', 'message'], template: '<section role="alert">{{ title }} {{ message }}<slot name="details" /><button @click="$emit(\'retry\')">重试</button></section>' },
|
||||
ApiErrorMeta: true,
|
||||
@@ -22,7 +28,7 @@ function render() {
|
||||
}
|
||||
describe('appointment work calendar', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks(); routing.query = { workAnchor: '2026-10-15' }; routing.canReadSource = true
|
||||
vi.clearAllMocks(); routing.query = { workAnchor: '2026-10-15' }; routing.canReadSource = true; routing.allowLeave = true
|
||||
routing.replace.mockResolvedValue(undefined); routing.push.mockResolvedValue(undefined)
|
||||
vi.mocked(getWorkCalendar).mockResolvedValue(workCalendar)
|
||||
vi.mocked(getWorkSummary).mockResolvedValue(workSummary)
|
||||
@@ -32,10 +38,43 @@ describe('appointment work calendar', () => {
|
||||
it('opens the real work calendar by default and converts its half-open helper range to the inclusive API contract', async () => {
|
||||
const wrapper = render(); await flushPromises()
|
||||
expect(wrapper.text()).toContain('预约工作日历')
|
||||
expect(wrapper.get('[data-calendar] .work-inbox').attributes('aria-label')).toBe('全局待办')
|
||||
expect(getWorkCalendar).toHaveBeenCalledWith({ from: '2026-09-28', to: '2026-11-08', flow: undefined, types: ['PICKUP', 'WAREHOUSE_RECEIPT', 'DELIVERY', 'TRANSPORT', 'EMPTY_RETURN'] }, expect.any(AbortSignal))
|
||||
expect(getWorkSummary).toHaveBeenCalledTimes(1); expect(getWorkItems).not.toHaveBeenCalled(); expect(listArrivalCalendar).not.toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
it('keeps day/type selection and pagination context in the overlay and clears selection on close', async () => {
|
||||
vi.mocked(getWorkItems).mockResolvedValue({ records: [workItem()], page: 1, size: 8, total: 30, pages: 4 })
|
||||
const wrapper = render(); await flushPromises()
|
||||
const calendar = wrapper.getComponent({ name: 'WorkCalendarPanel' })
|
||||
calendar.vm.$emit('day', '2026-10-15', 'PICKUP'); await flushPromises()
|
||||
expect(calendar.props()).toMatchObject({ selectedDate: '2026-10-15', selectedType: 'PICKUP' })
|
||||
expect(wrapper.get('.panel-header').text()).toContain('2026-10-15 · 提柜')
|
||||
expect(wrapper.find('.panel-body .work-pagination').exists()).toBe(false)
|
||||
expect(wrapper.get('.panel-footer').text()).toContain('共 30 项')
|
||||
await wrapper.get('.panel-footer .work-pagination button:last-child').trigger('click'); await flushPromises()
|
||||
expect(getWorkItems).toHaveBeenLastCalledWith(expect.objectContaining({ date: '2026-10-15', types: ['PICKUP'], page: 2 }), expect.any(AbortSignal))
|
||||
await wrapper.get('[aria-label="关闭工作面板"]').trigger('click'); await flushPromises()
|
||||
expect(wrapper.find('.work-panel').exists()).toBe(false)
|
||||
expect(calendar.props('selectedDate')).toBeUndefined()
|
||||
wrapper.unmount()
|
||||
})
|
||||
it('expands the existing detail without remounting or bypassing the unsaved close guard', async () => {
|
||||
const wrapper = render(); await flushPromises()
|
||||
await wrapper.get('[data-view="ALL"]').trigger('click'); await flushPromises()
|
||||
await wrapper.get('.work-row-action button').trigger('click'); await flushPromises()
|
||||
const detail = wrapper.getComponent({ name: 'WorkDetailPanel' }).vm
|
||||
routing.allowLeave = false
|
||||
await wrapper.get('.panel-tools button:first-child').trigger('click')
|
||||
expect(wrapper.get('.work-panel').classes()).toContain('is-expanded')
|
||||
expect(wrapper.getComponent({ name: 'WorkDetailPanel' }).vm).toBe(detail)
|
||||
await wrapper.get('[aria-label="关闭工作面板"]').trigger('click'); await flushPromises()
|
||||
expect(wrapper.find('.work-panel').exists()).toBe(true)
|
||||
routing.allowLeave = true
|
||||
await wrapper.get('[aria-label="关闭工作面板"]').trigger('click'); await flushPromises()
|
||||
expect(wrapper.find('.work-panel').exists()).toBe(false)
|
||||
wrapper.unmount()
|
||||
})
|
||||
it('sends an explicit safe return context with external business navigation', async () => {
|
||||
routing.query = { workAnchor: '2026-10-15', workItem: 'work:1', workScope: 'global', workView: 'UNDATED', workPage: '3', workOrigins: '["work:0"]' }
|
||||
const wrapper = render(); await flushPromises()
|
||||
|
||||
Reference in New Issue
Block a user