- /console/rules tabs: rule groups, custom conditions, defaults, acquisition policies, suppressions - editor: rule string parsed live via /rules/parse (position + suggestions) and synced both ways with layer cards; hard excludes; expectedRevision conflicts keep local edits - preview against recent releases or pasted titles with pri / level / reasons / trace - single-rule groups shown as preference tags; disable/delete writes suppression records - /console/preferences redirects to /console/rules (origin passed through) Co-Authored-By: Claude Opus 5.5 <[email protected]>
44 lines
1.8 KiB
TypeScript
44 lines
1.8 KiB
TypeScript
import type { RuleExpr, RuleLayer } from "@umt/contracts";
|
|
import { describe, expect, it } from "vitest";
|
|
import { cardsOf, cardToLayer, exprToString, layersToString } from "./expr.ts";
|
|
|
|
const a = (id: string): RuleExpr => ({ op: "atom", id });
|
|
const not = (arg: RuleExpr): RuleExpr => ({ op: "not", arg });
|
|
const and = (...args: RuleExpr[]): RuleExpr => ({ op: "and", args });
|
|
const or = (...args: RuleExpr[]): RuleExpr => ({ op: "or", args });
|
|
|
|
describe("规则串序列化", () => {
|
|
it("只加必要括号,原子大写", () => {
|
|
expect(exprToString(and(a("specsub"), a("CNSUB")))).toBe("SPECSUB & CNSUB");
|
|
expect(exprToString(or(and(a("4K"), a("HDR")), a("1080P")))).toBe("4K & HDR | 1080P");
|
|
expect(exprToString(and(or(a("4K"), a("1080P")), not(a("BLU"))))).toBe("(4K | 1080P) & !BLU");
|
|
expect(exprToString(not(or(a("REMUX"), a("BLU"))))).toBe("!(REMUX | BLU)");
|
|
});
|
|
|
|
it("多层用 > 连接", () => {
|
|
const layers: RuleLayer[] = [{ expr: and(a("SPECSUB"), a("CNSUB")) }, { expr: a("BLU") }];
|
|
expect(layersToString(layers)).toBe("SPECSUB & CNSUB > BLU");
|
|
});
|
|
});
|
|
|
|
describe("层卡片往返", () => {
|
|
it("两层以内的表达式可转卡片再转回", () => {
|
|
const layers: RuleLayer[] = [
|
|
{ expr: and(or(a("4K"), a("1080P")), not(a("BLU"))) },
|
|
{ expr: not(a("REMUX")) },
|
|
{ expr: or(a("CNSUB"), not(and(a("HDR"), a("DOLBY")))) },
|
|
];
|
|
const cards = cardsOf(layers);
|
|
expect(cards).not.toBeNull();
|
|
expect(cards?.map((c) => cardToLayer(c))).toEqual(layers);
|
|
});
|
|
|
|
it("嵌套太深时返回 null,只能在规则串里改", () => {
|
|
expect(cardsOf([{ expr: and(a("A"), or(a("B"), and(a("C"), a("D")))) }])).toBeNull();
|
|
});
|
|
|
|
it("空层转不出表达式", () => {
|
|
expect(cardToLayer({ op: "and", items: [] })).toBeNull();
|
|
});
|
|
});
|