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:
267
frontend/tests/composables/useMachineDetailCustomFields.test.ts
Normal file
267
frontend/tests/composables/useMachineDetailCustomFields.test.ts
Normal 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)
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user