130 lines
4.7 KiB
TypeScript
130 lines
4.7 KiB
TypeScript
|
|
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' }));
|
|||
|
|
});
|
|||
|
|
});
|