import { describe, expect, it } from 'vitest' import { astToSql, buildAdvancedAst, buildQuickAst, defaultOperator, emptyConditionValue, hasConditionValue, operatorsForField, } from '@/components/fms-module-common/queryUtils' const fields = new Map([ ['name', { b_field: 'name', b_type: 'input' }], ['amount', { b_field: 'amount', b_type: 'money' }], ['created', { b_field: 'created', b_type: 'datetime' }], ['status', { b_field: 'status', b_type: 'select' }], ]) describe('查询条件 AST 构造', () => { it('按字段类型给出默认操作符:文本 like、日期 between、其余 eq', () => { expect(defaultOperator({ b_type: 'input' })).toBe('like') expect(defaultOperator({ b_type: 'datetime' })).toBe('between') expect(defaultOperator({ b_type: 'select' })).toBe('eq') expect(operatorsForField({ b_type: 'money' })).toContain('between') }) it('生成转义后的 like / 不等 / 空值条件', () => { expect( astToSql({ type: 'condition', field: 'name', operator: 'like', value: "O'Reilly" }, fields), ).toBe("[name] LIKE N'%O''Reilly%'") expect( astToSql({ type: 'condition', field: 'name', operator: 'not_like', value: 'a' }, fields), ).toBe("[name] NOT LIKE N'%a%'") expect( astToSql({ type: 'condition', field: 'status', operator: 'ne', value: 'done' }, fields), ).toBe("[status] <> N'done'") expect( astToSql({ type: 'condition', field: 'name', operator: 'is_null', value: '' }, fields), ).toBe('[name] IS NULL') }) it('区间条件可退化为单边比较,日期单边取对应端点', () => { expect( astToSql( { type: 'condition', field: 'amount', operator: 'between', value: ['10', ''] }, fields, ), ).toBe('[amount] >= 10') expect( astToSql( { type: 'condition', field: 'created', operator: 'ge', value: ['2026-01-01', ''] }, fields, ), ).toBe("[created] >= N'2026-01-01 00:00:00'") expect( astToSql( { type: 'condition', field: 'created', operator: 'le', value: ['', '2026-01-31'] }, fields, ), ).toBe("[created] <= N'2026-01-31 23:59:59'") }) it('嵌套条件组按各自逻辑加括号,空组不产生条件', () => { const ast = { type: 'group', logic: 'and', children: [ { type: 'condition', field: 'status', operator: 'eq', value: 'new' }, { type: 'group', logic: 'or', children: [ { type: 'condition', field: 'name', operator: 'like', value: 'a' }, { type: 'condition', field: 'name', operator: 'like', value: '' }, ], }, ], } expect(astToSql(ast, fields)).toBe("([status] = N'new' AND [name] LIKE N'%a%')") expect(astToSql({ type: 'group', logic: 'and', children: [] }, fields)).toBe('') }) it('常用条件跳过空值,操作符取模块配置', () => { const items = [ { field: fields.get('name'), config: { b_operator: 'like' } }, { field: fields.get('status'), config: { b_operator: 'eq' } }, ] const ast = buildQuickAst(items, { name: 'abc', status: '' }) expect(ast.children).toHaveLength(1) expect(astToSql(ast, fields)).toBe("[name] LIKE N'%abc%'") }) it('高级条件按每项 join 生成 AST,单条件的组不再套一层', () => { const ast = buildAdvancedAst({ nodes: [ { join: 'or', field: 'name', operator: 'like', value: 'a' }, { join: 'or', field: 'name', operator: 'like', value: 'b' }, ], groups: [ { id: 'g1', join: 'or', nodes: [{ join: 'and', field: 'status', operator: 'eq', value: 'new' }], }, ], }) // join 决定每项与上一相邻项的连接;首项 join 忽略;单条件的组等价于一个普通条件 expect(astToSql(ast, fields)).toBe( "([name] LIKE N'%a%' OR [name] LIKE N'%b%' OR [status] = N'new')", ) }) it('支持 (A 且 B) 或 C:组行内且、组与前一行或', () => { const ast = buildAdvancedAst({ nodes: [{ join: 'or', field: 'name', operator: 'eq', value: '张三' }], groups: [ { id: 'g1', join: 'or', nodes: [ { join: 'and', field: 'amount', operator: 'gt', value: 100 }, { join: 'and', field: 'amount', operator: 'gt', value: 1000 }, ], }, ], }) expect(astToSql(ast, fields)).toBe("([name] = N'张三' OR ([amount] > 100 AND [amount] > 1000))") }) it('同一层可混用 且/或:(a 且 b) 或 c 从左到右加括号', () => { const ast = buildAdvancedAst({ nodes: [ { join: 'and', field: 'name', operator: 'like', value: 'a' }, { join: 'and', field: 'name', operator: 'like', value: 'b' }, { join: 'or', field: 'name', operator: 'like', value: 'c' }, ], }) expect(astToSql(ast, fields)).toBe( "(([name] LIKE N'%a%' AND [name] LIKE N'%b%') OR [name] LIKE N'%c%')", ) }) it('区间 / 多选只要有一项即算已填写,空值按操作符重置', () => { expect(hasConditionValue(['', '20'])).toBe(true) expect(hasConditionValue(['', ''])).toBe(false) expect(hasConditionValue(undefined)).toBe(false) expect(emptyConditionValue({ b_type: 'money' }, 'between')).toEqual(['', '']) expect(emptyConditionValue({ b_type: 'select' }, 'eq')).toBeUndefined() }) })