mirror of
https://github.com/block/buzz.git
synced 2026-11-11 20:01:09 +00:00
Merge remote-tracking branch 'origin/main' into kennylopez-buzz-onboarding-v3
Signed-off-by: kenny lopez <klopez4212@gmail.com> # Conflicts: # desktop/src/features/agents/ui/AgentCreationPreview.tsx
This commit is contained in:
@@ -20,6 +20,7 @@ function createCatalogEvent(input: {
|
||||
createdAt?: number;
|
||||
shared?: boolean;
|
||||
avatarUrl?: string;
|
||||
description?: string;
|
||||
}): RelayEvent {
|
||||
const ownerPrivateKey =
|
||||
input.ownerPrivateKey ??
|
||||
@@ -43,6 +44,7 @@ function createCatalogEvent(input: {
|
||||
display_name: input.displayName,
|
||||
system_prompt: input.systemPrompt,
|
||||
avatar_url: input.avatarUrl ?? null,
|
||||
description: input.description ?? null,
|
||||
runtime: null,
|
||||
model: null,
|
||||
provider: null,
|
||||
@@ -309,6 +311,7 @@ test("built-in persona edits persist", async ({ page }) => {
|
||||
|
||||
const dialog = page.getByTestId("persona-dialog");
|
||||
await dialog.getByLabel("Agent name").fill("My Fizz");
|
||||
await dialog.getByLabel("Description").fill("Helps teams ship reliably.");
|
||||
await dialog.getByLabel("Agent instruction").fill("User-edited instructions");
|
||||
await dialog.getByRole("button", { name: "Save changes" }).click();
|
||||
|
||||
@@ -316,13 +319,22 @@ test("built-in persona edits persist", async ({ page }) => {
|
||||
await expect(page.getByTestId("agents-library-personas")).toContainText(
|
||||
"My Fizz",
|
||||
);
|
||||
await expect(
|
||||
page.getByTestId("persona-agent-row-builtin:fizz"),
|
||||
).toContainText("Helps teams ship reliably.");
|
||||
const personas = await invokeTauri<
|
||||
Array<{ id: string; display_name: string; system_prompt: string }>
|
||||
Array<{
|
||||
id: string;
|
||||
display_name: string;
|
||||
description: string | null;
|
||||
system_prompt: string;
|
||||
}>
|
||||
>(page, "list_personas");
|
||||
expect(
|
||||
personas.find((persona) => persona.id === "builtin:fizz"),
|
||||
).toMatchObject({
|
||||
display_name: "My Fizz",
|
||||
description: "Helps teams ship reliably.",
|
||||
system_prompt: "User-edited instructions",
|
||||
});
|
||||
});
|
||||
@@ -624,8 +636,38 @@ test("team cards use the thread-style overlapping avatar stack", async ({
|
||||
);
|
||||
expect(boxes[1]?.left).toBeLessThan(boxes[0]?.right ?? 0);
|
||||
expect(boxes[2]?.left).toBeLessThan(boxes[1]?.right ?? 0);
|
||||
await expect(avatars.first()).not.toHaveCSS("mask-image", "none");
|
||||
await expect(avatars.last()).toHaveCSS("mask-image", "none");
|
||||
const overlapStyles = await avatars.evaluateAll((elements) =>
|
||||
elements.map((element) => {
|
||||
const styles = getComputedStyle(element);
|
||||
const outline = getComputedStyle(element, "::before");
|
||||
return {
|
||||
maskImage: styles.maskImage,
|
||||
outlineBackground: outline.backgroundColor,
|
||||
outlineBorderRadius: outline.borderRadius,
|
||||
outlineInset: outline.inset,
|
||||
};
|
||||
}),
|
||||
);
|
||||
expect(overlapStyles).toEqual([
|
||||
{
|
||||
maskImage: "none",
|
||||
outlineBackground: "rgb(255, 255, 255)",
|
||||
outlineBorderRadius: "calc(30% + 2px)",
|
||||
outlineInset: "-2px",
|
||||
},
|
||||
{
|
||||
maskImage: "none",
|
||||
outlineBackground: "rgb(255, 255, 255)",
|
||||
outlineBorderRadius: "calc(30% + 2px)",
|
||||
outlineInset: "-2px",
|
||||
},
|
||||
{
|
||||
maskImage: "none",
|
||||
outlineBackground: "rgb(255, 255, 255)",
|
||||
outlineBorderRadius: "calc(30% + 2px)",
|
||||
outlineInset: "-2px",
|
||||
},
|
||||
]);
|
||||
const avatarSurfaceStyles = await avatars
|
||||
.locator(":scope > *")
|
||||
.evaluateAll((elements) =>
|
||||
@@ -704,7 +746,7 @@ test("agent defaults stays in the header without an actions menu", async ({
|
||||
defaultsDialog.getByTestId("global-agent-model"),
|
||||
).toHaveAttribute("data-value", "gpt-5.5[high]");
|
||||
await expect(defaultsDialog.getByTestId("global-agent-model")).toContainText(
|
||||
"gpt-5.5[high]",
|
||||
"GPT-5.5 (high)",
|
||||
);
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(defaultsDialog).toHaveCount(0);
|
||||
@@ -806,34 +848,55 @@ test("agent catalog chooser order stays stable when selection changes", async ({
|
||||
expect(await getCatalogOrder(page)).toEqual(before);
|
||||
});
|
||||
|
||||
test("catalog detail pane shows the full persona details", async ({ page }) => {
|
||||
const personaId = "custom:researcher";
|
||||
await seedActiveIdentity(page, TEST_IDENTITIES.tyler);
|
||||
test("catalog detail pane shows the full persona details before Add agent", async ({
|
||||
page,
|
||||
}) => {
|
||||
const personaId = "remote-researcher";
|
||||
const remoteCatalogId = `catalog:${TEST_IDENTITIES.alice.pubkey}:${personaId}`;
|
||||
const description = `Maps evidence across systems: ${"界".repeat(180)}`;
|
||||
await installMockBridge(page, {
|
||||
personas: [
|
||||
{
|
||||
id: personaId,
|
||||
displayName: "Researcher",
|
||||
personaCatalogEvents: [
|
||||
createCatalogEvent({
|
||||
ownerPubkey: TEST_IDENTITIES.alice.pubkey,
|
||||
sourcePersonaId: personaId,
|
||||
displayName: "Alice’s Researcher",
|
||||
description,
|
||||
systemPrompt: "Research the question and cite the evidence.",
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
await gotoApp(page);
|
||||
await page.getByTestId("open-agents-view").click();
|
||||
await sharePersonaToCatalog(page, "Researcher");
|
||||
await openPersonaCatalog(page);
|
||||
|
||||
await selectCatalogPersona(page, personaId);
|
||||
const useAgentTarget = page.getByTestId(
|
||||
`community-catalog-use-agent-${personaId}`,
|
||||
const catalogRow = page.getByTestId(
|
||||
`community-catalog-agent-${remoteCatalogId}`,
|
||||
);
|
||||
await expect(catalogRow).toContainText("Alice’s Researcher");
|
||||
const rowDescription = page.getByTestId(
|
||||
`community-catalog-agent-description-${remoteCatalogId}`,
|
||||
);
|
||||
await expect(rowDescription).toHaveText(description);
|
||||
await expect(rowDescription).toHaveCSS("overflow", "hidden");
|
||||
await catalogRow.click();
|
||||
|
||||
const useAgentTarget = page.getByTestId(
|
||||
`community-catalog-use-agent-${remoteCatalogId}`,
|
||||
);
|
||||
const detailDescription = page.getByTestId("persona-catalog-description");
|
||||
|
||||
await expect(page.getByTestId("community-catalog-detail-pane")).toContainText(
|
||||
"Researcher",
|
||||
"Alice’s Researcher",
|
||||
);
|
||||
await expect(page.getByTestId("community-catalog-detail-pane")).toContainText(
|
||||
"Added by You",
|
||||
"Added by alice",
|
||||
);
|
||||
await expect(detailDescription).toHaveText(description);
|
||||
const detailWidth = await detailDescription.evaluate((element) => ({
|
||||
clientWidth: element.clientWidth,
|
||||
scrollWidth: element.scrollWidth,
|
||||
}));
|
||||
expect(detailWidth.scrollWidth).toBeLessThanOrEqual(detailWidth.clientWidth);
|
||||
await expect(page.getByTestId("community-catalog-detail-pane")).toContainText(
|
||||
"Research the question and cite the evidence.",
|
||||
);
|
||||
@@ -851,10 +914,10 @@ test("catalog detail pane shows the full persona details", async ({ page }) => {
|
||||
);
|
||||
await expect(useAgentTarget).toHaveAttribute(
|
||||
"aria-label",
|
||||
"Researcher is already in My Agents",
|
||||
"Add Alice’s Researcher from Community Catalog",
|
||||
);
|
||||
await expect(useAgentTarget).toHaveText("Added to My Agents");
|
||||
await expect(useAgentTarget).toBeDisabled();
|
||||
await expect(useAgentTarget).toHaveText("Add agent");
|
||||
await expect(useAgentTarget).toBeEnabled();
|
||||
});
|
||||
|
||||
type AgentShareCommand = { command: string; payload: unknown };
|
||||
|
||||
@@ -41,7 +41,7 @@ async function waitForMockLiveSubscription(
|
||||
test.describe("channel shared header backdrop", () => {
|
||||
test.use({ viewport: { width: 1280, height: 720 } });
|
||||
|
||||
test("spans channel and split auxiliary columns with one backdrop", async ({
|
||||
test("backs a scrolled split auxiliary header above the shared channel backdrop", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page);
|
||||
@@ -82,6 +82,43 @@ test.describe("channel shared header backdrop", () => {
|
||||
await replyButton.click({ force: true });
|
||||
await expect(page.getByTestId("message-thread-panel")).toBeVisible();
|
||||
|
||||
await page.evaluate(
|
||||
({ channelName, parentEventId, pubkey }) => {
|
||||
for (let index = 0; index < 24; index += 1) {
|
||||
(window as MockMessageWindow).__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({
|
||||
channelName,
|
||||
content: `Scrollable thread reply ${index + 1}. `.repeat(4),
|
||||
parentEventId,
|
||||
pubkey,
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
channelName: CHANNEL_NAME,
|
||||
parentEventId: rootId,
|
||||
pubkey: ALICE_PUBKEY,
|
||||
},
|
||||
);
|
||||
|
||||
const threadBody = page.getByTestId("message-thread-body");
|
||||
await expect
|
||||
.poll(() =>
|
||||
threadBody.evaluate(
|
||||
(element) => element.scrollHeight > element.clientHeight,
|
||||
),
|
||||
)
|
||||
.toBe(true);
|
||||
await threadBody.evaluate((element) => {
|
||||
element.scrollTop = element.scrollHeight;
|
||||
element.dispatchEvent(new Event("scroll"));
|
||||
});
|
||||
await expect
|
||||
.poll(() => threadBody.evaluate((element) => element.scrollTop))
|
||||
.toBeGreaterThan(0);
|
||||
|
||||
const paneBackdrop = page.getByTestId("auxiliary-panel-header-backdrop");
|
||||
await expect(paneBackdrop).toHaveCount(1);
|
||||
|
||||
const sharedBackdrop = page.getByTestId("channel-shared-header-backdrop");
|
||||
await expect(sharedBackdrop).toHaveCount(1);
|
||||
|
||||
@@ -93,6 +130,9 @@ test.describe("channel shared header backdrop", () => {
|
||||
const [
|
||||
hostBox,
|
||||
backdropBox,
|
||||
paneBackdropBox,
|
||||
paneBackdropBackground,
|
||||
paneBackdropFilter,
|
||||
backdropFilter,
|
||||
backdropZIndex,
|
||||
headerZIndex,
|
||||
@@ -101,6 +141,13 @@ test.describe("channel shared header backdrop", () => {
|
||||
] = await Promise.all([
|
||||
page.getByTestId("channel-drop-zone").locator("..").boundingBox(),
|
||||
sharedBackdrop.boundingBox(),
|
||||
paneBackdrop.boundingBox(),
|
||||
paneBackdrop.evaluate(
|
||||
(element) => getComputedStyle(element).backgroundColor,
|
||||
),
|
||||
paneBackdrop.evaluate(
|
||||
(element) => getComputedStyle(element).backdropFilter,
|
||||
),
|
||||
sharedBackdrop.evaluate(
|
||||
(element) => getComputedStyle(element).backdropFilter,
|
||||
),
|
||||
@@ -120,6 +167,13 @@ test.describe("channel shared header backdrop", () => {
|
||||
|
||||
expect(hostBox).not.toBeNull();
|
||||
expect(backdropBox).not.toBeNull();
|
||||
expect(paneBackdropBox).not.toBeNull();
|
||||
expect(Math.round(paneBackdropBox?.y ?? 0)).toBe(
|
||||
Math.round(backdropBox?.y ?? 0),
|
||||
);
|
||||
expect(Math.round(paneBackdropBox?.height ?? 0)).toBe(52);
|
||||
expect(paneBackdropBackground).not.toBe("rgba(0, 0, 0, 0)");
|
||||
expect(paneBackdropFilter).not.toBe("none");
|
||||
expect(Math.round(backdropBox?.x ?? 0)).toBe(Math.round(hostBox?.x ?? 0));
|
||||
expect(Math.round(backdropBox?.width ?? 0)).toBe(
|
||||
Math.round(hostBox?.width ?? 0),
|
||||
|
||||
@@ -1472,10 +1472,9 @@ test("create channel template selector matches the lifecycle controls", async ({
|
||||
await expect(page.getByTestId("create-channel-description")).toHaveValue(
|
||||
"Coordinate a new project from planning through launch.",
|
||||
);
|
||||
await expect(page.getByTestId("create-channel-permissions")).toContainText(
|
||||
"Private",
|
||||
);
|
||||
await page.getByTestId("create-channel-permissions").click();
|
||||
await expect(
|
||||
page.getByTestId("create-channel-permissions-option-private"),
|
||||
).toHaveAttribute("aria-pressed", "true");
|
||||
await page.getByTestId("create-channel-permissions-option-open").click();
|
||||
await expect(page.getByTestId("create-channel-template-summary")).toHaveText(
|
||||
"Open · Canvas included · 1 agent · 1 team",
|
||||
@@ -1498,6 +1497,12 @@ test("create channel exposes templates when the library is empty", async ({
|
||||
const templateContainer = page.getByTestId(
|
||||
"create-channel-template-container",
|
||||
);
|
||||
await expect(
|
||||
page.getByTestId("create-channel-channel-type").getByRole("button"),
|
||||
).toHaveText(["Temporary", "Ongoing"]);
|
||||
await expect(
|
||||
page.getByTestId("create-channel-permissions").getByRole("button"),
|
||||
).toHaveText(["Private", "Public"]);
|
||||
await expect(templateContainer).toContainText("TemplateOptional");
|
||||
const typeBox = await typeContainer.boundingBox();
|
||||
const visibilityBox = await visibilityContainer.boundingBox();
|
||||
@@ -1539,8 +1544,9 @@ test("create ephemeral stream shows sidebar and header affordances", async ({
|
||||
await page
|
||||
.getByTestId("create-channel-description")
|
||||
.fill("Auto-cleaned test stream");
|
||||
await page.getByTestId("create-channel-channel-type").click();
|
||||
await page.getByLabel("Temporary channel").click();
|
||||
await page
|
||||
.getByTestId("create-channel-channel-type-option-temporary")
|
||||
.click();
|
||||
const channelTypeContainer = page.getByTestId(
|
||||
"create-channel-channel-type-container",
|
||||
);
|
||||
@@ -1550,22 +1556,20 @@ test("create ephemeral stream shows sidebar and header affordances", async ({
|
||||
await expect(
|
||||
page.getByTestId("create-channel-permissions-container"),
|
||||
).toBeVisible();
|
||||
await expect(page.getByTestId("create-channel-permissions")).toContainText(
|
||||
"Public",
|
||||
);
|
||||
await page.getByTestId("create-channel-permissions").click();
|
||||
await page.getByTestId("create-channel-permissions-option-private").click();
|
||||
await expect(page.getByTestId("create-channel-permissions")).toContainText(
|
||||
"Private",
|
||||
);
|
||||
await expect(
|
||||
page.getByTestId("create-channel-permissions-option-private"),
|
||||
).toHaveCount(0);
|
||||
await page.getByTestId("create-channel-permissions").click();
|
||||
await expect(
|
||||
page.getByTestId("create-channel-permissions-option-open"),
|
||||
).toBeVisible();
|
||||
).toHaveAttribute("aria-pressed", "true");
|
||||
await page.getByTestId("create-channel-permissions-option-private").click();
|
||||
await expect(
|
||||
page.getByTestId("create-channel-permissions-option-private"),
|
||||
).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(
|
||||
page.getByTestId("create-channel-permissions-option-open"),
|
||||
).toHaveAttribute("aria-pressed", "false");
|
||||
await page.getByTestId("create-channel-permissions-option-open").click();
|
||||
await expect(
|
||||
page.getByTestId("create-channel-permissions-option-open"),
|
||||
).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("create-channel-ttl")).toContainText("7 days");
|
||||
await page.getByTestId("create-channel-ttl").click();
|
||||
await page.getByTestId("create-channel-ttl-option-1209600").click();
|
||||
@@ -1674,8 +1678,9 @@ test("ephemeral countdown refreshes when switching channels after a clock jump",
|
||||
await page
|
||||
.getByTestId("create-channel-description")
|
||||
.fill("Auto-cleaned test stream");
|
||||
await page.getByTestId("create-channel-channel-type").click();
|
||||
await page.getByLabel("Temporary channel").click();
|
||||
await page
|
||||
.getByTestId("create-channel-channel-type-option-temporary")
|
||||
.click();
|
||||
await page.getByTestId("create-channel-submit").click();
|
||||
await expect(page.getByTestId("chat-title")).toContainText(channelName);
|
||||
}
|
||||
|
||||
@@ -248,6 +248,49 @@ test.describe("global agent config screenshots", () => {
|
||||
expect(saved).toMatchObject({ preferred_runtime: "codex" });
|
||||
});
|
||||
|
||||
test("defaults render Databricks model labels without changing persisted ids", async ({
|
||||
page,
|
||||
}) => {
|
||||
const modelId = "data_workflow_tools.goose.goose-glm-5-3";
|
||||
await installMockBridge(page, {
|
||||
globalAgentConfig: {
|
||||
preferred_runtime: "goose",
|
||||
provider: "databricks_v2",
|
||||
model: modelId,
|
||||
env_vars: {},
|
||||
},
|
||||
discoverAgentModels: {
|
||||
models: [{ id: modelId, name: modelId }],
|
||||
supportsSwitching: true,
|
||||
selectedModel: modelId,
|
||||
},
|
||||
runtimeFileConfigs: {
|
||||
goose: {
|
||||
provider: "databricks_v2",
|
||||
model: modelId,
|
||||
satisfiedEnvKeys: ["DATABRICKS_HOST"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await openAiDefaultsSettings(page);
|
||||
|
||||
const model = page.getByTestId("global-agent-model");
|
||||
await expect(model).toHaveText("GLM-5.3");
|
||||
|
||||
const persisted = await page.evaluate(async () =>
|
||||
(
|
||||
window as typeof window & {
|
||||
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
|
||||
command: string,
|
||||
payload: unknown,
|
||||
) => Promise<unknown>;
|
||||
}
|
||||
).__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.("get_global_agent_config", null),
|
||||
);
|
||||
expect(persisted).toMatchObject({ model: modelId });
|
||||
});
|
||||
|
||||
test("defaults honor credentials set in the harness config file", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
@@ -425,10 +425,10 @@ test("duplicate owned agents preserve provenance and exact pubkey selection", as
|
||||
await expect(
|
||||
page.getByTestId(`composer-address-lock-${relayPubkey}`),
|
||||
).toHaveCount(0);
|
||||
await input.fill("local");
|
||||
await input.pressSequentially("local");
|
||||
await page.getByTestId("send-message").click();
|
||||
await expect
|
||||
.poll(() => readOutgoingMentionPubkeys(page, "local"))
|
||||
.poll(() => readOutgoingMentionPubkeys(page, "@carl local"))
|
||||
.toEqual([managedPubkey]);
|
||||
await expect(input).toHaveText("@carl ");
|
||||
|
||||
@@ -448,7 +448,7 @@ test("duplicate owned agents preserve provenance and exact pubkey selection", as
|
||||
await expect(
|
||||
page.getByTestId(`composer-address-lock-${managedPubkey}`),
|
||||
).toHaveCount(0);
|
||||
await input.fill("remote");
|
||||
await input.pressSequentially("remote");
|
||||
await page.getByTestId("send-message").click();
|
||||
const sendWithoutInviting = page.getByRole("button", { name: "Do nothing" });
|
||||
try {
|
||||
@@ -458,7 +458,7 @@ test("duplicate owned agents preserve provenance and exact pubkey selection", as
|
||||
// In-channel selections send immediately without opening the prompt.
|
||||
}
|
||||
await expect
|
||||
.poll(() => readOutgoingMentionPubkeys(page, "remote"))
|
||||
.poll(() => readOutgoingMentionPubkeys(page, "@carl remote"))
|
||||
.toEqual([relayPubkey]);
|
||||
|
||||
await page.getByTestId("channel-members-trigger").click();
|
||||
@@ -1673,30 +1673,60 @@ test("forum sends revalidate relay-agent authorization before signing", async ({
|
||||
.not.toContain(ALLOWLIST_RELAY_AGENT_PUBKEY);
|
||||
});
|
||||
|
||||
test("relay-only allowlisted agents are visible in channel mentions", async ({
|
||||
test("managed agents use the channel roster for membership labels", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, {
|
||||
relayAgents: [
|
||||
managedAgents: [
|
||||
{
|
||||
pubkey: ALLOWLIST_RELAY_AGENT_PUBKEY,
|
||||
name: "quinn",
|
||||
respondTo: "allowlist",
|
||||
respondToAllowlist: [MOCK_VIEWER_PUBKEY],
|
||||
channelNames: ["general"],
|
||||
pubkey: IN_CHANNEL_MANAGED_AGENT_PUBKEY,
|
||||
name: "carl",
|
||||
status: "running",
|
||||
},
|
||||
],
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.getByTestId("channel-general").click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(
|
||||
(channelId) =>
|
||||
window.__BUZZ_E2E_QUERY_CLIENT__?.getQueryState([
|
||||
"channels",
|
||||
channelId,
|
||||
"members",
|
||||
])?.status,
|
||||
GENERAL_CHANNEL_ID,
|
||||
),
|
||||
)
|
||||
.toBe("success");
|
||||
await page.evaluate(
|
||||
async ({ channelId, pubkey }) => {
|
||||
const invoke = window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__;
|
||||
if (!invoke) throw new Error("Mock bridge is not installed.");
|
||||
await invoke("add_channel_members", {
|
||||
channelId,
|
||||
pubkeys: [pubkey],
|
||||
role: "bot",
|
||||
});
|
||||
await window.__BUZZ_E2E_QUERY_CLIENT__?.invalidateQueries({
|
||||
queryKey: ["channels"],
|
||||
exact: true,
|
||||
});
|
||||
},
|
||||
{
|
||||
channelId: GENERAL_CHANNEL_ID,
|
||||
pubkey: IN_CHANNEL_MANAGED_AGENT_PUBKEY,
|
||||
},
|
||||
);
|
||||
|
||||
const input = page.getByTestId("message-input");
|
||||
await input.fill("@quinn");
|
||||
await input.fill("@carl");
|
||||
|
||||
const dropdown = autocomplete(page);
|
||||
await expect(dropdown.getByText("quinn")).toBeVisible();
|
||||
await expect(dropdown.getByText("agent")).toBeVisible();
|
||||
const carlRow = autocomplete(page).locator("button", { hasText: "carl" });
|
||||
await expect(carlRow).toBeVisible();
|
||||
await expect(carlRow.getByText("agent")).toBeVisible();
|
||||
await expect(carlRow.getByText("not in channel")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("relay-agent directory errors fail closed and recover after a fresh fetch", async ({
|
||||
@@ -1734,7 +1764,27 @@ test("relay-agent directory errors fail closed and recover after a fresh fetch",
|
||||
queryKey: ["relay-agents"],
|
||||
});
|
||||
});
|
||||
await expect(autocomplete(page).getByText("quinn")).toHaveCount(0);
|
||||
await expect
|
||||
.poll(async () =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
window.__BUZZ_E2E_QUERY_CLIENT__?.getQueryState(["relay-agents"])
|
||||
?.fetchStatus,
|
||||
),
|
||||
)
|
||||
.toBe("fetching");
|
||||
await expect(autocomplete(page).getByText("quinn")).toBeVisible({
|
||||
timeout: 200,
|
||||
});
|
||||
await expect
|
||||
.poll(async () =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
window.__BUZZ_E2E_QUERY_CLIENT__?.getQueryState(["relay-agents"])
|
||||
?.fetchStatus,
|
||||
),
|
||||
)
|
||||
.toBe("idle");
|
||||
await expect(autocomplete(page).getByText("quinn")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -1917,6 +1967,56 @@ test("targeted revocation before send causes no agent side effects", async ({
|
||||
}
|
||||
});
|
||||
|
||||
test("selected relay agents are invited as bots before sending", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installMockBridge(page, {
|
||||
relayAgents: [
|
||||
{
|
||||
pubkey: ALLOWLIST_RELAY_AGENT_PUBKEY,
|
||||
name: "quinn",
|
||||
respondTo: "allowlist",
|
||||
respondToAllowlist: [MOCK_VIEWER_PUBKEY],
|
||||
channelNames: ["general"],
|
||||
},
|
||||
],
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.getByTestId("channel-general").click();
|
||||
|
||||
const input = page.getByTestId("message-input");
|
||||
await input.fill("@quinn");
|
||||
const quinnRow = autocomplete(page).locator("button", { hasText: "quinn" });
|
||||
await expect(quinnRow).toBeVisible();
|
||||
await expect(quinnRow.getByText("not in channel")).toHaveCount(0);
|
||||
await quinnRow.click();
|
||||
await page.keyboard.type("hello");
|
||||
|
||||
const baselinePayloadCount = (await readCommandPayloadLog(page)).length;
|
||||
await page.getByTestId("send-message").click();
|
||||
const inviteButton = page.getByRole("button", {
|
||||
name: "Invite",
|
||||
exact: true,
|
||||
});
|
||||
await expect(inviteButton).toBeVisible();
|
||||
await inviteButton.click();
|
||||
|
||||
await expect
|
||||
.poll(() => readOutgoingMentionPubkeys(page, "@quinn hello"))
|
||||
.toContain(ALLOWLIST_RELAY_AGENT_PUBKEY);
|
||||
const sendCommands = (await readCommandPayloadLog(page)).slice(
|
||||
baselinePayloadCount,
|
||||
);
|
||||
const addCommand = sendCommands.find(
|
||||
(entry) => entry.command === "add_channel_members",
|
||||
);
|
||||
expect(addCommand?.payload).toMatchObject({
|
||||
channelId: GENERAL_CHANNEL_ID,
|
||||
pubkeys: [ALLOWLIST_RELAY_AGENT_PUBKEY],
|
||||
role: "bot",
|
||||
});
|
||||
});
|
||||
|
||||
test("selected relay agents revoked after the invite prompt cause no side effects", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { installMockBridge } from "../helpers/bridge";
|
||||
|
||||
const SHOTS = "test-results/persistent-agent-audience";
|
||||
const CHANNEL_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
|
||||
const RANDOM_CHANNEL_ID = "9dae0116-799b-5071-a0a8-fdd30a91a35d";
|
||||
const AGENT_A = "a".repeat(64);
|
||||
const AGENT_B = "b".repeat(64);
|
||||
const THREAD_ROOT_ID = "mock-general-welcome";
|
||||
@@ -84,11 +85,11 @@ async function readComposerCaret(input: Locator) {
|
||||
});
|
||||
}
|
||||
|
||||
async function pressPrimaryShift(page: Page, key: "M") {
|
||||
async function pressPrimaryShiftM(page: Page) {
|
||||
const isMac = await page.evaluate(() =>
|
||||
/mac|iphone|ipad|ipod/i.test(navigator.platform),
|
||||
);
|
||||
await page.keyboard.press(`${isMac ? "Meta" : "Control"}+Shift+${key}`);
|
||||
await page.keyboard.press(`${isMac ? "Meta" : "Control"}+Shift+M`);
|
||||
}
|
||||
|
||||
async function readOutgoingMentionPubkeys(page: Page, content: string) {
|
||||
@@ -156,6 +157,7 @@ async function emitMockMessage(
|
||||
async function installAudienceFixtures(
|
||||
page: Page,
|
||||
options: {
|
||||
agentAName?: string;
|
||||
deferredComposerUploads?: boolean;
|
||||
sendMessageDelayMs?: number;
|
||||
sendMessageErrors?: string[];
|
||||
@@ -171,12 +173,13 @@ async function installAudienceFixtures(
|
||||
usersBatchDelayMs?: number;
|
||||
} = {},
|
||||
) {
|
||||
const { agentAName = "Morgarita", ...bridgeOptions } = options;
|
||||
await installMockBridge(page, {
|
||||
...options,
|
||||
...bridgeOptions,
|
||||
managedAgents: [
|
||||
{
|
||||
pubkey: AGENT_A,
|
||||
name: "Morgarita",
|
||||
name: agentAName,
|
||||
status: "running",
|
||||
channelNames: ["general"],
|
||||
},
|
||||
@@ -285,6 +288,104 @@ test("automatically mentions multiple agents from the mention picker", async ({
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("keeps the composer and global automatic mention settings synchronized", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
|
||||
const composer = channelComposer(page);
|
||||
await composer.getByTestId("message-insert-mention").click();
|
||||
const optionsTrigger = composer.getByTestId("mention-options-trigger");
|
||||
await expect(optionsTrigger).toHaveAttribute("aria-expanded", "false");
|
||||
await composer
|
||||
.getByTestId("mention-autocomplete")
|
||||
.getByRole("button", { name: "Automatically mention Morgarita" })
|
||||
.click();
|
||||
await expect(optionsTrigger).toHaveAttribute("aria-expanded", "true");
|
||||
const composerToggle = composer.getByTestId(
|
||||
"mention-keep-agents-pinned-toggle",
|
||||
);
|
||||
await expect(composerToggle).toHaveAttribute("data-state", "unchecked");
|
||||
await expect(composerToggle).toHaveAttribute("data-state", "checked", {
|
||||
timeout: 1_500,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByTestId("composer-auto-pin-confirmation")
|
||||
.getByRole("button", { name: "Turn off" })
|
||||
.click();
|
||||
await expect(composer.getByTestId("mention-autocomplete")).toBeVisible();
|
||||
await expect(optionsTrigger).toHaveAttribute("aria-expanded", "true");
|
||||
await expect(composerToggle).toHaveAttribute("data-state", "checked");
|
||||
await expect(composerToggle).toHaveAttribute("data-state", "unchecked", {
|
||||
timeout: 1_500,
|
||||
});
|
||||
|
||||
await page.getByTestId("open-settings").click();
|
||||
await page.getByTestId("profile-popover-settings").click();
|
||||
await expect(page.getByTestId("settings-view")).toBeVisible();
|
||||
await page.getByTestId("settings-nav-agents").click();
|
||||
const settingsToggle = page
|
||||
.getByTestId("settings-automatic-agent-mentions")
|
||||
.getByRole("switch", { name: "Automatically mention agents" });
|
||||
await expect(settingsToggle).toHaveAttribute("data-state", "unchecked");
|
||||
|
||||
await page.getByTestId("settings-back-to-app").click();
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
await composer.getByTestId("message-insert-mention").click();
|
||||
await composer.getByTestId("mention-options-trigger").click();
|
||||
await expect(
|
||||
composer.getByTestId("mention-keep-agents-pinned-toggle"),
|
||||
).toHaveAttribute("data-state", "unchecked");
|
||||
await expect(
|
||||
composer.getByRole("button", {
|
||||
name: "Automatically mention Morgarita",
|
||||
}),
|
||||
).toHaveAttribute("aria-pressed", "false");
|
||||
});
|
||||
|
||||
test("hides automatic mention state while disabled without clearing the draft", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
await automaticallyMention(composer, "Morgarita");
|
||||
await input.type("draft text");
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByTestId("channel-management-trigger").click();
|
||||
await expect(page.getByTestId("channel-management-sheet")).toBeVisible();
|
||||
await page.getByTestId("channel-management-archive").click();
|
||||
await expect(page.getByTestId("channel-management-unarchive")).toBeVisible();
|
||||
await page.getByTestId("auxiliary-panel-close").click();
|
||||
|
||||
await expect(input).toHaveAttribute("contenteditable", "false");
|
||||
await expect(input).toHaveText("@Morgarita draft text");
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
|
||||
await page.getByTestId("channel-management-trigger").click();
|
||||
await expect(page.getByTestId("channel-management-sheet")).toBeVisible();
|
||||
await page.getByTestId("channel-management-unarchive").click();
|
||||
await expect(page.getByTestId("channel-management-archive")).toBeVisible();
|
||||
await page.getByTestId("auxiliary-panel-close").click();
|
||||
|
||||
await expect(input).toHaveAttribute("contenteditable", "true");
|
||||
await expect(input).toHaveText("@Morgarita draft text");
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("Tab inserts a one-time agent mention by default", async ({ page }) => {
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
@@ -346,7 +447,7 @@ test("disabling automatic mentions leaves the composer empty after send", async
|
||||
.toContain(AGENT_A);
|
||||
});
|
||||
|
||||
test("primary+Shift+M addresses the default agent, then selects the highlighted agent", async ({
|
||||
test("primary+Shift+M addresses the default agent, then toggles the highlighted agent in place", async ({
|
||||
page,
|
||||
}) => {
|
||||
await keepMentionedAgentsPinned(page);
|
||||
@@ -356,21 +457,21 @@ test("primary+Shift+M addresses the default agent, then selects the highlighted
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
await input.fill("draft text");
|
||||
await pressPrimaryShift(page, "M");
|
||||
await pressPrimaryShiftM(page);
|
||||
|
||||
await expect(input).toHaveText("@alice draft text");
|
||||
await expect(input.locator(".agent-mention-highlight")).toHaveText("alice");
|
||||
await expect(
|
||||
page.getByTestId("composer-auto-pin-confirmation"),
|
||||
).toContainText("alice will be mentioned automatically");
|
||||
await pressPrimaryShift(page, "M");
|
||||
await pressPrimaryShiftM(page);
|
||||
await expect(input).toHaveText("draft text");
|
||||
await pressPrimaryShift(page, "M");
|
||||
await pressPrimaryShiftM(page);
|
||||
await expect(input).toHaveText("@alice draft text");
|
||||
await expect(
|
||||
composer
|
||||
.getByTestId("composer-address-locks")
|
||||
.getByRole("button", { name: /^Stop automatically mentioning / }),
|
||||
.getByRole("button", { name: /^Don't automatically mention / }),
|
||||
).toHaveCount(1);
|
||||
|
||||
await input.fill("@Vog");
|
||||
@@ -378,9 +479,9 @@ test("primary+Shift+M addresses the default agent, then selects the highlighted
|
||||
await expect(menu.getByTestId(`mention-suggestion-${AGENT_B}`)).toHaveClass(
|
||||
/(?:^|\s)bg-accent(?:\s|$)/,
|
||||
);
|
||||
await pressPrimaryShift(page, "M");
|
||||
await pressPrimaryShiftM(page);
|
||||
|
||||
await expect(menu).toHaveCount(0);
|
||||
await expect(menu).toBeVisible();
|
||||
await expect(input).toHaveText("@Vogue ");
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_B}`),
|
||||
@@ -391,7 +492,7 @@ test("primary+Shift+M addresses the default agent, then selects the highlighted
|
||||
await expect(
|
||||
composer
|
||||
.getByTestId("composer-address-locks")
|
||||
.getByRole("button", { name: /^Stop automatically mentioning / }),
|
||||
.getByRole("button", { name: /^Don't automatically mention / }),
|
||||
).toHaveCount(1);
|
||||
});
|
||||
|
||||
@@ -407,13 +508,13 @@ test("primary+Shift+M favors the most recently mentioned eligible agent", async
|
||||
await input.press("ArrowLeft");
|
||||
await input.press("ArrowLeft");
|
||||
await expect.poll(() => readComposerCaret(input)).toBe(8);
|
||||
await pressPrimaryShift(page, "M");
|
||||
await pressPrimaryShiftM(page);
|
||||
|
||||
await expect(input).toHaveText("@Vogue draft text");
|
||||
await expect.poll(() => readComposerCaret(input)).toBe(15);
|
||||
await pressPrimaryShift(page, "M");
|
||||
await pressPrimaryShiftM(page);
|
||||
await expect(input).toHaveText("draft text");
|
||||
await pressPrimaryShift(page, "M");
|
||||
await pressPrimaryShiftM(page);
|
||||
await expect(input).toHaveText("@Vogue draft text");
|
||||
});
|
||||
|
||||
@@ -468,12 +569,14 @@ test("the mention button opens settings and can undo an address", async ({
|
||||
await expect(page.getByTestId("user-profile-panel")).toHaveCount(0);
|
||||
await expect(
|
||||
menu.getByRole("button", {
|
||||
name: "Stop automatically mentioning Morgarita",
|
||||
name: "Don't automatically mention Morgarita in this conversation",
|
||||
}),
|
||||
).toHaveAttribute("aria-pressed", "true");
|
||||
|
||||
await menu
|
||||
.getByRole("button", { name: "Stop automatically mentioning Morgarita" })
|
||||
.getByRole("button", {
|
||||
name: "Don't automatically mention Morgarita in this conversation",
|
||||
})
|
||||
.click();
|
||||
await expect(input).toHaveText("draft text");
|
||||
await expect(
|
||||
@@ -587,6 +690,188 @@ test("always-mentioned agents remain selected without replaying their animation
|
||||
).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("the unfocused main composer keeps its dismissed mention menu closed through a thread send", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installAudienceFixtures(page, { sendMessageDelayMs: 1_500 });
|
||||
await openGeneral(page);
|
||||
|
||||
const mainComposer = channelComposer(page);
|
||||
const mainInput = mainComposer.getByTestId("message-input");
|
||||
await automaticallyMention(mainComposer, "Morgarita");
|
||||
await mainInput.fill("@Morgarita earlier message");
|
||||
await mainInput.press("Enter");
|
||||
await expect(mainInput).toHaveText("@Morgarita ");
|
||||
await mainInput.fill("@Morgarita");
|
||||
await expect(mainInput).toHaveText("@Morgarita");
|
||||
await expect(mainComposer.getByTestId("mention-autocomplete")).toBeVisible();
|
||||
await mainInput.press("Escape");
|
||||
await expect(mainComposer.getByTestId("mention-autocomplete")).toHaveCount(0);
|
||||
|
||||
const rootMessage = page.locator(
|
||||
`[data-testid="message-row"][data-message-id="${THREAD_ROOT_ID}"]`,
|
||||
);
|
||||
await rootMessage
|
||||
.getByRole("button", { name: "Reply" })
|
||||
.evaluate((button: HTMLButtonElement) => button.click());
|
||||
|
||||
const threadPanel = page.getByTestId("message-thread-panel");
|
||||
await expect(threadPanel).toBeVisible();
|
||||
const threadInput = threadPanel.getByTestId("message-input");
|
||||
await threadInput.click();
|
||||
await expect(threadInput).toBeFocused();
|
||||
await expect(mainComposer.getByTestId("mention-autocomplete")).toHaveCount(0);
|
||||
await mainComposer.evaluate((element) => {
|
||||
element.dataset.mentionMenuReopened = "false";
|
||||
new MutationObserver(() => {
|
||||
if (element.querySelector('[data-testid="mention-autocomplete"]')) {
|
||||
element.dataset.mentionMenuReopened = "true";
|
||||
}
|
||||
}).observe(element, { childList: true, subtree: true });
|
||||
});
|
||||
|
||||
const reply = `Thread reply ${Date.now()}`;
|
||||
await threadInput.fill(reply);
|
||||
await threadInput.press("Enter");
|
||||
await expect(mainInput).toHaveAttribute("contenteditable", "false");
|
||||
await expect(threadPanel).toContainText(reply);
|
||||
await expect(mainInput).toHaveAttribute("contenteditable", "true");
|
||||
|
||||
expect(await mainComposer.getAttribute("data-mention-menu-reopened")).toBe(
|
||||
"false",
|
||||
);
|
||||
await expect(mainComposer.getByTestId("mention-autocomplete")).toHaveCount(0);
|
||||
|
||||
// Refocusing the drafted composer must not resurrect the menu the user
|
||||
// dismissed with Escape: the setEditable toggles around the send used to
|
||||
// emit a phantom update that replayed the stale "@Morgarita" query and
|
||||
// flipped the mention state back open, so a bare click brought the menu
|
||||
// back without any typing.
|
||||
await mainInput.click();
|
||||
await expect(mainInput).toBeFocused();
|
||||
await expect(mainComposer.getByTestId("mention-autocomplete")).toHaveCount(0);
|
||||
expect(await mainComposer.getAttribute("data-mention-menu-reopened")).toBe(
|
||||
"false",
|
||||
);
|
||||
});
|
||||
|
||||
test("pressing a mention overlay's own container keeps it open", async ({
|
||||
page,
|
||||
}) => {
|
||||
await keepMentionedAgentsPinned(page);
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
await composer.getByTestId("message-insert-mention").click();
|
||||
const list = composer.getByTestId("mention-autocomplete");
|
||||
await expect(list).toBeVisible();
|
||||
await expect(input).toBeFocused();
|
||||
|
||||
// A mousedown landing on the list container itself — its padding ring here,
|
||||
// a native scrollbar on platforms that render one — steals focus from the
|
||||
// editor unless the default is prevented, and the focus gate would then
|
||||
// unmount the menu mid-press.
|
||||
const listBox = await list.boundingBox();
|
||||
if (!listBox) throw new Error("mention list is not laid out");
|
||||
await list.click({ position: { x: 2, y: listBox.height / 2 } });
|
||||
await expect(input).toBeFocused();
|
||||
await expect(list).toBeVisible();
|
||||
|
||||
// Same hazard on the options surface, where it needs no exotic scrollbar
|
||||
// setting to reproduce: the switch's label text is a container press, so the
|
||||
// overlay used to vanish before the forwarded click reached the switch.
|
||||
await composer.getByTestId("mention-options-trigger").click();
|
||||
const preference = composer.getByTestId("mention-keep-agents-pinned-toggle");
|
||||
await expect(preference).toHaveAttribute("data-state", "checked");
|
||||
await composer
|
||||
.getByText("Automatically mention agents", { exact: true })
|
||||
.click();
|
||||
await expect(preference).toHaveAttribute("data-state", "unchecked");
|
||||
await expect(input).toBeFocused();
|
||||
await expect(list).toBeVisible();
|
||||
});
|
||||
|
||||
test("the mention Options controls are reachable and operable by keyboard", async ({
|
||||
page,
|
||||
}) => {
|
||||
await keepMentionedAgentsPinned(page);
|
||||
await installAudienceFixtures(page);
|
||||
await openThread(page);
|
||||
|
||||
const mainComposer = channelComposer(page);
|
||||
const mainInput = mainComposer.getByTestId("message-input");
|
||||
await mainInput.click();
|
||||
await mainInput.fill("@Mor");
|
||||
const list = mainComposer.getByTestId("mention-autocomplete");
|
||||
await expect(list).toBeVisible();
|
||||
await expect(mainInput).toBeFocused();
|
||||
|
||||
// Trip a flag if the overlay ever unmounts from here on — "operable while
|
||||
// the surface stays mounted" has to hold through every focus handoff below,
|
||||
// not just at the polled assertion boundaries.
|
||||
await mainComposer.evaluate((element) => {
|
||||
element.dataset.mentionMenuUnmounted = "false";
|
||||
new MutationObserver(() => {
|
||||
if (!element.querySelector('[data-testid="mention-autocomplete"]')) {
|
||||
element.dataset.mentionMenuUnmounted = "true";
|
||||
}
|
||||
}).observe(element, { childList: true, subtree: true });
|
||||
});
|
||||
|
||||
// Forward Tab still selects the highlighted suggestion, so Shift+Tab is the
|
||||
// route into the overlay. It only reaches the Options controls if the focus
|
||||
// gate treats them as composer-owned focus rather than unmounting on the
|
||||
// editor's blur.
|
||||
await mainInput.press("Shift+Tab");
|
||||
const optionsTrigger = mainComposer.getByTestId("mention-options-trigger");
|
||||
await expect(optionsTrigger).toBeFocused();
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
const preference = mainComposer.getByTestId(
|
||||
"mention-keep-agents-pinned-toggle",
|
||||
);
|
||||
await expect(preference).toBeVisible();
|
||||
await expect(preference).toHaveAttribute("data-state", "checked");
|
||||
|
||||
// The switch sits before its trigger in the expanded surface's tab order.
|
||||
await page.keyboard.press("Shift+Tab");
|
||||
await expect(preference).toBeFocused();
|
||||
await page.keyboard.press("Space");
|
||||
await expect(preference).toHaveAttribute("data-state", "unchecked");
|
||||
|
||||
await expect(list).toBeVisible();
|
||||
expect(await mainComposer.getAttribute("data-mention-menu-unmounted")).toBe(
|
||||
"false",
|
||||
);
|
||||
|
||||
// Escape is the way back out: focus returns to the editor and the menu
|
||||
// closes, rather than stranding focus on a control that just unmounted.
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(mainInput).toBeFocused();
|
||||
await expect(list).toHaveCount(0);
|
||||
|
||||
// Forward Tab is unchanged by the Shift+Tab route.
|
||||
await mainInput.fill("@Morg");
|
||||
await expect(list).toBeVisible();
|
||||
await mainInput.press("Tab");
|
||||
await expect(mainInput).toHaveText("@Morgarita ");
|
||||
await expect(list).toHaveCount(0);
|
||||
|
||||
// Ownership is still per-composer: focus moving to a sibling composer hides
|
||||
// this composer's menu, which is what stops a background composer from
|
||||
// resurrecting a stale one. Programmatic focus, not a click — a pointerdown
|
||||
// would dismiss the menu through its outside-press handler and mask the gate
|
||||
// under test.
|
||||
await mainInput.fill("@Mor");
|
||||
await expect(list).toBeVisible();
|
||||
const threadInput = threadComposer(page).getByTestId("message-input");
|
||||
await threadInput.focus();
|
||||
await expect(threadInput).toBeFocused();
|
||||
await expect(list).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("a failed always-mentioned send shakes the composer avatar without replaying its selection animation", async ({
|
||||
page,
|
||||
}) => {
|
||||
@@ -724,44 +1009,513 @@ test("the auto-pin popover can turn off automatic agent mentions", async ({
|
||||
);
|
||||
await autoPinConfirmation.getByRole("button", { name: "Turn off" }).click();
|
||||
|
||||
await expect(composer.getByTestId("mention-autocomplete")).toBeVisible();
|
||||
await expect(composer.getByTestId("mention-options-trigger")).toHaveAttribute(
|
||||
"aria-expanded",
|
||||
"true",
|
||||
);
|
||||
await expect(
|
||||
composer.getByTestId("mention-keep-agents-pinned-toggle"),
|
||||
).toHaveAttribute("data-state", "unchecked");
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
await expect(autoPinConfirmation).toHaveCount(0);
|
||||
|
||||
await composer.getByTestId("message-insert-mention").click();
|
||||
await expect(composer.getByTestId("mention-autocomplete")).toBeVisible();
|
||||
await composer.getByTestId("mention-options-trigger").click();
|
||||
await expect(
|
||||
composer.getByTestId("mention-keep-agents-pinned-toggle"),
|
||||
).toHaveAttribute("data-state", "unchecked");
|
||||
|
||||
await input.press("Escape");
|
||||
await expect(composer.getByTestId("mention-autocomplete")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("channel automatic mentions carry into threads and stay synchronized", async ({
|
||||
test("the auto-pin popover remains open while hovered", async ({ page }) => {
|
||||
await keepMentionedAgentsPinned(page);
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
await input.fill("@Mor");
|
||||
await expect(composer.getByTestId("mention-autocomplete")).toBeVisible();
|
||||
await input.press("Tab");
|
||||
|
||||
const autoPinConfirmation = page.getByTestId(
|
||||
"composer-auto-pin-confirmation",
|
||||
);
|
||||
await autoPinConfirmation.hover();
|
||||
await page.waitForTimeout(4_250);
|
||||
await expect(autoPinConfirmation).toBeVisible();
|
||||
|
||||
await input.press("Escape");
|
||||
await expect(autoPinConfirmation).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("removing the mention chip dismisses the auto-pin popover", async ({
|
||||
page,
|
||||
}) => {
|
||||
await keepMentionedAgentsPinned(page);
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
await input.fill("@Mor");
|
||||
await expect(composer.getByTestId("mention-autocomplete")).toBeVisible();
|
||||
await input.press("Tab");
|
||||
|
||||
const autoPinConfirmation = page.getByTestId(
|
||||
"composer-auto-pin-confirmation",
|
||||
);
|
||||
await expect(autoPinConfirmation).toBeVisible();
|
||||
|
||||
const selectAllShortcut = await page.evaluate(() =>
|
||||
/mac|iphone|ipad|ipod/i.test(navigator.platform) ? "Meta+A" : "Control+A",
|
||||
);
|
||||
await input.press(selectAllShortcut);
|
||||
await input.press("Backspace");
|
||||
|
||||
await expect(input).toHaveText("");
|
||||
await expect(autoPinConfirmation).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("automatic mentions are scoped to their channel or thread composer", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
await automaticallyMention(channelComposer(page), "Morgarita");
|
||||
await expect(
|
||||
channelComposer(page).getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toBeVisible();
|
||||
|
||||
await openThread(page);
|
||||
const channelAutomaticMention = channelComposer(page).getByTestId(
|
||||
`composer-address-lock-${AGENT_A}`,
|
||||
);
|
||||
const threadAutomaticMention = threadComposer(page).getByTestId(
|
||||
`composer-address-lock-${AGENT_A}`,
|
||||
);
|
||||
await expect(channelAutomaticMention).toBeVisible();
|
||||
await expect(threadAutomaticMention).toBeVisible();
|
||||
await expect(
|
||||
threadComposer(page).getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
|
||||
await threadComposer(page)
|
||||
await automaticallyMention(threadComposer(page), "Vogue");
|
||||
await openGeneral(page);
|
||||
await expect(
|
||||
channelComposer(page).getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
channelComposer(page).getByTestId(`composer-address-lock-${AGENT_B}`),
|
||||
).toHaveCount(0);
|
||||
|
||||
await openThread(page);
|
||||
await expect(
|
||||
threadComposer(page).getByTestId(`composer-address-lock-${AGENT_B}`),
|
||||
).toBeVisible();
|
||||
|
||||
await openThread(page, "mock-general-alice");
|
||||
await expect(
|
||||
threadComposer(page).getByTestId(`composer-address-lock-${AGENT_B}`),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("a thread automatic mention preserves an explicitly unpinned root agent", async ({
|
||||
page,
|
||||
}) => {
|
||||
await keepMentionedAgentsPinned(page);
|
||||
await installAudienceFixtures(page, { agentAName: "claude code" });
|
||||
await openGeneral(page);
|
||||
|
||||
const rootComposer = channelComposer(page);
|
||||
const rootInput = rootComposer.getByTestId("message-input");
|
||||
await automaticallyMention(rootComposer, "claude code");
|
||||
await expect(
|
||||
rootComposer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toBeVisible();
|
||||
await rootComposer
|
||||
.getByTestId(`composer-address-lock-remove-${AGENT_A}`)
|
||||
.click();
|
||||
await expect(threadAutomaticMention).toHaveCount(0);
|
||||
await expect(channelAutomaticMention).toHaveCount(0);
|
||||
await expect(rootInput).toHaveText("");
|
||||
await expect(
|
||||
rootComposer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
|
||||
await rootInput.fill("@cla");
|
||||
await expect(rootComposer.getByTestId("mention-autocomplete")).toBeVisible();
|
||||
await rootInput.press("Tab");
|
||||
await rootInput.type("one time");
|
||||
await rootInput.press("Enter");
|
||||
await expect(rootInput).toHaveText("");
|
||||
await expect(
|
||||
rootComposer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
|
||||
await openThread(page);
|
||||
const activeThreadComposer = threadComposer(page);
|
||||
const threadInput = activeThreadComposer.getByTestId("message-input");
|
||||
await threadInput.fill("@cla");
|
||||
await expect(
|
||||
activeThreadComposer.getByTestId("mention-autocomplete"),
|
||||
).toBeVisible();
|
||||
await threadInput.press("Tab");
|
||||
await expect(
|
||||
activeThreadComposer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toBeVisible();
|
||||
await threadInput.type("thread message");
|
||||
await threadInput.press("Enter");
|
||||
|
||||
await openGeneral(page);
|
||||
await expect(
|
||||
channelComposer(page).getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
|
||||
const restoredRootInput = channelComposer(page).getByTestId("message-input");
|
||||
await restoredRootInput.fill("@cla");
|
||||
await expect(
|
||||
channelComposer(page).getByTestId("mention-autocomplete"),
|
||||
).toBeVisible();
|
||||
await restoredRootInput.press("Tab");
|
||||
await expect(
|
||||
channelComposer(page).getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
await restoredRootInput.type("one time");
|
||||
await restoredRootInput.press("Enter");
|
||||
|
||||
await expect(restoredRootInput).toHaveText("");
|
||||
await expect(
|
||||
channelComposer(page).getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("an unchecked agent remains excluded while automatic mentions stay enabled", async ({
|
||||
page,
|
||||
}) => {
|
||||
await keepMentionedAgentsPinned(page);
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
await automaticallyMention(channelComposer(page), "Morgarita");
|
||||
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
await composer.getByTestId(`composer-address-lock-remove-${AGENT_A}`).click();
|
||||
await expect(input).toHaveText("");
|
||||
await input.fill("@Mor");
|
||||
await expect(composer.getByTestId("mention-autocomplete")).toBeVisible();
|
||||
await input.press("Tab");
|
||||
await input.type("one time");
|
||||
await input.press("Enter");
|
||||
|
||||
await expect(input).toHaveText("");
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("re-adding a deleted automatic mention restores its automatic mention state immediately", async ({
|
||||
page,
|
||||
}) => {
|
||||
await keepMentionedAgentsPinned(page);
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
await automaticallyMention(composer, "Morgarita");
|
||||
|
||||
const selectAllShortcut = await page.evaluate(() =>
|
||||
/mac|iphone|ipad|ipod/i.test(navigator.platform) ? "Meta+A" : "Control+A",
|
||||
);
|
||||
await input.press(selectAllShortcut);
|
||||
await input.press("Backspace");
|
||||
|
||||
await expect(input).toHaveText("");
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
|
||||
await input.fill("@Mor");
|
||||
await expect(composer.getByTestId("mention-autocomplete")).toBeVisible();
|
||||
await input.press("Tab");
|
||||
await expect(input).toHaveText("@Morgarita ");
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toBeVisible();
|
||||
|
||||
await input.type("re-added");
|
||||
await input.press("Enter");
|
||||
|
||||
await expect(input).toHaveText("@Morgarita ");
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("implicit automatic mentions stay out of persisted drafts", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
await automaticallyMention(channelComposer(page), "Morgarita");
|
||||
const input = channelComposer(page).getByTestId("message-input");
|
||||
await input.type("draft text");
|
||||
|
||||
await openThread(page);
|
||||
await openGeneral(page);
|
||||
|
||||
await expect(input).toHaveText("@Morgarita draft text");
|
||||
await expect(input.locator(".agent-mention-highlight")).toHaveCount(1);
|
||||
await input.pressSequentially(" continues");
|
||||
await expect(input).toHaveText("@Morgarita draft text continues");
|
||||
await expect(input.locator(".agent-mention-highlight")).toHaveCount(1);
|
||||
|
||||
await page.goto(`/#/channels/${RANDOM_CHANNEL_ID}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
||||
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate((channelId) => {
|
||||
for (const storageKey of Object.keys(window.localStorage)) {
|
||||
if (!storageKey.startsWith("buzz-drafts.v2:")) continue;
|
||||
const drafts = JSON.parse(
|
||||
window.localStorage.getItem(storageKey) ?? "{}",
|
||||
) as Record<string, { channelId?: string; content?: string }>;
|
||||
const draft = drafts[channelId];
|
||||
if (draft?.channelId === channelId) return draft.content ?? "";
|
||||
}
|
||||
return "";
|
||||
}, CHANNEL_ID),
|
||||
)
|
||||
.toBe("draft text continues");
|
||||
});
|
||||
|
||||
test("an authored duplicate leading mention survives draft restoration", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
await automaticallyMention(channelComposer(page), "Morgarita");
|
||||
const input = channelComposer(page).getByTestId("message-input");
|
||||
await input.pressSequentially("@Morgarita authored duplicate");
|
||||
|
||||
await openThread(page);
|
||||
await openGeneral(page);
|
||||
|
||||
await expect(input).toHaveText("@Morgarita @Morgarita authored duplicate");
|
||||
// Exact typed mentions now resolve on Space, so both the automatic prefix and
|
||||
// the authored duplicate retain mention identity after restoration.
|
||||
await expect(input.locator(".agent-mention-highlight")).toHaveCount(2);
|
||||
|
||||
await page.goto(`/#/channels/${RANDOM_CHANNEL_ID}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate((channelId) => {
|
||||
for (const storageKey of Object.keys(window.localStorage)) {
|
||||
if (!storageKey.startsWith("buzz-drafts.v2:")) continue;
|
||||
const drafts = JSON.parse(
|
||||
window.localStorage.getItem(storageKey) ?? "{}",
|
||||
) as Record<string, { channelId?: string; content?: string }>;
|
||||
const draft = drafts[channelId];
|
||||
if (draft?.channelId === channelId) return draft.content ?? "";
|
||||
}
|
||||
return "";
|
||||
}, CHANNEL_ID),
|
||||
)
|
||||
.toBe("@Morgarita authored duplicate");
|
||||
});
|
||||
|
||||
test("typed deletion preserves an identical authored mention in drafts", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
await automaticallyMention(composer, "Morgarita");
|
||||
|
||||
const selectAllShortcut = await page.evaluate(() =>
|
||||
/mac|iphone|ipad|ipod/i.test(navigator.platform) ? "Meta+A" : "Control+A",
|
||||
);
|
||||
await input.press(selectAllShortcut);
|
||||
await input.press("Backspace");
|
||||
await expect(input).toHaveText("");
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toHaveCount(0);
|
||||
|
||||
await input.pressSequentially("@Morgarita manual after typed deletion");
|
||||
await page.goto(`/#/channels/${RANDOM_CHANNEL_ID}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate((channelId) => {
|
||||
for (const storageKey of Object.keys(window.localStorage)) {
|
||||
if (!storageKey.startsWith("buzz-drafts.v2:")) continue;
|
||||
const draft = (
|
||||
JSON.parse(
|
||||
window.localStorage.getItem(storageKey) ?? "{}",
|
||||
) as Record<string, { content?: string }>
|
||||
)[channelId];
|
||||
if (draft) return draft.content ?? "";
|
||||
}
|
||||
return "";
|
||||
}, CHANNEL_ID),
|
||||
)
|
||||
.toBe("@Morgarita manual after typed deletion");
|
||||
});
|
||||
|
||||
test("removing an automatic mention preserves an identical authored mention in drafts", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
|
||||
await automaticallyMention(composer, "Morgarita");
|
||||
await composer.getByTestId(`composer-address-lock-remove-${AGENT_A}`).click();
|
||||
await input.pressSequentially("@Morgarita manual after removal");
|
||||
await page.goto(`/#/channels/${RANDOM_CHANNEL_ID}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate((channelId) => {
|
||||
for (const storageKey of Object.keys(window.localStorage)) {
|
||||
if (!storageKey.startsWith("buzz-drafts.v2:")) continue;
|
||||
const draft = (
|
||||
JSON.parse(
|
||||
window.localStorage.getItem(storageKey) ?? "{}",
|
||||
) as Record<string, { content?: string }>
|
||||
)[channelId];
|
||||
if (draft) return draft.content ?? "";
|
||||
}
|
||||
return "";
|
||||
}, CHANNEL_ID),
|
||||
)
|
||||
.toBe("@Morgarita manual after removal");
|
||||
});
|
||||
|
||||
test("multiple automatic mentions stay out of persisted drafts", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
await automaticallyMention(composer, "Morgarita");
|
||||
await automaticallyMention(composer, "Vogue");
|
||||
await input.pressSequentially("draft text");
|
||||
|
||||
await openThread(page);
|
||||
await openGeneral(page);
|
||||
await expect(input).toHaveText("@Vogue @Morgarita draft text");
|
||||
await expect(input.locator(".agent-mention-highlight")).toHaveCount(2);
|
||||
await page.goto(`/#/channels/${RANDOM_CHANNEL_ID}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate((channelId) => {
|
||||
for (const storageKey of Object.keys(window.localStorage)) {
|
||||
if (!storageKey.startsWith("buzz-drafts.v2:")) continue;
|
||||
const draft = (
|
||||
JSON.parse(
|
||||
window.localStorage.getItem(storageKey) ?? "{}",
|
||||
) as Record<string, { content?: string }>
|
||||
)[channelId];
|
||||
if (draft) return draft.content ?? "";
|
||||
}
|
||||
return "";
|
||||
}, CHANNEL_ID),
|
||||
)
|
||||
.toBe("draft text");
|
||||
});
|
||||
|
||||
test("re-enabling an automatic mention preserves an authored duplicate after draft restoration", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installAudienceFixtures(page);
|
||||
await openGeneral(page);
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
|
||||
await automaticallyMention(composer, "Morgarita");
|
||||
await composer.getByTestId(`composer-address-lock-remove-${AGENT_A}`).click();
|
||||
await expect(input).toHaveText("");
|
||||
|
||||
await automaticallyMention(composer, "Morgarita");
|
||||
await input.pressSequentially("@Morgarita authored duplicate");
|
||||
await openThread(page);
|
||||
await openGeneral(page);
|
||||
|
||||
await expect(input).toHaveText("@Morgarita @Morgarita authored duplicate");
|
||||
await expect(input.locator(".agent-mention-highlight")).toHaveCount(2);
|
||||
|
||||
await page.goto(`/#/channels/${RANDOM_CHANNEL_ID}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate((channelId) => {
|
||||
for (const storageKey of Object.keys(window.localStorage)) {
|
||||
if (!storageKey.startsWith("buzz-drafts.v2:")) continue;
|
||||
const drafts = JSON.parse(
|
||||
window.localStorage.getItem(storageKey) ?? "{}",
|
||||
) as Record<string, { channelId?: string; content?: string }>;
|
||||
const draft = drafts[channelId];
|
||||
if (draft?.channelId === channelId) return draft.content ?? "";
|
||||
}
|
||||
return "";
|
||||
}, CHANNEL_ID),
|
||||
)
|
||||
.toBe("@Morgarita authored duplicate");
|
||||
});
|
||||
|
||||
test("a restored multi-word automatic mention remains a chip with the caret after its space", async ({
|
||||
page,
|
||||
}) => {
|
||||
await installAudienceFixtures(page, { agentAName: "claude code" });
|
||||
await openGeneral(page);
|
||||
const originalComposer = channelComposer(page);
|
||||
await automaticallyMention(originalComposer, "claude code");
|
||||
const originalInput = originalComposer.getByTestId("message-input");
|
||||
await originalInput.pressSequentially("hello");
|
||||
await originalInput.press("Enter");
|
||||
await expect
|
||||
.poll(() => readOutgoingMentionPubkeys(page, "@claude code hello"))
|
||||
.toContain(AGENT_A);
|
||||
await expect(originalInput).toHaveText("@claude code ");
|
||||
|
||||
await page.goto(`/#/channels/${RANDOM_CHANNEL_ID}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
||||
await openGeneral(page);
|
||||
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
const expectedContent = "@claude code ";
|
||||
await expect(input).toHaveText(expectedContent);
|
||||
await expect(input.locator(".agent-mention-highlight")).toHaveCount(1);
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
composer.getByRole("button", { name: "Manage automatic agent mentions" }),
|
||||
).toBeVisible();
|
||||
await page.waitForTimeout(500);
|
||||
await expect(input.locator(".agent-mention-highlight")).toHaveCount(1);
|
||||
await expect(input).toBeFocused();
|
||||
await expect
|
||||
.poll(() => readComposerCaret(input))
|
||||
.toBe(expectedContent.length);
|
||||
|
||||
await input.pressSequentially("follow-up");
|
||||
await expect(input).toHaveText("@claude code follow-up");
|
||||
await expect(input.locator(".agent-mention-highlight")).toHaveCount(1);
|
||||
await expect(
|
||||
composer.getByTestId(`composer-address-lock-${AGENT_A}`),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("reduced motion removes addressed agents without spatial animation", async ({
|
||||
@@ -785,7 +1539,7 @@ test("reduced motion removes addressed agents without spatial animation", async
|
||||
await expect(removeButton).toHaveCSS("transform", "none");
|
||||
|
||||
await removeButton.click();
|
||||
await expect(input).toHaveText("@Morgarita ");
|
||||
await expect(input).toHaveText("");
|
||||
await expect(removeButton).toHaveCount(0);
|
||||
});
|
||||
|
||||
@@ -832,7 +1586,7 @@ test("captures the lightweight auto-pin popover", async ({ page }) => {
|
||||
const composer = channelComposer(page);
|
||||
const input = composer.getByTestId("message-input");
|
||||
await input.fill("draft text");
|
||||
await pressPrimaryShift(page, "M");
|
||||
await pressPrimaryShiftM(page);
|
||||
await expect(input).toHaveText("@alice draft text");
|
||||
|
||||
const addressControl = composer
|
||||
|
||||
@@ -69,7 +69,6 @@ test.describe("welcome and channel agent entry points", () => {
|
||||
await page
|
||||
.getByTestId("create-channel-description")
|
||||
.fill("A private channel for getting oriented in this workspace.");
|
||||
await page.getByTestId("create-channel-permissions").click();
|
||||
await page.getByTestId("create-channel-permissions-option-private").click();
|
||||
await page.getByTestId("create-channel-submit").click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("Welcome");
|
||||
@@ -109,7 +108,6 @@ test.describe("welcome and channel agent entry points", () => {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await openCreateChannelDialog(page);
|
||||
await page.getByTestId("create-channel-name").fill("Welcome");
|
||||
await page.getByTestId("create-channel-permissions").click();
|
||||
await page.getByTestId("create-channel-permissions-option-private").click();
|
||||
await page.getByTestId("create-channel-submit").click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("Welcome");
|
||||
|
||||
Reference in New Issue
Block a user