Files
portal/app/tests/Frontend/ErrorView.spec.ts
T
Дмитрий 034657788d phase2(errors): ErrorView 404/403/500 + Laravel fallback
- ErrorView универсальный с конфигурацией через route.meta.errorCode
  (404/403/500). По v8_errors.html: full-bleed теало-нуар bg, top-brand,
  err-code 96px JBM с accent на средней цифре, title/desc, 2 actions,
  опциональные status-list (500) и err-id с copy-btn (403/500).
- AppShell: meta.layout='error' → RouterView напрямую (ErrorView сам
  предоставляет v-app).
- Router: /403, /500, catch-all /:pathMatch(.*)*  → ErrorView с meta.errorCode.
- web.php: явные Route::view + Route::fallback (срабатывает после Pest
  runtime-routes, не ломает SetTenantContextTest).
- cspell-words.txt: резолвится, роуты.

Vitest +8 (всего 118/118 за 9.39s):
- 404 default + 403 с REQ-ID + 500 с INC-ID + status-list (API/Telegram/YooKassa) +
  404 actions (На дашборд + Назад) + 403 mailto-link + 500 status-link +
  brand-блок + 404 НЕ содержит REQ/INC/status-list (regression-guard).
- stubs:{VApp/VMain} как passthrough — обходим Vuetify layout-injection в jsdom.

Регресс: lint+type+format OK; vitest 118/118; vite build (ErrorView lazy-chunk;
main app-chunk 101.01KB упал на 7KB благодаря shared chunk'ам); story:build
19/26 за 30.96s; Pest 48/48 за 4.88s (fallback не сломал runtime-routes).

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

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

103 lines
4.7 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 { createRouter, createMemoryHistory } from 'vue-router';
import ErrorView from '../../resources/js/views/errors/ErrorView.vue';
// ErrorView читает route.meta.errorCode для конфигурации экрана.
const mountErrorView = async (errorCode: '404' | '403' | '500') => {
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/error', component: ErrorView, meta: { errorCode } },
{ path: '/dashboard', component: { template: '<div>dashboard stub</div>' } },
],
});
await router.push('/error');
await router.isReady();
return mount(ErrorView, {
global: {
plugins: [createVuetify(), router],
// ErrorView содержит v-app + v-main — те же layout-проблемы что у DealDetailDrawer.
// Stub'им VApp/VMain как passthrough.
stubs: {
VApp: { template: '<div class="v-app-stub"><slot /></div>' },
VMain: { template: '<div class="v-main-stub"><slot /></div>' },
},
},
});
};
describe('ErrorView.vue', () => {
it('по умолчанию (errorCode=404) показывает «404 / Страница не найдена»', async () => {
const wrapper = await mountErrorView('404');
const text = wrapper.text();
expect(wrapper.find('.err-code').text()).toBe('404');
expect(text).toContain('Страница не найдена');
expect(text).toContain('Все рабочие экраны Лидерра доступны через дашборд');
});
it('errorCode=403 показывает «403 / У вас нет доступа» + RequestId', async () => {
const wrapper = await mountErrorView('403');
const text = wrapper.text();
expect(wrapper.find('.err-code').text()).toBe('403');
expect(text).toContain('У вас нет доступа');
expect(text).toContain('REQ-3F8A2-0007');
expect(text).toContain('Запрос');
});
it('errorCode=500 показывает «500 / Что-то пошло не так» + IncidentId + status-list', async () => {
const wrapper = await mountErrorView('500');
const text = wrapper.text();
expect(wrapper.find('.err-code').text()).toBe('500');
expect(text).toContain('Что-то пошло не так');
expect(text).toContain('INC-2026-0507-0034');
expect(text).toContain('Инцидент');
// status-list только на 500.
expect(text).toContain('API · OK');
expect(text).toContain('Telegram · деградация');
});
it('404 содержит «На дашборд» primary + «Назад» secondary', async () => {
const wrapper = await mountErrorView('404');
const text = wrapper.text();
expect(text).toContain('На дашборд');
expect(text).toContain('Назад');
});
it('403 содержит «На дашборд» + «Написать в поддержку» (mailto)', async () => {
const wrapper = await mountErrorView('403');
const text = wrapper.text();
expect(text).toContain('На дашборд');
expect(text).toContain('Написать в поддержку');
const mailtoLink = wrapper.find('a[href^="mailto:"]');
expect(mailtoLink.exists()).toBe(true);
expect(mailtoLink.attributes('href')).toBe('mailto:support@liderra.app');
});
it('500 содержит «Попробовать снова» + «Статус сервиса» (external link)', async () => {
const wrapper = await mountErrorView('500');
const text = wrapper.text();
expect(text).toContain('Попробовать снова');
expect(text).toContain('Статус сервиса');
const statusLink = wrapper.find('a[href^="https://status."]');
expect(statusLink.exists()).toBe(true);
});
it('содержит брендовый блок «Лидерра.» в шапке', async () => {
const wrapper = await mountErrorView('404');
const brand = wrapper.find('.top-brand');
expect(brand.exists()).toBe(true);
expect(brand.text()).toContain('Лидерра');
});
it('404 НЕ содержит RequestId или status-list', async () => {
const wrapper = await mountErrorView('404');
const text = wrapper.text();
expect(text).not.toContain('REQ-');
expect(text).not.toContain('INC-');
expect(text).not.toContain('API · OK');
});
});