Takes the flat module count from 66 to 42, continuing what admin/ and
calendar/ started.
lib/mailbox/ archiveDate, emptyFolder, folderMove, labelTree,
mailboxName, mailboxRoute
lib/sieve/ sieve, sieveApply, sieveFolders
lib/input/ keyboard, swipe, touch, listSelection, dropUpload
lib/notify/ notify, webpush, webpushEnable
lib/sw/ swCache, swFacts, staleBuild
lib/text/ html, markdown, text, emlName
FOUR THINGS THE FILENAMES GET WRONG, each checked by reading the file
rather than trusting what it is called:
- appFolder is not a mailbox. It is the `ihasmail` folder in JMAP
*Files*, where the client keeps signature images and synced settings.
It stays flat.
- format holds no formatting of text. It re-exports the date and clock
formatters, so it belongs with dates/datetime, not with text/.
- preview is the file viewer deciding what it can show without
downloading, and source is where to point someone asking for this
instance's AGPL source. Neither is about text.
- notify is not Web Push. It is the tab title, the favicon badge and
the new-mail sound -- in-app notification, which is why it sits with
webpush rather than under sw/ with the service worker's own concerns.
threadScroll stays flat too: it decides where a conversation opens, which
is view state rather than a gesture, and input/ is honest only if
everything in it interprets something the reader did.
No behavior change. Almost every reference was on the @/ alias; eight
relative imports in files that did not move, or that moved away from a
sibling, needed rewriting by hand.
94 lines
4.1 KiB
TypeScript
94 lines
4.1 KiB
TypeScript
/**
|
|
* The two sentence builders, which had no tests while they were building
|
|
* English by concatenation -- and no test would have caught the thing wrong
|
|
* with them, since the English output was correct. These pin the two
|
|
* properties that matter now: every fragment goes through the catalog, and
|
|
* the joining is Intl's rather than a hardcoded " and ".
|
|
*/
|
|
import { describe, expect, it } from "vitest";
|
|
import { describeRule as describeSieve } from "../sieve/sieve";
|
|
import { describeRule as describeRecurrence, weekdayOptions } from "../calendar/recurrence";
|
|
import { setUiLanguageForFormatting } from "../datetime";
|
|
import { setCatalog } from "../i18n";
|
|
|
|
describe("sieve describeRule", () => {
|
|
it("names the header and operator through the catalog", () => {
|
|
const s = describeSieve({
|
|
id: "1", name: "r", join: "allof", enabled: true,
|
|
tests: [{ type: "header", header: "subject", op: "contains", value: "invoice" }],
|
|
actions: [{ type: "fileinto", mailbox: "Work" }],
|
|
} as never);
|
|
expect(s).toContain("Subject");
|
|
expect(s).toContain("contains");
|
|
expect(s).toContain("invoice");
|
|
expect(s).toContain("Work");
|
|
});
|
|
|
|
it("joins an allof rule as a conjunction and anyof as a disjunction", () => {
|
|
const base = {
|
|
id: "1", name: "r", enabled: true,
|
|
tests: [
|
|
{ type: "header", header: "from", op: "is", value: "a@b" },
|
|
{ type: "header", header: "to", op: "is", value: "c@d" },
|
|
],
|
|
actions: [{ type: "keep" }],
|
|
};
|
|
expect(describeSieve({ ...base, join: "allof" } as never)).toContain(" and ");
|
|
expect(describeSieve({ ...base, join: "anyof" } as never)).toContain(" or ");
|
|
});
|
|
|
|
it("says 'always' when a rule has no tests", () => {
|
|
const s = describeSieve({ id: "1", name: "r", join: "allof", enabled: true, tests: [], actions: [{ type: "stop" }] } as never);
|
|
expect(s).toContain("always");
|
|
});
|
|
});
|
|
|
|
describe("recurrence describeRule", () => {
|
|
it("describes the simple frequencies", () => {
|
|
expect(describeRecurrence(undefined)).toBe("Does not repeat");
|
|
expect(describeRecurrence({ "@type": "RecurrenceRule", frequency: "daily" } as never)).toBe("Daily");
|
|
expect(describeRecurrence({ "@type": "RecurrenceRule", frequency: "daily", interval: 3 } as never)).toBe("Every 3 days");
|
|
});
|
|
|
|
it("recognizes Monday to Friday as every weekday", () => {
|
|
const rule = {
|
|
"@type": "RecurrenceRule", frequency: "weekly",
|
|
byDay: ["mo", "tu", "we", "th", "fr"].map((day) => ({ "@type": "NDay", day })),
|
|
};
|
|
expect(describeRecurrence(rule as never)).toBe("Every weekday");
|
|
});
|
|
|
|
it("uses a word, not a suffix, for the nth weekday of a month", () => {
|
|
const s = describeRecurrence({
|
|
"@type": "RecurrenceRule", frequency: "monthly",
|
|
byDay: [{ "@type": "NDay", day: "tu", nthOfPeriod: 2 }],
|
|
} as never);
|
|
expect(s).toContain("second");
|
|
expect(s).not.toContain("2nd");
|
|
});
|
|
|
|
it("wraps the sentence for count and until rather than appending to it", () => {
|
|
const s = describeRecurrence({ "@type": "RecurrenceRule", frequency: "daily", count: 5 } as never);
|
|
expect(s).toBe("Daily, 5 times");
|
|
const u = describeRecurrence({ "@type": "RecurrenceRule", frequency: "daily", until: "2026-05-03T00:00:00" } as never);
|
|
expect(u).toBe("Daily, until 2026-05-03");
|
|
});
|
|
|
|
it("takes its weekday names from the locale, not a table of English", () => {
|
|
setUiLanguageForFormatting("de-DE");
|
|
const names = weekdayOptions().map((w) => w.label);
|
|
expect(names[0]).toBe("Montag");
|
|
expect(names).toHaveLength(7);
|
|
// The narrow forms collide in English ("T" for both Tuesday and Thursday),
|
|
// which is why they cannot be catalog keys and come from Intl instead.
|
|
expect(weekdayOptions().map((w) => w.short)).toHaveLength(7);
|
|
setUiLanguageForFormatting(null);
|
|
});
|
|
|
|
it("renders a translated rule through the catalog", () => {
|
|
setCatalog("de", { strings: { Daily: "Täglich" }, plurals: {} });
|
|
expect(describeRecurrence({ "@type": "RecurrenceRule", frequency: "daily" } as never)).toBe("Täglich");
|
|
setCatalog("en", { strings: {}, plurals: {} });
|
|
});
|
|
});
|