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]);
}
});
});