diff --git a/src/ui/search.test.ts b/src/ui/search.test.ts new file mode 100644 index 0000000..f1cae41 --- /dev/null +++ b/src/ui/search.test.ts @@ -0,0 +1,99 @@ +import { describe, it, expect } from "vitest"; +import { SumEngine } from "../engine"; +import { parseResultQuery } from "./search"; +import Decimal from "decimal.js"; + +describe("parseResultQuery", () => { + const engine = new SumEngine(); + + it("parses '>=' operator correctly", () => { + const res = parseResultQuery(engine, ">= 100"); + expect(res).not.toBeNull(); + expect(res?.op).toBe(">="); + expect(res?.threshold.kind).toBe("quantity"); + if (res?.threshold.kind === "quantity") { + expect(res.threshold.value.eq(new Decimal(100))).toBe(true); + } + }); + + it("parses '<=' operator correctly", () => { + const res = parseResultQuery(engine, "<= 50"); + expect(res).not.toBeNull(); + expect(res?.op).toBe("<="); + expect(res?.threshold.kind).toBe("quantity"); + if (res?.threshold.kind === "quantity") { + expect(res.threshold.value.eq(new Decimal(50))).toBe(true); + } + }); + + it("parses '>' operator correctly", () => { + const res = parseResultQuery(engine, "> 0"); + expect(res).not.toBeNull(); + expect(res?.op).toBe(">"); + expect(res?.threshold.kind).toBe("quantity"); + if (res?.threshold.kind === "quantity") { + expect(res.threshold.value.eq(new Decimal(0))).toBe(true); + } + }); + + it("parses '<' operator correctly", () => { + const res = parseResultQuery(engine, "< -10"); + expect(res).not.toBeNull(); + expect(res?.op).toBe("<"); + expect(res?.threshold.kind).toBe("quantity"); + if (res?.threshold.kind === "quantity") { + expect(res.threshold.value.eq(new Decimal(-10))).toBe(true); + } + }); + + it("parses '=' operator correctly", () => { + const res = parseResultQuery(engine, "= 42.5"); + expect(res).not.toBeNull(); + expect(res?.op).toBe("="); + expect(res?.threshold.kind).toBe("quantity"); + if (res?.threshold.kind === "quantity") { + expect(res.threshold.value.eq(new Decimal(42.5))).toBe(true); + } + }); + + it("parses '~' operator correctly", () => { + const res = parseResultQuery(engine, "~ 3.14"); + expect(res).not.toBeNull(); + expect(res?.op).toBe("~"); + expect(res?.threshold.kind).toBe("quantity"); + if (res?.threshold.kind === "quantity") { + expect(res.threshold.value.eq(new Decimal(3.14))).toBe(true); + } + }); + + it("handles whitespace correctly", () => { + const res = parseResultQuery(engine, " >= 200 "); + expect(res).not.toBeNull(); + expect(res?.op).toBe(">="); + expect(res?.threshold.kind).toBe("quantity"); + if (res?.threshold.kind === "quantity") { + expect(res.threshold.value.eq(new Decimal(200))).toBe(true); + } + }); + + it("returns null for invalid operator", () => { + const res = parseResultQuery(engine, "!= 100"); + expect(res).toBeNull(); + }); + + it("returns null for invalid value string", () => { + // "abc" cannot be evaluated to a quantity + const res = parseResultQuery(engine, ">= abc"); + expect(res).toBeNull(); + }); + + it("returns null for empty string", () => { + const res = parseResultQuery(engine, ""); + expect(res).toBeNull(); + }); + + it("returns null when no expression matches after operator", () => { + const res = parseResultQuery(engine, ">="); + expect(res).toBeNull(); + }); +}); diff --git a/src/ui/search.ts b/src/ui/search.ts index 7b332a0..97f3671 100644 --- a/src/ui/search.ts +++ b/src/ui/search.ts @@ -32,7 +32,7 @@ export interface SearchController { const $ = (sel: string): T => document.querySelector(sel) as T; -function parseResultQuery(engine: SumEngine, q: string): { op: string; threshold: Value } | null { +export function parseResultQuery(engine: SumEngine, q: string): { op: string; threshold: Value } | null { const m = /^(>=|<=|>|<|=|~)\s*(.+)$/.exec(q.trim()); if (!m) return null; const v = engine.evaluateExpression(m[2].trim());