diff --git a/desktop/src/features/onboarding/hooks.ts b/desktop/src/features/onboarding/hooks.ts index 7495af48e..f8c08afc2 100644 --- a/desktop/src/features/onboarding/hooks.ts +++ b/desktop/src/features/onboarding/hooks.ts @@ -82,14 +82,15 @@ export async function initializeStarterChannels( let starterChannels: Awaited< ReturnType > | null = null; - let starterChannelsError: unknown = null; try { starterChannels = await ensureStarterChannels({ ensureStarterChannels: ensureStarterChannelsCommand, getChannels, }); } catch (error) { - starterChannelsError = error; + // Public starter channels are optional. Owners may have deliberately + // deleted their deterministic starter channels; that must not strand a + // new member after the required private Welcome channel succeeds. console.warn("Failed to initialize public starter channels.", error); } @@ -145,16 +146,6 @@ export async function initializeStarterChannels( notifyWelcomeChannelReady(welcomeChannel.id); } const focusChannelId = focus ? welcomeChannel.id : undefined; - if (starterChannelsError) { - return { - ok: false, - focusChannelId, - reason: - starterChannelsError instanceof Error - ? starterChannelsError.message - : "Failed to set up starter channels", - }; - } return { ok: true, focusChannelId }; } catch (error) { console.warn("Failed to initialize starter channels.", error); diff --git a/desktop/tests/e2e/deep-link-invite.spec.ts b/desktop/tests/e2e/deep-link-invite.spec.ts index 0b3ccb7b5..42c4f8ea9 100644 --- a/desktop/tests/e2e/deep-link-invite.spec.ts +++ b/desktop/tests/e2e/deep-link-invite.spec.ts @@ -8,7 +8,7 @@ import { seedActiveIdentity } from "../helpers/onboarding"; // Invite claiming waits until setup finishes and the final identity is known. const DEFAULT_MOCK_PUBKEY = "deadbeef".repeat(8); -const WELCOME_FAILURE_PUBKEY = TEST_IDENTITIES.tyler.pubkey; +const COMMUNITY_ONBOARDING_PUBKEY = TEST_IDENTITIES.tyler.pubkey; const TRANSACTION_STORAGE_KEY = "buzz-community-onboarding-transaction.v1"; const COMMUNITY_RELAY_URL = "wss://hive.example.com"; @@ -262,7 +262,63 @@ test("queued add-community links open and acknowledge one at a time", async ({ ]); }); -test("Welcome failure retries once before allowing starter channel setup to be skipped", async ({ +test("deleted public starter channels do not strand community onboarding", async ({ + page, +}) => { + const starterError = + "starter channels created but metadata not yet available"; + await seedActiveIdentity(page, TEST_IDENTITIES.tyler); + await page.addInitScript( + ({ pubkey, relayUrl, storageKey }) => { + window.localStorage.setItem( + `buzz-machine-onboarding-complete.v2:${pubkey}`, + "true", + ); + const timestamp = new Date().toISOString(); + window.localStorage.setItem( + storageKey, + JSON.stringify({ + id: "txn-deleted-starters-1", + source: "deep-link-join", + stage: "team-intro", + relayUrl, + communityName: "hive", + communityId: "e2e-default-community", + createdAt: timestamp, + updatedAt: timestamp, + }), + ); + }, + { + pubkey: COMMUNITY_ONBOARDING_PUBKEY, + relayUrl: COMMUNITY_RELAY_URL, + storageKey: TRANSACTION_STORAGE_KEY, + }, + ); + await installMockBridge( + page, + { ensureStarterChannelsErrors: [starterError] }, + { relayWsUrl: COMMUNITY_RELAY_URL, skipOnboardingSeed: true }, + ); + await page.goto("/"); + + await page.getByRole("button", { name: "Take me to Buzz" }).click(); + + await expect(page.getByTestId("community-onboarding-flow")).toHaveCount(0); + await expect(page).toHaveURL(/#\/channels\/[^/]+$/); + await expect(page.getByTestId("chat-title")).toContainText("Welcome"); + await expect(page.getByText(starterError)).toHaveCount(0); + expect( + await page.evaluate( + () => + window.__BUZZ_E2E_COMMANDS__?.filter( + (command) => command === "ensure_starter_channels", + ).length ?? 0, + ), + ).toBe(1); +}); + +test("required Welcome creation failure keeps community onboarding open", async ({ page, }) => { const welcomeError = "Channel creation is not permitted."; @@ -289,80 +345,26 @@ test("Welcome failure retries once before allowing starter channel setup to be s ); }, { - pubkey: WELCOME_FAILURE_PUBKEY, + pubkey: COMMUNITY_ONBOARDING_PUBKEY, relayUrl: COMMUNITY_RELAY_URL, storageKey: TRANSACTION_STORAGE_KEY, }, ); await installMockBridge( page, - { ensureStarterChannelsErrors: [welcomeError, welcomeError, welcomeError] }, + { createChannelErrors: [welcomeError] }, { relayWsUrl: COMMUNITY_RELAY_URL, skipOnboardingSeed: true }, ); await page.goto("/"); - for (const name of ["fizz", "honey", "bumble"]) { - const character = page.getByTestId(`starter-persona-${name}`); - await expect(character).toBeVisible(); - await expect(character).toHaveAttribute( - "src", - `/onboarding/starter-team/${name}.png`, - ); - } - - const enterButton = page.getByRole("button", { name: "Take me to Buzz" }); - await enterButton.click(); + await page.getByRole("button", { name: "Take me to Buzz" }).click(); + await expect(page.getByTestId("community-onboarding-flow")).toBeVisible(); await expect(page.getByText(`${welcomeError} Try again.`)).toBeVisible(); - await expect(enterButton).toBeEnabled(); - const backButton = page.getByRole("button", { name: "Back" }); - await expect(backButton).toBeVisible(); - await backButton.click(); - await expect( - page.getByRole("heading", { name: "Build your profile" }), - ).toBeVisible(); - await page.getByLabel("Community username").fill("Tyler"); - await page.getByTestId("community-profile-next").click(); - - await enterButton.click(); - await expect(page.getByText(`${welcomeError} Try again.`)).toBeVisible(); - await expect(enterButton).toBeEnabled(); - - await enterButton.click(); - - const skipButton = page.getByRole("button", { name: "Skip for now" }); - await expect(page.getByText(welcomeError, { exact: true })).toBeVisible(); - await expect(skipButton).toBeEnabled(); - await expect(page.getByRole("button", { name: "Back" })).toBeVisible(); - - const starterChannelAttempts = await page.evaluate( - () => - window.__BUZZ_E2E_COMMANDS__?.filter( - (command) => command === "ensure_starter_channels", - ).length ?? 0, - ); - expect(starterChannelAttempts).toBe(3); - - await skipButton.click(); - - await expect(page.getByTestId("community-onboarding-flow")).toHaveCount(0); - expect( - await page.evaluate( - () => - window.__BUZZ_E2E_COMMANDS__?.filter( - (command) => command === "ensure_starter_channels", - ).length ?? 0, - ), - ).toBe(3); - await expect - .poll(() => - page.evaluate( - (transaction) => window.localStorage.getItem(transaction), - TRANSACTION_STORAGE_KEY, - ), - ) - .toBeNull(); + page.getByRole("button", { name: "Take me to Buzz" }), + ).toBeEnabled(); + await expect(page.getByTestId("chat-title")).toHaveCount(0); }); test("persisted deep-link invite hands off to Joining after machine onboarding", async ({ diff --git a/desktop/tests/e2e/onboarding.spec.ts b/desktop/tests/e2e/onboarding.spec.ts index c54163367..b1e96aaf2 100644 --- a/desktop/tests/e2e/onboarding.spec.ts +++ b/desktop/tests/e2e/onboarding.spec.ts @@ -2985,27 +2985,6 @@ test("first-run onboarding keeps the shell hidden and lands on private Welcome a await expectWelcomeGuideIntro(page); }); -function retryToast(page: Page, title: string) { - return page - .locator("[data-sonner-toast][data-removed='false']") - .filter({ hasText: title }); -} - -async function retryToastAction( - page: Page, - { command, title }: { command: string; title: string }, -) { - const activeToast = retryToast(page, title); - await expect( - activeToast.getByRole("button", { name: "Retry" }), - ).toBeVisible(); - const before = await commandCount(page, command); - await activeToast - .getByRole("button", { name: "Retry" }) - .dispatchEvent("click"); - await expect.poll(() => commandCount(page, command)).toBeGreaterThan(before); -} - async function commandCount(page: Page, command: string) { return page.evaluate( (target) => @@ -3015,16 +2994,14 @@ async function commandCount(page: Page, command: string) { ); } -test("failed starter channel retries recreate actionable toasts", async ({ +test("failed public starter channel setup does not show a retry toast", async ({ page, }) => { const starterError = "Mock starter channel setup failed."; await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); await installMockBridge( page, - { - ensureStarterChannelsErrors: [starterError, starterError, starterError], - }, + { ensureStarterChannelsErrors: [starterError, starterError] }, { skipOnboardingSeed: true }, ); await page.goto("/"); @@ -3033,45 +3010,12 @@ test("failed starter channel retries recreate actionable toasts", async ({ await completeProfileOnboarding(page); await expectPrivateWelcomeLanding(page); - const title = "Couldn't set up starter channels"; - const activeToast = retryToast(page, title); - await expect(activeToast).toContainText(starterError); - await retryToastAction(page, { - command: "ensure_starter_channels", - title, - }); - await expect(activeToast).toContainText(starterError); await expect( - activeToast.getByRole("button", { name: "Retry" }), - ).toBeVisible(); -}); - -test("successful starter channel retry clears its actionable toast", async ({ - page, -}) => { - const starterError = "Mock starter channel setup failed."; - await seedActiveIdentity(page, BLANK_TYLER_IDENTITY); - await installMockBridge( - page, - { - ensureStarterChannelsErrors: [starterError, starterError], - }, - { skipOnboardingSeed: true }, - ); - await page.goto("/"); - - await page.getByTestId("onboarding-display-name").fill("Morty QA"); - await completeProfileOnboarding(page); - - const title = "Couldn't set up starter channels"; - await expect(retryToast(page, title)).toContainText(starterError); - await retryToastAction(page, { - command: "ensure_starter_channels", - title, - }); - await expect(retryToast(page, title)).toHaveCount(0); - await expectWelcomeView(page); - await expectStarterChannels(page); + page + .locator("[data-sonner-toast][data-removed='false']") + .filter({ hasText: "Couldn't set up starter channels" }), + ).toHaveCount(0); + expect(await commandCount(page, "ensure_starter_channels")).toBe(2); }); test("first-run onboarding posts the live Fizz kickoff", async ({ page }) => {