1import { describe, expect, it } from "@effect/vitest";
2
3import * as Schema from "effect/Schema";
4
5import {
6 BrowserProfileId,
7 BUILT_IN_BROWSER_PROFILES,
8 DEFAULT_BROWSER_PROFILE_ID,
9 INCOGNITO_BROWSER_PROFILE_ID,
10 findBrowserProfile,
11 isBuiltInBrowserProfileId,
12 resolveBrowserProfiles,
13 type BrowserProfile,
14} from "./browserProfile.ts";
15
16const work: BrowserProfile = { id: "profile-work", name: "Work", kind: "persistent" };
17
18describe("resolveBrowserProfiles", () => {
19 it("lists built-ins ahead of the user's own profiles", () => {
20 const resolved = resolveBrowserProfiles([work]);
21
22 expect(resolved.map((profile) => profile.id)).toEqual([
23 DEFAULT_BROWSER_PROFILE_ID,
24 INCOGNITO_BROWSER_PROFILE_ID,
25 work.id,
26 ]);
27 });
28
29 it("drops stored entries that collide with a built-in id", () => {
30 // Built-ins are synthesized rather than stored, so a hand-edited settings
31 // file must not be able to shadow Default with a persistent partition of
32 // its own — every tab already opened under Default would follow it.
33 const resolved = resolveBrowserProfiles([
34 { id: DEFAULT_BROWSER_PROFILE_ID, name: "Hijacked", kind: "persistent" },
35 { id: INCOGNITO_BROWSER_PROFILE_ID, name: "Not incognito", kind: "persistent" },
36 work,
37 ]);
38
39 expect(resolved).toEqual([...BUILT_IN_BROWSER_PROFILES, work]);
40 });
41
42 it("keeps incognito ephemeral", () => {
43 const incognito = findBrowserProfile(resolveBrowserProfiles([]), INCOGNITO_BROWSER_PROFILE_ID);
44
45 expect(incognito?.kind).toBe("incognito");
46 });
47});
48
49describe("findBrowserProfile", () => {
50 it("returns nothing for an id that no longer exists", () => {
51 // The settings UI relies on this to fall back rather than opening tabs
52 // into a partition with no profile behind it.
53 expect(findBrowserProfile(resolveBrowserProfiles([]), work.id)).toBeUndefined();
54 expect(findBrowserProfile(resolveBrowserProfiles([work]), undefined)).toBeUndefined();
55 });
56});
57
58describe("isBuiltInBrowserProfileId", () => {
59 it("separates built-ins from user profiles", () => {
60 expect(isBuiltInBrowserProfileId(DEFAULT_BROWSER_PROFILE_ID)).toBe(true);
61 expect(isBuiltInBrowserProfileId(INCOGNITO_BROWSER_PROFILE_ID)).toBe(true);
62 expect(isBuiltInBrowserProfileId(work.id)).toBe(false);
63 });
64});
65
66describe("resolveBrowserProfiles normalization", () => {
67 it("keeps only the first entry for a repeated id", () => {
68 // Both map to the same Electron partition, so presenting two would offer
69 // isolated identities that in fact share every cookie.
70 const resolved = resolveBrowserProfiles([
71 { id: "work", name: "Work", kind: "persistent" },
72 { id: "work", name: "Work (old)", kind: "persistent" },
73 ]);
74
75 expect(resolved.filter((profile) => profile.id === "work")).toEqual([
76 { id: "work", name: "Work", kind: "persistent" },
77 ]);
78 });
79
80 it("reports a custom incognito profile as persistent", () => {
81 // Partition persistence is keyed off the built-in incognito id alone, so
82 // a custom profile claiming that kind keeps its cookies across restarts.
83 // Labelling it ephemeral would be a promise the partition layer breaks.
84 const resolved = resolveBrowserProfiles([
85 { id: "throwaway", name: "Throwaway", kind: "incognito" },
86 ]);
87
88 expect(resolved.find((profile) => profile.id === "throwaway")).toEqual({
89 id: "throwaway",
90 name: "Throwaway",
91 kind: "persistent",
92 });
93 });
94
95 it("still lets the built-in incognito profile stay ephemeral", () => {
96 const incognito = resolveBrowserProfiles([]).find(
97 (profile) => profile.id === INCOGNITO_BROWSER_PROFILE_ID,
98 );
99
100 expect(incognito?.kind).toBe("incognito");
101 });
102});
103
104describe("BrowserProfileId", () => {
105 it("rejects control characters", () => {
106 // Ids are folded into delimiter-joined cache keys on the client, so one
107 // carrying the delimiter would resolve to another profile's partition.
108 expect(Schema.is(BrowserProfileId)("profile-a\u0000b")).toBe(false);
109 expect(Schema.is(BrowserProfileId)("profile-a")).toBe(true);
110 });
111});