import { describe, it, expect, beforeEach } from 'vitest'; import { mount } from '@vue/test-utils'; import { createVuetify } from 'vuetify'; import { createPinia, setActivePinia } from 'pinia'; import DealDetailDrawer from '../../resources/js/components/deals/DealDetailDrawer.vue'; import { MOCK_DEALS } from '../../resources/js/composables/mockDeals'; import { MOCK_EVENTS } from '../../resources/js/composables/mockDealEvents'; beforeEach(() => { setActivePinia(createPinia()); }); // DealDetailDrawer использует v-navigation-drawer, который требует layout- // контекст от v-app/v-layout. В Vitest auto-import недоступен — stub'им // v-navigation-drawer как passthrough div чтобы slot-content рендерился // и был доступен для assertion. describe('DealDetailDrawer.vue', () => { const factory = (props: { open: boolean; deal: (typeof MOCK_DEALS)[number] | null }) => mount(DealDetailDrawer, { props, global: { plugins: [createVuetify()], stubs: { VNavigationDrawer: { template: '
', props: ['modelValue'], }, }, }, }); const sampleDeal = MOCK_DEALS[0]; // Анна Соколова it('не рендерит контент когда open=false', () => { const wrapper = factory({ open: false, deal: sampleDeal }); expect(wrapper.find('.drawer-stub').exists()).toBe(false); }); it('не рендерит контент когда deal=null (даже при open=true)', () => { const wrapper = factory({ open: true, deal: null }); // Drawer открыт, но deal нет — content внутри v-if не рендерится. const stub = wrapper.find('.drawer-stub'); if (stub.exists()) { // Нет hero/section элементов внутри. expect(wrapper.find('.hero').exists()).toBe(false); } }); it('рендерит hero с именем сделки и id', () => { const wrapper = factory({ open: true, deal: sampleDeal }); const text = wrapper.text(); expect(text).toContain(sampleDeal.name); expect(text).toContain(`#${sampleDeal.id}`); }); it('рендерит phone как кликабельную ссылку tel:', () => { const wrapper = factory({ open: true, deal: sampleDeal }); const phoneLink = wrapper.find('.phone-link'); expect(phoneLink.exists()).toBe(true); expect(phoneLink.attributes('href')).toMatch(/^tel:\+/); expect(phoneLink.text()).toBe(sampleDeal.phone); }); it('рендерит status-chip с nameRu статуса сделки', () => { const wrapper = factory({ open: true, deal: sampleDeal }); // sampleDeal.statusSlug='new' → 'Новые'. expect(wrapper.text()).toContain('Новые'); }); it('рендерит секцию параметров с проектом, стоимостью, менеджером', () => { const wrapper = factory({ open: true, deal: sampleDeal }); const text = wrapper.text(); expect(text).toContain('Параметры'); expect(text).toContain(sampleDeal.project); expect(text).toContain(sampleDeal.manager.name); expect(text).toMatch(/1\s+850\s*₽/); // sampleDeal.cost = 1850 }); it('рендерит timeline с MOCK_EVENTS (6 событий)', () => { const wrapper = factory({ open: true, deal: sampleDeal }); const items = wrapper.findAll('.timeline-item'); expect(items).toHaveLength(MOCK_EVENTS.length); }); it('emit-ит update:open=false при close-кнопке', async () => { const wrapper = factory({ open: true, deal: sampleDeal }); // Vuetify v-btn рендерит как button. close-btn — единственный с aria-label. const closeBtn = wrapper.find('button[aria-label="Закрыть панель"]'); if (closeBtn.exists()) { await closeBtn.trigger('click'); expect(wrapper.emitted('update:open')).toBeTruthy(); expect(wrapper.emitted('update:open')?.[0]).toEqual([false]); } }); });