9ee4e9a79f
- 12-я колонка «Корзина», результат «В корзину» просит только причину; - в шапке «Отказа» дробь «4/2» с подсказкой «из них 2 после ручного тестирования»; при нуле дробь не рисуется — «69/0» это шум; - у «Выслано КП» поле даты подписано «если договорились» и необязательно; - орган фильтра по датам на ОБОИХ экранах, два режима + период. Приёмка глазами пройдена живьём по всем семи пунктам, включая «Воронку отдела» (снимки в docs/superpowers/screens/2026-08-01-korzina-filtry/priemka/). Браузер поймал то, чего не видели тесты: при смене режима оставался прежний период, и «что менялось за завтра» давало пустую доску — теперь период возвращается к «Сегодня», на это заведён отдельный тест. Прогон: сервер 1245/1245, фронт 1567/1567. Журнал схемы — v9.31.
591 lines
27 KiB
TypeScript
591 lines
27 KiB
TypeScript
import { mount, flushPromises } from '@vue/test-utils';
|
||
import { createVuetify } from 'vuetify';
|
||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||
|
||
vi.mock('../../resources/js/api/sales', () => ({
|
||
updateProspectContacts: vi.fn().mockResolvedValue({}),
|
||
listProspectNotes: vi.fn().mockResolvedValue([]),
|
||
addProspectNote: vi.fn(),
|
||
}));
|
||
import SalesProspectDialog from '../../resources/js/components/sales/SalesProspectDialog.vue';
|
||
import { updateProspectContacts, listProspectNotes, addProspectNote } from '../../resources/js/api/sales';
|
||
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',
|
||
prev_stage: null,
|
||
firm_name: 'ООО Тест',
|
||
city: 'Ростов',
|
||
phone: '+7800',
|
||
site: 'test.ru',
|
||
inn: '6161',
|
||
rating_label: 'горячая',
|
||
source: 'search',
|
||
legal_name: null,
|
||
contacts: [],
|
||
payload: {},
|
||
next_call_at: null,
|
||
reason: null,
|
||
registered_email: null,
|
||
test_channel: null,
|
||
test_target: null,
|
||
kp_channel: null,
|
||
kp_target: null,
|
||
notes: null,
|
||
...overrides,
|
||
};
|
||
}
|
||
|
||
interface DialogVm {
|
||
action: string;
|
||
reason: string;
|
||
nextCallAt: string;
|
||
email: string;
|
||
testChannel: string;
|
||
testTarget: string;
|
||
kpChannel: string;
|
||
kpTarget: string;
|
||
resultReminder: string;
|
||
siteHref: string | null;
|
||
availableActions: { value: string; title: string }[];
|
||
infoRows: { label: string; value: string; href?: string }[];
|
||
contactRows: { name: string; position: string; phones: string[] }[];
|
||
notes: { id: number; kind: string; title: string | null; body: string; author: string | null; created_at: string }[];
|
||
summary: string;
|
||
submit: () => Promise<void>;
|
||
error: string;
|
||
}
|
||
|
||
function factory(p: SalesProspect) {
|
||
return mount(SalesProspectDialog, {
|
||
props: { modelValue: true, prospect: p },
|
||
global: { plugins: [vuetify] },
|
||
});
|
||
}
|
||
|
||
describe('SalesProspectDialog', () => {
|
||
beforeEach(() => {
|
||
vi.clearAllMocks();
|
||
document.body.innerHTML = '';
|
||
});
|
||
|
||
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', async () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
(w.vm as unknown as DialogVm).action = 'rejected';
|
||
(w.vm as unknown as DialogVm).reason = 'дорого';
|
||
await (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(' | ');
|
||
|
||
// Показываем — из девятки владельца (§18)
|
||
expect(text).toContain('Иванов Иван Иванович'); // директор
|
||
expect(text).toContain('директор'); // должность
|
||
expect(text).toContain('+79001234567'); // тел. директора (предполагаемый)
|
||
expect(text).toContain('коллтрекинг'); // каналы
|
||
expect(text).toContain('6161000000'); // ИНН фирмы
|
||
expect(text).toContain('г. Ростов, ул. Ленина 1'); // адрес
|
||
expect(text).toMatch(/80 000.*250 000/); // вилка бюджета
|
||
|
||
// НЕ показываем — вне девятки
|
||
expect(text).not.toContain('616100000000'); // личный ИНН руководителя (ПДн)
|
||
expect(text).not.toContain('ivan@test.ru'); // почта директора
|
||
expect(text).not.toContain('1234567890123'); // ОГРН
|
||
expect(text).not.toContain('1 200'); // запросы в Директе
|
||
});
|
||
|
||
it('«Зарегистрировался» доступен на обычной стадии, скрыт на user', () => {
|
||
const a = (w: ReturnType<typeof factory>) => (w.vm as unknown as DialogVm).availableActions.map((x) => x.value);
|
||
expect(a(factory(prospect({ stage: 'negotiation' })))).toContain('registered');
|
||
expect(a(factory(prospect({ stage: 'user' })))).not.toContain('registered');
|
||
expect(a(factory(prospect({ stage: 'testing' })))).not.toContain('registered');
|
||
});
|
||
|
||
it('«Зарегистрировался» без e-mail — save не эмитится; с e-mail — эмитит {action:registered,email}', async () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
(w.vm as unknown as DialogVm).action = 'registered';
|
||
await (w.vm as unknown as DialogVm).submit();
|
||
expect(w.emitted('save')).toBeFalsy();
|
||
|
||
(w.vm as unknown as DialogVm).email = 'client@example.com';
|
||
await (w.vm as unknown as DialogVm).submit();
|
||
expect(w.emitted('save')?.[0]?.[0]).toMatchObject({ action: 'registered', email: 'client@example.com' });
|
||
});
|
||
|
||
it('«Вернуть в Новые» есть только на стадии «Взят в работу» и эмитит back_to_new', async () => {
|
||
const inWork = factory(prospect({ stage: 'in_work' }));
|
||
const vm = inWork.vm as unknown as DialogVm & { canReturnToNew: boolean; returnToNew: () => void };
|
||
expect(vm.canReturnToNew).toBe(true);
|
||
vm.returnToNew();
|
||
expect(inWork.emitted('save')?.[0]?.[0]).toMatchObject({ action: 'back_to_new' });
|
||
|
||
const other = factory(prospect({ stage: 'negotiation' }));
|
||
expect((other.vm as unknown as { canReturnToNew: boolean }).canReturnToNew).toBe(false);
|
||
});
|
||
|
||
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('Директор');
|
||
});
|
||
|
||
it('юрлицо из карточки показывается в списке данных', () => {
|
||
const w = factory(prospect({ legal_name: 'ООО «Улыбка Плюс»' }));
|
||
const rows = (w.vm as unknown as DialogVm).infoRows;
|
||
expect(rows.find((r) => r.label === 'Юрлицо')?.value).toBe('ООО «Улыбка Плюс»');
|
||
});
|
||
|
||
it('юрлицо из карточки важнее старого значения из поиска', () => {
|
||
const w = factory(
|
||
prospect({ legal_name: 'ООО «Из карточки»', payload: { legal_name: 'ООО «Из поиска»' } }),
|
||
);
|
||
const rows = (w.vm as unknown as DialogVm).infoRows;
|
||
expect(rows.find((r) => r.label === 'Юрлицо')?.value).toBe('ООО «Из карточки»');
|
||
});
|
||
|
||
it('показывает ТОЛЬКО девять полей владельца, в его порядке', () => {
|
||
const w = factory(
|
||
prospect({
|
||
legal_name: 'ООО "ОМДЕНТ"',
|
||
site: 'omdent.ru',
|
||
inn: '5501261235',
|
||
phone: '+73812244222',
|
||
payload: {
|
||
address: 'г Омск, ул Химиков, д 34',
|
||
channels: ['Яндекс.Директ', 'VK-myTarget (пиксель)'],
|
||
direct_budget_min: 10415,
|
||
direct_budget: 23656,
|
||
director: 'Захаров Александр Владимирович',
|
||
director_post: 'Генеральный директор',
|
||
contact_phone: '9136518088, 9133343217',
|
||
// лишнее из поиска — показывать НЕ должны
|
||
ogrn: '1145543043095',
|
||
legal_status: 'действующая',
|
||
director_inn: '550405815600',
|
||
contact_email: 'zav5700@mail.ru',
|
||
confidence: 'высокая',
|
||
advertises: 'да',
|
||
direct_keys: 8,
|
||
},
|
||
}),
|
||
);
|
||
const labels = (w.vm as unknown as DialogVm).infoRows.map((r) => r.label);
|
||
|
||
expect(labels).toEqual([
|
||
'Юрлицо',
|
||
'Сайт',
|
||
'ИНН фирмы',
|
||
'Адрес',
|
||
'Телефон общий',
|
||
'Каналы',
|
||
'Бюджет',
|
||
'Директор',
|
||
'Тел. директора (предполагаемый)',
|
||
]);
|
||
});
|
||
|
||
it('оценка, ОГРН, почта директора и прочее больше не показываются', () => {
|
||
const w = factory(
|
||
prospect({ rating_label: 'горячая', payload: { ogrn: '114', contact_email: 'z@m.ru' } }),
|
||
);
|
||
const labels = (w.vm as unknown as DialogVm).infoRows.map((r) => r.label);
|
||
expect(labels).not.toContain('Оценка');
|
||
expect(labels).not.toContain('ОГРН');
|
||
expect(labels).not.toContain('Почта директора');
|
||
expect(labels).not.toContain('Достоверность');
|
||
});
|
||
|
||
it('пустые поля не показываются', () => {
|
||
const w = factory(prospect({ legal_name: null, site: null, inn: null, phone: null, payload: {} }));
|
||
expect((w.vm as unknown as DialogVm).infoRows).toEqual([]);
|
||
});
|
||
|
||
it('директор с должностью склеивается в одну строку', () => {
|
||
const w = factory(
|
||
prospect({ payload: { director: 'Захаров А. В.', director_post: 'Генеральный директор' } }),
|
||
);
|
||
const rows = (w.vm as unknown as DialogVm).infoRows;
|
||
expect(rows.find((r) => r.label === 'Директор')?.value).toBe('Захаров А. В. — Генеральный директор');
|
||
});
|
||
|
||
it('бюджет показывается вилкой', () => {
|
||
const w = factory(prospect({ payload: { direct_budget_min: 10415, direct_budget: 23656 } }));
|
||
const rows = (w.vm as unknown as DialogVm).infoRows;
|
||
expect(rows.find((r) => r.label === 'Бюджет')?.value).toBe('10 415 – 23 656 ₽/мес');
|
||
});
|
||
|
||
it('контакты карточки разворачиваются в форму для правки', () => {
|
||
const w = factory(
|
||
prospect({ contacts: [{ name: 'Захаров', position: 'директор', phones: ['+79136518088'] }] }),
|
||
);
|
||
const rows = (w.vm as unknown as DialogVm).contactRows;
|
||
expect(rows).toHaveLength(1);
|
||
expect(rows[0]).toEqual({ name: 'Захаров', position: 'директор', phones: ['+79136518088'] });
|
||
});
|
||
|
||
it('без контактов даёт одну пустую строку — есть куда записать после обзвона', () => {
|
||
const w = factory(prospect());
|
||
const rows = (w.vm as unknown as DialogVm).contactRows;
|
||
expect(rows).toHaveLength(1);
|
||
expect(rows[0].name).toBe('');
|
||
});
|
||
|
||
it('журнал подгружается при открытии карточки', async () => {
|
||
(listProspectNotes as ReturnType<typeof vi.fn>).mockResolvedValue([
|
||
{ id: 2, kind: 'note', title: null, body: 'СВЕЖАЯ', author: 'Таня', created_at: '2026-07-18T10:00:00+03:00' },
|
||
{ id: 1, kind: 'stage', title: null, body: 'Взял в работу', author: 'Таня', created_at: '2026-07-17T10:00:00+03:00' },
|
||
]);
|
||
const w = factory(prospect({ id: 7 }));
|
||
await flushPromises();
|
||
|
||
expect(listProspectNotes).toHaveBeenCalledWith(7);
|
||
expect((w.vm as unknown as DialogVm).notes).toHaveLength(2);
|
||
expect((w.vm as unknown as DialogVm).notes[0].body).toBe('СВЕЖАЯ');
|
||
});
|
||
|
||
it('краткое содержание уходит вместе с результатом разговора', async () => {
|
||
const w = factory(prospect({ stage: 'in_work' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
vm.action = 'negotiation';
|
||
vm.nextCallAt = '2026-07-20T10:30';
|
||
vm.summary = 'Директор просил перезвонить с ценами';
|
||
await vm.submit();
|
||
|
||
expect(w.emitted('save')?.[0]?.[0]).toMatchObject({
|
||
action: 'negotiation',
|
||
next_call_at: '2026-07-20T10:30',
|
||
summary: 'Директор просил перезвонить с ценами',
|
||
});
|
||
});
|
||
|
||
it('недозвон отдаёт причину и дату следующего перезвона', async () => {
|
||
const w = factory(prospect({ stage: 'in_work' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
vm.action = 'no_answer';
|
||
vm.reason = 'не берёт трубку';
|
||
vm.nextCallAt = '2026-07-21T09:00';
|
||
await vm.submit();
|
||
|
||
expect(w.emitted('save')?.[0]?.[0]).toMatchObject({
|
||
action: 'no_answer',
|
||
reason: 'не берёт трубку',
|
||
next_call_at: '2026-07-21T09:00',
|
||
});
|
||
});
|
||
|
||
it('недозвон без даты перезвона тоже сохраняется', async () => {
|
||
const w = factory(prospect({ stage: 'in_work' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
vm.action = 'no_answer';
|
||
vm.reason = 'занято';
|
||
await vm.submit();
|
||
|
||
const payload = w.emitted('save')?.[0]?.[0] as Record<string, unknown>;
|
||
expect(payload.action).toBe('no_answer');
|
||
expect(payload.next_call_at ?? null).toBeNull();
|
||
});
|
||
it('одна кнопка «Сохранить»: контакты уходят вместе с результатом', async () => {
|
||
const w = factory(prospect({ id: 42, stage: 'in_work' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
vm.contactRows = [
|
||
{ name: 'Денчик', position: 'Дир', phones: ['9990000009', ''] },
|
||
{ name: '', position: '', phones: [''] },
|
||
];
|
||
vm.action = 'negotiation';
|
||
vm.nextCallAt = '2026-07-20T17:35';
|
||
vm.summary = 'Сказал наберёт меня маркетолог';
|
||
await vm.submit();
|
||
await flushPromises();
|
||
|
||
expect(updateProspectContacts).toHaveBeenCalledWith(42, [
|
||
{ name: 'Денчик', position: 'Дир', phones: ['9990000009'] },
|
||
]);
|
||
expect(w.emitted('save')?.[0]?.[0]).toMatchObject({ action: 'negotiation' });
|
||
});
|
||
|
||
it('«Сохранить» без результата сохраняет контакты и закрывает окно', async () => {
|
||
const w = factory(prospect({ id: 42 }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
vm.contactRows = [{ name: 'Денчик', position: '', phones: ['9990000009'] }];
|
||
await vm.submit();
|
||
await flushPromises();
|
||
|
||
expect(updateProspectContacts).toHaveBeenCalled();
|
||
expect(w.emitted('save')).toBeFalsy();
|
||
expect(w.emitted('update:modelValue')?.[0]?.[0]).toBe(false);
|
||
});
|
||
|
||
it('контакты не сохранились → результат не отправляем, показываем ошибку', async () => {
|
||
(updateProspectContacts as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error('нет сети'));
|
||
const w = factory(prospect({ id: 42, stage: 'in_work' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
vm.action = 'no_answer';
|
||
vm.reason = 'занято';
|
||
await vm.submit();
|
||
await flushPromises();
|
||
|
||
expect(w.emitted('save')).toBeFalsy();
|
||
expect(vm.error).not.toBe('');
|
||
});
|
||
|
||
it('отдельной кнопки «Сохранить контакты» больше нет', () => {
|
||
const w = factory(prospect());
|
||
expect(w.text()).not.toContain('Сохранить контакты');
|
||
});
|
||
it('запись с заголовком: сверху что решили, снизу текст разговора', async () => {
|
||
(listProspectNotes as ReturnType<typeof vi.fn>).mockResolvedValue([
|
||
{
|
||
id: 3,
|
||
kind: 'note',
|
||
title: 'Договорились на созвон 20.07.2026 17:35',
|
||
body: 'Сказал наберёт меня маркетолог',
|
||
author: 'Таня Михалева',
|
||
created_at: '2026-07-18T17:39:00+03:00',
|
||
},
|
||
]);
|
||
const w = factory(prospect({ id: 7 }));
|
||
await flushPromises();
|
||
|
||
const item = document.body.querySelector('.prospect-log-item');
|
||
expect(item?.querySelector('.prospect-log-title')?.textContent?.trim())
|
||
.toBe('Договорились на созвон 20.07.2026 17:35');
|
||
expect(item?.querySelector('.prospect-log-body')?.textContent?.trim())
|
||
.toBe('Сказал наберёт меня маркетолог');
|
||
});
|
||
|
||
it('запись без заголовка рисуется без пустой строки сверху', async () => {
|
||
(listProspectNotes as ReturnType<typeof vi.fn>).mockResolvedValue([
|
||
{ id: 1, kind: 'stage', title: null, body: 'Взял в работу', author: 'Таня', created_at: '2026-07-18T10:00:00+03:00' },
|
||
]);
|
||
const w = factory(prospect({ id: 7 }));
|
||
await flushPromises();
|
||
|
||
expect(document.body.querySelector('.prospect-log-title')).toBeNull();
|
||
expect(document.body.querySelector('.prospect-log-body')?.textContent?.trim()).toBe('Взял в работу');
|
||
});
|
||
it('оба новых результата есть в списке на обычной стадии', () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
const values = (w.vm as unknown as DialogVm).availableActions.map((a) => a.value);
|
||
expect(values).toContain('manual_testing');
|
||
expect(values).toContain('kp_sent');
|
||
});
|
||
|
||
it('платящему клиенту оба новых результата недоступны', () => {
|
||
for (const stage of ['topped_up', 'user']) {
|
||
const values = (factory(prospect({ stage })).vm as unknown as DialogVm)
|
||
.availableActions.map((a) => a.value);
|
||
expect(values).not.toContain('manual_testing');
|
||
expect(values).not.toContain('kp_sent');
|
||
}
|
||
});
|
||
|
||
it('после ручного тестирования «Зарегистрировался» остаётся доступен', () => {
|
||
const values = (factory(prospect({ stage: 'manual_testing' })).vm as unknown as DialogVm)
|
||
.availableActions.map((a) => a.value);
|
||
expect(values).toContain('registered');
|
||
});
|
||
|
||
it('ручное тестирование: без канала, без значения и без даты — save не эмитится', async () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
|
||
vm.action = 'manual_testing';
|
||
await vm.submit();
|
||
expect(w.emitted('save')).toBeFalsy();
|
||
|
||
vm.testChannel = 'phone';
|
||
await vm.submit();
|
||
expect(w.emitted('save')).toBeFalsy();
|
||
|
||
vm.testTarget = '79990001122';
|
||
await vm.submit();
|
||
expect(w.emitted('save')).toBeFalsy();
|
||
});
|
||
|
||
it('ручное тестирование: с каналом, значением и датой эмитит save с нужными полями', async () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
|
||
vm.action = 'manual_testing';
|
||
vm.testChannel = 'site';
|
||
vm.testTarget = ' primer-firma.ru ';
|
||
vm.nextCallAt = '2026-08-05T12:00';
|
||
await vm.submit();
|
||
await flushPromises();
|
||
|
||
expect(w.emitted('save')?.[0][0]).toEqual({
|
||
action: 'manual_testing',
|
||
test_channel: 'site',
|
||
test_target: 'primer-firma.ru',
|
||
next_call_at: '2026-08-05T12:00',
|
||
summary: '',
|
||
});
|
||
});
|
||
|
||
it('выслано КП: без канала и без адреса — save не эмитится', async () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
|
||
vm.action = 'kp_sent';
|
||
await vm.submit();
|
||
expect(w.emitted('save')).toBeFalsy();
|
||
|
||
vm.kpChannel = 'email';
|
||
await vm.submit();
|
||
expect(w.emitted('save')).toBeFalsy();
|
||
});
|
||
|
||
it('выслано КП: без даты созвона всё равно сохраняется — дата необязательна', async () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
|
||
vm.action = 'kp_sent';
|
||
vm.kpChannel = 'email';
|
||
vm.kpTarget = 'director@primer-firma.ru';
|
||
await vm.submit();
|
||
await flushPromises();
|
||
|
||
// Поля next_call_at в письме на сервер нет вовсе — не пустая строка.
|
||
expect(w.emitted('save')?.[0][0]).toEqual({
|
||
action: 'kp_sent',
|
||
kp_channel: 'email',
|
||
kp_target: 'director@primer-firma.ru',
|
||
summary: '',
|
||
});
|
||
});
|
||
|
||
it('выслано КП: с каналом, адресом и датой эмитит save с нужными полями', async () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
|
||
vm.action = 'kp_sent';
|
||
vm.kpChannel = 'whatsapp';
|
||
vm.kpTarget = '79990001122';
|
||
vm.nextCallAt = '2026-08-05T12:00';
|
||
await vm.submit();
|
||
await flushPromises();
|
||
|
||
expect(w.emitted('save')?.[0][0]).toEqual({
|
||
action: 'kp_sent',
|
||
kp_channel: 'whatsapp',
|
||
kp_target: '79990001122',
|
||
next_call_at: '2026-08-05T12:00',
|
||
summary: '',
|
||
});
|
||
});
|
||
it('корзина: результат есть в списке на обычной стадии, платящему — нет', () => {
|
||
const onWork = (factory(prospect({ stage: 'negotiation' })).vm as unknown as DialogVm)
|
||
.availableActions.map((a) => a.value);
|
||
expect(onWork).toContain('trash');
|
||
|
||
for (const stage of ['topped_up', 'user']) {
|
||
const values = (factory(prospect({ stage })).vm as unknown as DialogVm)
|
||
.availableActions.map((a) => a.value);
|
||
expect(values).not.toContain('trash');
|
||
}
|
||
});
|
||
|
||
it('корзина: без причины save не эмитится', async () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
|
||
vm.action = 'trash';
|
||
await vm.submit();
|
||
expect(w.emitted('save')).toBeFalsy();
|
||
});
|
||
|
||
it('корзина: с причиной эмитит save без даты созвона', async () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
const vm = w.vm as unknown as DialogVm;
|
||
|
||
vm.action = 'trash';
|
||
vm.reason = 'не наш профиль';
|
||
// Дату могли набрать до переключения результата — в корзину она уйти не должна.
|
||
vm.nextCallAt = '2026-08-05T12:00';
|
||
await vm.submit();
|
||
await flushPromises();
|
||
|
||
expect(w.emitted('save')?.[0][0]).toEqual({
|
||
action: 'trash',
|
||
reason: 'не наш профиль',
|
||
summary: '',
|
||
});
|
||
});
|
||
|
||
it('корзина: из корзины можно вернуться — «Зарегистрировался» остаётся доступен', () => {
|
||
const values = (factory(prospect({ stage: 'trash' })).vm as unknown as DialogVm)
|
||
.availableActions.map((a) => a.value);
|
||
expect(values).toContain('registered');
|
||
});
|
||
|
||
it('на карточке в ручном тестировании показывает, куда отправляли тест', () => {
|
||
const w = factory(prospect({ stage: 'manual_testing', test_channel: 'phone', test_target: '79990001122' }));
|
||
expect((w.vm as unknown as DialogVm).resultReminder).toBe('Тест отправлен: телефон 79990001122');
|
||
});
|
||
|
||
it('на карточке с высланным КП показывает, куда его отправляли', () => {
|
||
const w = factory(prospect({ stage: 'kp_sent', kp_channel: 'whatsapp', kp_target: '79990001122' }));
|
||
expect((w.vm as unknown as DialogVm).resultReminder).toBe('КП отправлено: ватсап 79990001122');
|
||
});
|
||
|
||
it('на обычной стадии напоминания нет', () => {
|
||
const w = factory(prospect({ stage: 'negotiation' }));
|
||
expect((w.vm as unknown as DialogVm).resultReminder).toBe('');
|
||
});
|
||
});
|