Renaming or editing a Sieve rule moved it to the bottom of the list, and in Sieve the order is the order the rules run in, so mail started being filed by a different rule than before. Putting it back took a click per place moved. Two things did it. saveAndApply always appended the rule it was given — right for a rule created from a message, wrong for one being edited. And the "Also apply to existing messages" tick defaulted to on wherever it was offered, so every edit in Settings went down that path, including a plain rename. The rule now keeps its seat: a shared upsertRule replaces by id in place and only appends what is genuinely new. The tick defaults to on only in "Filter messages like this…", where applying it is the point, and the toast no longer calls an edited rule "created". Fixes #24
40 lines
2.4 KiB
TypeScript
40 lines
2.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { newRule, rulesToSieve, sieveToRules, testToSieve, sieveString, upsertRule } from "../sieve";
|
|
|
|
describe("sieve codec", () => {
|
|
it("escapes strings", () => {
|
|
expect(sieveString('a "quoted" \\ value')).toBe('"a \\"quoted\\" \\\\ value"');
|
|
});
|
|
it("generates tests", () => {
|
|
expect(testToSieve({ type: "header", header: "subject", op: "contains", value: "hi" })).toBe('header :contains "subject" "hi"');
|
|
expect(testToSieve({ type: "header", header: "x-foo", op: "notexists", value: "" })).toBe('not exists "x-foo"');
|
|
expect(testToSieve({ type: "address", header: "from", part: "domain", op: "is", value: "example.com" })).toBe('address :domain :is "from" "example.com"');
|
|
expect(testToSieve({ type: "size", op: "over", value: 2048 })).toBe("size :over 2048");
|
|
});
|
|
it("round-trips rules through a script", () => {
|
|
const rules = [
|
|
newRule({ id: "r1", name: "Newsletters", tests: [{ type: "header", header: "list-id", op: "exists", value: "" }], actions: [{ type: "fileinto", mailbox: "Newsletters" }, { type: "markread" }, { type: "stop" }] }),
|
|
newRule({ id: "r2", name: "Big", enabled: false, join: "anyof", tests: [{ type: "size", op: "over", value: 5_000_000 }], actions: [{ type: "addflag", flag: "big" }] }),
|
|
];
|
|
const script = rulesToSieve(rules);
|
|
expect(script).toContain('require ["fileinto", "imap4flags"];');
|
|
expect(script).toContain('if exists "list-id"');
|
|
expect(script).toContain('fileinto "Newsletters";');
|
|
expect(script).toContain('addflag "\\\\Seen";');
|
|
expect(script).toContain("# (disabled) Big");
|
|
expect(sieveToRules(script)).toEqual(rules);
|
|
});
|
|
it("keeps an edited rule in its place and appends a new one", () => {
|
|
const rules = ["r1", "r2", "r3"].map((id) => newRule({ id, name: id }));
|
|
const renamed = { ...rules[1]!, name: "Renamed" };
|
|
expect(upsertRule(rules, renamed).map((r) => r.id)).toEqual(["r1", "r2", "r3"]);
|
|
expect(upsertRule(rules, renamed)[1]!.name).toBe("Renamed");
|
|
expect(upsertRule(rules, newRule({ id: "r4" })).map((r) => r.id)).toEqual(["r1", "r2", "r3", "r4"]);
|
|
expect(rules.map((r) => r.name)).toEqual(["r1", "r2", "r3"]);
|
|
});
|
|
it("reports hand-written scripts as raw", () => {
|
|
expect(sieveToRules('require ["fileinto"];\nif true { keep; }')).toBeNull();
|
|
expect(sieveToRules("")).toEqual([]);
|
|
});
|
|
});
|