A mailing list is an address that passes mail on to everyone on it. To Stalwart it is its own object, x:MailingList, behind sysMailingList*, so it gets its own section under Directory after Groups: search, fifty to a page with each list's recipient count, and a panel to create, edit and delete one. Recipients are a property of the list, so unlike a group's members they save with the rest of the panel. What Save sends for them is only what was added and removed, one recipients/<address> pointer each -- the patch the live server accepted -- so a recipient added elsewhere while the panel was open is not taken out. They can be pasted several at a time, from a spreadsheet column, a comma-separated line or Name <address>; anything with an @ that is not an address stays in the box with a note. Past a dozen, a filter narrows them. That is all a list is in Stalwart -- no owners, moderation or posting rules -- so that is all the panel offers. The mock answers x:MailingList with two lists, the recipient set's live shape, and the refusals a wrong address, a clash with an account and a missing permission get. Twenty-five new strings and one plural, in all nine catalogues.
49 lines
2.6 KiB
TypeScript
49 lines
2.6 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { client } from "@/jmap/client";
|
|
import { createList, parseAddresses, queryLists, recipientsPatch } from "@/lib/adminLists";
|
|
|
|
describe("a mailing list's recipients", () => {
|
|
it("are saved as what was added and removed, one pointer each", () => {
|
|
// The live server adds a set key on `true`, removes it on `null`, and
|
|
// leaves the rest -- so a recipient added elsewhere meanwhile survives.
|
|
expect(recipientsPatch(["[email protected]", "[email protected]"], ["[email protected]", "[email protected]"])).toEqual({
|
|
"recipients/[email protected]": null,
|
|
"recipients/[email protected]": true,
|
|
});
|
|
expect(recipientsPatch(["[email protected]"], ["[email protected]"])).toEqual({});
|
|
});
|
|
|
|
it("compare without regard to case, and escape what a pointer cannot hold", () => {
|
|
expect(recipientsPatch(["[email protected]"], ["[email protected]"])).toEqual({});
|
|
expect(recipientsPatch([], ["odd/[email protected]"])).toEqual({ "recipients/[email protected]": true });
|
|
});
|
|
|
|
it("come out of a paste of names, commas and angle brackets, and keep what isn't an address", () => {
|
|
expect(parseAddresses('Ada Lovelace <[email protected]>, [email protected]; "Alan" [email protected]\[email protected] mailto:[email protected]')).toEqual({
|
|
addresses: ["[email protected]", "[email protected]", "[email protected]", "[email protected]"],
|
|
rejected: [],
|
|
});
|
|
expect(parseAddresses("ada@, @example.org, someone@nowhere")).toEqual({ addresses: [], rejected: ["ada@", "@example.org", "someone@nowhere"] });
|
|
});
|
|
});
|
|
|
|
describe("the list calls", () => {
|
|
it("search on text, and leave the filter out when there is none", async () => {
|
|
const call = vi.spyOn(client, "call").mockResolvedValue({ ids: [], total: 0 });
|
|
await queryLists({ text: " board ", position: 50, limit: 50 });
|
|
expect(call).toHaveBeenLastCalledWith("x:MailingList/query", { filter: { text: "board" }, position: 50, limit: 50, calculateTotal: true });
|
|
await queryLists({});
|
|
expect(call).toHaveBeenLastCalledWith("x:MailingList/query", { position: 0, calculateTotal: true });
|
|
call.mockRestore();
|
|
});
|
|
|
|
it("create one with its recipients as a set", async () => {
|
|
const call = vi.spyOn(client, "call").mockResolvedValue({ created: { n: { id: "l9" } } });
|
|
expect(await createList({ name: "team", domainId: "d1", description: " ", recipients: ["[email protected]", "[email protected]"] })).toBe("l9");
|
|
expect(call).toHaveBeenCalledWith("x:MailingList/set", {
|
|
create: { n: { name: "team", domainId: "d1", description: null, recipients: { "[email protected]": true, "[email protected]": true }, aliases: {} } },
|
|
});
|
|
call.mockRestore();
|
|
});
|
|
});
|