Use American English spelling throughout

This commit is contained in:
2026-09-15 11:45:58 -07:00
parent 6ba89696ee
commit d1731efdb9
169 changed files with 786 additions and 795 deletions
+1 -1
View File
@@ -94,7 +94,7 @@ describe("generated passwords", () => {
expect(p).not.toMatch(/[01lIO]/);
});
it("skip bytes that would favour the start of the alphabet", () => {
it("skip bytes that would favor the start of the alphabet", () => {
// 256 % 55 leaves 36 byte values over; a plain modulo would hand those to
// the first 36 characters twice as often. Bytes of 220 and up are dropped
// and more are drawn, so a batch of nothing but those costs a draw.
+5 -5
View File
@@ -1,12 +1,12 @@
import { describe, expect, it, vi } from "vitest";
import { client, JmapMethodError } from "@/jmap/client";
import { balancedColumns, countObjects, isRefused, loadMetrics, summariseMetrics, type MetricRecord } from "@/lib/adminDashboard";
import { balancedColumns, countObjects, isRefused, loadMetrics, summarizeMetrics, type MetricRecord } from "@/lib/adminDashboard";
const counter = (metric: string, count: number, timestamp = "2026-09-15T14:00:00Z"): MetricRecord => ({ "@type": "Counter", metric, count, timestamp });
describe("the dashboard's message numbers", () => {
it("adds received and sent up over the metric names Stalwart's own dashboard uses", () => {
const stats = summariseMetrics([
const stats = summarizeMetrics([
counter("queue.message-queued", 6),
counter("queue.message-queued", 4, "2026-09-15T13:00:00Z"),
counter("queue.authenticated-message-queued", 2),
@@ -20,7 +20,7 @@ describe("the dashboard's message numbers", () => {
});
it("reads memory from the newest gauge, not the first one listed", () => {
const stats = summariseMetrics([
const stats = summarizeMetrics([
{ "@type": "Gauge", metric: "server.memory", count: 100, timestamp: "2026-09-15T12:00:00Z" },
{ "@type": "Gauge", metric: "server.memory", count: 300, timestamp: "2026-09-15T14:00:00Z" },
{ "@type": "Gauge", metric: "queue.count", count: 7, timestamp: "2026-09-15T15:00:00Z" },
@@ -29,8 +29,8 @@ describe("the dashboard's message numbers", () => {
});
it("tells a history that records nothing from a quiet day", () => {
expect(summariseMetrics([]).recorded).toBe(false);
const quiet = summariseMetrics([{ "@type": "Gauge", metric: "server.memory", count: 1, timestamp: "2026-09-15T14:00:00Z" }]);
expect(summarizeMetrics([]).recorded).toBe(false);
const quiet = summarizeMetrics([{ "@type": "Gauge", metric: "server.memory", count: 1, timestamp: "2026-09-15T14:00:00Z" }]);
expect(quiet).toMatchObject({ recorded: true, received: 0, sent: 0 });
});
});
+1 -1
View File
@@ -62,7 +62,7 @@ describe("the account query", () => {
* live server gave, or one its source says it gives.
*/
describe("refusals in the reader's language", () => {
it("recognises the registry's validators and says it again, without the server's words", () => {
it("recognizes the registry's validators and says it again, without the server's words", () => {
// Live, 2026-09-13: a reserved TLD, and a catch-all without a domain.
const domain = describeDirectoryError(new DirectoryError("invalidPatch", "Invalid domain name", ["name"]), "domain");
expect(domain).toMatch(/isn't a valid domain name/);
+4 -4
View File
@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest";
import { describeLinked, dkimAlgorithm, looksLikeDomain, normaliseDomain, parseZoneFile } from "@/lib/adminDomains";
import { describeLinked, dkimAlgorithm, looksLikeDomain, normalizeDomain, parseZoneFile } from "@/lib/adminDomains";
/**
* Written the way Stalwart's BIND serialiser writes it (dns-update's
* Written the way Stalwart's BIND serializer writes it (dns-update's
* `BindSerializer`): `name IN TYPE value`, and a TXT over 255 bytes as a
* parenthesised run of quoted chunks.
* parenthesized run of quoted chunks.
*/
const long = "v=DKIM1; k=rsa; h=sha256; p=" + "A".repeat(400);
const zone = [
@@ -46,7 +46,7 @@ describe("reading the zone file", () => {
describe("domain names", () => {
it("are written back lower-case without the root dot", () => {
expect(normaliseDomain(" Example.COM. ")).toBe("example.com");
expect(normalizeDomain(" Example.COM. ")).toBe("example.com");
});
it("are checked loosely before the server decides", () => {
@@ -34,7 +34,7 @@ describe("the span an availability bar covers", () => {
expect(w.span).toBeGreaterThan(0);
});
it("marks a single day every three hours, labelling every six", () => {
it("marks a single day every three hours, labeling every six", () => {
const w = availabilityWindow(at("2026-09-02T09:00:00"), at("2026-09-02T10:00:00"));
expect(w.scale).toBe("hours");
expect(hours(w)).toEqual(["2@0", "2@3", "2@6", "2@9", "2@12", "2@15", "2@18", "2@21"]);
+2 -2
View File
@@ -73,7 +73,7 @@ describe("birthdaysInRange", () => {
expect(birthdaysInRange([card("c2", "", { month: 6, day: 15 })], s, e)).toEqual([]);
});
it("falls back to a name built from components, then to the organisation", () => {
it("falls back to a name built from components, then to the organization", () => {
const [s, e] = range("2026-01-01", "2027-01-01");
const parts = {
id: "c1",
@@ -115,7 +115,7 @@ describe("birthdaysInRange", () => {
expect(out.map((b) => b.name)).toEqual(["Amy", "Zoe"]);
});
it("gives each occurrence a stable, unique id that marks it as synthesised", () => {
it("gives each occurrence a stable, unique id that marks it as synthesized", () => {
const [s, e] = range("2025-01-01", "2027-01-01");
const out = birthdaysInRange([card("c1", "Ada", { month: 6, day: 15 })], s, e);
expect(new Set(out.map((b) => b.id)).size).toBe(out.length);
+1 -1
View File
@@ -6,7 +6,7 @@ import { DEFAULT_APP_NAME } from "@/lib/brand";
*
* `APP_NAME` is a runtime variable, so every place showing the name has to ask
* the server rather than have it written in. The sign-in page did not (#236's
* neighbour): it fetched `/api/config`, received the name and used only
* neighbor): it fetched `/api/config`, received the name and used only
* `sourceUrl`, so a rebranded instance still said "ihasmail" on the page a new
* user meets first. These pin the shape of the answer rather than the name.
*/
+2 -2
View File
@@ -97,14 +97,14 @@ describe("explicit date formats", () => {
});
describe("clock preference", () => {
it("honours 24-hour regardless of locale", () => {
it("honors 24-hour regardless of locale", () => {
setDateTimePrefs({ locale: "en-US", timeFormat: "24" });
expect(formatClock(SAMPLE)).toBe("18:23");
expect(uses24Hour()).toBe(true);
expect(formatHourLabel(13)).toBe("13");
expect(formatHourLabel(9)).toBe("09");
});
it("honours 12-hour regardless of locale", () => {
it("honors 12-hour regardless of locale", () => {
setDateTimePrefs({ locale: "de-DE", timeFormat: "12" });
expect(formatClock(SAMPLE)).toBe("6:23 PM");
expect(uses24Hour()).toBe(false);
+5 -5
View File
@@ -2,7 +2,7 @@
* 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 catalogue, and
* 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";
@@ -12,7 +12,7 @@ import { setUiLanguageForFormatting } from "../datetime";
import { setCatalog } from "../i18n";
describe("sieve describeRule", () => {
it("names the header and operator through the catalogue", () => {
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" }],
@@ -50,7 +50,7 @@ describe("recurrence describeRule", () => {
expect(describeRecurrence({ "@type": "RecurrenceRule", frequency: "daily", interval: 3 } as never)).toBe("Every 3 days");
});
it("recognises Monday to Friday as every weekday", () => {
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 })),
@@ -80,12 +80,12 @@ describe("recurrence describeRule", () => {
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 catalogue keys and come from Intl instead.
// 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 catalogue", () => {
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: {} });
+2 -2
View File
@@ -93,14 +93,14 @@ describe("canMoveFolderTo", () => {
});
describe("folderColor", () => {
it("returns the colour chosen for that folder, and null for the rest", () => {
it("returns the color chosen for that folder, and null for the rest", () => {
const colors = { work: "#7c3aed" };
expect(folderColor(colors, "work")).toBe("#7c3aed");
expect(folderColor(colors, "news")).toBeNull();
expect(folderColor({}, "work")).toBeNull();
});
it("is keyed by id, so a renamed folder keeps its colour", () => {
it("is keyed by id, so a renamed folder keeps its color", () => {
// The id is stable across a rename; the name and path are not.
expect(folderColor({ mb1: "#0f766e" }, "mb1")).toBe("#0f766e");
});
+22 -22
View File
@@ -34,7 +34,7 @@ describe("sanitizeEmailHtml", () => {
});
describe("htmlDeclaresColors", () => {
it("is false for mail that brings no colours", () => {
it("is false for mail that brings no colors", () => {
expect(htmlDeclaresColors("<p>Hi there</p>")).toBe(false);
expect(htmlDeclaresColors("<div><b>bold</b> and <i>italic</i></div>", "font-family:Arial")).toBe(false);
expect(htmlDeclaresColors('<a href="https://x.io/?color=red">link</a>')).toBe(false);
@@ -54,9 +54,9 @@ describe("htmlDeclaresColors", () => {
/**
* Forcing the theme onto mail that styles itself — issue #290.
*
* The switch above it leaves nearly all HTML mail alone, because one colour
* The switch above it leaves nearly all HTML mail alone, because one color
* anywhere opts a message out. What this half has to get right is telling a
* sheet the design sits on from a surface painted on top of it: neutralise the
* sheet the design sits on from a surface painted on top of it: neutralize the
* first and the white card goes away, keep the second and a button keeps a
* label you can still read.
*/
@@ -70,15 +70,15 @@ describe("relativeLuminance", () => {
expect(relativeLuminance("rgba(255,255,255,0.5)")).toBeCloseTo(1, 5);
});
it("has nothing to say about a colour it cannot read", () => {
it("has nothing to say about a color it cannot read", () => {
// Not a failure: the caller treats null as "no deliberate surface", which
// is the safe way round — an unreadable colour must not keep a white sheet.
// is the safe way round — an unreadable color must not keep a white sheet.
expect(relativeLuminance("color-mix(in srgb, red, blue)")).toBeNull();
expect(relativeLuminance("var(--brand)")).toBeNull();
expect(relativeLuminance("")).toBeNull();
});
it("treats a fully transparent colour as painting nothing", () => {
it("treats a fully transparent color as painting nothing", () => {
expect(relativeLuminance("rgba(0,0,0,0)")).toBeNull();
expect(relativeLuminance("transparent")).toBeNull();
});
@@ -100,21 +100,21 @@ describe("markKeptSurfaces", () => {
* Marking is only half of it — the other half is the rule in EMAIL_BASE_CSS
* that reads the marks, and #310 was a bug in that half rather than in the
* marking. So these assert what the reader actually sees: does the
* neutraliser hit this element? The selector is lifted out of the stylesheet
* neutralizer hit this element? The selector is lifted out of the stylesheet
* rather than copied, so a test cannot quietly drift from the rule it checks.
*/
const NEUTRALISER = (() => {
const NEUTRALIZER = (() => {
const m = EMAIL_BASE_CSS.match(
/\.ihm-email-root\.forced\s+(\*:not\([^{]*?)\s*\{\s*color: inherit/,
);
if (!m) throw new Error("could not find the neutraliser rule in EMAIL_BASE_CSS");
if (!m) throw new Error("could not find the neutralizer rule in EMAIL_BASE_CSS");
return m[1]!.trim();
})();
/** True when the theme is forced onto this element rather than leaving it alone. */
const neutralised = (el: Element) => el.matches(NEUTRALISER);
const neutralized = (el: Element) => el.matches(NEUTRALIZER);
it("keeps a coloured button and drops the white sheet around it", () => {
it("keeps a colored button and drops the white sheet around it", () => {
// The shape reported in #290: a Shopify/Klaviyo template whose outer 600px
// wrapper carries bgcolor="#ffffff" and whose CTA carries bgcolor="#1155CC".
const d = frag('<table bgcolor="#ffffff"><tr><td bgcolor="#1155CC"><a style="color:#FFFFFF">Buy</a></td></tr></table>');
@@ -127,7 +127,7 @@ describe("markKeptSurfaces", () => {
expect(d.querySelector("a")!.hasAttribute("data-ihm-in-keep")).toBe(true);
});
it("neutralises a light panel nested inside a dark painted card", () => {
it("neutralizes a light panel nested inside a dark painted card", () => {
// The shape reported in #310: a dark Klaviyo campaign whose 600px cards
// are dark enough to be marked, with light content tables inside them.
// Those tables used to inherit the card's exemption and render as beige
@@ -153,11 +153,11 @@ describe("markKeptSurfaces", () => {
// The fix, stated the way the reader experiences it: the nested sheet is
// themed, and so is the copy inside it. Before #310 both were exempt for
// being descendants of the card.
expect(neutralised(nested)).toBe(true);
expect(neutralised(d.querySelector("td")!)).toBe(true);
expect(neutralized(nested)).toBe(true);
expect(neutralized(d.querySelector("td")!)).toBe(true);
// The card itself is still left alone, and the page surround still goes.
expect(neutralised(card)).toBe(false);
expect(neutralised(surround)).toBe(true);
expect(neutralized(card)).toBe(false);
expect(neutralized(surround)).toBe(true);
});
it("still keeps a button that sits inside a nested light panel", () => {
@@ -172,11 +172,11 @@ describe("markKeptSurfaces", () => {
'</div>',
);
expect(markKeptSurfaces(d)).toBe(2);
expect(neutralised(d.querySelector("table")!)).toBe(true);
expect(neutralised(d.querySelector("td")!)).toBe(false);
expect(neutralized(d.querySelector("table")!)).toBe(true);
expect(neutralized(d.querySelector("td")!)).toBe(false);
// The label keeps its white, which is the thing #294 bought and this must
// not spend.
expect(neutralised(d.querySelector("a")!)).toBe(false);
expect(neutralized(d.querySelector("a")!)).toBe(false);
});
it("leaves no light panel exempt across the whole reported specimen", () => {
@@ -201,7 +201,7 @@ describe("markKeptSurfaces", () => {
expect(panels.length).toBe(21);
expect(markKeptSurfaces(d)).toBe(7);
expect(panels.filter((p) => !neutralised(p))).toHaveLength(0);
expect(panels.filter((p) => !neutralized(p))).toHaveLength(0);
});
it("reads an inline background as well as the attribute", () => {
@@ -233,7 +233,7 @@ describe("markKeptSurfaces", () => {
* The control that actually stops it is layout containment on an ancestor of
* the shadow host, which mail CSS has no selector for; that lives in app.css
* and is asserted at the bottom of this file, because jsdom does no layout and
* cannot prove it here. These cover the second line of defence.
* cannot prove it here. These cover the second line of defense.
*/
describe("mail CSS cannot climb out of its card", () => {
const render = (html: string) => sanitizeEmailHtml(html).html;
@@ -271,7 +271,7 @@ describe("mail CSS cannot climb out of its card", () => {
describe("the containment that mail CSS cannot override", () => {
it("is still applied to the message body container", async () => {
// jsdom does no layout, so this asserts the control is present rather than
// that it works; the behaviour was verified in a real browser. Without it,
// that it works; the behavior was verified in a real browser. Without it,
// a message can cover the viewport regardless of what the sanitizer does.
const { readFile } = await import("node:fs/promises");
const { join } = await import("node:path");
@@ -36,7 +36,7 @@ describe("deciding whether a message has an HTML alternative", () => {
expect(hasHtmlAlternative({ type: "text/htmlish" }, "<p>Hi</p>")).toBe(false);
});
it("matches the type case-insensitively, since a header may be capitalised", () => {
it("matches the type case-insensitively, since a header may be capitalized", () => {
expect(hasHtmlAlternative({ type: "TEXT/HTML" }, "<p>Hi</p>")).toBe(true);
});
+5 -5
View File
@@ -29,12 +29,12 @@ describe("t", () => {
expect(currentLanguage()).toBe("en");
});
it("translates once a catalogue is in force", () => {
it("translates once a catalog is in force", () => {
setCatalog("de", de);
expect(t("Archive")).toBe("Archivieren");
});
it("falls back per string, not per catalogue", () => {
it("falls back per string, not per catalog", () => {
setCatalog("de", de);
expect(t("Report spam")).toBe("Report spam");
});
@@ -60,7 +60,7 @@ describe("interpolation", () => {
describe("plural", () => {
const FORMS = { one: "{n} message", other: "{n} messages" };
it("picks the English form without a catalogue", () => {
it("picks the English form without a catalog", () => {
expect(plural(1, FORMS)).toBe("1 message");
expect(plural(0, FORMS)).toBe("0 messages");
expect(plural(5, FORMS)).toBe("5 messages");
@@ -73,7 +73,7 @@ describe("plural", () => {
expect(plural(7, FORMS)).toBe("7 сообщений"); // many
});
it("falls back to `other` when the catalogue lacks the category", () => {
it("falls back to `other` when the catalog lacks the category", () => {
setCatalog("de", de);
// German has no "few"; asking for 3 must not render undefined.
expect(plural(3, FORMS)).toBe("3 Nachrichten");
@@ -95,7 +95,7 @@ describe("tNode", () => {
it("lets a translator move the element", () => {
// Splitting the sentence into two t() calls could not do this: the
// fragments would render in the English order whatever the catalogue said.
// fragments would render in the English order whatever the catalog said.
setCatalog("de", de);
expect(render(tNode("Open {scheme} links here", { scheme: <code>mailto:</code> })))
.toBe("<code>mailto:</code>-Links hier öffnen");
+1 -1
View File
@@ -111,7 +111,7 @@ describe("parseIcsDuration", () => {
});
describe("looksLikeCalendar", () => {
it("recognises a calendar and rejects an error page", () => {
it("recognizes a calendar and rejects an error page", () => {
expect(looksLikeCalendar("BEGIN:VCALENDAR\r\nEND:VCALENDAR")).toBe(true);
expect(looksLikeCalendar("<!doctype html><title>404</title>")).toBe(false);
});
+3 -3
View File
@@ -30,7 +30,7 @@ const find = (e: JSCalendarEvent[], prefix: string) => eventLines(e).filter((l)
const one = (e: JSCalendarEvent, prefix: string) => find([e], prefix)[0];
describe("the document around the events", () => {
it("is a calendar a reader will recognise", () => {
it("is a calendar a reader will recognize", () => {
const l = lines([base]);
expect(l[0]).toBe("BEGIN:VCALENDAR");
expect(l).toContain("VERSION:2.0");
@@ -105,7 +105,7 @@ describe("recurrence", () => {
expect(one(e, "RRULE")).toBe("RRULE:FREQ=MONTHLY;BYDAY=-1TH");
});
it("turns a cancelled occurrence into an EXDATE", () => {
it("turns a canceled occurrence into an EXDATE", () => {
const e = { ...weekly, recurrenceOverrides: { "2026-09-09T09:00:00": null } };
expect(one(e, "EXDATE")).toBe("EXDATE;TZID=Europe/Berlin:20260909T090000");
expect(find([e], "BEGIN:VEVENT")).toHaveLength(1);
@@ -166,7 +166,7 @@ describe("the rest of an event", () => {
expect(one(e, "TRANSP")).toBe("TRANSP:TRANSPARENT");
});
it("writes the organiser and the guests, with what each answered", () => {
it("writes the organizer and the guests, with what each answered", () => {
const e = {
...base,
organizerCalendarAddress: "mailto:[email protected]",
+4 -4
View File
@@ -19,7 +19,7 @@ describe("resolveUiLanguage", () => {
});
it("refuses a language whose strings are not shipped", () => {
// The account travels between machines and can outlive a catalogue. A
// The account travels between machines and can outlive a catalog. A
// page that says lang="fr" while rendering English is worse than one that
// admits to English: it stops the reader translating it themselves.
// Derived rather than named, so shipping another language does not turn
@@ -30,7 +30,7 @@ describe("resolveUiLanguage", () => {
});
it("carries the Beta flag until a person has signed the language off", () => {
// Not a completeness measure. A catalogue can be word-for-word finished
// Not a completeness measure. A catalog can be word-for-word finished
// and still read like a machine wrote it, which is what this marks.
// Every shipped language except English is unreviewed, and stays marked
// until a person says otherwise.
@@ -40,12 +40,12 @@ describe("resolveUiLanguage", () => {
}
});
it("honours one that is", () => {
it("honors one that is", () => {
for (const l of UI_LANGUAGES) expect(resolveUiLanguage(l.tag)).toBe(l.tag);
});
it("only offers languages that resolve to themselves", () => {
// Guards the ordering mistake: adding a picker entry before its catalogue.
// Guards the ordering mistake: adding a picker entry before its catalog.
for (const l of UI_LANGUAGES) {
expect(resolveUiLanguage(l.tag)).toBe(l.tag);
expect(l.name.trim()).not.toBe("");
+1 -1
View File
@@ -94,7 +94,7 @@ describe("comparatorsFor, custom levels", () => {
});
describe("optional sorts, which a server is allowed to refuse", () => {
it("recognises the keyword properties", () => {
it("recognizes the keyword properties", () => {
expect(isOptionalSort({ property: "hasKeyword", keyword: "$seen" })).toBe(true);
expect(isOptionalSort({ property: "someInThreadHaveKeyword", keyword: "$flagged" })).toBe(true);
expect(isOptionalSort({ property: "receivedAt" })).toBe(false);
+7 -7
View File
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it } from "vitest";
import { isLocalisedName, mailboxDisplayName, mailboxDisplayPath } from "@/lib/mailboxName";
import { isLocalizedName, mailboxDisplayName, mailboxDisplayPath } from "@/lib/mailboxName";
import { setCatalog, type Catalog } from "@/lib/i18n";
import type { Mailbox } from "@/jmap/types";
@@ -19,7 +19,7 @@ const mb = (id: string, name: string, role: string | null = null, parentId: stri
afterEach(() => setCatalog("en", { strings: {}, plurals: {} }));
describe("mailboxDisplayName", () => {
it("is the server's name until a catalogue says otherwise", () => {
it("is the server's name until a catalog says otherwise", () => {
expect(mailboxDisplayName(mb("1", "Deleted Items", "trash"))).toBe("Deleted Items");
});
@@ -43,18 +43,18 @@ describe("mailboxDisplayName", () => {
});
});
describe("isLocalisedName", () => {
describe("isLocalizedName", () => {
it("tells an editor when the name on screen is not the server's", () => {
// A rename box prefilled with "Papierkorb" would rename the folder to that
// the moment somebody pressed Save — a real change made by accident.
expect(isLocalisedName(mb("1", "Deleted Items", "trash"))).toBe(true);
expect(isLocalisedName(mb("2", "Newsletters"))).toBe(false);
expect(isLocalisedName(mb("3", "Work", "subscribed"))).toBe(false);
expect(isLocalizedName(mb("1", "Deleted Items", "trash"))).toBe(true);
expect(isLocalizedName(mb("2", "Newsletters"))).toBe(false);
expect(isLocalizedName(mb("3", "Work", "subscribed"))).toBe(false);
});
});
describe("mailboxDisplayPath", () => {
it("localises each part that has a role and leaves the rest", () => {
it("localizes each part that has a role and leaves the rest", () => {
setCatalog("de", de);
const all = { a: mb("a", "Inbox", "inbox"), b: mb("b", "Projects", null, "a") };
expect(mailboxDisplayPath(all.b!, all)).toBe("Posteingang / Projects");
+1 -1
View File
@@ -35,7 +35,7 @@ describe("renderMarkdown", () => {
/*
* Markdown passes raw HTML through by design, and the file came from
* somewhere else -- an upload, or a share from another account. Every one of
* these renders as a script tag without a sanitiser.
* these renders as a script tag without a sanitizer.
*/
it("takes out anything that would execute", () => {
const html = renderMarkdown("<script>alert(1)</script>\n\n<img src=x onerror=alert(1)>\n\n<iframe src='https://evil.example'></iframe>\n");
+1 -1
View File
@@ -85,7 +85,7 @@ describe("the rest of the schema", () => {
expect(phones).toContainEqual(expect.objectContaining({ number: "3", features: { pager: true } }));
});
it("reads the organisation, its units and the job title", () => {
it("reads the organization, its units and the job title", () => {
const c = card("dn: cn=X\ncn: X\no: Example Corp\nou: Research\nou: Optics\ntitle: Lens Grinder\n")!;
expect(values(c.organizations)[0]).toMatchObject({
name: "Example Corp",
+2 -2
View File
@@ -5,7 +5,7 @@ describe("the palettes themselves", () => {
it("has a light and a dark half for every one of them", () => {
// The reason there is no "this palette is dark only" machinery: there is
// no such palette. ihasmail's own gained a light half, and the override,
// the toggle's memory and a greyed-out control all went with it.
// the toggle's memory and a grayed-out control all went with it.
expect(PALETTES.map((p) => p.id)).toEqual([
"default", "ihasmail", "dracula", "gruvbox", "rose-pine", "tokyo-night",
"catppuccin", "solarized", "ayu", "kanagawa", "everforest", "primer",
@@ -75,7 +75,7 @@ describe("legacyTheme, read by a device still on an older build", () => {
});
describe("toggleTarget", () => {
it("flips the mode and keeps the colours, whatever the palette", () => {
it("flips the mode and keeps the colors, whatever the palette", () => {
for (const palette of ["default", "ihasmail", "gruvbox", "dracula", "rose-pine", "tokyo-night"] as const) {
expect(toggleTarget({ palette, mode: "dark" }, false)).toEqual({ palette, mode: "light" });
expect(toggleTarget({ palette, mode: "light" }, false)).toEqual({ palette, mode: "dark" });
@@ -30,7 +30,7 @@ describe("permission labels", () => {
* does not: a missing one would show English in the middle of a translated
* picker, and a stale one would never be looked up.
*/
describe("the permission catalogues", () => {
describe("the permission catalogs", () => {
const modules = import.meta.glob<{ permissionCatalog: PermissionCatalog }>("../../locales/permissions/*.ts");
const tagOf = (path: string) => path.split("/").pop()!.replace(/\.ts$/, "");
const languages = UI_LANGUAGES.map((l) => l.tag).filter((tag) => tag !== "en");
+1 -1
View File
@@ -7,7 +7,7 @@ import { settingsAlreadyLoadedFor, stopSettingsSync } from "../settingsSync";
* Settings used to live only in localStorage, so nothing followed the user
* between devices — issue #54, whose sharpest case is the default identity:
* with none set, the address that sorts first wins, so mail goes out from an
* address the recipient may not recognise.
* address the recipient may not recognize.
*
* The split is written as a list of exceptions, which means the interesting
* test is not "does this key sync" but "does a key added later sync without
+1 -1
View File
@@ -109,7 +109,7 @@ describe("sharing a file", () => {
await expect(shareFile(aFile())).resolves.toBe("unsupported");
});
it("raises anything it does not recognise, so a real fault is still reported", async () => {
it("raises anything it does not recognize, so a real fault is still reported", async () => {
stubNavigator({
share: vi.fn(async () => { throw new DOMException("boom", "DataError"); }),
canShare: (() => true) as unknown as Navigator["canShare"],
+1 -1
View File
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import { buildMarkerSignature, byteLength, compactHtml, markerOf, signatureTooLong, SIGNATURE_LIMIT } from "../signatureHtml";
describe("signature compaction", () => {
it("strips office cruft and non-essential styles but keeps colours and links", () => {
it("strips office cruft and non-essential styles but keeps colors and links", () => {
const src = `<!--[if gte mso 9]><xml>x</xml><![endif]--><div class="WordSection1" style="mso-margin-top-alt:auto;line-height:115%;font-family:'Calibri',sans-serif;color:windowtext"><p class="MsoNormal" style="margin:0cm;font-size:11pt"><span lang="EN-US" style="font-size:12pt;color:#1F4E79;mso-fareast-language:EN-US"><b>John Coffey</b></span><o:p></o:p></p><p><span></span></p><a href="https://linuxexpert.org" target="_blank" data-x="1">linuxexpert.org</a><img src="https://x/y.png" width="100" style="mso-foo:bar"></div>`;
const out = compactHtml(src);
expect(out).not.toContain("mso-");
+3 -3
View File
@@ -6,8 +6,8 @@ import { catalog as de } from "@/locales/de";
/**
* The briefing is the only thing standing between a notification action and a
* button labelled in a language the reader does not use — the worker is plain
* JavaScript outside the bundle and cannot reach a catalogue.
* button labeled in a language the reader does not use — the worker is plain
* JavaScript outside the bundle and cannot reach a catalog.
*
* It is also the only place the archive mailbox is named, and getting that
* wrong does not fail visibly: a message would be filed somewhere, just not
@@ -45,7 +45,7 @@ describe("the worker's briefing", () => {
});
it("carries the worker's text in the language the tab is in", async () => {
// The worker has no catalogue. Everything it will say has to be said here
// The worker has no catalog. Everything it will say has to be said here
// first, or a German reader gets English buttons on their lock screen.
setCatalog("de", de);
const { store } = fakeCaches();
+1 -1
View File
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import { SWIPE_CHOICES, describeSwipe, type SwipeAction } from "../swipe";
/**
* A swipe names what it is about to do on a coloured strip the reader sees for
* A swipe names what it is about to do on a colored strip the reader sees for
* about a third of a second before letting go. These check that the name is
* true in the folder it is being read in — which is the whole reason the
* descriptor exists rather than a fixed label per setting.
+1 -1
View File
@@ -138,7 +138,7 @@ describe("remembering the palette you were on", () => {
expect(toggleTarget(away, false)).toEqual({ palette: "ihasmail", mode: "dark" });
});
it("keeps the colours when the palette has both sides", () => {
it("keeps the colors when the palette has both sides", () => {
const away = toggleTarget({ palette: "gruvbox", mode: "dark" }, false);
expect(away.palette).toBe("gruvbox");
expect(away.mode).toBe("light");
+1 -1
View File
@@ -65,7 +65,7 @@ const file = (name: string, body: string, extra: Attr[] = []): Attr[] => [
const text = (a: Uint8Array) => new TextDecoder().decode(a);
describe("isTnef", () => {
it("recognises the types and the filename", () => {
it("recognizes the types and the filename", () => {
expect(isTnef("application/ms-tnef", null)).toBe(true);
expect(isTnef("application/vnd.ms-tnef; name=winmail.dat", null)).toBe(true);
expect(isTnef("application/octet-stream", "winmail.dat")).toBe(true);
+1 -1
View File
@@ -27,7 +27,7 @@ describe("lockAxis", () => {
expect(lockAxis(30, 25)).toBe("y");
});
it("counts distance on either axis towards committing", () => {
it("counts distance on either axis toward committing", () => {
expect(lockAxis(0, AXIS_SLOP)).toBe("y");
expect(lockAxis(AXIS_SLOP, 0)).toBe("x");
});
@@ -5,7 +5,7 @@ import { createRoot, type Root } from "react-dom/client";
/*
* The guard's answers, and which of them the dialog leans on.
*
* It shipped with "Discard changes" as the only choice carrying a colour, which
* It shipped with "Discard changes" as the only choice carrying a color, which
* made losing the work the easy thing to click on a dialog whose entire purpose
* is to stop that (#175). The emphasis belongs on the safe answer; the
* destructive one stays legible as destructive without being the loudest thing