c8012896e3
- BillingView (/billing): page-head со stats (кошелёк/лиды/runway-дни) + pending banner v-alert info («1 платёж в обработке через ЮKassa, auto-cancel 30 мин») + 3 wallet-cards (Кошелёк ₽ primary card теало-нуар + LIVE; Баланс лидов ГЦК; Тариф «Команда» 990₽/мес + 3 фичи) + transactions panel (4 tabs + v-data-table 5 колонок: Дата/Операция/ID/Статус-chip/Сумма ± JBM tnum) + invoices list (PDF + 1С 8.3 XML). - composables/mockBilling.ts соответствует схеме v8.7 §4.4-4.5: 8 mock транзакций (types: topup/lead_charge/refund/tariff_charge; statuses: pending/completed/rejected) + 4 invoices (pdf/xml_1c83) + pending payment. - Маршрут /billing (meta.layout=app) в router + web.php. Format helpers: «+ N ₽» / «− N ₽» / «— 0 ₽» rejected; Intl.NumberFormat ru-RU. Vitest +11 (всего 90/90 за 7.96s): - заголовок + page-stats nbsp regex + pending banner + 3 wallet-cards + 3 фичи тарифа + 4 tabs + дефолт «Все» 8 строк + format «+/−» + rejected «— 0 ₽» + 4 invoice rows + PDF/1С 8.3 XML labels. Регресс: lint+type+format OK; vitest 90/90; vite build (BillingView lazy-chunk; VDataTable вынесен в общий chunk 79.84KB - shared с DealsView); story:build 16/23 за 32.16s; Pest 48/48 за 4.89s. CLAUDE.md v1.26->v1.27, реестр Открытых_вопросов v1.35->v1.36. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95 lines
3.9 KiB
TypeScript
95 lines
3.9 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
||
import { mount } from '@vue/test-utils';
|
||
import { createVuetify } from 'vuetify';
|
||
import BillingView from '../../resources/js/views/BillingView.vue';
|
||
import { MOCK_INVOICES, MOCK_TRANSACTIONS } from '../../resources/js/composables/mockBilling';
|
||
|
||
describe('BillingView.vue', () => {
|
||
const factory = () =>
|
||
mount(BillingView, {
|
||
global: { plugins: [createVuetify()] },
|
||
});
|
||
|
||
it('монтируется и содержит заголовок «Биллинг и тарифы»', () => {
|
||
const wrapper = factory();
|
||
expect(wrapper.find('h1').text()).toBe('Биллинг и тарифы');
|
||
});
|
||
|
||
it('содержит page-stats с кошельком, лидами, runway-днями', () => {
|
||
const wrapper = factory();
|
||
const text = wrapper.text();
|
||
expect(text).toMatch(/14\s+250\s*₽/); // кошелёк ₽
|
||
expect(text).toContain('285');
|
||
expect(text).toContain('лидов запас');
|
||
expect(text).toContain('4 дня');
|
||
});
|
||
|
||
it('показывает pending banner («1 платёж в обработке»)', () => {
|
||
const wrapper = factory();
|
||
const text = wrapper.text();
|
||
expect(text).toContain('1 платёж в обработке');
|
||
expect(text).toMatch(/5\s+000\s*₽/);
|
||
expect(text).toContain('ЮKassa');
|
||
});
|
||
|
||
it('содержит 3 wallet-cards (₽ / лиды / тариф)', () => {
|
||
const wrapper = factory();
|
||
const text = wrapper.text();
|
||
expect(text).toContain('Кошелёк ₽');
|
||
expect(text).toContain('LIVE');
|
||
expect(text).toContain('Баланс лидов (ГЦК)');
|
||
expect(text).toContain('Тариф');
|
||
expect(text).toContain('Команда');
|
||
expect(text).toContain('990 ₽/мес');
|
||
});
|
||
|
||
it('содержит 3 фичи тарифа', () => {
|
||
const wrapper = factory();
|
||
const text = wrapper.text();
|
||
expect(text).toContain('до 10 проектов');
|
||
expect(text).toContain('4 менеджера');
|
||
expect(text).toContain('Канбан, Webhook, API');
|
||
});
|
||
|
||
it('содержит 4 tab-кнопки в transactions panel', () => {
|
||
const wrapper = factory();
|
||
const text = wrapper.text();
|
||
expect(text).toContain('Все');
|
||
expect(text).toContain('Пополнения');
|
||
expect(text).toContain('Списания');
|
||
expect(text).toContain('Возвраты');
|
||
});
|
||
|
||
it('таблица транзакций по умолчанию (Все) содержит все mock-строки', () => {
|
||
const wrapper = factory();
|
||
const rows = wrapper.findAll('tbody tr');
|
||
expect(rows.length).toBe(MOCK_TRANSACTIONS.length);
|
||
});
|
||
|
||
it('форматирует положительную сумму как «+ N ₽» и отрицательную как «− N ₽»', () => {
|
||
const wrapper = factory();
|
||
const text = wrapper.text();
|
||
expect(text).toMatch(/\+\s+5\s+000\s*₽/); // +5000 топап
|
||
expect(text).toMatch(/−\s+6\s+600\s*₽/); // -6600 списание
|
||
});
|
||
|
||
it('rejected-транзакция показывает «— 0 ₽»', () => {
|
||
const wrapper = factory();
|
||
expect(wrapper.text()).toContain('— 0 ₽');
|
||
});
|
||
|
||
it('содержит invoices section с 4 строками', () => {
|
||
const wrapper = factory();
|
||
expect(wrapper.text()).toContain('Счета и УПД');
|
||
const rows = wrapper.findAll('.inv-row');
|
||
expect(rows.length).toBe(MOCK_INVOICES.length);
|
||
});
|
||
|
||
it('invoice показывает PDF или 1С 8.3 XML формат', () => {
|
||
const wrapper = factory();
|
||
const text = wrapper.text();
|
||
expect(text).toContain('PDF');
|
||
expect(text).toContain('1С 8.3 XML');
|
||
});
|
||
});
|