Files
Inventory/frontend/tests/composables/useComponentCreate.test.ts
2026-04-06 16:52:41 +02:00

349 lines
11 KiB
TypeScript

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')
})
})