Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,270 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { and, condition, or } from "../builder/index.js";
import type { Rule } from "../types/index.js";
import {
analyzeRule,
countRules,
estimateCost,
findFields,
hasAsyncComponents,
isOptimisationStable,
optimizeRule,
} from "./optimizer.js";
describe("optimizeRule — flatten", () => {
it("flattens nested AND of AND", { timeout: 5000 }, () => {
const r = and(
and(condition("a", "equals", 1), condition("b", "equals", 2)),
condition("c", "equals", 3)
);
const opt = optimizeRule(r);
const rules = (opt.rule as { rules: readonly Rule[] }).rules;
expect(rules.length).toBe(3);
});
it("flattens nested OR of OR", { timeout: 5000 }, () => {
const r = or(
or(condition("a", "equals", 1), condition("b", "equals", 2)),
condition("c", "equals", 3)
);
const opt = optimizeRule(r);
expect((opt.rule as { rules: readonly Rule[] }).rules.length).toBe(3);
});
it("does not flatten AND of OR", { timeout: 5000 }, () => {
const r = and(
or(condition("a", "equals", 1), condition("b", "equals", 2)),
condition("c", "equals", 3)
);
const opt = optimizeRule(r, {
dedupe: false,
simplify: false,
sort: false,
});
expect((opt.rule as { rules: readonly Rule[] }).rules.length).toBe(2);
});
it("flatten can be disabled", { timeout: 5000 }, () => {
const r = and(
and(condition("a", "equals", 1), condition("b", "equals", 2)),
condition("c", "equals", 3)
);
const opt = optimizeRule(r, {
flatten: false,
dedupe: false,
simplify: false,
sort: false,
});
expect((opt.rule as { rules: readonly Rule[] }).rules.length).toBe(2);
});
});
describe("optimizeRule — dedupe", () => {
it("removes structural duplicates", { timeout: 5000 }, () => {
const c = condition("a", "equals", 1);
const r = and(c, c, condition("b", "equals", 2));
const opt = optimizeRule(r, { sort: false, simplify: false });
expect((opt.rule as { rules: readonly Rule[] }).rules.length).toBe(2);
});
it("dedupe recurses into groups", { timeout: 5000 }, () => {
const group: Rule = {
type: "group",
name: "g",
rules: [condition("a", "equals", 1), condition("a", "equals", 1)],
};
const opt = optimizeRule(group);
expect((opt.rule as { rules: readonly Rule[] }).rules.length).toBe(1);
});
it("dedupe can be disabled", { timeout: 5000 }, () => {
const c = condition("a", "equals", 1);
const r = and(c, c);
const opt = optimizeRule(r, { dedupe: false, simplify: false });
expect((opt.rule as { rules: readonly Rule[] }).rules.length).toBe(2);
});
});
describe("optimizeRule — simplify", () => {
it("unwraps single-child AND/OR", { timeout: 5000 }, () => {
const r: Rule = {
type: "logical",
operator: "and",
rules: [condition("a", "equals", 1)],
};
const opt = optimizeRule(r, {
flatten: false,
dedupe: false,
sort: false,
});
expect(opt.rule.type).toBe("condition");
});
it("preserves NOT (single-child by design)", { timeout: 5000 }, () => {
const r: Rule = {
type: "logical",
operator: "not",
rules: [condition("a", "equals", 1)],
};
const opt = optimizeRule(r);
expect(opt.rule.type).toBe("logical");
expect((opt.rule as { operator: string }).operator).toBe("not");
});
it("simplify can be disabled", { timeout: 5000 }, () => {
const r: Rule = {
type: "logical",
operator: "and",
rules: [condition("a", "equals", 1)],
};
const opt = optimizeRule(r, {
simplify: false,
flatten: false,
dedupe: false,
sort: false,
});
expect(opt.rule.type).toBe("logical");
});
});
describe("optimizeRule — sort", () => {
it("sorts AND children by ascending cost", { timeout: 5000 }, () => {
const r = and(
condition("a", "matches", "^x$"),
condition("b", "equals", 1)
);
const opt = optimizeRule(r, {
flatten: false,
dedupe: false,
simplify: false,
});
const rules = (opt.rule as { rules: readonly Rule[] }).rules;
expect((rules[0] as { operator: string }).operator).toBe("equals");
});
it("does not sort NOT or group implicitly", { timeout: 5000 }, () => {
const r: Rule = {
type: "logical",
operator: "not",
rules: [condition("a", "equals", 1)],
};
const opt = optimizeRule(r);
expect(opt.rule.type).toBe("logical");
});
it("sort can be disabled", { timeout: 5000 }, () => {
const r = and(
condition("a", "matches", "^x$"),
condition("b", "equals", 1)
);
const opt = optimizeRule(r, {
sort: false,
dedupe: false,
simplify: false,
flatten: false,
});
expect((opt.rule as { rules: readonly Rule[] }).rules[0]?.type).toBe(
"condition"
);
});
it("group children pass through sort", { timeout: 5000 }, () => {
const r: Rule = {
type: "group",
name: "g",
rules: [
condition("a", "matches", "^x$"),
condition("b", "equals", 1),
],
};
const opt = optimizeRule(r);
expect(opt.rule.type).toBe("group");
});
});
describe("analyzeRule", () => {
it("counts total + depth + fields", { timeout: 5000 }, () => {
const r = and(
condition("a", "equals", 1),
or(condition("b", "equals", 2), condition("c", "equals", 3))
);
const stats = analyzeRule(r);
expect(stats.totalRules).toBe(5);
expect(stats.maxDepth).toBe(2);
expect(stats.fields).toEqual(expect.arrayContaining(["a", "b", "c"]));
});
it("detects async via custom/computed", { timeout: 5000 }, () => {
const custom: Rule = {
type: "custom",
name: "c",
validate: async () => true,
};
expect(analyzeRule(custom).hasAsync).toBe(true);
const computed: Rule = {
type: "computed",
field: "x",
operator: "equals",
compute: () => 1,
};
expect(analyzeRule(computed).hasAsync).toBe(true);
});
it("hasAsyncComponents alias", { timeout: 5000 }, () => {
expect(hasAsyncComponents(condition("a", "equals", 1))).toBe(false);
expect(
hasAsyncComponents({
type: "custom",
name: "c",
validate: () => true,
})
).toBe(true);
});
it("countRules / findFields / estimateCost helpers", {
timeout: 5000,
}, () => {
const r = and(
condition("a", "equals", 1),
condition("b", "matches", "^x$")
);
expect(countRules(r)).toBe(3);
expect(findFields(r)).toEqual(expect.arrayContaining(["a", "b"]));
expect(estimateCost(r)).toBeGreaterThan(0);
});
it("isOptimisationStable returns true when no changes", {
timeout: 5000,
}, () => {
const r = condition("a", "equals", 1);
expect(isOptimisationStable(r)).toBe(true);
});
it("isOptimisationStable returns false when optimisations apply", {
timeout: 5000,
}, () => {
const r = and(
and(condition("a", "equals", 1), condition("b", "equals", 2))
);
expect(isOptimisationStable(r)).toBe(false);
});
it("walks group children", { timeout: 5000 }, () => {
const r: Rule = {
type: "group",
name: "g",
rules: [condition("a", "equals", 1), condition("b", "equals", 2)],
};
expect(analyzeRule(r).totalRules).toBe(3);
});
});
describe("optimizeRule — result", () => {
it("reports before + after stats", { timeout: 5000 }, () => {
const r = and(
and(condition("a", "equals", 1), condition("b", "equals", 2))
);
const opt = optimizeRule(r);
expect(opt.before.totalRules).toBeGreaterThan(opt.after.totalRules);
});
});