2026-05-08 17:32:58 +03:00
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
|
|
import { mount } from '@vue/test-utils';
|
|
|
|
|
|
import { createVuetify } from 'vuetify';
|
|
|
|
|
|
import FunnelChart from '../../resources/js/components/charts/FunnelChart.vue';
|
|
|
|
|
|
import { LEAD_STATUSES } from '../../resources/js/composables/leadStatuses';
|
|
|
|
|
|
|
|
|
|
|
|
describe('FunnelChart.vue', () => {
|
|
|
|
|
|
const factory = (props?: Record<string, unknown>) =>
|
|
|
|
|
|
mount(FunnelChart, {
|
|
|
|
|
|
props,
|
|
|
|
|
|
global: { plugins: [createVuetify()] },
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('монтируется и содержит заголовок «Воронка»', () => {
|
|
|
|
|
|
const wrapper = factory();
|
|
|
|
|
|
expect(wrapper.text()).toContain('Воронка');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-17 18:18:00 +03:00
|
|
|
|
it('содержит ровно 5 сегментов в bar (по числу lead_statuses)', () => {
|
2026-05-08 17:32:58 +03:00
|
|
|
|
const wrapper = factory();
|
|
|
|
|
|
const segs = wrapper.findAll('.funnel-seg');
|
2026-05-17 18:18:00 +03:00
|
|
|
|
expect(segs).toHaveLength(5);
|
2026-05-08 17:32:58 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-17 18:18:00 +03:00
|
|
|
|
it('содержит ровно 5 list-items', () => {
|
2026-05-08 17:32:58 +03:00
|
|
|
|
const wrapper = factory();
|
|
|
|
|
|
const items = wrapper.findAll('.funnel-list-item');
|
2026-05-17 18:18:00 +03:00
|
|
|
|
expect(items).toHaveLength(5);
|
2026-05-08 17:32:58 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('использует правильные slug-имена из schema (НЕ из BRANDBOOK)', () => {
|
|
|
|
|
|
const wrapper = factory();
|
|
|
|
|
|
const text = wrapper.text();
|
2026-05-17 18:18:00 +03:00
|
|
|
|
// Проверка что все 5 имён из lead_statuses присутствуют.
|
2026-05-08 17:32:58 +03:00
|
|
|
|
LEAD_STATUSES.forEach((s) => {
|
|
|
|
|
|
expect(text).toContain(s.nameRu);
|
|
|
|
|
|
});
|
|
|
|
|
|
// Гарантия что мы НЕ используем имена из BRANDBOOK §3.6 (расхождение #1).
|
|
|
|
|
|
// Например, "Думает" / "Не дозвон." / "Спам" / "КП" — handoff-only.
|
|
|
|
|
|
expect(text).not.toContain('Думает');
|
|
|
|
|
|
expect(text).not.toContain('Спам');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-17 18:18:00 +03:00
|
|
|
|
it('сортирует список по убыванию count (in_progress 96 — первый)', () => {
|
2026-05-08 17:32:58 +03:00
|
|
|
|
const wrapper = factory();
|
|
|
|
|
|
const names = wrapper.findAll('.funnel-list-item .name').map((n) => n.text());
|
2026-05-17 18:18:00 +03:00
|
|
|
|
expect(names[0]).toBe('В работе'); // count=96 — самый большой в DEFAULT_COUNTS.
|
2026-05-08 17:32:58 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('применяет colorHex из lead_statuses к dots и сегментам', () => {
|
|
|
|
|
|
const wrapper = factory();
|
|
|
|
|
|
const segs = wrapper.findAll<HTMLElement>('.funnel-seg');
|
|
|
|
|
|
// Первый segment в bar — статус с sortOrder=1 (new, цвет #3B82F6).
|
|
|
|
|
|
expect(segs[0].element.style.background).toContain('rgb(59, 130, 246)');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('считает total как сумму counts', () => {
|
2026-05-17 18:18:00 +03:00
|
|
|
|
const wrapper = factory({ counts: { new: 10, won: 20 } });
|
2026-05-08 17:32:58 +03:00
|
|
|
|
const text = wrapper.text();
|
|
|
|
|
|
// total = 10 + 20 = 30 (остальные слаги с counts={} → 0).
|
|
|
|
|
|
expect(text).toContain('30 лидов');
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|