Files
portal/app/tests/Frontend/deals-api.spec.ts
T
Дмитрий c7fd90c08d fix(deals): читать проекты из конверта { data } + чинить фикстуры LeadStatus
DealsView крашился (Cannot read properties of undefined reading 'map'): listProjects() читал data.projects, но ProjectController::index() отдаёт { data: [...] } после миграции на JsonResource — availableProjects=undefined ломал .map, фильтр «Проект» был пуст. Фикс: читать data.data ?? []. + deals-api.spec.ts тест на новый конверт + защитный []. + DealDetailHero.spec.ts: фикстуры LeadStatus (isSystem/sortOrder вместо order) — устранён pre-existing type-check error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:20:53 +03:00

189 lines
7.4 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';
vi.mock('../../resources/js/api/client', () => ({
apiClient: {
get: vi.fn(),
post: vi.fn(),
patch: vi.fn(),
delete: vi.fn(),
},
ensureCsrfCookie: vi.fn(),
}));
import {
createDeal,
bulkDeleteDeals,
bulkRestoreDeals,
updateDeal,
transitionDeals,
exportDeals,
exportDealsXlsx,
getDeal,
listDeals,
listManagers,
listProjects,
} from '../../resources/js/api/deals';
import { apiClient, ensureCsrfCookie } from '../../resources/js/api/client';
const FAKE_DEAL = {
id: 1,
tenant_id: 1,
project_id: 1,
project_name: 'Test',
phone: '+79161234567',
contact_name: 'X',
status: 'new',
manager_id: null,
manager_name: null,
manager_initials: null,
received_at: null,
comment: null,
assigned_at: null,
};
describe('api/deals', () => {
beforeEach(() => vi.clearAllMocks());
it('createDeal() POSTs /api/deals + ensureCsrfCookie + unwraps data.deal', async () => {
vi.mocked(apiClient.post).mockResolvedValue({ data: { deal: FAKE_DEAL, message: 'ok' } });
const r = await createDeal({ tenant_id: 1, project_name: 'P', phone: '+79161234567' });
expect(ensureCsrfCookie).toHaveBeenCalledOnce();
expect(apiClient.post).toHaveBeenCalledWith('/api/deals', {
tenant_id: 1,
project_name: 'P',
phone: '+79161234567',
});
expect(r.id).toBe(1);
});
it('bulkDeleteDeals() DELETE /api/deals с data payload', async () => {
vi.mocked(apiClient.delete).mockResolvedValue({ data: { deleted: 3, requested: 4 } });
const r = await bulkDeleteDeals({ tenant_id: 1, ids: [1, 2, 3, 4] });
expect(ensureCsrfCookie).toHaveBeenCalledOnce();
expect(apiClient.delete).toHaveBeenCalledWith('/api/deals', { data: { tenant_id: 1, ids: [1, 2, 3, 4] } });
expect(r.deleted).toBe(3);
});
it('bulkRestoreDeals() POSTs /api/deals/restore', async () => {
vi.mocked(apiClient.post).mockResolvedValue({ data: { restored: 2, requested: 2 } });
await bulkRestoreDeals({ tenant_id: 1, ids: [1, 2] });
expect(ensureCsrfCookie).toHaveBeenCalledOnce();
expect(apiClient.post).toHaveBeenCalledWith('/api/deals/restore', { tenant_id: 1, ids: [1, 2] });
});
it('updateDeal() PATCH /api/deals/{id} c partial payload', async () => {
vi.mocked(apiClient.patch).mockResolvedValue({ data: { deal: FAKE_DEAL } });
await updateDeal(42, { tenant_id: 1, comment: 'new' });
expect(ensureCsrfCookie).toHaveBeenCalledOnce();
expect(apiClient.patch).toHaveBeenCalledWith('/api/deals/42', { tenant_id: 1, comment: 'new' });
});
it('transitionDeals() POSTs /api/deals/transition с status', async () => {
vi.mocked(apiClient.post).mockResolvedValue({ data: { updated: 5, requested: 5, status: 'won' } });
const r = await transitionDeals({ tenant_id: 1, ids: [1, 2, 3, 4, 5], status: 'won' });
expect(apiClient.post).toHaveBeenCalledWith('/api/deals/transition', {
tenant_id: 1,
ids: [1, 2, 3, 4, 5],
status: 'won',
});
expect(r.status).toBe('won');
});
it('exportDeals() POSTs /api/deals/export с format=csv + responseType=text', async () => {
vi.mocked(apiClient.post).mockResolvedValue({ data: 'id,phone\n1,+79..' });
const r = await exportDeals({ tenant_id: 1, ids: [1] });
expect(ensureCsrfCookie).toHaveBeenCalledOnce();
expect(apiClient.post).toHaveBeenCalledWith(
'/api/deals/export',
{ tenant_id: 1, ids: [1], format: 'csv' },
{ responseType: 'text' },
);
expect(typeof r).toBe('string');
});
it('exportDealsXlsx() POSTs /api/deals/export с format=xlsx + responseType=blob', async () => {
const blob = new Blob(['fake'], { type: 'application/octet-stream' });
vi.mocked(apiClient.post).mockResolvedValue({ data: blob });
const r = await exportDealsXlsx({ tenant_id: 1, ids: [1, 2] });
expect(apiClient.post).toHaveBeenCalledWith(
'/api/deals/export',
{ tenant_id: 1, ids: [1, 2], format: 'xlsx' },
{ responseType: 'blob' },
);
expect(r).toBeInstanceOf(Blob);
});
it('getDeal() GET /api/deals/{id} с tenant_id param', async () => {
vi.mocked(apiClient.get).mockResolvedValue({ data: { deal: FAKE_DEAL, events: [] } });
const r = await getDeal(7, 1);
expect(apiClient.get).toHaveBeenCalledWith('/api/deals/7', { params: { tenant_id: 1 } });
expect(r.events).toEqual([]);
});
it('listDeals() GET /api/deals с маппингом camelCase→snake_case + onlyDeleted flag', async () => {
vi.mocked(apiClient.get).mockResolvedValue({
data: { deals: [], total: 0, limit: 50, offset: 0 },
});
await listDeals({
tenantId: 1,
statusIn: ['new', 'in_progress'],
projectId: 2,
managerId: 3,
search: 'q',
limit: 50,
offset: 0,
onlyDeleted: true,
});
expect(apiClient.get).toHaveBeenCalledWith('/api/deals', {
params: {
tenant_id: 1,
status_in: ['new', 'in_progress'],
project_id: 2,
manager_id: 3,
search: 'q',
limit: 50,
offset: 0,
only_deleted: 'true',
},
});
});
it('listDeals() без onlyDeleted → only_deleted=undefined', async () => {
vi.mocked(apiClient.get).mockResolvedValue({ data: { deals: [], total: 0, limit: 50, offset: 0 } });
await listDeals({ tenantId: 1 });
expect(apiClient.get).toHaveBeenCalledWith(
'/api/deals',
expect.objectContaining({ params: expect.objectContaining({ only_deleted: undefined }) }),
);
});
it('listManagers() GET /api/managers + unwraps data.managers', async () => {
vi.mocked(apiClient.get).mockResolvedValue({
data: {
managers: [{ id: 1, email: 'm@x.ru', first_name: 'M', last_name: 'X', name: 'M X', initials: 'MX' }],
},
});
const r = await listManagers(1);
expect(apiClient.get).toHaveBeenCalledWith('/api/managers', { params: { tenant_id: 1 } });
expect(r).toHaveLength(1);
});
it('listProjects() GET /api/projects + unwraps { data: [...] } (JsonResource collection)', async () => {
// ProjectController::index() отдаёт response()->json(['data' => ProjectResource::collection(...)]).
vi.mocked(apiClient.get).mockResolvedValue({
data: { data: [{ id: 1, name: 'B1_Окна СПб' }, { id: 2, name: 'B2_Двери' }] },
});
const r = await listProjects(1);
expect(apiClient.get).toHaveBeenCalledWith('/api/projects', { params: { tenant_id: 1 } });
expect(Array.isArray(r)).toBe(true);
expect(r).toHaveLength(2);
expect(r[0].name).toBe('B1_Окна СПб');
});
it('listProjects() возвращает [] при ответе без массива (защита от undefined.map)', async () => {
vi.mocked(apiClient.get).mockResolvedValue({ data: {} });
const r = await listProjects(1);
expect(r).toEqual([]);
});
});