Moves formBuilder types, formValidation, useConditionalLogic, useFormSteps, and formatFieldValue from apps/portal/src to packages/form-schema/src. Adds @form-schema path alias to both apps/portal and apps/app. Vue field components remain per-app to allow independent visual evolution. Behavior-neutral: all 35 Vitest tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95 lines
4.1 KiB
TypeScript
95 lines
4.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { evaluateConditionalLogic } from '@form-schema/composables/useConditionalLogic'
|
|
import type { ConditionalLogic } from '@form-schema/types/formBuilder'
|
|
|
|
describe('evaluateConditionalLogic', () => {
|
|
it('returns true when logic is null or undefined', () => {
|
|
expect(evaluateConditionalLogic(null, {})).toBe(true)
|
|
expect(evaluateConditionalLogic(undefined, {})).toBe(true)
|
|
expect(evaluateConditionalLogic({}, {})).toBe(true)
|
|
})
|
|
|
|
it('handles simple equals / not_equals', () => {
|
|
const logic: ConditionalLogic = {
|
|
show_when: { all: [{ field_slug: 'foo', operator: 'equals', value: 'bar' }] },
|
|
}
|
|
expect(evaluateConditionalLogic(logic, { foo: 'bar' })).toBe(true)
|
|
expect(evaluateConditionalLogic(logic, { foo: 'baz' })).toBe(false)
|
|
|
|
const negative: ConditionalLogic = {
|
|
show_when: { all: [{ field_slug: 'foo', operator: 'not_equals', value: 'bar' }] },
|
|
}
|
|
expect(evaluateConditionalLogic(negative, { foo: 'baz' })).toBe(true)
|
|
expect(evaluateConditionalLogic(negative, { foo: 'bar' })).toBe(false)
|
|
})
|
|
|
|
it('handles in / not_in over single and array values', () => {
|
|
const logic: ConditionalLogic = {
|
|
show_when: { all: [{ field_slug: 'tags', operator: 'in', value: ['a', 'b'] }] },
|
|
}
|
|
expect(evaluateConditionalLogic(logic, { tags: 'a' })).toBe(true)
|
|
expect(evaluateConditionalLogic(logic, { tags: 'c' })).toBe(false)
|
|
expect(evaluateConditionalLogic(logic, { tags: ['c', 'a'] })).toBe(true)
|
|
|
|
const negative: ConditionalLogic = {
|
|
show_when: { all: [{ field_slug: 'tags', operator: 'not_in', value: ['a'] }] },
|
|
}
|
|
expect(evaluateConditionalLogic(negative, { tags: 'a' })).toBe(false)
|
|
expect(evaluateConditionalLogic(negative, { tags: ['b', 'c'] })).toBe(true)
|
|
})
|
|
|
|
it('handles empty / not_empty across string, array, null', () => {
|
|
const empty: ConditionalLogic = { show_when: { all: [{ field_slug: 'x', operator: 'empty' }] } }
|
|
expect(evaluateConditionalLogic(empty, {})).toBe(true)
|
|
expect(evaluateConditionalLogic(empty, { x: null })).toBe(true)
|
|
expect(evaluateConditionalLogic(empty, { x: '' })).toBe(true)
|
|
expect(evaluateConditionalLogic(empty, { x: [] })).toBe(true)
|
|
expect(evaluateConditionalLogic(empty, { x: 'hi' })).toBe(false)
|
|
|
|
const notEmpty: ConditionalLogic = { show_when: { all: [{ field_slug: 'x', operator: 'not_empty' }] } }
|
|
expect(evaluateConditionalLogic(notEmpty, { x: 'hi' })).toBe(true)
|
|
expect(evaluateConditionalLogic(notEmpty, {})).toBe(false)
|
|
})
|
|
|
|
it('evaluates nested all/any groups', () => {
|
|
const logic: ConditionalLogic = {
|
|
show_when: {
|
|
all: [
|
|
{ field_slug: 'role', operator: 'equals', value: 'admin' },
|
|
{
|
|
any: [
|
|
{ field_slug: 'region', operator: 'equals', value: 'NL' },
|
|
{ field_slug: 'region', operator: 'equals', value: 'BE' },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
}
|
|
expect(evaluateConditionalLogic(logic, { role: 'admin', region: 'BE' })).toBe(true)
|
|
expect(evaluateConditionalLogic(logic, { role: 'admin', region: 'DE' })).toBe(false)
|
|
expect(evaluateConditionalLogic(logic, { role: 'guest', region: 'NL' })).toBe(false)
|
|
})
|
|
|
|
it('does not throw on missing field values', () => {
|
|
const eq: ConditionalLogic = {
|
|
show_when: { all: [{ field_slug: 'missing', operator: 'equals', value: 'x' }] },
|
|
}
|
|
expect(() => evaluateConditionalLogic(eq, {})).not.toThrow()
|
|
expect(evaluateConditionalLogic(eq, {})).toBe(false)
|
|
|
|
const emptyCheck: ConditionalLogic = {
|
|
show_when: { all: [{ field_slug: 'missing', operator: 'empty' }] },
|
|
}
|
|
expect(evaluateConditionalLogic(emptyCheck, {})).toBe(true)
|
|
})
|
|
|
|
it('handles greater_than / less_than with non-numeric fallback', () => {
|
|
const gt: ConditionalLogic = {
|
|
show_when: { all: [{ field_slug: 'age', operator: 'greater_than', value: 18 }] },
|
|
}
|
|
expect(evaluateConditionalLogic(gt, { age: 20 })).toBe(true)
|
|
expect(evaluateConditionalLogic(gt, { age: 17 })).toBe(false)
|
|
expect(evaluateConditionalLogic(gt, { age: 'not-a-number' })).toBe(false)
|
|
})
|
|
})
|