Files
portal/app/tests/Frontend/ProjectLimitOverloadDialog.spec.ts
T
Дмитрий 0a4d4f3224 feat(projects): UX «запустится сам после пополнения» + бейдж «ждёт пополнения»
Окно нехватки баланса («Проекты») и «Конкурентное поле» теперь честно
говорят, что взведённый проект запустится сам после пополнения (вместо
«пополните, чтобы запустить»); добавлена кнопка «Пока не надо» (оставить
черновиком). Бейдж взведённого проекта — «ждёт пополнения / запустится
сам» вместо «не хватает баланса». Синхронно в «Проектах» и «Конкурентном
поле». Тесты: ProjectLimitOverloadDialog + ProjectCardBadge обновлены/
дополнены; 8 фронт-спеков 59/59.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 21:41:53 +03:00

127 lines
5.2 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, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import { createVuetify } from 'vuetify';
import { createRouter, createMemoryHistory } from 'vue-router';
import ProjectLimitOverloadDialog from '../../resources/js/components/projects/ProjectLimitOverloadDialog.vue';
/**
* Task 12 — переделка диалога «перегрузки лимита» в «запуск отложен».
* Billing v2 Spec C §6.2, план 2026-07-02-launch-gate-balance.md.
* Новое поле payload: topup_rub (сумма пополнения в рублях).
*/
const payload = {
current_balance_rub: '200.00',
current_capacity_leads: 4,
would_be_required_leads: 12,
deficit_leads: 8,
topup_rub: '400.00',
};
const mountDialog = (props?: Record<string, unknown>) =>
mount(ProjectLimitOverloadDialog, {
props: { modelValue: true, payload, ...props },
global: {
plugins: [createVuetify()],
stubs: { VDialog: { template: '<div><slot /></div>' } },
},
});
describe('ProjectLimitOverloadDialog — Task 12 (запуск отложен)', () => {
it('монтируется без ошибок', () => {
const w = mountDialog();
expect(w.exists()).toBe(true);
});
it('показывает текст «не запущен»', () => {
const w = mountDialog();
expect(w.text().toLowerCase()).toContain('не запущен');
});
it('показывает сумму пополнения из topup_rub', () => {
const w = mountDialog();
expect(w.text()).toContain('400');
});
it('содержит кнопку [data-test="topup"]', () => {
const w = mountDialog();
expect(w.find('[data-test="topup"]').exists()).toBe(true);
});
it('содержит кнопку [data-test="reduce"]', () => {
const w = mountDialog();
expect(w.find('[data-test="reduce"]').exists()).toBe(true);
});
it('содержит кнопку закрытия [data-test="close"]', () => {
const w = mountDialog();
expect(w.find('[data-test="close"]').exists()).toBe(true);
});
it('[data-test="topup"] закрывает диалог (update:modelValue=false)', async () => {
const w = mountDialog();
await w.find('[data-test="topup"]').trigger('click');
const emitted = w.emitted('update:modelValue') as unknown[][];
expect(emitted).toBeTruthy();
expect(emitted[0][0]).toBe(false);
});
it('[data-test="topup"] ведёт в /billing через router.push', async () => {
const router = createRouter({
history: createMemoryHistory(),
routes: [{ path: '/billing', component: { template: '<div>billing</div>' } }],
});
const push = vi.spyOn(router, 'push');
const w = mount(ProjectLimitOverloadDialog, {
props: { modelValue: true, payload },
global: { plugins: [createVuetify(), router], stubs: { VDialog: { template: '<div><slot /></div>' } } },
});
await w.find('[data-test="topup"]').trigger('click');
expect(push).toHaveBeenCalledWith('/billing');
});
it('[data-test="reduce"] эмитит set-zero', async () => {
const w = mountDialog();
await w.find('[data-test="reduce"]').trigger('click');
expect(w.emitted('set-zero')).toBeTruthy();
});
it('[data-test="close"] закрывает диалог (update:modelValue=false)', async () => {
const w = mountDialog();
await w.find('[data-test="close"]').trigger('click');
const emitted = w.emitted('update:modelValue') as unknown[][];
expect(emitted).toBeTruthy();
expect(emitted[0][0]).toBe(false);
});
it('payload=null — без падения, карточка не рендерится', () => {
const w = mount(ProjectLimitOverloadDialog, {
props: { modelValue: true, payload: null },
global: { plugins: [createVuetify()], stubs: { VDialog: { template: '<div><slot /></div>' } } },
});
expect(w.find('[data-testid="overload-dialog"]').exists()).toBe(false);
});
it('текст на «вы», без «ты»', () => {
const w = mountDialog();
const text = w.text();
expect(text).not.toContain('У тебя');
expect(text).not.toContain('пополни ');
expect(text).not.toContain('поставь ');
});
// Фича 2026-07-15: честный текст «запустится сам после пополнения» + «Пока не надо».
it('говорит, что проект запустится сам после пополнения', () => {
const w = mountDialog();
expect(w.text()).toContain('запустится сам');
});
it('содержит кнопку «Пока не надо» [data-test="keep-draft"] и эмитит keep-as-draft', async () => {
const w = mountDialog();
const btn = w.find('[data-test="keep-draft"]');
expect(btn.exists()).toBe(true);
await btn.trigger('click');
expect(w.emitted('keep-as-draft')).toBeTruthy();
});
});