Files
portal/app/tests/Frontend/RemindersView.spec.ts
T

130 lines
4.7 KiB
TypeScript
Raw Normal View History

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' }));
});
});