268 lines
9.1 KiB
TypeScript
268 lines
9.1 KiB
TypeScript
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)
|
|
}
|
|
})
|
|
})
|