Files
buzz/desktop/tests/e2e/invite-link-copy.spec.ts
Wes 310df2ec33 desktop: restore direct community member adds (#3634)
## Summary

- restore direct community member adds in **Settings → Invites**
- keep one consolidated entry point: the dialog now supports both adding
someone directly and sharing an invite link
- accept npub or 64-character hex keys while preserving role hierarchy
(owners: Member/Admin; admins: Member only)
- verify an npub direct-add publishes the decoded hex key in a kind
`9030` NIP-IA event

## Why

The Invites consolidation left `AddMemberDialog` without a live mount
point, so the existing direct-add capability disappeared even though its
mutation path still existed. This reuses that implementation rather than
introducing a second one.

## Before and after

| Before | After |
| --- | --- |
| The consolidated dialog only offered a share link; there was no
direct-add path. | The same dialog now presents direct add and
share-link controls as one invitation flow. |
| ![Before: invite dialog with share-link controls
only](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3634/before-invite-dialog.png)
| ![After: polished community invite dialog with direct-add and
share-link
sections](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3634/invite-dialog-polished.png)
|

<details>
<summary>Before: Invites page entry point</summary>

![Before: Invites settings
page](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3634/before-invites-page.png)

</details>

## Verification

- `pnpm --dir desktop build:e2e`
- `pnpm exec playwright test
tests/e2e/invites-settings-screenshots.spec.ts --project=smoke` — 4
passed
- targeted Biome check on modified files
- push hook: Desktop check and 3,783 Desktop tests passed
- GitHub CI green except Desktop E2E Relay still running at last status
snapshot

---------

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Co-authored-by: kenny lopez <klopez4212@gmail.com>
2026-07-30 07:04:49 -07:00

89 lines
3.1 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { installMockBridge } from "../helpers/bridge";
import { openSettings } from "../helpers/settings";
let invitePayloads: Record<string, unknown>[];
test.beforeEach(async ({ page }) => {
invitePayloads = [];
await page.context().grantPermissions(["clipboard-read", "clipboard-write"], {
origin: "http://127.0.0.1:4173",
});
await installMockBridge(page, {
relayRequiresMembership: true,
});
await page.route("**/api/invites", async (route) => {
invitePayloads.push(route.request().postDataJSON());
await route.fulfill({
contentType: "application/json",
json: {
code: "qr-download-test",
expires_at: Math.floor(Date.now() / 1000) + 86_400,
url: "buzz://join?relay=wss%3A%2F%2Frelay.example.com&code=qr-download-test",
},
status: 200,
});
});
});
test("copies a freshly minted invite link without showing a URL or QR code", async ({
page,
}) => {
await page.goto("/");
await openSettings(page, "community-members");
await expect(page.getByTestId("settings-community-members")).toBeVisible();
await page.getByTestId("community-invite-dialog-trigger").click();
await expect(page.getByTestId("member-pubkey-input")).toBeVisible();
await expect(page.getByTestId("member-role")).toHaveCount(0);
await expect(page.getByTestId("confirm-add-member")).toHaveCount(0);
await expect(page.getByTestId("invite-link-url")).toHaveCount(0);
await expect(page.getByTestId("invite-link-qr-code")).toHaveCount(0);
await expect(page.getByTestId("invite-link-max-uses-trigger")).toHaveText(
"No limit",
);
await page.getByTestId("copy-invite-link").click();
await expect(page.getByTestId("copy-invite-link")).toContainText("Copied");
expect(invitePayloads).toEqual([{ ttl_secs: 3 * 24 * 60 * 60 }]);
const payload = await page.evaluate(() => {
const log = (
window as Window & {
__BUZZ_E2E_COMMAND_LOG__?: Array<{
command: string;
payload: Record<string, unknown> | null;
}>;
}
).__BUZZ_E2E_COMMAND_LOG__;
return log?.findLast(({ command }) => command === "copy_text_to_clipboard")
?.payload;
});
expect(payload).toEqual({
text: "buzz://join?relay=wss%3A%2F%2Frelay.example.com&code=qr-download-test",
});
});
test("sets a selected invite-use limit", async ({ page }) => {
await page.goto("/");
await openSettings(page, "community-members");
await page.getByTestId("community-invite-dialog-trigger").click();
const maxUsesTrigger = page.getByTestId("invite-link-max-uses-trigger");
await maxUsesTrigger.click();
await expect(
page.getByRole("menuitemradio", { name: "No limit" }),
).toBeVisible();
await expect(
page.getByRole("menuitemradio", { name: "25 uses" }),
).toBeVisible();
await page.getByTestId("invite-link-max-uses-10").click();
await expect(maxUsesTrigger).toHaveText("10 uses");
await page.getByTestId("copy-invite-link").click();
await expect(page.getByTestId("copy-invite-link")).toContainText("Copied");
expect(invitePayloads).toEqual([
{ max_uses: 10, ttl_secs: 3 * 24 * 60 * 60 },
]);
});