Files
admin-web/src/modules/appointment-calendar/AppointmentCalendarPanel.test.ts
T

117 lines
8.0 KiB
TypeScript

import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import AppointmentCalendarPanel from './AppointmentCalendarPanel.vue'
import { scheduleTypes, type ScheduleEvent } from './schedule'
import { projectTripCalendar, type TripCalendarEntry } from './tripCalendar'
const event = (type: ScheduleEvent['type'], index = 0): ScheduleEvent => ({ key: `${type}-${index}`, type, date: '2026-10-19', time: '', title: `TEST-CONT ${type}`, fields: [{ label: '仓库', value: 'ONT8' }] })
const render = (events: ScheduleEvent[] = [], initialDate = '2026-10-19') => mount(AppointmentCalendarPanel, { props: { initialDate, events },
global: { stubs: { ElButton: { template: '<button><slot /></button>' } } } })
function reportedDispatch() {
const row: TripCalendarEntry = { sourceType: 'DISPATCH', dispatchNo: 'PC-CALENDAR-001', containerNumbers: ['TSTU1234567', 'TSTU7654321'], cabinetNumbers: ['D1821', 'D1822'], status: 'PLANNED', originWarehouseId: '1', originWarehouseCode: 'LAX', originWarehouseName: '洛杉矶仓', originCountryCode: 'US', originTimezone: 'America/Los_Angeles', originWarehouseStatus: 'ACTIVE', plannedDepartureAt: '2026-10-22T16:00:00Z', expectedDriverEndAt: null, departedAt: null, completedAt: null, stops: [{ name: '洛杉矶仓' }, { name: 'ONT8' }], incompleteReasons: [] }
return projectTripCalendar({ records: [row, row], unresolvedRecords: [] }, { from: '2026-10-01', to: '2026-11-01' }).events
}
describe('four-type appointment calendar', () => {
it.each(['TSTU1234567', 'tstu1234567', 'TSTU7654321', ' tstu7654321 ', 'D1821', ' d1821 ', 'D1822', '1821'])('finds the one shared dispatch by associated container %s on its origin-local date, not the Beijing date', async keyword => {
const wrapper = render(reportedDispatch(), '2026-10-23')
const search = wrapper.get('[aria-label="搜索当前日历"]')
expect(search.attributes('placeholder')).toBe('柜号、排车单号、仓库')
await search.setValue(keyword)
expect(wrapper.findAll('.detail-event')).toHaveLength(0)
expect(wrapper.get('[aria-label="当天安排"]').text()).toContain('当天暂无安排')
expect(wrapper.get('[data-date="2026-10-23"]').findAll('.event-chip')).toHaveLength(0)
expect(wrapper.get('[data-date="2026-10-22"]').findAll('.event-chip')).toHaveLength(1)
await wrapper.get('[data-date="2026-10-22"]').trigger('click')
expect(wrapper.findAll('.detail-event')).toHaveLength(1)
expect(wrapper.get('.detail-event').text()).toContain('PC-CALENDAR-001')
expect(wrapper.get('.detail-event').text()).toContain('09:00')
expect(wrapper.get('.detail-event').text()).toContain('TSTU1234567、TSTU7654321')
expect(wrapper.get('.detail-event').text()).toContain('D1821、D1822')
expect(wrapper.get('.detail-event').text()).toContain('2026-10-22 09:00')
expect(wrapper.emitted('range')).toBeUndefined()
wrapper.unmount()
})
it('does not match an unrelated container and can find the same event by the dispatch number', async () => {
const wrapper = render(reportedDispatch(), '2026-10-22')
await wrapper.get('[aria-label="搜索当前日历"]').setValue('UNRELATED9999')
expect(wrapper.findAll('.detail-event')).toHaveLength(0)
expect(wrapper.findAll('.event-chip')).toHaveLength(0)
await wrapper.get('[aria-label="搜索当前日历"]').setValue('PC-CALENDAR-001')
expect(wrapper.findAll('.detail-event')).toHaveLength(1)
expect(wrapper.findAll('.event-chip')).toHaveLength(1)
expect(wrapper.emitted('range')).toBeUndefined()
wrapper.unmount()
})
it('shows all four types by default with a calendar and persistent daily detail', () => {
const wrapper = render(scheduleTypes.map(type => event(type)))
expect(wrapper.findAll('[aria-label="安排类型"] input')).toHaveLength(4)
expect(wrapper.get('[aria-label="安排类型"]').text()).toBe('到港排车还柜计划到仓')
expect(wrapper.findAll('.detail-event')).toHaveLength(4)
expect(wrapper.get('[aria-label="当天安排"] header').text()).toContain('4 项安排')
expect(wrapper.findAll('.calendar-day')).toHaveLength(35)
expect(wrapper.find('[data-date="2026-11-01"]').exists()).toBe(true)
expect(wrapper.find('[data-date="2026-11-02"]').exists()).toBe(false)
expect(wrapper.text()).not.toMatch(/新增预约|全局待办|需复核|中转|直送/)
})
it('filters locally without requesting another range', async () => {
const wrapper = render([event('arrival'), event('dispatch')])
await wrapper.get('[aria-label="安排类型"] input').setValue(false)
expect(wrapper.findAll('.detail-event')).toHaveLength(1)
await wrapper.get('[aria-label="搜索当前日历"]').setValue('missing')
expect(wrapper.text()).toContain('当天暂无安排')
expect(wrapper.emitted('range')).toBeUndefined()
})
it('offers creation in the header only when authorized', async () => {
const wrapper = render()
expect(wrapper.findAll('button').some(button => button.text() === '新增预约')).toBe(false)
await wrapper.setProps({ canCreate: true })
await wrapper.findAll('.calendar-controls button').find(button => button.text() === '新增预约')!.trigger('click')
expect(wrapper.emitted('create')).toEqual([[]])
expect(wrapper.emitted('range')).toBeUndefined()
wrapper.unmount()
})
it('changes the selected date locally and requests one range per mode or period change', async () => {
const wrapper = render()
await wrapper.get('[data-date="2026-10-20"]').trigger('click')
expect(wrapper.emitted('range')).toBeUndefined()
expect(wrapper.get('[aria-label="当天安排"]').text()).toContain('2026-10-20')
await wrapper.get('[aria-label="预约视图"]').findAll('button')[1]!.trigger('click')
expect(wrapper.emitted('range')).toEqual([[{ from: '2026-10-19', to: '2026-10-26' }]])
await wrapper.get('[aria-label="下一周"]').trigger('click')
expect(wrapper.emitted('range')?.at(-1)).toEqual([{ from: '2026-10-26', to: '2026-11-02' }])
})
it('limits previews to three while retaining every event in the daily details', () => {
const wrapper = render(Array.from({ length: 23 }, (_, index) => event('arrival', index)))
expect(wrapper.get('[data-date="2026-10-19"]').findAll('.event-chip')).toHaveLength(3)
expect(wrapper.get('.event-more').text()).toBe('还有 20 条')
expect(wrapper.findAll('.detail-event')).toHaveLength(23)
})
it('keeps undated records outside date cells and exposes them explicitly', async () => {
const wrapper = render([{ ...event('warehouse'), date: null }])
expect(wrapper.findAll('.event-chip')).toHaveLength(0)
await wrapper.get('.undated-button').trigger('click')
expect(wrapper.findAll('.detail-event')).toHaveLength(1)
expect(wrapper.get('[aria-label="当天安排"]').text()).toContain('日期未定')
})
it('retains business links and exposes editing only with authoritative eligibility', async () => {
const row = { ...event('warehouse'), sourcePath: '/container-loading/overseas/1', canEdit: true }
const wrapper = render([row])
await wrapper.get('.detail-event footer').findAll('button')[0]!.trigger('click')
await wrapper.get('.detail-event footer').findAll('button')[1]!.trigger('click')
expect(wrapper.emitted('open')).toEqual([[row]])
expect(wrapper.emitted('edit')).toEqual([[row]])
await wrapper.setProps({ events: [{ ...row, canEdit: false, sourcePath: undefined }] })
expect(wrapper.find('.detail-event footer').exists()).toBe(false)
})
it('distinguishes loading and partial failure from a complete empty day', async () => {
const wrapper = render()
await wrapper.setProps({ loading: true })
expect(wrapper.text()).toContain('正在读取安排')
expect(wrapper.text()).not.toContain('当天暂无安排')
await wrapper.setProps({ loading: false, incomplete: true })
expect(wrapper.get('[aria-label="当天安排"] header').text()).toContain('当前已加载 0 项安排')
expect(wrapper.text()).not.toContain('部分数据不可用')
expect(wrapper.text()).not.toContain('当天暂无安排')
})
})