Files
portal/app/tests/Frontend/ReportsView.spec.ts
T
Дмитрий 4de39e70b2 phase2(reports): ReportsView - асинхронная генерация отчётов с очередью
- ReportsView (/reports): form-card (Запросить) + jobs-list panel.
  Form: 4 type-cards radio-grid (Сделки/Менеджеры/Источники/Биллинг) +
  date-range + Проект/Менеджер v-select + 4 fmt-кнопки (CSV/XLSX/JSON/PDF) +
  quota-banner alert (CTO-7: 2/3 одновременных + CTO-6: 3 попытки/7 дней) +
  Запустить/Сброс.
- Jobs-list: 5 mock-rows × 4 статуса (done/running/queued/failed) с icon +
  meta JBM (FORMAT · size · rows · timeText) + status-chip + actions
  (Скачать done / Повторить failed && attempt<3 / Отменить queued /
  Удалить done|failed). v-progress-linear для running 62%.
- composables/mockReports.ts: type unions (4×4×4) + 5 mock-jobs + MOCK_QUOTA
  (CTO-6/7 значения).
- Маршрут /reports (meta.layout=app, lazy-import) в router + web.php.

Vitest +12 (всего 110/110 за 9.38s):
- заголовок + page-stats + 4 type-cards + дефолт active + 4 формата +
  quota-banner («2 из 3» / «3 попыток retry» / «7 дней») + 5 job-rows +
  done-«Готов»+Скачать-aria + running-«62%»+progressbar role + queued-Отменить +
  failed-«Ошибка»+«S3 timeout»+Повторить-aria + клик-переключение active.

Регресс: lint+type+format OK; vitest 110/110; vite build (ReportsView lazy-chunk;
main 108.19 KB); story:build 18/25 за 30.77s; Pest 48/48 за 4.58s.

CLAUDE.md v1.28->v1.29, реестр Открытых_вопросов v1.37->v1.38.

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

100 lines
4.3 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 ReportsView from '../../resources/js/views/ReportsView.vue';
import { MOCK_JOBS, REPORT_TYPES } from '../../resources/js/composables/mockReports';
describe('ReportsView.vue', () => {
const factory = () =>
mount(ReportsView, {
global: { plugins: [createVuetify()] },
});
it('монтируется и содержит заголовок «Отчёты»', () => {
const wrapper = factory();
expect(wrapper.find('h1').text()).toBe('Отчёты');
});
it('содержит page-stats с очередью и обработано-за-месяц', () => {
const wrapper = factory();
const text = wrapper.text();
expect(text).toContain('очередь');
expect(text).toContain('обработано за месяц');
expect(text).toContain('средний размер');
});
it('содержит 4 карточки типа отчёта (REPORT_TYPES)', () => {
const wrapper = factory();
const cards = wrapper.findAll('.tc-card');
expect(cards).toHaveLength(REPORT_TYPES.length);
const text = wrapper.text();
REPORT_TYPES.forEach((t) => expect(text).toContain(t.name));
});
it('по умолчанию активна карточка «Сделки · детально»', () => {
const wrapper = factory();
const dealsCard = wrapper.findAll('.tc-card').find((c) => c.text().includes('Сделки · детально'));
expect(dealsCard?.classes()).toContain('active');
});
it('содержит 4 кнопки формата (CSV/XLSX/JSON/PDF)', () => {
const wrapper = factory();
const text = wrapper.text();
['CSV', 'XLSX', 'JSON', 'PDF'].forEach((f) => expect(text).toContain(f));
});
it('quota-banner показывает 2/3 одновременных + 3 retry / 7 дней (CTO-6/CTO-7)', () => {
const wrapper = factory();
const text = wrapper.text();
expect(text).toContain('2 из 3');
expect(text).toContain('3 попыток retry');
expect(text).toContain('7 дней');
});
it('jobs-list содержит все 5 mock-job', () => {
const wrapper = factory();
const rows = wrapper.findAll('.job-row');
expect(rows.length).toBe(MOCK_JOBS.length);
});
it('done-job показывает «Готов» + Скачать-кнопку', () => {
const wrapper = factory();
const text = wrapper.text();
expect(text).toContain('Готов');
// Кнопка Скачать (mdi-download) присутствует — ищем aria-label.
expect(wrapper.find('[aria-label="Скачать"]').exists()).toBe(true);
});
it('running-job показывает «В работе · NN%» с прогрессом', () => {
const wrapper = factory();
const text = wrapper.text();
expect(text).toContain('В работе · 62%');
// v-progress-linear рендерит aria role=progressbar.
expect(wrapper.find('[role="progressbar"]').exists()).toBe(true);
});
it('queued-job показывает «В очереди» + Отменить-кнопку', () => {
const wrapper = factory();
const text = wrapper.text();
expect(text).toContain('В очереди');
expect(wrapper.find('[aria-label="Отменить"]').exists()).toBe(true);
});
it('failed-job показывает «Ошибка» + Повторить (canRetry для attempt<3)', () => {
const wrapper = factory();
const text = wrapper.text();
expect(text).toContain('Ошибка');
expect(text).toContain('S3 timeout');
expect(wrapper.find('[aria-label="Повторить"]').exists()).toBe(true);
});
it('переключение типа отчёта меняет active-карточку', async () => {
const wrapper = factory();
const cards = wrapper.findAll('.tc-card');
const managersCard = cards.find((c) => c.text().includes('Менеджеры'));
await managersCard!.trigger('click');
await wrapper.vm.$nextTick();
expect(managersCard!.classes()).toContain('active');
});
});