Files
plainleaf/client/runtime_api/translate_query.test.ts
T

168 lines
4.6 KiB
TypeScript

import { expect, test } from "vitest";
import {
translateListRequest,
serializeLuaValue,
type Filter,
} from "./translate_query.ts";
test("serializeLuaValue: numbers, booleans, null, strings", () => {
expect(serializeLuaValue("42")).toBe("42");
expect(serializeLuaValue("-3.14")).toBe("-3.14");
expect(serializeLuaValue("true")).toBe("true");
expect(serializeLuaValue("false")).toBe("false");
expect(serializeLuaValue("null")).toBe("nil");
expect(serializeLuaValue("hello")).toBe('"hello"');
expect(serializeLuaValue('a"b\nc')).toBe('"a\\"b\\nc"');
});
test("serializeLuaValue: explicit type prefix overrides inference", () => {
expect(serializeLuaValue("num:42")).toBe("42");
expect(serializeLuaValue("str:42")).toBe('"42"');
expect(serializeLuaValue("bool:true")).toBe("true");
});
test("translateListRequest: equality filter", () => {
const out = translateListRequest({
tag: "task",
filters: [{ field: "done", op: "eq", value: "false" }],
order: [],
limit: 100,
offset: 0,
});
expect(out.equivalentLua).toBe(
`from _it = index.tag("task") where _it.done == false limit 100`,
);
});
test("translateListRequest: every operator emits expected source", () => {
const cases: Array<[Filter["op"], string, string]> = [
["ne", "5", "_it.f ~= 5"],
["gt", "5", "_it.f > 5"],
["gte", "5", "_it.f >= 5"],
["lt", "5", "_it.f < 5"],
["lte", "5", "_it.f <= 5"],
["startsWith", "Hi", `string.sub(_it.f, 1, #"Hi") == "Hi"`],
["contains", "x", `string.find(_it.f, "x", 1, true) ~= nil`],
];
for (const [op, raw, expectedClause] of cases) {
const out = translateListRequest({
tag: "x",
filters: [{ field: "f", op, value: raw }],
order: [],
limit: 10,
offset: 0,
});
expect(out.equivalentLua).toContain(expectedClause);
}
});
test("translateListRequest: in operator expands to or-chain", () => {
const out = translateListRequest({
tag: "x",
filters: [{ field: "status", op: "in", value: "a,b,c" }],
order: [],
limit: 10,
offset: 0,
});
expect(out.equivalentLua).toContain(
`(_it.status == "a") or (_it.status == "b") or (_it.status == "c")`,
);
});
test("translateListRequest: multiple filters are AND-ed", () => {
const out = translateListRequest({
tag: "task",
filters: [
{ field: "done", op: "eq", value: "false" },
{ field: "priority", op: "gte", value: "2" },
],
order: [],
limit: 50,
offset: 0,
});
expect(out.equivalentLua).toContain(
`where (_it.done == false) and (_it.priority >= 2)`,
);
});
test("translateListRequest: ordering, offset, select", () => {
const out = translateListRequest({
tag: "page",
filters: [],
order: [
{ field: "lastModified", desc: true },
{ field: "name", desc: false },
],
limit: 10,
offset: 20,
select: ["name", "lastModified"],
});
expect(out.equivalentLua).toContain(
"order by _it.lastModified desc, _it.name",
);
expect(out.equivalentLua).toContain("limit 10, 20");
expect(out.equivalentLua).toContain(
"select { name = _it.name, lastModified = _it.lastModified }",
);
});
test("translateListRequest: dotted field paths allowed", () => {
const out = translateListRequest({
tag: "page",
filters: [{ field: "meta.author", op: "eq", value: "alice" }],
order: [],
limit: 10,
offset: 0,
});
expect(out.equivalentLua).toContain(`_it.meta.author == "alice"`);
});
test("translateListRequest rejects bad field paths", () => {
expect(() =>
translateListRequest({
tag: "x",
filters: [{ field: "1bad", op: "eq", value: "v" }],
order: [],
limit: 10,
offset: 0,
}),
).toThrow(/bad_field/);
expect(() =>
translateListRequest({
tag: "x",
filters: [{ field: "a; drop", op: "eq", value: "v" }],
order: [],
limit: 10,
offset: 0,
}),
).toThrow(/bad_field/);
});
test("translateListRequest: empty in-list rejected", () => {
expect(() =>
translateListRequest({
tag: "x",
filters: [{ field: "f", op: "in", value: "" }],
order: [],
limit: 10,
offset: 0,
}),
).toThrow(/bad_query/);
});
test("translateListRequest: result also produces a LuaCollectionQuery", () => {
const out = translateListRequest({
tag: "task",
filters: [{ field: "done", op: "eq", value: "false" }],
order: [{ field: "priority", desc: true }],
limit: 5,
offset: 0,
});
expect(out.query.limit).toBe(5);
expect(out.query.offset).toBe(0);
expect(out.query.where).toBeDefined();
expect(out.query.orderBy).toHaveLength(1);
expect(out.query.objectVariable).toBe("_it");
});