mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@sprout-oss.stage.blox.sqprod.co>
325 lines
10 KiB
TypeScript
325 lines
10 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
|
|
import { seedActiveIdentity } from "../helpers/onboarding";
|
|
|
|
// Community deep links that arrive before machine onboarding complete are
|
|
// drained from Rust into a persisted transaction and acknowledged immediately.
|
|
// 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 TRANSACTION_STORAGE_KEY = "buzz-community-onboarding-transaction.v1";
|
|
const COMMUNITY_RELAY_URL = "wss://hive.example.com";
|
|
|
|
const PENDING_JOIN_LINK = {
|
|
id: "dl-join-1",
|
|
kind: "join" as const,
|
|
relayUrl: "wss://hive.example.com",
|
|
code: "abc.def",
|
|
};
|
|
|
|
const PENDING_CONNECT_LINK = {
|
|
id: "dl-connect-1",
|
|
kind: "connect" as const,
|
|
relayUrl: "wss://hive.example.com",
|
|
code: null,
|
|
};
|
|
|
|
const PENDING_ADD_COMMUNITY_LINK = {
|
|
id: "dl-add-community-1",
|
|
kind: "add-community" as const,
|
|
relayUrl: "wss://acme.communities.buzz.xyz",
|
|
code: null,
|
|
name: "Acme Team",
|
|
};
|
|
|
|
const SECOND_PENDING_ADD_COMMUNITY_LINK = {
|
|
id: "dl-add-community-2",
|
|
kind: "add-community" as const,
|
|
relayUrl: "wss://beta.communities.buzz.xyz",
|
|
code: null,
|
|
name: "Beta Team",
|
|
};
|
|
|
|
test("join deep link is acknowledged without claiming before setup", async ({
|
|
page,
|
|
}) => {
|
|
let claimCalls = 0;
|
|
await page.route("**/api/invites/claim", async (route) => {
|
|
claimCalls++;
|
|
await route.abort();
|
|
});
|
|
await installMockBridge(
|
|
page,
|
|
{ pendingCommunityDeepLinks: [PENDING_JOIN_LINK] },
|
|
{ skipCommunitySeed: true, skipOnboardingSeed: true },
|
|
);
|
|
await page.goto("/");
|
|
|
|
const gate = page.getByTestId("pending-invite-gate");
|
|
await expect(gate).toBeVisible();
|
|
await expect(
|
|
page.getByRole("heading", { name: "Opening community link" }),
|
|
).toBeVisible();
|
|
await page.getByTestId("pending-invite-continue").click();
|
|
await expect(gate).toHaveCount(0);
|
|
await expect(page.getByTestId("machine-onboarding-gate")).toBeVisible();
|
|
expect(claimCalls).toBe(0);
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(
|
|
(key) => window.localStorage.getItem(key),
|
|
TRANSACTION_STORAGE_KEY,
|
|
),
|
|
)
|
|
.toContain('"stage":"claiming"');
|
|
});
|
|
|
|
test("connect deep link shows a static acknowledgment during setup", async ({
|
|
page,
|
|
}) => {
|
|
// No invite code means nothing to confirm against the relay — the gate
|
|
// acknowledges the link and waits for the user instead of auto-advancing.
|
|
await installMockBridge(
|
|
page,
|
|
{ pendingCommunityDeepLinks: [PENDING_CONNECT_LINK] },
|
|
{ skipCommunitySeed: true, skipOnboardingSeed: true },
|
|
);
|
|
await page.goto("/");
|
|
|
|
const gate = page.getByTestId("pending-invite-gate");
|
|
await expect(gate).toBeVisible();
|
|
await expect(
|
|
page.getByRole("heading", { name: "Opening community link" }),
|
|
).toBeVisible();
|
|
await expect(gate).toContainText("hive");
|
|
|
|
// Continue setup dismisses the gate but keeps the transaction: the
|
|
// connect resumes in CommunityOnboardingFlow after machine setup.
|
|
await page.getByTestId("pending-invite-continue").click();
|
|
await expect(gate).toHaveCount(0);
|
|
await expect(page.getByTestId("machine-onboarding-gate")).toBeVisible();
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(
|
|
(key) => window.localStorage.getItem(key),
|
|
TRANSACTION_STORAGE_KEY,
|
|
),
|
|
)
|
|
.toContain('"acknowledged":true');
|
|
});
|
|
|
|
test("add-community deep link opens one editable prefill and acknowledges the queue", async ({
|
|
page,
|
|
}) => {
|
|
await installMockBridge(
|
|
page,
|
|
{ pendingCommunityDeepLinks: [PENDING_ADD_COMMUNITY_LINK] },
|
|
{ seedPreviewFeatures: true },
|
|
);
|
|
await page.goto("/");
|
|
|
|
await expect(
|
|
page.getByRole("heading", { name: "Add Community" }),
|
|
).toBeVisible();
|
|
const relayInput = page.locator("#ws-relay-url");
|
|
const nameInput = page.locator("#ws-name");
|
|
await expect(relayInput).toHaveValue(PENDING_ADD_COMMUNITY_LINK.relayUrl);
|
|
await expect(nameInput).toHaveValue(PENDING_ADD_COMMUNITY_LINK.name);
|
|
|
|
await nameInput.fill("Edited Team");
|
|
await expect(nameInput).toHaveValue("Edited Team");
|
|
|
|
await page.getByRole("button", { name: "Cancel" }).click();
|
|
await expect(
|
|
page.getByRole("heading", { name: "Add Community" }),
|
|
).toHaveCount(0);
|
|
|
|
await page.getByTestId("sidebar-profile-avatar-button").click();
|
|
await page.getByTestId("community-switcher").click();
|
|
await page.getByRole("menuitem", { name: "Add Community" }).click();
|
|
await expect(relayInput).toHaveValue("");
|
|
await expect(nameInput).toHaveValue("");
|
|
|
|
const acknowledgements = await page.evaluate(() =>
|
|
(window.__BUZZ_E2E_COMMAND_LOG__ ?? []).filter(
|
|
(entry) => entry.command === "acknowledge_pending_community_deep_link",
|
|
),
|
|
);
|
|
expect(acknowledgements).toEqual([
|
|
{
|
|
command: "acknowledge_pending_community_deep_link",
|
|
payload: { id: PENDING_ADD_COMMUNITY_LINK.id },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("queued add-community links open and acknowledge one at a time", async ({
|
|
page,
|
|
}) => {
|
|
await installMockBridge(
|
|
page,
|
|
{
|
|
pendingCommunityDeepLinks: [
|
|
PENDING_ADD_COMMUNITY_LINK,
|
|
SECOND_PENDING_ADD_COMMUNITY_LINK,
|
|
],
|
|
},
|
|
{ seedPreviewFeatures: true },
|
|
);
|
|
await page.goto("/");
|
|
|
|
const relayInput = page.locator("#ws-relay-url");
|
|
const nameInput = page.locator("#ws-name");
|
|
await expect(relayInput).toHaveValue(PENDING_ADD_COMMUNITY_LINK.relayUrl);
|
|
await expect(nameInput).toHaveValue(PENDING_ADD_COMMUNITY_LINK.name);
|
|
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
(window.__BUZZ_E2E_COMMAND_LOG__ ?? [])
|
|
.filter(
|
|
(entry) =>
|
|
entry.command === "acknowledge_pending_community_deep_link",
|
|
)
|
|
.map((entry) => entry.payload),
|
|
),
|
|
)
|
|
.toEqual([{ id: PENDING_ADD_COMMUNITY_LINK.id }]);
|
|
|
|
await page.getByRole("button", { name: "Cancel" }).click();
|
|
|
|
await expect(relayInput).toHaveValue(
|
|
SECOND_PENDING_ADD_COMMUNITY_LINK.relayUrl,
|
|
);
|
|
await expect(nameInput).toHaveValue(SECOND_PENDING_ADD_COMMUNITY_LINK.name);
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
(window.__BUZZ_E2E_COMMAND_LOG__ ?? [])
|
|
.filter(
|
|
(entry) =>
|
|
entry.command === "acknowledge_pending_community_deep_link",
|
|
)
|
|
.map((entry) => entry.payload),
|
|
),
|
|
)
|
|
.toEqual([
|
|
{ id: PENDING_ADD_COMMUNITY_LINK.id },
|
|
{ id: SECOND_PENDING_ADD_COMMUNITY_LINK.id },
|
|
]);
|
|
});
|
|
|
|
test("Welcome failure can be skipped without abandoning community onboarding", 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: WELCOME_FAILURE_PUBKEY,
|
|
relayUrl: COMMUNITY_RELAY_URL,
|
|
storageKey: TRANSACTION_STORAGE_KEY,
|
|
},
|
|
);
|
|
await installMockBridge(
|
|
page,
|
|
{ ensureStarterChannelsErrors: [welcomeError, welcomeError] },
|
|
{ relayWsUrl: COMMUNITY_RELAY_URL, skipOnboardingSeed: true },
|
|
);
|
|
await page.goto("/");
|
|
|
|
await page.getByRole("button", { name: "Enter hive" }).click();
|
|
|
|
await expect(page.getByText(welcomeError)).toBeVisible();
|
|
await expect(
|
|
page.getByRole("button", { name: "Preparing Welcome…" }),
|
|
).toBeEnabled();
|
|
const skip = page.getByRole("button", { name: "Skip for now" });
|
|
await expect(skip).toBeVisible();
|
|
await skip.click();
|
|
|
|
const completionKey = `buzz-community-onboarding-complete.v1:${encodeURIComponent(COMMUNITY_RELAY_URL)}:${WELCOME_FAILURE_PUBKEY}`;
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(
|
|
({ completion, transaction }) => ({
|
|
completion: window.localStorage.getItem(completion),
|
|
transaction: window.localStorage.getItem(transaction),
|
|
}),
|
|
{ completion: completionKey, transaction: TRANSACTION_STORAGE_KEY },
|
|
),
|
|
)
|
|
.toEqual({ completion: "true", transaction: null });
|
|
await expect(page.getByTestId("community-onboarding-flow")).toHaveCount(0);
|
|
await expect(page.getByTestId("app-sidebar")).toBeVisible();
|
|
});
|
|
|
|
test("persisted deep-link invite hands off to Joining after machine onboarding", async ({
|
|
page,
|
|
}) => {
|
|
// Deterministic claim failure (no real relay behind the mock bridge): the
|
|
// spec asserts the handoff reaches the "Joining …" claiming screen, not
|
|
// that the claim itself succeeds.
|
|
await page.route("**/api/invites/claim", (route) => route.abort());
|
|
await page.addInitScript(
|
|
({ pubkey, storageKey }) => {
|
|
window.localStorage.setItem(
|
|
`buzz-machine-onboarding-complete.v2:${pubkey}`,
|
|
"true",
|
|
);
|
|
const timestamp = new Date().toISOString();
|
|
window.localStorage.setItem(
|
|
storageKey,
|
|
JSON.stringify({
|
|
id: "txn-deep-link-1",
|
|
source: "deep-link-join",
|
|
stage: "claiming",
|
|
relayUrl: "wss://hive.example.com",
|
|
inviteCode: "abc.def",
|
|
communityName: "hive",
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
}),
|
|
);
|
|
},
|
|
{ pubkey: DEFAULT_MOCK_PUBKEY, storageKey: TRANSACTION_STORAGE_KEY },
|
|
);
|
|
await installMockBridge(page, undefined, {
|
|
skipCommunitySeed: true,
|
|
skipOnboardingSeed: true,
|
|
});
|
|
await page.goto("/");
|
|
|
|
// Machine onboarding is complete, so the transaction owns the screen.
|
|
await expect(page.getByTestId("community-onboarding-flow")).toBeVisible();
|
|
await expect(
|
|
page.getByRole("heading", { name: "Joining hive" }),
|
|
).toBeVisible();
|
|
await expect(page.getByTestId("pending-invite-gate")).toHaveCount(0);
|
|
|
|
// The claim was attempted and its failure surfaced with a Retry.
|
|
await expect(page.getByRole("button", { name: "Retry" })).toBeVisible();
|
|
});
|