290e7dbc34
- 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>
49 lines
1.9 KiB
TypeScript
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);
|
|
});
|
|
});
|