1import { describe, expect, it } from "vite-plus/test";
2import type { BrowserImportSource } from "@t3tools/contracts";
3
4import {
5 canCloseWizard,
6 initialWizardStep,
7 initialTargetSelection,
8 isRetryableReason,
9 formatSkippedDomains,
10 fullDiskAccessRecheckStep,
11 outcomeToStep,
12 refreshedSourceProfileDirectory,
13 refreshedSourceStep,
14 resolveWizardTarget,
15} from "./browserImportWizard.logic";
16
17describe("wizard target selection", () => {
18 it("treats an existing profile whose id is new as an existing target", () => {
19 const profiles = [{ id: "new", name: "Existing new" }];
20 const selection = initialTargetSelection(false, profiles);
21
22 expect(selection).toEqual({ kind: "existing", profileId: "new" });
23 expect(resolveWizardTarget(selection, "generated", profiles)).toEqual({
24 kind: "existing",
25 profileId: "new",
26 name: "Existing new",
27 });
28 });
29
30 it("rejects an existing target that is no longer listed", () => {
31 expect(
32 resolveWizardTarget({ kind: "existing", profileId: "removed" }, "generated", [
33 { id: "default", name: "Default" },
34 ]),
35 ).toBeUndefined();
36 });
37});
38
39const source = (over: Partial<BrowserImportSource> = {}): BrowserImportSource => ({
40 id: "helium",
41 name: "Helium",
42 profiles: [{ directory: "Default", name: "You" }],
43 ...over,
44});
45
46describe("initialWizardStep", () => {
47 it("opens on the quit screen when the browser is running", () => {
48 expect(initialWizardStep(source({ unavailable: "browserRunning" }))).toEqual({ step: "quit" });
49 });
50
51 it("opens on configure when the source is ready", () => {
52 expect(initialWizardStep(source())).toEqual({ step: "configure" });
53 });
54
55 it("asks for Full Disk Access before choosing an import target", () => {
56 expect(initialWizardStep(source({ profiles: [], unavailable: "needsFullDiskAccess" }))).toEqual(
57 {
58 step: "fullDiskAccess",
59 resume: "configure",
60 },
61 );
62 });
63
64 it("blocks on a reason nothing local can fix", () => {
65 expect(initialWizardStep(source({ unavailable: "unsupportedPlatform" }))).toEqual({
66 step: "blocked",
67 reason: "unsupportedPlatform",
68 });
69 });
70
71 it("blocks a ready source that reports no importable profiles", () => {
72 expect(initialWizardStep(source({ profiles: [] }))).toEqual({
73 step: "blocked",
74 reason: "unknownSourceProfile",
75 });
76 });
77
78 it("handles source availability before checking its profiles", () => {
79 expect(
80 initialWizardStep(source({ profiles: [], unavailable: "needsKeychainApproval" })),
81 ).toEqual({ step: "blocked", reason: "needsKeychainApproval" });
82 });
83});
84
85describe("canCloseWizard", () => {
86 it("keeps the wizard open while an import writes its target profile", () => {
87 expect(canCloseWizard({ step: "importing" })).toBe(false);
88 expect(canCloseWizard({ step: "configure" })).toBe(true);
89 expect(canCloseWizard({ step: "blocked", reason: "readFailed" })).toBe(true);
90 });
91});
92
93describe("outcomeToStep", () => {
94 it("lands on done after a successful import", () => {
95 expect(
96 outcomeToStep({
97 kind: "imported",
98 imported: 12,
99 skipped: 3,
100 skippedDomains: ["example.com"],
101 targetName: "Work",
102 }),
103 ).toEqual({
104 step: "done",
105 imported: 12,
106 skipped: 3,
107 skippedDomains: ["example.com"],
108 targetName: "Work",
109 });
110 });
111
112 it("routes a reopened browser back to the quit screen", () => {
113 expect(outcomeToStep({ kind: "blocked", reason: "browserRunning" })).toEqual({ step: "quit" });
114 });
115
116 it("routes a Full Disk Access refusal to its own screen", () => {
117 expect(outcomeToStep({ kind: "blocked", reason: "needsFullDiskAccess" })).toEqual({
118 step: "fullDiskAccess",
119 resume: "import",
120 checked: true,
121 });
122 });
123
124 it("surfaces every other failure on the blocked screen", () => {
125 expect(outcomeToStep({ kind: "blocked", reason: "readFailed" })).toEqual({
126 step: "blocked",
127 reason: "readFailed",
128 });
129 });
130});
131
132describe("refreshedSourceStep", () => {
133 it("moves to configure once a quit browser frees its cookies", () => {
134 expect(refreshedSourceStep(source())).toEqual({ step: "configure" });
135 });
136
137 it("stays on quit while the browser is still running", () => {
138 expect(refreshedSourceStep(source({ unavailable: "browserRunning" }))).toEqual({
139 step: "quit",
140 });
141 });
142
143 it("blocks when the source vanished from the list", () => {
144 expect(refreshedSourceStep(undefined)).toEqual({ step: "blocked", reason: "unknownSource" });
145 });
146
147 it("blocks when the refreshed source has no profiles", () => {
148 expect(refreshedSourceStep(source({ profiles: [] }))).toEqual({
149 step: "blocked",
150 reason: "unknownSourceProfile",
151 });
152 });
153});
154
155describe("fullDiskAccessRecheckStep", () => {
156 it("marks a still-denied access check for visible feedback", () => {
157 expect(fullDiskAccessRecheckStep(source({ unavailable: "needsFullDiskAccess" }))).toEqual({
158 step: "fullDiskAccess",
159 resume: "configure",
160 checked: true,
161 });
162 });
163
164 it("moves on once access reveals the source profiles", () => {
165 expect(fullDiskAccessRecheckStep(source())).toEqual({ step: "configure" });
166 });
167});
168
169describe("refreshedSourceProfileDirectory", () => {
170 const refreshed = source({
171 profiles: [
172 { directory: "Default", name: "Personal" },
173 { directory: "Profile 2", name: "Work" },
174 ],
175 });
176
177 it("preserves a selected non-first profile after the browser quits", () => {
178 expect(refreshedSourceProfileDirectory("Profile 2", refreshed)).toBe("Profile 2");
179 });
180
181 it("falls back only when the old profile vanished and clears an empty result", () => {
182 expect(refreshedSourceProfileDirectory("Removed", refreshed)).toBe("Default");
183 expect(refreshedSourceProfileDirectory("Removed", source({ profiles: [] }))).toBe("");
184 });
185});
186
187describe("isRetryableReason", () => {
188 it("offers a retry for failures a second attempt can clear", () => {
189 expect(isRetryableReason("needsKeychainApproval")).toBe(true);
190 expect(isRetryableReason("keychainUnavailable")).toBe(true);
191 expect(isRetryableReason("readFailed")).toBe(true);
192 });
193
194 it("does not offer a retry for a permanent failure", () => {
195 expect(isRetryableReason("unsupportedPlatform")).toBe(false);
196 expect(isRetryableReason("unknownSourceProfile")).toBe(false);
197 // Retrying the same new-profile import cannot lower the profile count.
198 expect(isRetryableReason("profileLimitReached")).toBe(false);
199 });
200
201 it("offers a retry once the user has signed in to create the missing key", () => {
202 // The blocked copy tells the user to sign in and retry, so the screen
203 // has to offer the retry it asks for.
204 expect(isRetryableReason("keychainItemMissing")).toBe(true);
205 });
206
207 it("offers a retry when the cookies landed but the new profile was not saved", () => {
208 expect(isRetryableReason("profileNotSaved")).toBe(true);
209 });
210});
211
212describe("formatSkippedDomains", () => {
213 it("joins a short list naturally", () => {
214 expect(formatSkippedDomains([])).toBe("");
215 expect(formatSkippedDomains(["a.com"])).toBe("a.com");
216 expect(formatSkippedDomains(["a.com", "b.com"])).toBe("a.com and b.com");
217 expect(formatSkippedDomains(["a.com", "b.com", "c.com"])).toBe("a.com, b.com and c.com");
218 });
219
220 it("summarizes a long list", () => {
221 expect(formatSkippedDomains(["a.com", "b.com", "c.com", "d.com", "e.com"])).toBe(
222 "a.com, b.com, c.com and 2 more",
223 );
224 });
225});