import { describe, it, expect, vi, beforeEach } from 'vitest' import { mockLinkSKF, mockLinkFAG } from '../fixtures/mockData' // --------------------------------------------------------------------------- // Mocks — API layer // --------------------------------------------------------------------------- const mockGet = vi.fn() const mockPost = vi.fn() const mockPatch = vi.fn() const mockDel = vi.fn() const mockPostFormData = vi.fn() vi.mock('~/composables/useApi', () => ({ useApi: () => ({ get: mockGet, post: mockPost, patch: mockPatch, put: vi.fn(), delete: mockDel, postFormData: mockPostFormData, }), })) // --------------------------------------------------------------------------- // Mocks — Toast // --------------------------------------------------------------------------- const mockShowSuccess = vi.fn() const mockShowError = vi.fn() vi.mock('~/composables/useToast', () => ({ useToast: () => ({ showSuccess: mockShowSuccess, showError: mockShowError, showInfo: vi.fn(), showToast: vi.fn(), toasts: { value: [] }, clearAll: vi.fn(), }), })) // --------------------------------------------------------------------------- // Mocks — useComposants (createComposant) // --------------------------------------------------------------------------- const mockCreateComposant = vi.fn() vi.mock('~/composables/useComposants', () => ({ useComposants: () => ({ createComposant: mockCreateComposant, composants: { value: [] }, loading: { value: false }, }), })) // --------------------------------------------------------------------------- // Mocks — usePieces, useProducts // --------------------------------------------------------------------------- vi.mock('~/composables/usePieces', () => ({ usePieces: () => ({ pieces: { value: [] }, loading: { value: false }, }), })) vi.mock('~/composables/useProducts', () => ({ useProducts: () => ({ products: { value: [] }, loading: { value: false }, }), })) // --------------------------------------------------------------------------- // Mocks — useComponentTypes, usePieceTypes, useProductTypes // --------------------------------------------------------------------------- const mockLoadComponentTypes = vi.fn().mockResolvedValue(undefined) const mockComponentTypes = { value: [] as any[] } vi.mock('~/composables/useComponentTypes', () => ({ useComponentTypes: () => ({ componentTypes: mockComponentTypes, loadComponentTypes: mockLoadComponentTypes, loadingComponentTypes: { value: false }, }), })) vi.mock('~/composables/usePieceTypes', () => ({ usePieceTypes: () => ({ pieceTypes: { value: [] }, loadPieceTypes: vi.fn().mockResolvedValue(undefined), }), })) vi.mock('~/composables/useProductTypes', () => ({ useProductTypes: () => ({ productTypes: { value: [] }, loadProductTypes: vi.fn().mockResolvedValue(undefined), }), })) // --------------------------------------------------------------------------- // Mocks — useDocuments (uploadDocuments) // --------------------------------------------------------------------------- const mockUploadDocuments = vi.fn() vi.mock('~/composables/useDocuments', () => ({ useDocuments: () => ({ uploadDocuments: mockUploadDocuments, documents: { value: [] }, loading: { value: false }, }), })) // --------------------------------------------------------------------------- // Mocks — useConstructeurLinks (syncLinks) // --------------------------------------------------------------------------- const mockSyncLinks = vi.fn().mockResolvedValue(undefined) vi.mock('~/composables/useConstructeurLinks', () => ({ useConstructeurLinks: () => ({ fetchLinks: vi.fn().mockResolvedValue([]), syncLinks: mockSyncLinks, }), })) // --------------------------------------------------------------------------- // Mocks — useCustomFieldInputs (saveAll) // --------------------------------------------------------------------------- const mockSaveAll = vi.fn().mockResolvedValue([]) const mockRefreshCF = vi.fn() vi.mock('~/composables/useCustomFieldInputs', () => ({ useCustomFieldInputs: () => ({ fields: { value: [] }, requiredFilled: { value: true }, saveAll: mockSaveAll, refresh: mockRefreshCF, }), })) // --------------------------------------------------------------------------- // Mocks — usePermissions (auto-imported in Nuxt) // --------------------------------------------------------------------------- // usePermissions is Nuxt auto-imported (no explicit import in source), // so we stub it as a global function. vi.stubGlobal('usePermissions', () => ({ canEdit: { value: true }, canManage: { value: true }, isAdmin: { value: false }, isGranted: () => true, })) // --------------------------------------------------------------------------- // Mocks — useConstructeurs (used by useComposants internally) // --------------------------------------------------------------------------- vi.mock('~/composables/useConstructeurs', () => ({ useConstructeurs: () => ({ ensureConstructeurs: vi.fn().mockResolvedValue([]), }), })) // --------------------------------------------------------------------------- // Mocks — shared utils that touch structure // --------------------------------------------------------------------------- vi.mock('~/shared/utils/structureAssignmentHelpers', () => ({ hasAssignments: () => false, initializeStructureAssignments: () => null, isAssignmentNodeComplete: () => true, serializeStructureAssignments: () => null, })) vi.mock('~/shared/utils/structureDisplayUtils', () => ({ getStructurePieces: () => [], resolvePieceLabel: (p: any) => p?.name ?? '', resolveProductLabel: (p: any) => p?.name ?? '', resolveSubcomponentLabel: (p: any) => p?.name ?? '', fetchModelTypeNames: vi.fn().mockResolvedValue({}), buildTypeLabelMap: () => ({}), })) vi.mock('~/shared/modelUtils', () => ({ formatStructurePreview: () => '', normalizeStructureForEditor: (s: any) => s, })) vi.mock('~/shared/utils/errorMessages', () => ({ humanizeError: (msg: string) => msg, })) vi.mock('~/shared/constructeurUtils', () => ({ uniqueConstructeurIds: (ids: string[]) => [...new Set(ids)], constructeurIdsFromLinks: (links: any[]) => links.map((l: any) => l.constructeurId), })) // --------------------------------------------------------------------------- // Import under test (AFTER all vi.mock calls) // --------------------------------------------------------------------------- import { useComponentCreate } from '~/composables/useComponentCreate' // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /** A minimal ModelType matching the `COMPONENT` category filter. */ const mockModelType = { id: 'tc-moteur', name: 'Moteur électrique', category: 'COMPONENT', structure: null, } beforeEach(() => { vi.clearAllMocks() // Provide at least one COMPONENT type so selectedType resolves mockComponentTypes.value = [mockModelType] }) // --------------------------------------------------------------------------- // submitCreation — payload completeness // --------------------------------------------------------------------------- describe('submitCreation — payload completeness', () => { it('includes all form fields in createComposant payload', async () => { const createdComp = { id: 'comp-new-001', name: 'Moteur principal' } mockCreateComposant.mockResolvedValue({ success: true, data: createdComp }) const composable = useComponentCreate() // Select a type composable.selectedTypeId.value = 'tc-moteur' // Wait a tick so watchers fire await new Promise(r => setTimeout(r, 0)) // Fill form fields composable.creationForm.name = 'Moteur principal' composable.creationForm.description = 'Un moteur triphasé' composable.creationForm.reference = 'MOT-001' composable.creationForm.prix = '1500' await composable.submitCreation() expect(mockCreateComposant).toHaveBeenCalledTimes(1) const payload = mockCreateComposant.mock.calls[0]![0] expect(payload).toMatchObject({ name: 'Moteur principal', description: 'Un moteur triphasé', reference: 'MOT-001', prix: '1500', typeComposantId: 'tc-moteur', }) }) it('saves custom fields after component creation (saveAll is called)', async () => { const createdComp = { id: 'comp-cf-001', name: 'Composant CF' } mockCreateComposant.mockResolvedValue({ success: true, data: createdComp }) const composable = useComponentCreate() composable.selectedTypeId.value = 'tc-moteur' await new Promise(r => setTimeout(r, 0)) composable.creationForm.name = 'Composant CF' await composable.submitCreation() expect(mockCreateComposant).toHaveBeenCalledTimes(1) expect(mockSaveAll).toHaveBeenCalledTimes(1) }) it('syncs constructeur links after creation with correct entity type and ID', async () => { const createdComp = { id: 'comp-link-001', name: 'Composant Links' } mockCreateComposant.mockResolvedValue({ success: true, data: createdComp }) const composable = useComponentCreate() composable.selectedTypeId.value = 'tc-moteur' await new Promise(r => setTimeout(r, 0)) composable.creationForm.name = 'Composant Links' // Add constructeur links composable.constructeurLinks.value = [mockLinkSKF, mockLinkFAG] await composable.submitCreation() expect(mockSyncLinks).toHaveBeenCalledTimes(1) expect(mockSyncLinks).toHaveBeenCalledWith( 'composant', 'comp-link-001', [], [mockLinkSKF, mockLinkFAG], ) }) it('uploads documents with correct composantId context', async () => { const createdComp = { id: 'comp-doc-001', name: 'Composant Docs' } mockCreateComposant.mockResolvedValue({ success: true, data: createdComp }) mockUploadDocuments.mockResolvedValue({ success: true, data: [] }) const composable = useComponentCreate() composable.selectedTypeId.value = 'tc-moteur' await new Promise(r => setTimeout(r, 0)) composable.creationForm.name = 'Composant Docs' // Simulate selected documents const fakeFile = new File(['content'], 'schema.pdf', { type: 'application/pdf' }) composable.selectedDocuments.value = [fakeFile] await composable.submitCreation() expect(mockUploadDocuments).toHaveBeenCalledTimes(1) expect(mockUploadDocuments).toHaveBeenCalledWith( { files: [fakeFile], context: { composantId: 'comp-doc-001' }, }, { updateStore: false }, ) }) it('does not crash with zero constructeurs', async () => { const createdComp = { id: 'comp-no-cstr', name: 'Composant Simple' } mockCreateComposant.mockResolvedValue({ success: true, data: createdComp }) const composable = useComponentCreate() composable.selectedTypeId.value = 'tc-moteur' await new Promise(r => setTimeout(r, 0)) composable.creationForm.name = 'Composant Simple' // Ensure no constructeur links composable.constructeurLinks.value = [] await composable.submitCreation() expect(mockCreateComposant).toHaveBeenCalledTimes(1) expect(mockSyncLinks).not.toHaveBeenCalled() expect(mockShowSuccess).toHaveBeenCalledWith('Composant créé avec succès') }) })