From c7fd90c08d65d0740218f1b784f7f4d98952fd8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Wed, 20 May 2026 15:20:53 +0300 Subject: [PATCH] =?UTF-8?q?fix(deals):=20=D1=87=D0=B8=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D1=8B=20=D0=B8?= =?UTF-8?q?=D0=B7=20=D0=BA=D0=BE=D0=BD=D0=B2=D0=B5=D1=80=D1=82=D0=B0=20{?= =?UTF-8?q?=20data=20}=20+=20=D1=87=D0=B8=D0=BD=D0=B8=D1=82=D1=8C=20=D1=84?= =?UTF-8?q?=D0=B8=D0=BA=D1=81=D1=82=D1=83=D1=80=D1=8B=20LeadStatus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- app/resources/js/api/deals.ts | 6 ++++-- app/tests/Frontend/DealDetailHero.spec.ts | 6 +++--- app/tests/Frontend/deals-api.spec.ts | 15 ++++++++++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/resources/js/api/deals.ts b/app/resources/js/api/deals.ts index 6c19c1d8..35de5937 100644 --- a/app/resources/js/api/deals.ts +++ b/app/resources/js/api/deals.ts @@ -260,10 +260,12 @@ export interface ApiProject { } export async function listProjects(tenantId: number): Promise { - const { data } = await apiClient.get<{ projects: ApiProject[] }>('/api/projects', { + // ProjectController::index() отдаёт { data: ProjectResource::collection(...) }. + // `?? []` — защита от undefined.map в DealsView при нештатном ответе. + const { data } = await apiClient.get<{ data: ApiProject[] }>('/api/projects', { params: { tenant_id: tenantId }, }); - return data.projects; + return data.data ?? []; } /** diff --git a/app/tests/Frontend/DealDetailHero.spec.ts b/app/tests/Frontend/DealDetailHero.spec.ts index d0b0d451..8c7d4ae0 100644 --- a/app/tests/Frontend/DealDetailHero.spec.ts +++ b/app/tests/Frontend/DealDetailHero.spec.ts @@ -8,9 +8,9 @@ import type { LeadStatus } from '../../resources/js/composables/leadStatuses'; const vuetify = createVuetify(); const statuses: LeadStatus[] = [ - { slug: 'new', nameRu: 'Новая сделка', colorHex: '#5b2db2', order: 1 } as LeadStatus, - { slug: 'viewed', nameRu: 'Просмотрено', colorHex: '#5a2db2', order: 2 } as LeadStatus, - { slug: 'won', nameRu: 'Куплено', colorHex: '#00A36C', order: 3 } as LeadStatus, + { slug: 'new', nameRu: 'Новая сделка', isSystem: true, sortOrder: 1, colorHex: '#5b2db2' }, + { slug: 'viewed', nameRu: 'Просмотрено', isSystem: true, sortOrder: 2, colorHex: '#5a2db2' }, + { slug: 'won', nameRu: 'Куплено', isSystem: true, sortOrder: 3, colorHex: '#00A36C' }, ]; function makeDeal(over: Partial = {}): MockDeal { diff --git a/app/tests/Frontend/deals-api.spec.ts b/app/tests/Frontend/deals-api.spec.ts index b0489839..b3e78128 100644 --- a/app/tests/Frontend/deals-api.spec.ts +++ b/app/tests/Frontend/deals-api.spec.ts @@ -168,12 +168,21 @@ describe('api/deals', () => { expect(r).toHaveLength(1); }); - it('listProjects() GET /api/projects + unwraps data.projects', async () => { + it('listProjects() GET /api/projects + unwraps { data: [...] } (JsonResource collection)', async () => { + // ProjectController::index() отдаёт response()->json(['data' => ProjectResource::collection(...)]). vi.mocked(apiClient.get).mockResolvedValue({ - data: { projects: [{ id: 1, name: 'P', tag: 'site', type: 'webhook' }] }, + 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(r[0].name).toBe('P'); + 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([]); }); });