diff --git a/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx b/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx index c503919..bb925d0 100644 --- a/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx +++ b/app/(app)/create/screens/card/CommunicationMethodsScreen.tsx @@ -740,24 +740,24 @@ export function CommunicationMethodsScreen() { className={CREATE_FLOW_CARD_STACK_AREA_MAX_CLASS} aria-busy={!recommendationsReady} > - {recommendationsReady ? ( - { - markCreateFlowInteraction(); - setExpanded((prev) => !prev); - }} - hasMore={true} - toggleLabel={comm.page.seeAllLink} - compactRecommendedLimit={5} - compactCardIds={compactCardIds} - compactDesktopLayout="flexWrap" - headerLockupSize={mdUp ? "L" : "M"} - /> - ) : null} + {recommendationsReady && ( + { + markCreateFlowInteraction(); + setExpanded((prev) => !prev); + }} + hasMore={true} + toggleLabel={comm.page.seeAllLink} + compactRecommendedLimit={5} + compactCardIds={compactCardIds} + compactDesktopLayout="flexWrap" + headerLockupSize={mdUp ? "L" : "M"} + /> + )} diff --git a/app/(app)/create/screens/card/ConflictManagementScreen.tsx b/app/(app)/create/screens/card/ConflictManagementScreen.tsx index e7aa661..e352cd1 100644 --- a/app/(app)/create/screens/card/ConflictManagementScreen.tsx +++ b/app/(app)/create/screens/card/ConflictManagementScreen.tsx @@ -739,24 +739,24 @@ export function ConflictManagementScreen() { className={CREATE_FLOW_CARD_STACK_AREA_MAX_CLASS} aria-busy={!recommendationsReady} > - {recommendationsReady ? ( - { - markCreateFlowInteraction(); - setExpanded((prev) => !prev); - }} - hasMore={true} - toggleLabel={cm.page.seeAllLink} - compactRecommendedLimit={5} - compactCardIds={compactCardIds} - compactDesktopLayout="pyramidFive" - headerLockupSize={mdUp ? "L" : "M"} - /> - ) : null} + {recommendationsReady && ( + { + markCreateFlowInteraction(); + setExpanded((prev) => !prev); + }} + hasMore={true} + toggleLabel={cm.page.seeAllLink} + compactRecommendedLimit={5} + compactCardIds={compactCardIds} + compactDesktopLayout="pyramidFive" + headerLockupSize={mdUp ? "L" : "M"} + /> + )} diff --git a/app/(app)/create/screens/card/MembershipMethodsScreen.tsx b/app/(app)/create/screens/card/MembershipMethodsScreen.tsx index d32df61..4c603af 100644 --- a/app/(app)/create/screens/card/MembershipMethodsScreen.tsx +++ b/app/(app)/create/screens/card/MembershipMethodsScreen.tsx @@ -732,24 +732,24 @@ export function MembershipMethodsScreen() { className={CREATE_FLOW_CARD_STACK_AREA_MAX_CLASS} aria-busy={!recommendationsReady} > - {recommendationsReady ? ( - { - markCreateFlowInteraction(); - setExpanded((prev) => !prev); - }} - hasMore={true} - toggleLabel={mem.page.seeAllLink} - compactRecommendedLimit={5} - compactCardIds={compactCardIds} - compactDesktopLayout="pyramidFive" - headerLockupSize={mdUp ? "L" : "M"} - /> - ) : null} + {recommendationsReady && ( + { + markCreateFlowInteraction(); + setExpanded((prev) => !prev); + }} + hasMore={true} + toggleLabel={mem.page.seeAllLink} + compactRecommendedLimit={5} + compactCardIds={compactCardIds} + compactDesktopLayout="pyramidFive" + headerLockupSize={mdUp ? "L" : "M"} + /> + )} diff --git a/app/(app)/create/screens/right-rail/DecisionApproachesScreen.tsx b/app/(app)/create/screens/right-rail/DecisionApproachesScreen.tsx index d2e7df7..db629be 100644 --- a/app/(app)/create/screens/right-rail/DecisionApproachesScreen.tsx +++ b/app/(app)/create/screens/right-rail/DecisionApproachesScreen.tsx @@ -766,37 +766,37 @@ export function DecisionApproachesScreen() { className="flex w-full min-w-0 flex-col items-stretch gap-6 py-0" aria-busy={!recommendationsReady} > - {recommendationsReady ? ( - - {da.cardStack.expandedStackDescriptionBefore} - - {da.sidebar.descriptionLinkLabel} - - {da.cardStack.expandedStackDescriptionAfter} - - ) : ( - "" - ) - } - layout="singleStack" - compactRecommendedLimit={5} - compactCardIds={compactCardIds} - className="w-full" - headerLockupSize={mdUp ? "L" : "M"} - /> - ) : null} + {recommendationsReady && ( + + {da.cardStack.expandedStackDescriptionBefore} + + {da.sidebar.descriptionLinkLabel} + + {da.cardStack.expandedStackDescriptionAfter} + + ) : ( + "" + ) + } + layout="singleStack" + compactRecommendedLimit={5} + compactCardIds={compactCardIds} + className="w-full" + headerLockupSize={mdUp ? "L" : "M"} + /> + )} { +export async function prepareFreshCreateFlowEntry( + options: PrepareFreshCreateFlowEntryOptions = {}, +): Promise { + const signedIn = options.signedIn === true; clearAnonymousCreateFlowStorage(); clearCoreValueDetailsLocalStorage(); - if (!isBackendSyncEnabled()) return; + if (!signedIn || !isBackendSyncEnabled()) return; setFreshEntryPending(); try { await deleteServerDraft(); diff --git a/app/(app)/profile/ProfilePageClient.tsx b/app/(app)/profile/ProfilePageClient.tsx index 14eea43..765eb58 100644 --- a/app/(app)/profile/ProfilePageClient.tsx +++ b/app/(app)/profile/ProfilePageClient.tsx @@ -253,7 +253,7 @@ export default function ProfilePageClient() { }, [draft, router]); const handleStartNewCustomRule = useCallback(() => { - prepareFreshCreateFlowEntrySync(); + prepareFreshCreateFlowEntrySync({ signedIn: true }); router.push("/create/informational"); }, [router]); diff --git a/app/components/navigation/Top/Top.container.tsx b/app/components/navigation/Top/Top.container.tsx index 2e56239..066d578 100644 --- a/app/components/navigation/Top/Top.container.tsx +++ b/app/components/navigation/Top/Top.container.tsx @@ -57,9 +57,9 @@ const TopContainer = memo( * (see {@link prepareFreshCreateFlowEntrySync}). */ const handleCreateRuleClick = useCallback(() => { - prepareFreshCreateFlowEntrySync(); + prepareFreshCreateFlowEntrySync({ signedIn: loggedIn }); router.push("/create/informational"); - }, [router]); + }, [loggedIn, router]); // Schema markup for site navigation const schemaData = { diff --git a/tests/unit/prepareFreshCreateFlowEntry.test.ts b/tests/unit/prepareFreshCreateFlowEntry.test.ts new file mode 100644 index 0000000..3653f73 --- /dev/null +++ b/tests/unit/prepareFreshCreateFlowEntry.test.ts @@ -0,0 +1,89 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + FRESH_ENTRY_PENDING_KEY, + hasFreshEntryPending, + prepareFreshCreateFlowEntry, + prepareFreshCreateFlowEntrySync, +} from "../../app/(app)/create/utils/prepareFreshCreateFlowEntry"; +import { CREATE_FLOW_ANONYMOUS_KEY } from "../../app/(app)/create/utils/anonymousDraftStorage"; + +const deleteServerDraft = vi.fn(); +const isBackendSyncEnabled = vi.fn(); + +vi.mock("../../lib/create/api", () => ({ + deleteServerDraft: (...args: unknown[]) => deleteServerDraft(...args), +})); + +vi.mock("../../lib/create/backendSyncEnabled", () => ({ + isBackendSyncEnabled: () => isBackendSyncEnabled(), +})); + +describe("prepareFreshCreateFlowEntrySync", () => { + beforeEach(() => { + deleteServerDraft.mockReset(); + deleteServerDraft.mockResolvedValue(undefined); + isBackendSyncEnabled.mockReturnValue(true); + window.localStorage.clear(); + window.sessionStorage.clear(); + }); + + afterEach(() => { + window.localStorage.clear(); + window.sessionStorage.clear(); + }); + + it("clears local draft storage for guests without calling deleteServerDraft", () => { + window.localStorage.setItem( + CREATE_FLOW_ANONYMOUS_KEY, + JSON.stringify({ title: "Stale" }), + ); + + prepareFreshCreateFlowEntrySync(); + + expect(window.localStorage.getItem(CREATE_FLOW_ANONYMOUS_KEY)).toBeNull(); + expect(deleteServerDraft).not.toHaveBeenCalled(); + expect(hasFreshEntryPending()).toBe(false); + }); + + it("clears local draft storage and deletes the server draft when signed in", async () => { + prepareFreshCreateFlowEntrySync({ signedIn: true }); + + expect(deleteServerDraft).toHaveBeenCalledTimes(1); + expect(window.sessionStorage.getItem(FRESH_ENTRY_PENDING_KEY)).toBe("1"); + + await vi.waitFor(() => { + expect(hasFreshEntryPending()).toBe(false); + }); + }); + + it("skips server draft delete when backend sync is disabled", () => { + isBackendSyncEnabled.mockReturnValue(false); + + prepareFreshCreateFlowEntrySync({ signedIn: true }); + + expect(deleteServerDraft).not.toHaveBeenCalled(); + expect(hasFreshEntryPending()).toBe(false); + }); +}); + +describe("prepareFreshCreateFlowEntry", () => { + beforeEach(() => { + deleteServerDraft.mockReset(); + deleteServerDraft.mockResolvedValue(undefined); + isBackendSyncEnabled.mockReturnValue(true); + window.sessionStorage.clear(); + }); + + afterEach(() => { + window.sessionStorage.clear(); + }); + + it("awaits deleteServerDraft only when signed in", async () => { + await prepareFreshCreateFlowEntry(); + expect(deleteServerDraft).not.toHaveBeenCalled(); + + await prepareFreshCreateFlowEntry({ signedIn: true }); + expect(deleteServerDraft).toHaveBeenCalledTimes(1); + expect(hasFreshEntryPending()).toBe(false); + }); +});