chore: 版本 0.1.0 更新 TypeORM 适配包 toFindOptionsWhere/applyToQueryBuilder
Node.js Build / build (push) Successful in 5s
Node.js Build / build (push) Successful in 5s
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
Equal,
|
||||
Not,
|
||||
MoreThan,
|
||||
MoreThanOrEqual,
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
Like,
|
||||
In,
|
||||
Between,
|
||||
IsNull,
|
||||
FindOperator,
|
||||
} from 'typeorm';
|
||||
import {
|
||||
toFindOptionsWhere,
|
||||
applyToQueryBuilder,
|
||||
DEFAULT_OPERATOR_MAP,
|
||||
mapOperator,
|
||||
} from '../src/index.js';
|
||||
|
||||
/**
|
||||
* @returns {{ where: ReturnType<typeof vi.fn>, andWhere: ReturnType<typeof vi.fn>, orWhere: ReturnType<typeof vi.fn> }}
|
||||
*/
|
||||
function createMockQb() {
|
||||
return {
|
||||
where: vi.fn().mockReturnThis(),
|
||||
andWhere: vi.fn().mockReturnThis(),
|
||||
orWhere: vi.fn().mockReturnThis(),
|
||||
};
|
||||
}
|
||||
|
||||
describe('toFindOptionsWhere', () => {
|
||||
it('EQ 单条件 → Equal', () => {
|
||||
const out = toFindOptionsWhere([{ field: 'name', query: 'EQ', value: 'xx' }]);
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0].name).toBeInstanceOf(FindOperator);
|
||||
expect(out[0].name.type).toBe('equal');
|
||||
expect(out[0].name.value).toBe('xx');
|
||||
});
|
||||
|
||||
it('两个 AND 项合并到同一对象', () => {
|
||||
const out = toFindOptionsWhere([
|
||||
{ field: 'a', query: 'EQ', value: 1 },
|
||||
{ field: 'b', query: 'GT', value: 2, logic: 'AND' },
|
||||
]);
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0].a.type).toBe('equal');
|
||||
expect(out[0].a.value).toBe(1);
|
||||
expect(out[0].b.type).toBe('moreThan');
|
||||
expect(out[0].b.value).toBe(2);
|
||||
});
|
||||
|
||||
it('OR 项拆成数组两个元素', () => {
|
||||
const out = toFindOptionsWhere([
|
||||
{ field: 'a', query: 'EQ', value: 1 },
|
||||
{ field: 'b', query: 'EQ', value: 2, logic: 'OR' },
|
||||
]);
|
||||
expect(out).toHaveLength(2);
|
||||
expect(out[0]).toEqual({ a: Equal(1) });
|
||||
expect(out[1]).toEqual({ b: Equal(2) });
|
||||
});
|
||||
|
||||
it('IN 使用 values → In', () => {
|
||||
const out = toFindOptionsWhere([
|
||||
{ field: 'tags', query: 'IN', value: 'a,b', values: ['a', 'b'] },
|
||||
]);
|
||||
expect(out[0].tags.type).toBe('in');
|
||||
expect(out[0].tags.value).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('BETWEEN → Between', () => {
|
||||
const out = toFindOptionsWhere([
|
||||
{ field: 'n', query: 'BETWEEN', value: '1,10', values: ['1', '10'] },
|
||||
]);
|
||||
expect(out[0].n.type).toBe('between');
|
||||
expect(out[0].n.value).toEqual(['1', '10']);
|
||||
});
|
||||
|
||||
it('IS_NULL → IsNull', () => {
|
||||
const out = toFindOptionsWhere([{ field: 'd', query: 'IS_NULL' }]);
|
||||
expect(out[0].d.type).toBe('isNull');
|
||||
});
|
||||
|
||||
it('IS_NOT_NULL → Not(IsNull)', () => {
|
||||
const out = toFindOptionsWhere([{ field: 'd', query: 'IS_NOT_NULL' }]);
|
||||
expect(out[0].d.type).toBe('not');
|
||||
});
|
||||
|
||||
it('NE → Not(Equal)', () => {
|
||||
const out = toFindOptionsWhere([{ field: 's', query: 'NE', value: 'x' }]);
|
||||
expect(out[0].s.type).toBe('not');
|
||||
});
|
||||
|
||||
it('disabled/hidden 跳过', () => {
|
||||
const out = toFindOptionsWhere([
|
||||
{ field: 'a', query: 'EQ', value: 1, disabled: true },
|
||||
{ field: 'b', query: 'EQ', value: 2, hidden: true },
|
||||
{ field: 'c', query: 'EQ', value: 3 },
|
||||
]);
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0].c.value).toBe(3);
|
||||
});
|
||||
|
||||
it('skipEmpty:true 跳过 empty:true', () => {
|
||||
const out = toFindOptionsWhere(
|
||||
[
|
||||
{ field: 'a', query: 'EQ', value: null, empty: true },
|
||||
{ field: 'b', query: 'EQ', value: 2 },
|
||||
],
|
||||
{ skipEmpty: true },
|
||||
);
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0].b.value).toBe(2);
|
||||
});
|
||||
|
||||
it('空 value 且非 IS_NULL 跳过', () => {
|
||||
const out = toFindOptionsWhere([{ field: 'a', query: 'EQ', value: null }]);
|
||||
expect(out).toEqual([]);
|
||||
});
|
||||
|
||||
it('未知 query fallback → Equal', () => {
|
||||
const out = toFindOptionsWhere([{ field: 'a', query: 'FOO', value: 'x' }]);
|
||||
expect(out[0].a.type).toBe('equal');
|
||||
});
|
||||
|
||||
it('空输入 → []', () => {
|
||||
expect(toFindOptionsWhere([])).toEqual([]);
|
||||
expect(toFindOptionsWhere(null)).toEqual([]);
|
||||
});
|
||||
|
||||
it('GTE/LTE/LIKE 正确映射', () => {
|
||||
const out = toFindOptionsWhere([
|
||||
{ field: 'a', query: 'GTE', value: 1 },
|
||||
{ field: 'b', query: 'LTE', value: 2 },
|
||||
{ field: 'c', query: 'LIKE', value: 'xx' },
|
||||
]);
|
||||
expect(out[0].a.type).toBe('moreThanOrEqual');
|
||||
expect(out[0].b.type).toBe('lessThanOrEqual');
|
||||
expect(out[0].c.type).toBe('like');
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyToQueryBuilder', () => {
|
||||
it('单 EQ → andWhere("name = :p1", {p1:"xx"})', () => {
|
||||
const qb = createMockQb();
|
||||
const { parameters } = applyToQueryBuilder(qb, [
|
||||
{ field: 'name', query: 'EQ', value: 'xx' },
|
||||
]);
|
||||
expect(qb.andWhere).toHaveBeenCalledTimes(1);
|
||||
expect(qb.andWhere.mock.calls[0][0]).toBe('name = :p1');
|
||||
expect(qb.andWhere.mock.calls[0][1]).toEqual({ p1: 'xx' });
|
||||
expect(parameters).toEqual({ p1: 'xx' });
|
||||
});
|
||||
|
||||
it('LIKE 自动加 %', () => {
|
||||
const qb = createMockQb();
|
||||
applyToQueryBuilder(qb, [{ field: 'name', query: 'LIKE', value: 'xx' }]);
|
||||
expect(qb.andWhere.mock.calls[0][0]).toBe('name LIKE :p1');
|
||||
expect(qb.andWhere.mock.calls[0][1]).toEqual({ p1: '%xx%' });
|
||||
});
|
||||
|
||||
it('alias 前缀 → u.name = :p1', () => {
|
||||
const qb = createMockQb();
|
||||
applyToQueryBuilder(qb, [{ field: 'name', query: 'EQ', value: 'xx' }], { alias: 'u' });
|
||||
expect(qb.andWhere.mock.calls[0][0]).toBe('u.name = :p1');
|
||||
});
|
||||
|
||||
it('AND 同层:两次 andWhere', () => {
|
||||
const qb = createMockQb();
|
||||
applyToQueryBuilder(qb, [
|
||||
{ field: 'a', query: 'EQ', value: 1 },
|
||||
{ field: 'b', query: 'EQ', value: 2, logic: 'AND' },
|
||||
]);
|
||||
expect(qb.andWhere).toHaveBeenCalledTimes(2);
|
||||
expect(qb.orWhere).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('OR 同层:andWhere 后 orWhere', () => {
|
||||
const qb = createMockQb();
|
||||
applyToQueryBuilder(qb, [
|
||||
{ field: 'a', query: 'EQ', value: 1 },
|
||||
{ field: 'b', query: 'EQ', value: 2, logic: 'OR' },
|
||||
]);
|
||||
expect(qb.andWhere).toHaveBeenCalledTimes(1);
|
||||
expect(qb.orWhere).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('IN 展开 → tags IN (:...p1)', () => {
|
||||
const qb = createMockQb();
|
||||
applyToQueryBuilder(qb, [
|
||||
{ field: 'tags', query: 'IN', value: 'a,b', values: ['a', 'b'] },
|
||||
]);
|
||||
expect(qb.andWhere.mock.calls[0][0]).toBe('tags IN (:...p1)');
|
||||
expect(qb.andWhere.mock.calls[0][1]).toEqual({ p1: ['a', 'b'] });
|
||||
});
|
||||
|
||||
it('嵌套分组 → 括号表达式', () => {
|
||||
const qb = createMockQb();
|
||||
applyToQueryBuilder(qb, [
|
||||
{ field: 'a', query: 'EQ', value: 1 },
|
||||
{
|
||||
logic: 'OR',
|
||||
children: [
|
||||
{ field: 'b', query: 'EQ', value: 2 },
|
||||
{ field: 'c', query: 'EQ', value: 3, logic: 'AND' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
expect(qb.andWhere).toHaveBeenCalledTimes(1);
|
||||
expect(qb.orWhere).toHaveBeenCalledTimes(1);
|
||||
const groupExpr = qb.orWhere.mock.calls[0][0];
|
||||
expect(groupExpr).toContain('(');
|
||||
expect(groupExpr).toContain(')');
|
||||
// b AND c inside the group, joined to a by OR
|
||||
expect(groupExpr).toContain('b = :p2');
|
||||
expect(groupExpr).toContain('c = :p3');
|
||||
});
|
||||
|
||||
it('参数递增 p1/p2', () => {
|
||||
const qb = createMockQb();
|
||||
const { parameters } = applyToQueryBuilder(qb, [
|
||||
{ field: 'a', query: 'EQ', value: 1 },
|
||||
{ field: 'b', query: 'EQ', value: 2, logic: 'AND' },
|
||||
]);
|
||||
expect(parameters.p1).toBe(1);
|
||||
expect(parameters.p2).toBe(2);
|
||||
});
|
||||
|
||||
it("method='where' 首条件用 where", () => {
|
||||
const qb = createMockQb();
|
||||
applyToQueryBuilder(qb, [{ field: 'a', query: 'EQ', value: 1 }], { method: 'where' });
|
||||
expect(qb.where).toHaveBeenCalledTimes(1);
|
||||
expect(qb.andWhere).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('空输入不调用 qb 方法', () => {
|
||||
const qb = createMockQb();
|
||||
const { parameters } = applyToQueryBuilder(qb, []);
|
||||
expect(qb.where).not.toHaveBeenCalled();
|
||||
expect(qb.andWhere).not.toHaveBeenCalled();
|
||||
expect(qb.orWhere).not.toHaveBeenCalled();
|
||||
expect(parameters).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('index exports', () => {
|
||||
it('DEFAULT_OPERATOR_MAP 含 IS_NULL/IS_NOT_NULL', () => {
|
||||
expect(DEFAULT_OPERATOR_MAP.IS_NULL).toBeTypeOf('function');
|
||||
expect(DEFAULT_OPERATOR_MAP.IS_NOT_NULL).toBeTypeOf('function');
|
||||
});
|
||||
|
||||
it('mapOperator 返回工厂函数', () => {
|
||||
const factory = mapOperator('EQ');
|
||||
expect(factory).toBeTypeOf('function');
|
||||
expect(factory('x').type).toBe('equal');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user