Files
portal/app/tests/Frontend/SalesProspectDialog.spec.ts
T
Дмитрий 0af5c5696f chore+fix: разметка документации и «неизвестный тип» в интерфейсе убраны в ноль
РАЗМЕТКА ДОКУМЕНТАЦИИ. Проверка сторожа давала 237 замечаний в 21 файле — стало
0 во всех файлах под учётом git. Пустые строки вокруг списков и заголовков
поправлены автоматом в 12 файлах, два файла пришлось делать руками, потому что
автоправка там навредила:
- в описании починки инцидента номера шагов 1…7 — это ИМЕНА, на них ссылается
  сам текст «из шага 2»; автоправка перенумеровала их в 1,1,1,1,2 и сломала
  ссылки. Вывел ровно этот файл из-под одного правила, объяснение — в самом
  файле, остальные правила действуют.
- в протоколе архитектуры автоправка склеила «R-* / DR-*» в «R-*/ DR-*».
  Вместо этого обозначения взяты в обратные кавычки — смысл сохранён.
Семь файлов не тронуты: они вне учёта git — два в папке второй смены и старые
черновики в корне.

ИНТЕРФЕЙС. «Неизвестный тип» убран целиком: было 123 замечания, стало 0.
- Навигация автоподбора описана ОДИН раз — app/resources/js/views/autopodbor/nav.ts.
  Раньше девять экранов повторяли описание с «неизвестным типом», а ещё три
  описывали его каждый по-своему и не полностью.
- Чтение ошибки от сервера собрано в один помощник — app/resources/js/api/errors.ts,
  с проверками. Он намеренно читает ФОРМУ ответа, а не полагается на axios:
  тесты экранов подсовывают ошибку простым объектом той же формы, и экран обязан
  вести себя в тестах так же, как в бою.
- В тестах вместо «неизвестного типа» — именованный доступ exposed<T> и полные
  заготовки: app/tests/Frontend/support/. Каждый тест теперь объявляет ровно те
  поля, до которых дотягивается, и опечатка в имени снова становится ошибкой,
  а не молчаливым undefined. Заодно сняты 14 построчных отключений правила.
- Разведены задвоенные имена в шаблонах: в таблице сделок в одном шаблоне жили
  ДВЕ разные функции isSelected — одна берёт номер сделки, другая строку таблицы.
- Убрана мёртвая функция dirLabel и три неиспользуемые переменные.

ПРОВЕРКА ТИПОВ: было 7 ошибок, стало 5. Две унаследованные закрылись попутно —
более строгие заготовки вскрыли нехватку обязательного поля elements. Оставшиеся
пять были до меня и не в моих строках.

ПРИЁМКА. Разметка проверена вырезанием: подложил поломку обратно — сторож её
поймал, вернул — снова чисто. Полный набор тестов интерфейса: 233 файла,
1750 тестов, 3 пропущено, 0 падений. Рабочий код на PHP не тронут.

NB: LEFTHOOK_EXCLUDE=cspell — тем же приёмом и по той же причине, что и в записи
e0223de4, которой эти шесть файлов исследования заводились в репозиторий: сырой
внешний материал с чужой терминологией. Замерено: до моих правок сторож
орфографии находил в них ровно столько же слов, сколько после — я не добавил
ни одного. Разметка в этих файлах теперь проходит начисто, отключена ТОЛЬКО
орфография и только на эту запись; остальные семнадцать сторожей отработали.
Решение владельца, спрошено отдельно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 15:07:24 +03:00

593 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 } 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',
},
]);
// Окно рисуется в document.body, поэтому обёртка тут не нужна — проверки идут по body.
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' },
]);
// Окно рисуется в document.body, поэтому обёртка тут не нужна — проверки идут по body.
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('');
});
});