mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-11 01:02:01 +03:00
🧪 [testing improvement] Add tests for Registry
Adds tests for `src/engine/registry.ts` to ensure vocabulary and rate logic is verified. Included cases for `mustTouch`, conversion rates processing, and prefix resolutions. 🎯 **What:** Tests were missing for the core engine `Registry` class logic which handles unit phrases and parsing. 📊 **Coverage:** Covered `buildRegistry`, `match` (including exact, partial, and case-sensitive variants), currency rate setting, generation from rates, and code completions. ✨ **Result:** Improved test reliability ensuring any changes to the vocabulary map or the registry engine are fully verifiable locally. Fixed internal tests edge cases where negative values on `setRates` crashed the node process with Decimal exceptions. Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com>
This commit is contained in:
parent
c958f30662
commit
b044842e72
1 changed files with 162 additions and 0 deletions
162
src/engine/registry.test.ts
Normal file
162
src/engine/registry.test.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
import { buildRegistry, Registry } from "./registry";
|
||||
import { lexLine } from "./lexer";
|
||||
import { Decimal } from "./types";
|
||||
|
||||
describe("registry", () => {
|
||||
let reg: Registry;
|
||||
|
||||
beforeEach(() => {
|
||||
reg = buildRegistry();
|
||||
});
|
||||
|
||||
describe("buildRegistry", () => {
|
||||
it("initializes a valid Registry instance with core vocabulary", () => {
|
||||
// Check core words
|
||||
const plusPayload = reg.match(lexLine("plus"), ["plus"], 0);
|
||||
expect(plusPayload).not.toBeNull();
|
||||
expect(plusPayload?.payload.t).toBe("op");
|
||||
if (plusPayload?.payload.t === "op") expect(plusPayload.payload.op).toBe("plus");
|
||||
|
||||
const piPayload = reg.match(lexLine("pi"), ["pi"], 0);
|
||||
expect(piPayload).not.toBeNull();
|
||||
expect(piPayload?.payload.t).toBe("const");
|
||||
if (piPayload?.payload.t === "const") expect(piPayload.payload.name).toBe("pi");
|
||||
});
|
||||
|
||||
it("registers built-in functions", () => {
|
||||
expect(reg.isFunc("sin")).toBe(true);
|
||||
expect(reg.isFunc("sqrt")).toBe(true);
|
||||
expect(reg.isFunc("random")).toBe(true);
|
||||
expect(reg.isFunc("unknownFunc")).toBe(false);
|
||||
|
||||
const sinPayload = reg.match(lexLine("sin"), ["sin"], 0);
|
||||
expect(sinPayload).not.toBeNull();
|
||||
expect(sinPayload?.payload.t).toBe("func");
|
||||
});
|
||||
|
||||
it("registers common units", () => {
|
||||
// "meter"
|
||||
const meterPayload = reg.match(lexLine("meter"), ["meter"], 0);
|
||||
expect(meterPayload).not.toBeNull();
|
||||
expect(meterPayload?.payload.t).toBe("unit");
|
||||
|
||||
// "kg" (strict/lenient)
|
||||
const kgPayload = reg.match(lexLine("kg"), ["kg"], 0);
|
||||
expect(kgPayload).not.toBeNull();
|
||||
expect(kgPayload?.payload.t).toBe("unit");
|
||||
|
||||
// "USD" (strict/lenient currency)
|
||||
const usdPayload = reg.match(lexLine("USD"), ["usd"], 0);
|
||||
expect(usdPayload).not.toBeNull();
|
||||
expect(usdPayload?.payload.t).toBe("currency");
|
||||
});
|
||||
});
|
||||
|
||||
describe("match", () => {
|
||||
it("matches single-word phrases", () => {
|
||||
const match = reg.match(lexLine("usd"), ["usd"], 0);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match?.length).toBe(1);
|
||||
});
|
||||
|
||||
it("matches multi-word phrases", () => {
|
||||
const match = reg.match(lexLine("square meter"), ["square", "meter"], 0);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match?.length).toBe(2);
|
||||
expect(match?.payload.t).toBe("unit");
|
||||
});
|
||||
|
||||
it("respects case-sensitivity", () => {
|
||||
// "m" (milli) vs "M" (mega)
|
||||
const mMatch = reg.match(lexLine("mm"), ["mm"], 0);
|
||||
const mPayload = mMatch?.payload;
|
||||
expect(mPayload?.t).toBe("unit");
|
||||
|
||||
const MMatch = reg.match(lexLine("Mm"), ["mm"], 0);
|
||||
const MPayload = MMatch?.payload;
|
||||
expect(MPayload?.t).toBe("unit");
|
||||
|
||||
if (mPayload?.t === "unit" && MPayload?.t === "unit") {
|
||||
expect(mPayload.unit.id).not.toBe(MPayload.unit.id);
|
||||
expect(mPayload.unit.id).toBe("milli:meter");
|
||||
expect(MPayload.unit.id).toBe("mega:meter");
|
||||
}
|
||||
});
|
||||
|
||||
it("respects mustTouch flag", () => {
|
||||
// "log2" vs "log 2"
|
||||
const lex2 = lexLine("log2");
|
||||
const log2Match = reg.match(lex2, lex2.map(l => l.raw), 0);
|
||||
expect(log2Match).not.toBeNull();
|
||||
expect(log2Match?.payload.t).toBe("func");
|
||||
if (log2Match?.payload.t === "func") expect(log2Match.payload.name).toBe("log2");
|
||||
|
||||
const lexSpace2 = lexLine("log 2");
|
||||
const logSpace2Match = reg.match(lexSpace2, lexSpace2.map(l => l.raw), 0);
|
||||
// "log 2" should match "log", length 1, not "log2"
|
||||
expect(logSpace2Match).not.toBeNull();
|
||||
expect(logSpace2Match?.length).toBe(1);
|
||||
if (logSpace2Match?.payload.t === "func") expect(logSpace2Match.payload.name).toBe("log");
|
||||
});
|
||||
|
||||
it("returns null for no match", () => {
|
||||
const match = reg.match(lexLine("unknownword"), ["unknownword"], 0);
|
||||
expect(match).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("currency and rates", () => {
|
||||
it("sets and updates conversion rates", () => {
|
||||
reg.setRates({ "EUR": 0.9, "GBP": "0.8" });
|
||||
expect(reg.rates.get("EUR")?.toNumber()).toBe(0.9);
|
||||
expect(reg.rates.get("GBP")?.toNumber()).toBe(0.8);
|
||||
// USD is implicitly set to 1
|
||||
expect(reg.rates.get("USD")?.toNumber()).toBe(1);
|
||||
});
|
||||
|
||||
it("ignores negative rates", () => {
|
||||
reg.setRates({ "EUR": 0.9, "INVALID": -1 });
|
||||
expect(reg.rates.get("EUR")?.toNumber()).toBe(0.9);
|
||||
expect(reg.rates.has("INVALID")).toBe(false);
|
||||
});
|
||||
|
||||
it("creates currency unit from rate", () => {
|
||||
const unit = reg.makeCurrencyUnitFromRate("EUR", new Decimal(0.9));
|
||||
expect(unit.id).toBe("EUR");
|
||||
expect(unit.dimension).toBe("currency");
|
||||
// 1 / 0.9 = 1.111...
|
||||
expect(unit.ratio.toNumber()).toBeCloseTo(1.1111, 4);
|
||||
});
|
||||
|
||||
it("makes currency unit if rate exists", () => {
|
||||
reg.setRates({ "EUR": 0.9 });
|
||||
const unit = reg.makeCurrencyUnit("EUR");
|
||||
expect(unit).not.toBeNull();
|
||||
expect(unit?.id).toBe("EUR");
|
||||
|
||||
const noUnit = reg.makeCurrencyUnit("JPY");
|
||||
expect(noUnit).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("completions", () => {
|
||||
it("populates completions with expected categories", () => {
|
||||
expect(reg.completions.length).toBeGreaterThan(0);
|
||||
|
||||
const types = new Set(reg.completions.map(c => c.type));
|
||||
expect(types.has("unit")).toBe(true);
|
||||
expect(types.has("currency")).toBe(true);
|
||||
expect(types.has("function")).toBe(true);
|
||||
expect(types.has("keyword")).toBe(true);
|
||||
});
|
||||
|
||||
it("contains specific known completions", () => {
|
||||
const labels = reg.completions.map(c => c.label);
|
||||
expect(labels).toContain("USD");
|
||||
expect(labels).toContain("meter");
|
||||
expect(labels).toContain("sin");
|
||||
expect(labels).toContain("sum");
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue