Files
portal/app/tests/Frontend/KanbanColumn.spec.ts
T
Дмитрий 261a3cd46f fix(portal): проект на паузе больше не пишет «Собирает заявки» + итог колонки в «Канбане»
Два бага, которых не видел ни один тест — их нашёл робот, снимавший инструкции
для гостей.

1) Карточка остановленного проекта показывала одновременно «На паузе» и зелёную
плашку «Собирает заявки»: плашка смотрела только на связь с поставщиком (там
всё «ок») и про паузу не знала. Теперь серым: «Остановлен вами — заявки не
приходят».

2) В «Канбане» сумма в шапке колонки всегда была прочерком: считалась по старому
демо-полю, которого у живых заявок нет (там ноль), хотя на карточках стояла
честная цена. Теперь считает по настоящему списанию.

Тесты: сначала падающие, потом фикс. Весь фронт 1229 зелёных.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 08:58:33 +03:00

61 lines
3.1 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 { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import { createVuetify } from 'vuetify';
import KanbanColumn from '../../resources/js/components/kanban/KanbanColumn.vue';
import { LEAD_STATUSES } from '../../resources/js/composables/leadStatuses';
import { MOCK_DEALS } from '../../resources/js/composables/mockDeals';
describe('KanbanColumn.vue', () => {
const status = LEAD_STATUSES[0]; // 'new'
const dealsForNew = MOCK_DEALS.filter((d) => d.statusSlug === 'new');
const factory = (props: { status: typeof status; deals: typeof MOCK_DEALS }) =>
mount(KanbanColumn, {
props,
global: { plugins: [createVuetify()] },
});
it('header содержит nameRu статуса и count', () => {
const wrapper = factory({ status, deals: dealsForNew });
const text = wrapper.text();
expect(text).toContain(status.nameRu);
expect(text).toContain(String(dealsForNew.length));
});
it('total = sum dealcost (форматированный «N ₽»)', () => {
const deals = MOCK_DEALS.slice(0, 2); // первые 2 — 1850 + 2400 = 4250
const wrapper = factory({ status, deals });
expect(wrapper.text()).toMatch(/4\s+250\s*₽/);
});
// Живые заявки приходят с сервера: списание лежит в costKopecks, а демо-поле cost у них
// ноль. Итог колонки считался по cost → в шапке всегда стоял прочерк, хотя на карточках
// честные 55 ₽ (поймано на кадрах разборов 14.07.2026).
it('total считает по настоящему списанию (costKopecks), а не по демо-полю cost', () => {
const deals = MOCK_DEALS.slice(0, 3).map((d) => ({ ...d, cost: 0, costKopecks: 55_00 }));
const wrapper = factory({ status, deals });
expect(wrapper.text()).toMatch(/165\s*₽/); // 3 × 55 ₽
expect(wrapper.text()).not.toContain('—');
});
it('total = «—» при пустом списке', () => {
const wrapper = factory({ status, deals: [] });
expect(wrapper.text()).toContain('—');
expect(wrapper.text()).toContain('пусто');
});
it('применяет colorHex статуса как CSS var --accent', () => {
const wrapper = factory({ status, deals: dealsForNew });
const head = wrapper.find('.column-head');
// Vue 3 inline-style сохраняет как есть (case-preserve) — матчим case-insensitive.
expect(head.attributes('style')?.toLowerCase()).toContain(status.colorHex.toLowerCase());
});
it('пробрасывает open от карточки → openDeal с id', async () => {
const wrapper = factory({ status, deals: dealsForNew });
await wrapper.findComponent({ name: 'KanbanCard' }).vm.$emit('open', dealsForNew[0].id);
expect(wrapper.emitted('openDeal')).toBeTruthy();
expect(wrapper.emitted('openDeal')?.[0]).toEqual([dealsForNew[0].id]);
});
});