Files
portal/app/tests/Frontend/AppLayout.spec.ts
T
Дмитрий 1d1353931d phase2(user-chip): реальный user в AppLayout/AdminLayout + Logout-menu
- AppLayout: userInitials/userShortName computed из auth-store, fallback цепочка → email → '?' / 'Гость'
- AdminLayout: тот же паттерн с админ-defaults 'АО' / 'Админ Оператор'
- v-menu offset=8 на user-chip: email + Настройки/Выйти из админки + Выйти
- handleLogout async: auth.logout() (swallows API errors) → router.push('/login')
- Vitest +3 в AppLayout.spec.ts (всего 145/145): store-mock + null-user + email-fallback
- AppShell.spec.ts получил createPinia в plugins
- Регресс: lint+type+format OK, vitest 145/145 за 11.01с, build 855ms, story:build 21/28 за 32.11с, Pest 67/67 за 6.16с
- CLAUDE.md v1.34→v1.35, реестр v1.43→v1.44

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

111 lines
4.5 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import { createVuetify } from 'vuetify';
import { createRouter, createMemoryHistory } from 'vue-router';
import AppLayout from '../../resources/js/layouts/AppLayout.vue';
import { useAuthStore } from '../../resources/js/stores/auth';
import type { AuthUser } from '../../resources/js/api/auth';
const mockUser: AuthUser = {
id: 1,
email: 'ivan.petrov@example.ru',
first_name: 'Иван',
last_name: 'Петров',
tenant_id: 1,
totp_enabled: false,
last_login_at: null,
};
// AppLayout содержит sidebar (8 nav-items в 3 группах) + topbar (crumb/search/user) + RouterView.
const mountAppLayout = async (path = '/dashboard', user: AuthUser | null = mockUser) => {
setActivePinia(createPinia());
const auth = useAuthStore();
auth.user = user;
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/dashboard', component: { template: '<div>dashboard</div>' } },
{ path: '/deals', component: { template: '<div>deals</div>' } },
{ path: '/kanban', component: { template: '<div>kanban</div>' } },
{ path: '/reminders', component: { template: '<div>reminders</div>' } },
{ path: '/billing', component: { template: '<div>billing</div>' } },
{ path: '/reports', component: { template: '<div>reports</div>' } },
{ path: '/managers', component: { template: '<div>managers</div>' } },
{ path: '/settings', component: { template: '<div>settings</div>' } },
],
});
await router.push(path);
await router.isReady();
return mount(AppLayout, {
global: { plugins: [createVuetify(), router] },
});
};
describe('AppLayout.vue', () => {
it('монтируется без ошибок', async () => {
const wrapper = await mountAppLayout();
expect(wrapper.exists()).toBe(true);
});
it('содержит брендовый блок «Лидерра.» в sidebar', async () => {
const wrapper = await mountAppLayout();
expect(wrapper.text()).toContain('Лидерра');
});
it('содержит 3 nav-группы: Работа, Финансы, Команда', async () => {
const wrapper = await mountAppLayout();
const text = wrapper.text();
expect(text).toContain('Работа');
expect(text).toContain('Финансы');
expect(text).toContain('Команда');
});
it('содержит все 8 nav-пунктов', async () => {
const wrapper = await mountAppLayout();
const text = wrapper.text();
['Дашборд', 'Сделки', 'Канбан', 'Напоминания', 'Биллинг', 'Отчёты', 'Менеджеры', 'Настройки'].forEach((label) =>
expect(text).toContain(label),
);
});
it('показывает счётчики только у пунктов с count', async () => {
const wrapper = await mountAppLayout();
const text = wrapper.text();
expect(text).toContain('247'); // Сделки
expect(text).toContain('12'); // Напоминания
expect(text).toContain('4'); // Менеджеры
});
it('breadcrumb показывает текущую страницу', async () => {
const wrapper = await mountAppLayout('/dashboard');
expect(wrapper.text()).toContain('Рабочая область');
expect(wrapper.text()).toContain('Дашборд');
});
it('user-chip показывает initials и shortName из store user', async () => {
const wrapper = await mountAppLayout();
const text = wrapper.text();
expect(text).toContain('ИП'); // initials Иван Петров
expect(text).toContain('Иван П.'); // shortName
});
it('при null user (гость) показывает «?» и «Гость»', async () => {
const wrapper = await mountAppLayout('/dashboard', null);
const text = wrapper.text();
expect(text).toContain('Гость');
expect(text).toContain('?');
});
it('при отсутствии first_name fallback на email', async () => {
const wrapper = await mountAppLayout('/dashboard', {
...mockUser,
first_name: null,
last_name: null,
});
expect(wrapper.text()).toContain('ivan.petrov@example.ru');
});
});