import { mount } from '@vue/test-utils' import { afterEach, expect, it, vi } from 'vitest' import WorkCalendarPanel from './WorkCalendarPanel.vue' import { workCalendar } from './work.test-support' import { workTypes } from './workApi' function render() { return mount(WorkCalendarPanel, { props: { anchor: '2026-10-15', mode: 'month', flow: '', types: [...workTypes], calendar: workCalendar, loading: false, failed: false, reference: false, arrivals: [], referenceReady: false } }) } afterEach(() => vi.useRealTimers()) it('keeps a busy day at five type summaries and consumes authoritative transport counts unchanged', async () => { const wrapper = render() const counts = { PICKUP: 300, WAREHOUSE_RECEIPT: 300, DELIVERY: 2, TRANSPORT: 1, EMPTY_RETURN: 300 } await wrapper.setProps({ calendar: { ...workCalendar, days: [{ ...workCalendar.days[0]!, total: 903, typeCounts: counts }] } }) const day = wrapper.get('[data-date="2026-10-15"]') expect(day.findAll('.type-counts button')).toHaveLength(5) expect(day.get('[data-type="TRANSPORT"]').text()).toContain('1') expect(day.get('[data-type="DELIVERY"]').text()).toContain('2') await day.get('[data-type="TRANSPORT"]').trigger('click') expect(wrapper.emitted('day')).toEqual([['2026-10-15', 'TRANSPORT']]); wrapper.unmount() }) it('offers all four flows directly and emits the original filter values with an exclusive selection', async () => { const wrapper = render() const buttons = wrapper.findAll('.flow-controls button') expect(buttons.map(button => button.text())).toEqual(['全部流程', '海外仓中转', '整柜直送', '路线待确认']) expect(wrapper.find('select').exists()).toBe(false) for (const [index, flow] of (['', 'TRANSFER', 'DIRECT', 'UNKNOWN'] as const).entries()) { await buttons[index]!.trigger('click') expect(wrapper.emitted('flow')?.at(-1)).toEqual([flow]) await wrapper.setProps({ flow }) expect(wrapper.findAll('.flow-controls button[aria-pressed=true]')).toHaveLength(1) expect(buttons[index]!.attributes('aria-pressed')).toBe('true') } wrapper.unmount() }) it('distinguishes today from the selected day and type without changing the selected scope', async () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-10-15T12:00:00')) const wrapper = render() await wrapper.setProps({ selectedDate: '2026-10-16' }) expect(wrapper.get('[data-date="2026-10-15"] .day-heading').attributes('aria-current')).toBe('date') expect(wrapper.get('[data-date="2026-10-15"] .day-heading').attributes('aria-pressed')).toBe('false') expect(wrapper.get('[data-date="2026-10-16"]').classes()).toContain('is-selected') await wrapper.setProps({ selectedDate: '2026-10-15', selectedType: 'DELIVERY' }) expect(wrapper.get('[data-date="2026-10-15"] [data-type="DELIVERY"]').attributes('aria-pressed')).toBe('true') expect(wrapper.get('[data-date="2026-10-15"] [data-type="PICKUP"]').attributes('aria-pressed')).toBe('false') wrapper.unmount() }) it('shows the actual cross-month interval and hides empty-day zeros while keeping dates clickable', async () => { const wrapper = render() expect(wrapper.get('.calendar-counts').text()).toContain('2026-09-28 ~ 2026-11-08') const empty = wrapper.get('[data-date="2026-10-01"] .day-heading') expect(empty.text()).toBe('1') expect(empty.attributes('disabled')).toBeUndefined() await empty.trigger('click'); expect(wrapper.emitted('day')).toEqual([['2026-10-01']]) expect(wrapper.get('footer').text()).toContain('未定日期请从上方待办查看') await wrapper.setProps({ mode: 'week' }) expect(wrapper.findAll('.work-day')).toHaveLength(7) expect(wrapper.get('.view-switch button:last-child').attributes('aria-pressed')).toBe('true') wrapper.unmount() }) it('renders server type counts instead of individual event cards and opens exact date/type details', async () => { const wrapper = render() expect(wrapper.findAll('.work-day')).toHaveLength(35) const day = wrapper.get('[data-date="2026-10-15"]') expect(day.get('.day-heading').text()).toContain('2 项') await day.get('[data-type="DELIVERY"]').trigger('click') expect(wrapper.emitted('day')).toEqual([['2026-10-15', 'DELIVERY']]); wrapper.unmount() }) it('does not conflate arrivals, no-booking requirements or completed work with confirmed work', async () => { const wrapper = render() await wrapper.setProps({ reference: true, referenceReady: true, arrivals: [{ containerBookingId: '1', cabinetNo: 'D-1', arrivalAt: '2026-10-15T10:00:00', arrivalType: 'ESTIMATED' }] }) expect(wrapper.get('[data-date="2026-10-15"] .day-heading').text()).toContain('2 项') expect(wrapper.get('.calendar-counts').text()).toContain('已完成 1') expect(wrapper.get('.calendar-counts').text()).toContain('已落实 0') await wrapper.get('.arrival-reference').trigger('click'); expect(wrapper.emitted('arrivals')).toEqual([['2026-10-15']]); wrapper.unmount() }) it('shows explicit all-off and failure states without manufacturing zero aggregates', async () => { const wrapper = render(); await wrapper.setProps({ types: [], calendar: null }) expect(wrapper.text()).toContain('全局待办仍可查看'); expect(wrapper.findAll('.type-counts button')).toHaveLength(0) await wrapper.setProps({ types: [...workTypes], failed: true }) expect(wrapper.get('.calendar-counts').text()).toBe('工作统计加载失败') expect(wrapper.get('[data-date="2026-10-15"] .day-heading').text()).not.toContain('0 项'); wrapper.unmount() })