Right-click a folder and pick one of the twelve colours the calendar already uses, or clear it again. The colour tints the folder's icon; the label keeps the sidebar's own contrast, which a dozen arbitrary colours would not reliably give it. Kept by mailbox id rather than by name, so a folder renamed or dragged somewhere else keeps its colour. Stored in settings, which live in this browser -- JMAP has nowhere on a Mailbox to put a colour, and every other colour in the app, labels and event categories included, already works this way. Worth knowing it does not follow you to another device. The cascade needed care: .nav-item svg sets the colour on the icon itself, so a colour inherited from a wrapper does nothing. Checking getComputedStyle on the wrapper said the icon was purple while the pixels stayed grey; the rule now targets the svg, and the check now reads the pixels.
84 lines
3.2 KiB
TypeScript
84 lines
3.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { canDropFolder, descendantIds, folderColor, movable } from "../folderMove";
|
|
import type { Id, Mailbox } from "@/jmap/types";
|
|
|
|
const mb = (id: string, name: string, parentId: string | null, role: Mailbox["role"] = null): Mailbox =>
|
|
({ id, name, parentId, role, sortOrder: 0, totalEmails: 0, unreadEmails: 0, totalThreads: 0, unreadThreads: 0, isSubscribed: true, myRights: {} as Mailbox["myRights"] });
|
|
|
|
/** root ── Work ── Clients ── EU
|
|
* └─ Archive (role)
|
|
* └─ Inbox (role) */
|
|
const tree: Record<Id, Mailbox> = Object.fromEntries([
|
|
mb("inbox", "Inbox", null, "inbox"),
|
|
mb("arch", "Archive", null, "archive"),
|
|
mb("work", "Work", null),
|
|
mb("clients", "Clients", "work"),
|
|
mb("eu", "EU", "clients"),
|
|
mb("news", "Newsletters", null),
|
|
].map((m) => [m.id, m]));
|
|
|
|
describe("movable", () => {
|
|
it("refuses folders the server gave a role", () => {
|
|
expect(movable(tree.inbox!)).toBe(false);
|
|
expect(movable(tree.arch!)).toBe(false);
|
|
expect(movable(tree.work!)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("descendantIds", () => {
|
|
it("finds the whole subtree, not just the children", () => {
|
|
expect([...descendantIds(tree, "work")].sort()).toEqual(["clients", "eu"]);
|
|
expect([...descendantIds(tree, "eu")]).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("canDropFolder", () => {
|
|
it("allows a plain move into another folder", () => {
|
|
expect(canDropFolder(tree, "news", "work")).toBe(true);
|
|
expect(canDropFolder(tree, "eu", "news")).toBe(true);
|
|
});
|
|
|
|
it("allows a move into a role folder, which may hold subfolders", () => {
|
|
expect(canDropFolder(tree, "news", "arch")).toBe(true);
|
|
});
|
|
|
|
it("refuses to move a folder into itself or its own subtree", () => {
|
|
expect(canDropFolder(tree, "work", "work")).toBe(false);
|
|
expect(canDropFolder(tree, "work", "clients")).toBe(false);
|
|
expect(canDropFolder(tree, "work", "eu")).toBe(false); // grandchild, not just child
|
|
});
|
|
|
|
it("refuses a move to the parent it already has", () => {
|
|
expect(canDropFolder(tree, "clients", "work")).toBe(false);
|
|
});
|
|
|
|
it("refuses to move a role folder anywhere", () => {
|
|
expect(canDropFolder(tree, "inbox", "work")).toBe(false);
|
|
expect(canDropFolder(tree, "arch", null)).toBe(false);
|
|
});
|
|
|
|
it("handles the root: allowed from a parent, refused when already there", () => {
|
|
expect(canDropFolder(tree, "eu", null)).toBe(true);
|
|
expect(canDropFolder(tree, "news", null)).toBe(false);
|
|
});
|
|
|
|
it("refuses a target that does not exist", () => {
|
|
expect(canDropFolder(tree, "news", "gone")).toBe(false);
|
|
expect(canDropFolder(tree, "gone", "work")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("folderColor", () => {
|
|
it("returns the colour 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", () => {
|
|
// The id is stable across a rename; the name and path are not.
|
|
expect(folderColor({ mb1: "#0f766e" }, "mb1")).toBe("#0f766e");
|
|
});
|
|
});
|