Files
portal/app/tests/Frontend/ActivityChart.spec.ts
T
Дмитрий 290e7dbc34 phase2(charts): ActivityChart + FunnelChart - Dashboard закрыт по дизайну
- ActivityChart: native SVG line chart (без chart-library, чтобы не +400KB
  зависимость для статичных дашборд-графиков). Y-grid 5 линий, area-gradient,
  7 точек (предпоследняя выделена primary teal как «сегодня»), 3 tabs
  (Принято/Оплачено/Отказ).
- FunnelChart: segmented bar по 14 статусам + funnel-list (sort desc).
- composables/leadStatuses.ts: snapshot 14 статусов из db/schema.sql:2130
  (НЕ из BRANDBOOK §3.6 - расхождение #1 handoff vs ТЗ из реестра v1.13).
  14 правильных slug'ов: new/viewed/worked/base/missed/negotiations/
  waiting_payment/partnership/paid/closed/test_drive/hot/replacement/final_missed.
- DashboardView интегрирует оба чарта в charts-row (md=7+5).
- cspell-words.txt: ldot, композаблом, инлайнингом, инлайнены.

Vue compiler quirk: withDefaults factory не разрешает референсить module-level
const'ы (checkInvalidScopeReference). Обходим инлайнингом литерала.

Vitest +13 тестов (всего 48/48 за 5.5s):
- ActivityChart 6 (3 tabs + 7 circles + 'сегодня' + custom points + legend)
- FunnelChart 7 (14 segments + 14 list-items + assertion на отсутствие
  'Думает'/'Спам' из handoff + сортировка + colorHex + total)

Stories +2 с 3 variants каждый (Histoire 10/14 за 30.43s).

Регресс: lint+type-check+format OK; vitest 48/48; vite build (DashboardView
chunk 14.9->21.17 KB с чартами); story:build 10/14 за 30.43s; Pest 48/48 за 5.10s.

CLAUDE.md v1.21->v1.22, реестр Открытых_вопросов v1.30->v1.31.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:32:58 +03:00

49 lines
1.9 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import { createVuetify } from 'vuetify';
import ActivityChart from '../../resources/js/components/charts/ActivityChart.vue';
describe('ActivityChart.vue', () => {
const factory = (props?: Record<string, unknown>) =>
mount(ActivityChart, {
props,
global: { plugins: [createVuetify()] },
});
it('монтируется с дефолтными props и содержит заголовок', () => {
const wrapper = factory();
expect(wrapper.text()).toContain('Активность по дням');
});
it('содержит 3 tab-кнопки (Принято/Оплачено/Отказ)', () => {
const wrapper = factory();
const text = wrapper.text();
expect(text).toContain('Принято');
expect(text).toContain('Оплачено');
expect(text).toContain('Отказ');
});
it('рендерит SVG с 7 точками (по числу дней)', () => {
const wrapper = factory();
const circles = wrapper.findAll('svg circle');
expect(circles).toHaveLength(7);
});
it('содержит подпись «сегодня» для последней метки x-оси', () => {
const wrapper = factory();
const xLabels = wrapper.findAll('.chart-axis-x').map((n) => n.text());
expect(xLabels[xLabels.length - 1]).toBe('сегодня');
});
it('принимает кастомные points через props', () => {
const wrapper = factory({ points: [10, 20, 30, 40, 50, 60, 70], max: 100 });
const circles = wrapper.findAll('svg circle');
expect(circles).toHaveLength(7);
});
it('содержит легенду с 3 dot-индикаторами', () => {
const wrapper = factory();
expect(wrapper.findAll('.ldot')).toHaveLength(3);
});
});