Files
portal/app/tests/Frontend/RemindersView.spec.ts
T
2026-05-16 11:41:09 +03:00

143 lines
5.3 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, beforeEach, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import { createVuetify } from 'vuetify';
import { createMemoryHistory, createRouter } from 'vue-router';
vi.mock('../../resources/js/api/reminders', () => ({
listReminders: vi.fn(),
createReminder: vi.fn(),
updateReminder: vi.fn(),
completeReminder: vi.fn(),
deleteReminder: vi.fn(),
}));
vi.mock('../../resources/js/api/client', () => ({
apiClient: {},
ensureCsrfCookie: vi.fn(),
extractValidationErrors: vi.fn(() => null),
extractErrorMessage: vi.fn(() => 'Произошла ошибка.'),
extractRateLimitRetry: vi.fn(() => null),
}));
import * as remindersApi from '../../resources/js/api/reminders';
import RemindersView from '../../resources/js/views/RemindersView.vue';
import type { ApiReminder } from '../../resources/js/api/reminders';
const mockReminder = (id: number, overrides: Partial<ApiReminder> = {}): ApiReminder => ({
id,
deal_id: 100 + id,
text: `Перезвонить #${id}`,
remind_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
completed_at: null,
is_sent: false,
sent_at: null,
created_at: new Date().toISOString(),
created_by: 1,
assignee_id: null,
creator_name: 'Иван Петров',
...overrides,
});
const factory = async (
apiResp: { items: ApiReminder[]; counts: { active: number; today: number; upcoming: number; overdue: number } } = {
items: [],
counts: { active: 0, today: 0, upcoming: 0, overdue: 0 },
},
) => {
setActivePinia(createPinia());
vi.mocked(remindersApi.listReminders).mockResolvedValue(apiResp);
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/reminders', component: RemindersView },
{ path: '/deals', component: { template: '<div>deals</div>' } },
],
});
await router.push('/reminders');
await router.isReady();
const wrapper = mount(RemindersView, {
global: {
plugins: [createVuetify(), router],
stubs: { ReminderDialog: true },
},
});
await new Promise((r) => setTimeout(r, 0));
await wrapper.vm.$nextTick();
return wrapper;
};
describe('RemindersView.vue', () => {
beforeEach(() => vi.clearAllMocks());
it('монтируется и содержит заголовок «Напоминания»', async () => {
const wrapper = await factory();
expect(wrapper.find('.page-title').text()).toBe('Напоминания');
});
it('содержит 4 tabs (Сегодня/Предстоит/Просрочено/Выполнено)', async () => {
const wrapper = await factory();
const text = wrapper.text();
['Сегодня', 'Предстоит', 'Просрочено', 'Выполнено'].forEach((label) => {
expect(text).toContain(label);
});
});
it('показывает counts на табах', async () => {
const wrapper = await factory({
items: [],
counts: { active: 5, today: 2, upcoming: 2, overdue: 1 },
});
const todayTab = wrapper.find('[data-testid="tab-today"]');
expect(todayTab.text()).toContain('2');
const overdueTab = wrapper.find('[data-testid="tab-overdue"]');
expect(overdueTab.text()).toContain('1');
});
it('при пустом списке показывает empty-state', async () => {
const wrapper = await factory();
expect(wrapper.find('[data-testid="reminders-empty"]').exists()).toBe(true);
});
it('рендерит список напоминаний', async () => {
const wrapper = await factory({
items: [mockReminder(1), mockReminder(2)],
counts: { active: 2, today: 2, upcoming: 0, overdue: 0 },
});
const items = wrapper.findAll('[data-testid="reminder-item"]');
expect(items).toHaveLength(2);
});
it('reload-btn вызывает listReminders повторно', async () => {
const wrapper = await factory();
const initialCalls = vi.mocked(remindersApi.listReminders).mock.calls.length;
await wrapper.find('[data-testid="reload-btn"]').trigger('click');
await wrapper.vm.$nextTick();
expect(vi.mocked(remindersApi.listReminders).mock.calls.length).toBeGreaterThan(initialCalls);
});
it('listReminders вызывается с filter=today по умолчанию', async () => {
await factory();
expect(remindersApi.listReminders).toHaveBeenCalledWith(expect.objectContaining({ filter: 'today' }));
});
// Audit C8: openDeal deep-links с query openId
it('openDeal(42) navigates на /deals?openId=42', async () => {
const wrapper = await factory({
items: [mockReminder(1)],
counts: { active: 1, today: 1, upcoming: 0, overdue: 0 },
});
const router = wrapper.vm.$router;
const pushSpy = vi.spyOn(router, 'push');
const vm = wrapper.vm as unknown as { openDeal: (id: number) => Promise<void> };
await vm.openDeal(42);
expect(pushSpy).toHaveBeenCalledWith({ path: '/deals', query: { openId: 42 } });
});
});