import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import { mount } from '@vue/test-utils' import { afterEach, describe, expect, it, vi } from 'vitest' import { nextTick } from 'vue' import AppToastContainer from '../../components/AppToastContainer.vue' import { useToast } from '../../composables/useToast' describe('AppToastContainer', () => { const toast = useToast() afterEach(() => { toast.clear() }) function mountContainer(component = AppToastContainer) { return mount(component, { global: { stubs: { teleport: true, }, }, }) } it('renders toasts from the shared toast queue', async () => { const wrapper = mountContainer() toast.success('Analysis complete', 10000) await nextTick() expect(wrapper.find('.mint-toast__item--success').exists()).toBe(true) expect(wrapper.find('.mint-toast__message').text()).toBe('Analysis complete') await wrapper.find('.mint-toast__item').trigger('click') expect(toast.toasts.value).toHaveLength(0) }) it('renders message-only toasts as the title (back-compat)', async () => { const wrapper = mountContainer() toast.info('Saved', 10000) await nextTick() expect(wrapper.find('.mint-toast__title').text()).toBe('Saved') expect(wrapper.find('.mint-toast__detail').exists()).toBe(false) expect(wrapper.find('.mint-toast__progress').exists()).toBe(true) }) it('renders detail and caps actions at two', async () => { const wrapper = mountContainer() toast.push({ title: 'Deleted 3 samples', detail: 'Undo within 30 s.', type: 'error', duration: 10000, actions: [ { label: 'Undo', onClick: () => {}, primary: true }, { label: 'Dismiss', onClick: () => {} }, { label: 'Extra', onClick: () => {} }, ], }) await nextTick() expect(wrapper.find('.mint-toast__detail').text()).toBe('Undo within 30 s.') expect(wrapper.findAll('.mint-toast__action')).toHaveLength(2) expect(wrapper.find('.mint-toast__action--primary').text()).toBe('Undo') }) it('renders no TTL bar and never auto-dismisses when progress is indeterminate', async () => { vi.useFakeTimers() try { const wrapper = mountContainer() toast.push({ title: 'Uploading', type: 'info', progress: 'indeterminate', duration: 100 }) await nextTick() expect(wrapper.find('.mint-toast__progress--indeterminate').exists()).toBe(true) vi.advanceTimersByTime(1000) expect(toast.toasts.value).toHaveLength(1) } finally { vi.useRealTimers() } }) it('renders the ×N pill for a collapsed stack', async () => { const wrapper = mountContainer() toast.push({ title: 'Retention time drift', type: 'warning', count: 4, duration: 10000 }) await nextTick() expect(wrapper.find('.mint-toast__count').text()).toBe('×4') expect(wrapper.find('.mint-toast__item--stacked').exists()).toBe(true) }) it('should place toasts 1rem from the top when no top bar offset is set', () => { const styles = readFileSync(resolve(process.cwd(), 'src/styles/components/toast.css'), 'utf8') expect(styles).toMatch(/\.mint-toast__container\s*\{[^}]*top:\s*var\(--mint-toast-offset-top,\s*1rem\);/s) }) })