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 => ({ 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: '
deals
' } }, ], }); 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 }; await vm.openDeal(42); expect(pushSpy).toHaveBeenCalledWith({ path: '/deals', query: { openId: 42 } }); }); });