Gather the privacy settings into a section of their own
General had grown five unrelated headings and was where anything without an obvious home ended up. Remote images were filed under "Reading", the read-receipt policy under "Composing", the undo-send window beside the default message format. They are the same kind of decision -- what reaches a sender, and what asks before something happens -- and they were the hardest settings in the app to find. Privacy & safety now holds all six, in three groups: remote content, read receipts, and the things that ask before it is too late. General keeps what it is actually about and is thirty lines shorter. The line against Security & sessions is worth stating, because two similar words next to each other in a nav is how a menu becomes something people hunt through. Security & sessions is credentials and access: password, two-factor state, app passwords, live sessions. Privacy & safety is how the app behaves towards the reader and towards senders. Nothing moved in storage. Settings are a flat object in settings.json and sections are only how they are grouped on screen, so this is a UI change with no migration and no key renames. Two things beyond the move. The senders trusted with remote images are now listed and can be withdrawn one at a time. A sender was added from a message and could then only be removed by finding another message from that same sender, which is not a way to review a list you cannot see. And General's lead said settings are stored in this browser, which is only true when the server has no FileNode support. They normally live in the account's own Files and follow it between devices, so the sentence contradicted the feature it sat above.
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { PrivacySettings } from "../PrivacySettings";
|
||||
import { GeneralSettings } from "../GeneralSettings";
|
||||
import { useSettings, DEFAULT_SETTINGS } from "@/store/settings";
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
/**
|
||||
* The point of the section is that nothing was lost on the way out of General.
|
||||
* A setting that stops being reachable is still stored, still applied, and
|
||||
* impossible to change -- which is worse than leaving it where it was.
|
||||
*/
|
||||
describe("Privacy & safety", () => {
|
||||
let host: HTMLDivElement;
|
||||
let root: Root;
|
||||
|
||||
const render = async (el: React.ReactNode) => {
|
||||
await act(async () => {
|
||||
root.render(el);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
host = document.createElement("div");
|
||||
document.body.appendChild(host);
|
||||
root = createRoot(host);
|
||||
useSettings.setState({ settings: { ...DEFAULT_SETTINGS } });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await act(async () => root.unmount());
|
||||
host.remove();
|
||||
});
|
||||
|
||||
it("offers every control that left General", async () => {
|
||||
await render(<PrivacySettings />);
|
||||
const text = host.textContent ?? "";
|
||||
expect(text).toContain("Remote images");
|
||||
expect(text).toContain("Always request read receipts");
|
||||
expect(text).toContain("When someone requests a read receipt");
|
||||
expect(text).toContain("Undo send window");
|
||||
expect(text).toContain("Attachment reminder");
|
||||
expect(text).toContain("Confirm before deleting");
|
||||
});
|
||||
|
||||
it("leaves none of them behind in General", async () => {
|
||||
await render(<GeneralSettings />);
|
||||
const text = host.textContent ?? "";
|
||||
for (const gone of [
|
||||
"Remote images",
|
||||
"Always request read receipts",
|
||||
"When someone requests a read receipt",
|
||||
"Undo send window",
|
||||
"Attachment reminder",
|
||||
"Confirm before deleting",
|
||||
]) {
|
||||
expect(text, `${gone} is in both sections`).not.toContain(gone);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps General's own settings where they were", async () => {
|
||||
await render(<GeneralSettings />);
|
||||
const text = host.textContent ?? "";
|
||||
expect(text).toContain("Reading pane");
|
||||
expect(text).toContain("Conversation view");
|
||||
expect(text).toContain("Default format");
|
||||
expect(text).toContain("Spell check while typing");
|
||||
});
|
||||
|
||||
it("writes through to the same stored settings the old controls used", async () => {
|
||||
await render(<PrivacySettings />);
|
||||
const select = [...host.querySelectorAll("select")].find((el) =>
|
||||
[...el.options].some((o) => o.value === "always"),
|
||||
);
|
||||
expect(select, "remote images select").toBeTruthy();
|
||||
await act(async () => {
|
||||
select!.value = "always";
|
||||
select!.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
});
|
||||
expect(useSettings.getState().settings.imagePolicy).toBe("always");
|
||||
});
|
||||
|
||||
it("hides the trusted-sender list until there is one", async () => {
|
||||
await render(<PrivacySettings />);
|
||||
expect(host.textContent).not.toContain("Always showing images from");
|
||||
|
||||
await act(async () => {
|
||||
useSettings.setState({ settings: { ...DEFAULT_SETTINGS, trustedImageSenders: ["[email protected]"] } });
|
||||
});
|
||||
await render(<PrivacySettings />);
|
||||
expect(host.textContent).toContain("Always showing images from");
|
||||
expect(host.textContent).toContain("[email protected]");
|
||||
});
|
||||
|
||||
it("removes a trusted sender, which nothing outside a message could do before", async () => {
|
||||
useSettings.setState({ settings: { ...DEFAULT_SETTINGS, trustedImageSenders: ["[email protected]", "[email protected]"] } });
|
||||
await render(<PrivacySettings />);
|
||||
const remove = host.querySelector<HTMLButtonElement>('button[aria-label*="[email protected]"]');
|
||||
expect(remove, "remove button").toBeTruthy();
|
||||
await act(async () => {
|
||||
remove!.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
expect(useSettings.getState().settings.trustedImageSenders).toEqual(["[email protected]"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user