2504f1b9ec
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
143 lines
5.3 KiB
TypeScript
143 lines
5.3 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' }));
|
||
});
|
||
|
||
// 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 } });
|
||
});
|
||
});
|