import { describe, it, expect } from 'vitest'; import { mount } from '@vue/test-utils'; import { createVuetify } from 'vuetify'; import DealsTable from '../../resources/js/components/deals/DealsTable.vue'; import type { MockDeal } from '../../resources/js/composables/mockDeals'; // ResponsiveTable показывает таблицу только на десктопе (>=1280); эти тесты // проверяют табличный слой — фиксируем десктоп-ширину. Object.defineProperty(window, 'innerWidth', { configurable: true, value: 1440 }); const vuetify = createVuetify(); const sampleDeals: MockDeal[] = [ { id: 1, name: '+7 (916) 100-00-01', phone: '+7 (916) 100-00-01', statusSlug: 'new', project: 'Окна', manager: { initials: 'AD', name: 'Admin' }, cost: 0, receivedMinutesAgo: 5, signalType: 'call', city: 'Москва', comment: 'звонил', receivedAt: '2026-05-15T09:00:00+00:00', }, { id: 2, name: '+7 (916) 100-00-02', phone: '+7 (916) 100-00-02', statusSlug: 'new', project: 'Двери', manager: { initials: 'AD', name: 'Admin' }, cost: 0, receivedMinutesAgo: 30, signalType: 'site', city: null, comment: null, receivedAt: '2026-05-14T09:00:00+00:00', }, ]; describe('DealsTable', () => { it('рендерит колонки реестра лидов', () => { const w = mount(DealsTable, { props: { deals: sampleDeals, selectedIds: [], statusBySlug: new Map() }, global: { plugins: [vuetify] }, }); const headers = w.findAll('thead th').map((h) => h.text()); ['Телефон', 'Источник', 'Город', 'Статус', 'Комментарий', 'Поставлен'].forEach((label) => { expect(headers.some((h) => h.includes(label))).toBe(true); }); }); it('город без значения рендерится как «—»', () => { const w = mount(DealsTable, { props: { deals: sampleDeals, selectedIds: [], statusBySlug: new Map() }, global: { plugins: [vuetify] }, }); expect(w.text()).toContain('—'); }); it('select-all чекбокс имеет aria-label', () => { const w = mount(DealsTable, { props: { deals: sampleDeals, selectedIds: [], statusBySlug: new Map() }, global: { plugins: [vuetify] }, }); expect(w.find('th .v-selection-control input[type="checkbox"][aria-label="Выбрать все сделки"]').exists()).toBe( true, ); }); it('клик по строке эмитит row-click с deal', async () => { const w = mount(DealsTable, { props: { deals: sampleDeals, selectedIds: [], statusBySlug: new Map() }, global: { plugins: [vuetify] }, }); await w.find('tbody tr').trigger('click'); expect(w.emitted('row-click')?.[0]?.[0]).toMatchObject({ id: 1 }); }); // Автоподбор (2026-07-08): колонки «Конкурент» + «Источник». const RouterLinkStub = { name: 'RouterLink', props: ['to'], template: '', }; function mountTable(deals: MockDeal[]) { return mount(DealsTable, { props: { deals, selectedIds: [], statusBySlug: new Map() }, global: { plugins: [vuetify], stubs: { RouterLink: RouterLinkStub } }, }); } it('рендерит колонки «Конкурент» и «Источник»', () => { const w = mountTable(sampleDeals); const headers = w.findAll('thead th').map((h) => h.text()); ['Конкурент', 'Источник'].forEach((label) => { expect(headers.some((h) => h.includes(label))).toBe(true); }); }); it('рисует ссылку на конкурента и на источник', () => { const deal: MockDeal = { id: 10, name: '79000000001', phone: '79000000001', statusSlug: 'new', project: 'X', manager: { initials: '—', name: '—' }, cost: 0, receivedMinutesAgo: 1, competitorId: 3, competitorName: 'AutoMoney', sourceIdentifier: '79135396546', sourceSignalType: 'call', sourceProjectId: 5, sourceIsActive: true, sourceExtraCount: 0, }; const w = mountTable([deal]); expect(w.text()).toContain('AutoMoney'); expect(w.text()).toContain('79135396546'); expect(w.text()).toContain('Звонки'); const links = w.findAllComponents(RouterLinkStub); expect(links.length).toBe(2); // Конкурент → autopodbor?competitor=3 expect(links[0].props('to')).toMatchObject({ name: 'autopodbor', query: { competitor: '3' } }); // Источник → autopodbor?competitor=3&project=5 expect(links[1].props('to')).toMatchObject({ name: 'autopodbor', query: { competitor: '3', project: '5' } }); }); it('«Конкурент» — только бренд до первой запятой, без описания', () => { const deal: MockDeal = { id: 12, name: '79000000002', phone: '79000000002', statusSlug: 'new', project: 'X', manager: { initials: '—', name: '—' }, cost: 0, receivedMinutesAgo: 1, competitorId: 4, competitorName: 'AutoMoney, микрокредитная компания', sourceIdentifier: '79135396546', sourceSignalType: 'call', sourceProjectId: 5, sourceIsActive: true, sourceExtraCount: 0, }; const w = mountTable([deal]); expect(w.text()).toContain('AutoMoney'); expect(w.text()).not.toContain('микрокредитная'); }); it('показывает « · +N» при нескольких источниках проекта', () => { const deal: MockDeal = { id: 11, name: 'x', phone: 'x', statusSlug: 'new', project: 'X', manager: { initials: '—', name: '—' }, cost: 0, receivedMinutesAgo: 1, competitorId: 3, competitorName: 'Сфера займа24', sourceIdentifier: '79029826282', sourceSignalType: 'call', sourceProjectId: 5, sourceIsActive: true, sourceExtraCount: 2, }; const w = mountTable([deal]); expect(w.text()).toContain('+2'); }); it('без конкурента — «Конкурент» = прочерк, «Источник» = название проекта без ссылок', () => { const deal: MockDeal = { id: 12, name: 'y', phone: '79000000002', statusSlug: 'new', project: 'Обычный проект', manager: { initials: '—', name: '—' }, cost: 0, receivedMinutesAgo: 1, competitorId: null, competitorName: null, sourceIdentifier: null, sourceSignalType: null, sourceProjectId: null, sourceIsActive: null, sourceExtraCount: 0, }; const w = mountTable([deal]); expect(w.text()).toContain('Обычный проект'); expect(w.findAllComponents(RouterLinkStub).length).toBe(0); }); });