test(machine-custom-fields) : add checkbox and data integrity tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 15:19:28 +02:00
parent eeba229574
commit eb68336723

View File

@@ -0,0 +1,267 @@
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)
}
})
})