2f6e88e784
Три правки после демо Этапа 1 (все по TDD): 1. Карточка показывает ВСЕ данные поиска из payload (тип ProspectPayload 1:1 с dataclass Firm; computed infoRows рендерит юрлицо, директора+личный ИНН, контакты, бюджет Директа вилкой, каналы/коллтрекинг, оценку). Демо-сидер кладёт полный синтетический payload. 2. Начальнику отдаётся manager_counts; фильтр показывает «Имя (N)». 3. Колонка «Переговоры» сортируется по next_call_at ASC (просроченные сверху). Гейты: бэк 12/12, фронт 19/19, Larastan 0. НЕ выкачено (ждём разрешения владельца). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
125 lines
5.2 KiB
TypeScript
125 lines
5.2 KiB
TypeScript
import { mount } from '@vue/test-utils';
|
||
import { createVuetify } from 'vuetify';
|
||
import { describe, expect, it } from 'vitest';
|
||
import SalesProspectDialog from '../../resources/js/components/sales/SalesProspectDialog.vue';
|
||
import type { SalesProspect } from '../../resources/js/api/sales';
|
||
|
||
const vuetify = createVuetify();
|
||
|
||
function prospect(overrides: Partial<SalesProspect> = {}): SalesProspect {
|
||
return {
|
||
id: 1,
|
||
sales_user_id: 1,
|
||
stage: 'new',
|
||
firm_name: 'ООО Тест',
|
||
city: 'Ростов',
|
||
phone: '+7800',
|
||
site: 'test.ru',
|
||
inn: '6161',
|
||
rating_label: 'горячая',
|
||
payload: {},
|
||
next_call_at: null,
|
||
reason: null,
|
||
registered_email: null,
|
||
notes: null,
|
||
...overrides,
|
||
};
|
||
}
|
||
|
||
interface DialogVm {
|
||
action: string;
|
||
reason: string;
|
||
nextCallAt: string;
|
||
siteHref: string | null;
|
||
availableActions: { value: string; title: string }[];
|
||
infoRows: { label: string; value: string; href?: string }[];
|
||
submit: () => void;
|
||
}
|
||
|
||
function factory(p: SalesProspect) {
|
||
return mount(SalesProspectDialog, {
|
||
props: { modelValue: true, prospect: p },
|
||
global: { plugins: [vuetify] },
|
||
});
|
||
}
|
||
|
||
describe('SalesProspectDialog', () => {
|
||
it('siteHref добавляет https к домену', () => {
|
||
const w = factory(prospect());
|
||
expect((w.vm as unknown as DialogVm).siteHref).toBe('https://test.ru');
|
||
});
|
||
|
||
it('для stage=user действие «Отказ» недоступно', () => {
|
||
const w = factory(prospect({ stage: 'user' }));
|
||
const values = (w.vm as unknown as DialogVm).availableActions.map((a) => a.value);
|
||
expect(values).not.toContain('rejected');
|
||
});
|
||
|
||
it('для обычной стадии «Отказ» доступен', () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
const values = (w.vm as unknown as DialogVm).availableActions.map((a) => a.value);
|
||
expect(values).toContain('rejected');
|
||
});
|
||
|
||
it('«Переговоры» без даты — submit не эмитит save', () => {
|
||
const w = factory(prospect());
|
||
(w.vm as unknown as DialogVm).action = 'negotiation';
|
||
(w.vm as unknown as DialogVm).submit();
|
||
expect(w.emitted('save')).toBeFalsy();
|
||
});
|
||
|
||
it('«Отказ» с причиной — эмитит save с action=rejected', () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
(w.vm as unknown as DialogVm).action = 'rejected';
|
||
(w.vm as unknown as DialogVm).reason = 'дорого';
|
||
(w.vm as unknown as DialogVm).submit();
|
||
expect(w.emitted('save')?.[0]?.[0]).toMatchObject({ action: 'rejected', reason: 'дорого' });
|
||
});
|
||
|
||
it('infoRows показывает ВСЕ данные поиска из payload', () => {
|
||
const w = factory(
|
||
prospect({
|
||
inn: '6161000000',
|
||
payload: {
|
||
legal_name: 'ООО «Тест»',
|
||
ogrn: '1234567890123',
|
||
address: 'г. Ростов, ул. Ленина 1',
|
||
legal_status: 'действующая',
|
||
director: 'Иванов Иван Иванович',
|
||
director_post: 'директор',
|
||
director_is_ip: false,
|
||
director_inn: '616100000000',
|
||
contact_phone: '+79001234567',
|
||
contact_email: 'ivan@test.ru',
|
||
direct_budget_min: 80000,
|
||
direct_budget: 250000,
|
||
direct_keys: 1200,
|
||
direct_ads: 30,
|
||
channels: ['Яндекс.Директ', 'коллтрекинг'],
|
||
},
|
||
}),
|
||
);
|
||
const rows = (w.vm as unknown as DialogVm).infoRows;
|
||
const text = rows.map((r) => `${r.label}: ${r.value}`).join(' | ');
|
||
|
||
expect(text).toContain('Иванов Иван Иванович'); // директор
|
||
expect(text).toContain('директор'); // должность
|
||
expect(text).toContain('616100000000'); // личный ИНН руководителя (ПДн)
|
||
expect(text).toContain('+79001234567'); // тел. директора
|
||
expect(text).toContain('ivan@test.ru'); // почта директора
|
||
expect(text).toContain('коллтрекинг'); // каналы
|
||
expect(text).toContain('1234567890123'); // ОГРН
|
||
expect(text).toContain('6161000000'); // ИНН фирмы
|
||
expect(text).toContain('г. Ростов, ул. Ленина 1'); // адрес
|
||
expect(text).toMatch(/80 000.*250 000/); // вилка бюджета Директа
|
||
expect(text).toContain('1 200'); // запросы в Директе (разряды пробелом)
|
||
});
|
||
|
||
it('infoRows пропускает пустые поля payload', () => {
|
||
const w = factory(prospect({ payload: {}, inn: null }));
|
||
const labels = (w.vm as unknown as DialogVm).infoRows.map((r) => r.label);
|
||
expect(labels).not.toContain('ОГРН');
|
||
expect(labels).not.toContain('Директор');
|
||
});
|
||
});
|