4de39e70b2
- 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>
100 lines
4.3 KiB
TypeScript
100 lines
4.3 KiB
TypeScript
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');
|
||
});
|
||
});
|