Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions desktop/src/features/onboarding/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,15 @@ export async function initializeStarterChannels(
let starterChannels: Awaited<
ReturnType<typeof ensureStarterChannels>
> | 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);
}

Expand Down Expand Up @@ -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);
Expand Down
120 changes: 61 additions & 59 deletions desktop/tests/e2e/deep-link-invite.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -262,10 +262,11 @@ 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 welcomeError = "Channel creation is not permitted.";
const starterError =
"starter channels created but metadata not yet available";
await seedActiveIdentity(page, TEST_IDENTITIES.tyler);
await page.addInitScript(
({ pubkey, relayUrl, storageKey }) => {
Expand All @@ -277,7 +278,7 @@ test("Welcome failure retries once before allowing starter channel setup to be s
window.localStorage.setItem(
storageKey,
JSON.stringify({
id: "txn-welcome-failure-1",
id: "txn-deleted-starters-1",
source: "deep-link-join",
stage: "team-intro",
relayUrl,
Expand All @@ -289,80 +290,81 @@ 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] },
{ ensureStarterChannelsErrors: [starterError] },
{ 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 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 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(3);
await expect
.poll(() =>
page.evaluate(
(transaction) => window.localStorage.getItem(transaction),
TRANSACTION_STORAGE_KEY,
),
)
.toBeNull();
).toBe(1);
});

test("required Welcome creation failure keeps community onboarding open", async ({
page,
}) => {
const welcomeError = "Channel creation is not permitted.";
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-welcome-failure-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,
{ createChannelErrors: [welcomeError] },
{ 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")).toBeVisible();
await expect(page.getByText(`${welcomeError} Try again.`)).toBeVisible();
await expect(
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 ({
Expand Down
70 changes: 7 additions & 63 deletions desktop/tests/e2e/onboarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand All @@ -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("/");
Expand All @@ -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 }) => {
Expand Down
Loading