349 lines
11 KiB
TypeScript
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')
|
|
})
|
|
})
|