import { describe, it, expect } from 'vitest' import { parseExpr, evaluateExpr, evaluateVisibleWhen } from './exprEvaluator' describe('parseExpr', () => { it('parses equality expression', () => { const ast = parseExpr("status == 'active'") expect(ast).toEqual({ type: 'eq', field: 'status', value: 'active', }) }) it('parses inequality expression', () => { const ast = parseExpr("type != 'internal'") expect(ast).toEqual({ type: 'neq', field: 'type', value: 'internal', }) }) it('parses AND expression', () => { const ast = parseExpr("status == 'active' AND role == 'admin'") expect(ast).toEqual({ type: 'and', left: { type: 'eq', field: 'status', value: 'active' }, right: { type: 'eq', field: 'role', value: 'admin' }, }) }) it('parses OR expression', () => { const ast = parseExpr("status == 'active' OR status == 'pending'") expect(ast).toEqual({ type: 'or', left: { type: 'eq', field: 'status', value: 'active' }, right: { type: 'eq', field: 'status', value: 'pending' }, }) }) it('parses NOT expression', () => { const ast = parseExpr("NOT status == 'deleted'") expect(ast).toEqual({ type: 'not', operand: { type: 'eq', field: 'status', value: 'deleted' }, }) }) it('parses parenthesized expression', () => { const ast = parseExpr("(status == 'a' OR status == 'b') AND role == 'admin'") expect(ast?.type).toBe('and') expect(ast?.left?.type).toBe('or') }) it('returns null for empty input', () => { expect(parseExpr('')).toBeNull() }) it('parses && and || operators', () => { const ast = parseExpr("a == '1' && b == '2' || c == '3'") expect(ast).toBeDefined() expect(ast?.type).toBe('or') }) }) describe('evaluateExpr', () => { it('evaluates equality true', () => { const ast = parseExpr("status == 'active'")! expect(evaluateExpr(ast, { status: 'active' })).toBe(true) }) it('evaluates equality false', () => { const ast = parseExpr("status == 'active'")! expect(evaluateExpr(ast, { status: 'inactive' })).toBe(false) }) it('evaluates inequality', () => { const ast = parseExpr("status != 'deleted'")! expect(evaluateExpr(ast, { status: 'active' })).toBe(true) expect(evaluateExpr(ast, { status: 'deleted' })).toBe(false) }) it('evaluates AND', () => { const ast = parseExpr("a == '1' AND b == '2'")! expect(evaluateExpr(ast, { a: '1', b: '2' })).toBe(true) expect(evaluateExpr(ast, { a: '1', b: '3' })).toBe(false) expect(evaluateExpr(ast, { a: '0', b: '2' })).toBe(false) }) it('evaluates OR', () => { const ast = parseExpr("a == '1' OR b == '2'")! expect(evaluateExpr(ast, { a: '1', b: 'x' })).toBe(true) expect(evaluateExpr(ast, { a: 'x', b: '2' })).toBe(true) expect(evaluateExpr(ast, { a: 'x', b: 'x' })).toBe(false) }) it('evaluates NOT', () => { const ast = parseExpr("NOT a == '1'")! expect(evaluateExpr(ast, { a: '1' })).toBe(false) expect(evaluateExpr(ast, { a: '2' })).toBe(true) }) it('handles missing field as empty string', () => { const ast = parseExpr("status == ''")! expect(evaluateExpr(ast, {})).toBe(true) }) it('converts non-string values to string', () => { const ast = parseExpr("count == '5'")! expect(evaluateExpr(ast, { count: 5 })).toBe(true) }) }) describe('evaluateVisibleWhen', () => { it('returns true for undefined expression', () => { expect(evaluateVisibleWhen(undefined, {})).toBe(true) }) it('returns true for empty string expression', () => { expect(evaluateVisibleWhen('', {})).toBe(true) }) it('evaluates complex expression', () => { expect( evaluateVisibleWhen("type == 'doctor' AND status == 'active'", { type: 'doctor', status: 'active', }), ).toBe(true) expect( evaluateVisibleWhen("type == 'doctor' AND status == 'active'", { type: 'doctor', status: 'inactive', }), ).toBe(false) }) })