import { describe, expect, it } from 'vitest' import { mount } from '@vue/test-utils' import AuditTrail from '../../components/AuditTrail.vue' import BatchProgressList from '../../components/BatchProgressList.vue' import ExperimentTimeline from '../../components/ExperimentTimeline.vue' import ReagentList from '../../components/ReagentList.vue' import SampleHierarchyTree from '../../components/SampleHierarchyTree.vue' import ScheduleCalendar from '../../components/ScheduleCalendar.vue' import WellPlate from '../../components/WellPlate.vue' /** * The `loading` / `error` props and the `#empty` / `#error` / `#loading` slots * are one contract shared by the data components, so they are asserted in one * place. Each entry pairs a component with the props that leave it empty and a * selector that only its populated rendering produces. */ const cases = [ { name: 'AuditTrail', component: AuditTrail, emptyProps: { entries: [] }, filledProps: { entries: [{ id: 'a', type: 'created', action: 'Created', timestamp: '2026-01-01T00:00:00Z' }], }, contentSelector: '.mint-audit-trail__list', }, { name: 'BatchProgressList', component: BatchProgressList, emptyProps: { items: [] }, filledProps: { items: [{ id: '1', label: 'One', status: 'pending' }] }, contentSelector: '.mint-batch-progress__list', }, { name: 'ExperimentTimeline', component: ExperimentTimeline, emptyProps: { modelValue: [] }, filledProps: { modelValue: [{ id: 's1', name: 'Step 1', order: 1, type: 'incubation' }] }, contentSelector: '.mint-timeline__step', }, { name: 'ReagentList', component: ReagentList, emptyProps: { modelValue: [] }, filledProps: { modelValue: [{ id: 'r1', name: 'Water' }] }, contentSelector: '.mint-reagent-list__table', }, { name: 'SampleHierarchyTree', component: SampleHierarchyTree, emptyProps: { nodes: [] }, filledProps: { nodes: [{ id: 'n1', label: 'Study', type: 'study' }] }, contentSelector: '.mint-sample-tree__node', }, ] as const describe('shared data component states', () => { describe.each(cases)('$name', ({ component, emptyProps, filledProps, contentSelector }) => { it('renders its content when neither loading nor error is passed', () => { const wrapper = mount(component, { props: { ...filledProps } as never }) expect(wrapper.find(contentSelector).exists()).toBe(true) expect(wrapper.find('[data-state]').exists()).toBe(false) }) it('renders an empty state instead of content when there is nothing to show', () => { const wrapper = mount(component, { props: { ...emptyProps } as never }) expect(wrapper.find('[data-state="empty"]').exists()).toBe(true) expect(wrapper.find(contentSelector).exists()).toBe(false) }) it('renders a loading state that replaces the content', () => { const wrapper = mount(component, { props: { ...filledProps, loading: true } as never }) expect(wrapper.find('[data-state="loading"]').exists()).toBe(true) expect(wrapper.find('.mint-spinner').exists()).toBe(true) expect(wrapper.find(contentSelector).exists()).toBe(false) }) it('renders an error state that wins over content and loading', () => { const wrapper = mount(component, { props: { ...filledProps, loading: true, error: 'Request failed' } as never, }) expect(wrapper.find('[data-state="error"]').exists()).toBe(true) expect(wrapper.find('[data-state="loading"]').exists()).toBe(false) expect(wrapper.find('.mint-empty-state__title').text()).toBe('Request failed') expect(wrapper.find(contentSelector).exists()).toBe(false) }) it('lets the empty slot replace the default placeholder', () => { const wrapper = mount(component, { props: { ...emptyProps } as never, slots: { empty: '

custom

' }, }) expect(wrapper.find('.slot-empty').exists()).toBe(true) expect(wrapper.find('[data-state="empty"]').exists()).toBe(false) }) it('lets the error slot replace the default placeholder', () => { const wrapper = mount(component, { props: { ...filledProps, error: 'Boom' } as never, slots: { error: '

custom

' }, }) expect(wrapper.find('.slot-error').exists()).toBe(true) expect(wrapper.find('[data-state="error"]').exists()).toBe(false) }) }) describe('opt-in empty states', () => { it('keeps the WellPlate grid when no emptyMessage is given', () => { const wrapper = mount(WellPlate, { props: { wells: {} } }) expect(wrapper.find('.mint-well-plate__scroll').exists()).toBe(true) expect(wrapper.find('[data-state]').exists()).toBe(false) }) it('replaces the WellPlate grid once an emptyMessage is given', () => { const wrapper = mount(WellPlate, { props: { wells: {}, emptyMessage: 'No plate loaded' }, }) expect(wrapper.find('[data-state="empty"]').exists()).toBe(true) expect(wrapper.find('.mint-well-plate__scroll').exists()).toBe(false) }) it('replaces the WellPlate grid on error', () => { const wrapper = mount(WellPlate, { props: { error: 'Boom' } }) expect(wrapper.find('[data-state="error"]').exists()).toBe(true) expect(wrapper.find('.mint-well-plate__scroll').exists()).toBe(false) }) it('keeps the ScheduleCalendar grid when no emptyMessage is given', () => { const wrapper = mount(ScheduleCalendar, { props: { events: [] } }) expect(wrapper.find('.mint-schedule__body').exists()).toBe(true) expect(wrapper.find('[data-state]').exists()).toBe(false) }) it('replaces the ScheduleCalendar grid once an emptyMessage is given', () => { const wrapper = mount(ScheduleCalendar, { props: { events: [], emptyMessage: 'Nothing scheduled' }, }) expect(wrapper.find('[data-state="empty"]').exists()).toBe(true) expect(wrapper.find('.mint-schedule__body').exists()).toBe(false) }) it('replaces the ScheduleCalendar grid while loading', () => { const wrapper = mount(ScheduleCalendar, { props: { loading: true } }) expect(wrapper.find('[data-state="loading"]').exists()).toBe(true) expect(wrapper.find('.mint-schedule__body').exists()).toBe(false) }) }) })