import { describe, it, expect, vi, beforeEach } from 'vitest' import { ref } from 'vue' import { useCustomFieldInputs } from '~/composables/useCustomFieldInputs' import { mockMachineCustomFieldDefs, mockMachineCustomFieldValues, } from '../fixtures/mockData' // --------------------------------------------------------------------------- // Mocks // --------------------------------------------------------------------------- const mockUpdateCustomFieldValue = vi.fn() const mockUpsertCustomFieldValue = vi.fn() vi.mock('~/composables/useCustomFields', () => ({ useCustomFields: () => ({ updateCustomFieldValue: mockUpdateCustomFieldValue, upsertCustomFieldValue: mockUpsertCustomFieldValue, }), })) vi.mock('~/composables/useToast', () => ({ useToast: () => ({ showSuccess: vi.fn(), showError: vi.fn(), showInfo: vi.fn(), showToast: vi.fn(), toasts: { value: [] }, clearAll: vi.fn(), }), })) beforeEach(() => { vi.clearAllMocks() mockUpdateCustomFieldValue.mockResolvedValue({ success: true }) mockUpsertCustomFieldValue.mockResolvedValue({ success: true, data: { id: 'new-mcfv-id', customField: { id: 'new-mcf-id' } }, }) }) // --------------------------------------------------------------------------- // Helper — create composable with machine context (no context filter) // --------------------------------------------------------------------------- function createMachineFields( defs = mockMachineCustomFieldDefs, vals = mockMachineCustomFieldValues, entityId = 'cl-machine-1', ) { return useCustomFieldInputs({ definitions: ref(defs), values: ref(vals), entityType: 'machine', entityId: ref(entityId), // No context — machine custom fields don't use machineContextOnly filtering }) } // --------------------------------------------------------------------------- // Machine custom field initialization // --------------------------------------------------------------------------- describe('machine custom field initialization', () => { it('loads all machine custom fields with values (5 fields)', () => { const { fields } = createMachineFields() expect(fields.value).toHaveLength(5) }) it('preserves text value (Numéro de série)', () => { const { fields } = createMachineFields() const textField = fields.value.find(f => f.name === 'Numéro de série') expect(textField?.value).toBe('SN-2025-001234') expect(textField?.type).toBe('text') }) it('preserves boolean value (En service = true)', () => { const { fields } = createMachineFields() const boolField = fields.value.find(f => f.name === 'En service') expect(boolField?.value).toBe('true') expect(boolField?.type).toBe('boolean') }) it('preserves number zero value (Puissance kW = 0)', () => { const { fields } = createMachineFields() const numField = fields.value.find(f => f.name === 'Puissance (kW)') expect(numField?.value).toBe('0') expect(numField?.type).toBe('number') }) it('preserves select value (Catégorie ATEX = Zone 1)', () => { const { fields } = createMachineFields() const selectField = fields.value.find(f => f.name === 'Catégorie ATEX') expect(selectField?.value).toBe('Zone 1') expect(selectField?.type).toBe('select') expect(selectField?.options).toEqual(['Zone 0', 'Zone 1', 'Zone 2', 'Non classé']) }) it('preserves date value (Date mise en service = 2025-01-15)', () => { const { fields } = createMachineFields() const dateField = fields.value.find(f => f.name === 'Date mise en service') expect(dateField?.value).toBe('2025-01-15') expect(dateField?.type).toBe('date') }) }) // --------------------------------------------------------------------------- // Boolean checkbox — the critical test // --------------------------------------------------------------------------- describe('boolean checkbox — the critical test', () => { it('toggle true to false sends "false" (not deleted) via update()', async () => { const { fields, update } = createMachineFields() const boolField = fields.value.find(f => f.name === 'En service')! expect(boolField.value).toBe('true') // Toggle to false boolField.value = 'false' await update(boolField) expect(mockUpdateCustomFieldValue).toHaveBeenCalledWith('mcfv-002', { value: 'false' }) }) it('toggle false to true sends "true"', async () => { // Start with boolean value = false const falseVal = { ...mockMachineCustomFieldValues[1]!, value: 'false' } const vals = mockMachineCustomFieldValues.map((v, i) => (i === 1 ? falseVal : v)) const { fields, update } = createMachineFields(mockMachineCustomFieldDefs, vals) const boolField = fields.value.find(f => f.name === 'En service')! expect(boolField.value).toBe('false') // Toggle to true boolField.value = 'true' await update(boolField) expect(mockUpdateCustomFieldValue).toHaveBeenCalledWith('mcfv-002', { value: 'true' }) }) it('boolean false is persisted in saveAll (not skipped)', async () => { // Only the boolean field with value "false" const boolDef = mockMachineCustomFieldDefs[1]! const boolVal = { ...mockMachineCustomFieldValues[1]!, value: 'false' } const { fields, saveAll } = createMachineFields([boolDef], [boolVal]) expect(fields.value[0]?.value).toBe('false') const failed = await saveAll() expect(failed).toEqual([]) expect(mockUpdateCustomFieldValue).toHaveBeenCalledWith('mcfv-002', { value: 'false' }) }) }) // --------------------------------------------------------------------------- // Number zero — not lost // --------------------------------------------------------------------------- describe('number zero — not lost', () => { it('preserves zero value after load', () => { const { fields } = createMachineFields() const numField = fields.value.find(f => f.name === 'Puissance (kW)')! expect(numField.value).toBe('0') }) it('saves zero value (not skipped) in saveAll', async () => { // Only the number field with value "0" const numDef = mockMachineCustomFieldDefs[2]! const numVal = mockMachineCustomFieldValues[2]! const { saveAll } = createMachineFields([numDef], [numVal]) const failed = await saveAll() expect(failed).toEqual([]) expect(mockUpdateCustomFieldValue).toHaveBeenCalledWith('mcfv-003', { value: '0' }) }) }) // --------------------------------------------------------------------------- // Select field // --------------------------------------------------------------------------- describe('select field', () => { it('preserves selected option', () => { const { fields } = createMachineFields() const selectField = fields.value.find(f => f.name === 'Catégorie ATEX')! expect(selectField.value).toBe('Zone 1') }) it('uses defaultValue when no value exists', () => { // Use defs with a select that has a defaultValue const defsWithDefault = mockMachineCustomFieldDefs.map((d, i) => i === 3 ? { ...d, defaultValue: 'Non classé' } : d, ) // No values for the select field const valsWithoutSelect = mockMachineCustomFieldValues.filter( v => v.customField.name !== 'Catégorie ATEX', ) const { fields } = createMachineFields(defsWithDefault, valsWithoutSelect) const selectField = fields.value.find(f => f.name === 'Catégorie ATEX')! expect(selectField.value).toBe('Non classé') }) }) // --------------------------------------------------------------------------- // Field isolation // --------------------------------------------------------------------------- describe('field isolation', () => { it('updating one field does not change other field values', async () => { const { fields, update } = createMachineFields() // Snapshot original values const originalValues = fields.value.map(f => ({ name: f.name, value: f.value })) // Update only the text field const textField = fields.value.find(f => f.name === 'Numéro de série')! textField.value = 'SN-UPDATED-999' await update(textField) // All other fields should still have their original values for (const field of fields.value) { if (field.name === 'Numéro de série') continue const original = originalValues.find(o => o.name === field.name) expect(field.value).toBe(original?.value) } }) it('saveAll preserves all field values even on partial failure', async () => { // Make the second call fail (boolean field) mockUpdateCustomFieldValue .mockResolvedValueOnce({ success: true }) // text — Numéro de série .mockResolvedValueOnce({ success: false }) // boolean — En service .mockResolvedValue({ success: true }) // rest succeed const { fields, saveAll } = createMachineFields() // Snapshot values before saveAll const valuesBefore = fields.value.map(f => ({ name: f.name, value: f.value })) const failed = await saveAll() // Only the boolean field should have failed expect(failed).toEqual(['En service']) // All field values should still be intact (not cleared or corrupted) for (const field of fields.value) { const before = valuesBefore.find(v => v.name === field.name) expect(field.value).toBe(before?.value) } }) })