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
17 changes: 6 additions & 11 deletions desktop/tests/e2e/empty-edit-delete.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { expect, test } from "@playwright/test";

import { installMockBridge } from "../helpers/bridge";
import { openGeneral, pressRowAction } from "../helpers/channelHeadReady";
import { clearComposer, replaceComposerText } from "../helpers/composer";

// A message the mock identity authors in #general. Editing/deleting one's own
// message is exactly Sam's workflow: "delete a message by clearing its edit."
Expand All @@ -16,9 +18,7 @@ async function openMoreActionsMenu(
page: import("@playwright/test").Page,
messageId: string,
) {
const row = page.locator(`[data-message-id="${messageId}"]`);
await row.hover();
await page.getByTestId(`more-actions-${messageId}`).click();
await pressRowAction(page.getByTestId(`more-actions-${messageId}`));
await expect(page.locator('[role="menuitem"]').first()).toBeVisible({
timeout: 5_000,
});
Expand All @@ -37,17 +37,14 @@ async function submitEmptyEdit(
// wait for it to populate before we clear it.
const input = page.getByTestId("message-input");
await expect(input).not.toBeEmpty({ timeout: 5_000 });
await input.click();
await page.keyboard.press("ControlOrMeta+A");
await page.keyboard.press("Backspace");
await expect(input).toBeEmpty();
await clearComposer(input);
await page.keyboard.press("Enter");
}

test.beforeEach(async ({ page }) => {
await installMockBridge(page);
await page.goto("/");
await page.getByTestId("channel-general").click();
await openGeneral(page);
await expect(page.getByTestId("chat-title")).toHaveText("general");
await expect
.poll(() =>
Expand Down Expand Up @@ -140,9 +137,7 @@ test("a non-empty edit still edits and never deletes", async ({ page }) => {
await expect(input).not.toBeEmpty({ timeout: 5_000 });
const editedContent = `Edited, not deleted ${Date.now()}`;

await input.click();
await page.keyboard.press("ControlOrMeta+A");
await page.keyboard.type(editedContent);
await replaceComposerText(input, editedContent);
await page.keyboard.press("Enter");

// No delete confirmation, edit mode exits, the row survives with new text.
Expand Down
67 changes: 39 additions & 28 deletions desktop/tests/e2e/file-attachment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@ import type { Locator, Page } from "@playwright/test";

import { waitForAnimations } from "../helpers/animations";
import { installMockBridge } from "../helpers/bridge";
import { openGeneral, pressRowAction } from "../helpers/channelHeadReady";
import { expectCornerRadiusPx, expectSmoothCorners } from "../helpers/css";

async function openMoreActionsMenu(page: Page, messageId: string) {
const row = page.locator(`[data-message-id="${messageId}"]`);
await row.hover();
await page.getByTestId(`more-actions-${messageId}`).click();
await pressRowAction(row.getByTestId(`more-actions-${messageId}`));
await expect(page.locator('[role="menuitem"]').first()).toBeVisible({
timeout: 5_000,
});
}

// Leave the bottom of the timeline with real wheel input. A programmatic
// scrollTop keeps the timeline's bottom intent armed, so the next content
// resize (the upload card mounting) pins it back down and unmounts the pill.
async function scrollTimelineUp(page: Page, timeline: Locator) {
await timeline.hover();
await page.mouse.wheel(0, -2_000);
}

// Exercises the generic file-attachment UI contract end-to-end through the
// mock Tauri bridge: paperclip upload → composer chip → send → FileCard in the
// timeline. This guards the frontend wiring (the riskiest, previously
Expand Down Expand Up @@ -166,7 +174,9 @@ test("opening edit during an immediate photo upload preserves the draft", async
page,
}) => {
await page.goto("/");
await page.getByTestId("channel-general").click();
// Open the menu only after history and its head refresh settle, so the row
// is not replaced under the keyboard focus.
await openGeneral(page);
await expect(page.getByTestId("chat-title")).toHaveText("general");
// Hold the upload at the IPC boundary until the edit assertion completes.
// A fixed delay races menu interaction and CI scheduling, so it can test
Expand Down Expand Up @@ -432,10 +442,7 @@ test("upload progress floats above the dock and lifts Jump to latest", async ({

const timeline = page.getByTestId("message-timeline");
await expect(timeline.locator("[data-message-id]").first()).toBeVisible();
await timeline.evaluate((element) => {
element.scrollTop = Math.max(500, element.scrollHeight / 2);
element.dispatchEvent(new Event("scroll", { bubbles: true }));
});
await scrollTimelineUp(page, timeline);
const jumpToLatest = page.getByTestId("message-scroll-to-latest");
await expect(jumpToLatest).toBeVisible();
const restingBox = await jumpToLatest.boundingBox();
Expand All @@ -444,29 +451,33 @@ test("upload progress floats above the dock and lifts Jump to latest", async ({
await page.getByTestId("send-message").click();
const uploadMotion = page.getByTestId("composer-upload-progress-motion");
await expect(uploadMotion).toBeVisible();
await timeline.evaluate((element) => {
element.scrollTop = Math.max(500, element.scrollHeight / 2);
element.dispatchEvent(new Event("scroll", { bubbles: true }));
});
await scrollTimelineUp(page, timeline);
await expect(jumpToLatest).toBeVisible();
await page.waitForTimeout(250);

const [uploadBox, dockBackdropBox, liftedBox] = await Promise.all([
uploadMotion.boundingBox(),
page.getByTestId("composer-dock-backdrop").boundingBox(),
jumpToLatest.boundingBox(),
]);
// The upload card slides in and the pill lifts above it, both animated.
// Poll the settled layout instead of reading it after a fixed 250 ms.
expect(restingBox).not.toBeNull();
expect(uploadBox).not.toBeNull();
expect(dockBackdropBox).not.toBeNull();
expect(liftedBox).not.toBeNull();
expect((dockBackdropBox?.y ?? 0) + 1).toBeGreaterThanOrEqual(
(uploadBox?.y ?? 0) + (uploadBox?.height ?? 0),
);
expect((liftedBox?.y ?? 0) + (liftedBox?.height ?? 0)).toBeLessThanOrEqual(
uploadBox?.y ?? 0,
);
expect(liftedBox?.y ?? 0).toBeLessThan((restingBox?.y ?? 0) - 10);
await expect
.poll(async () => {
const [uploadBox, dockBackdropBox, liftedBox] = await Promise.all([
uploadMotion.boundingBox(),
page.getByTestId("composer-dock-backdrop").boundingBox(),
jumpToLatest.boundingBox(),
]);
if (!uploadBox || !dockBackdropBox || !liftedBox || !restingBox) {
return "a box is missing";
}
return {
uploadAboveDockBackdrop:
dockBackdropBox.y + 1 >= uploadBox.y + uploadBox.height,
pillAboveUpload: liftedBox.y + liftedBox.height <= uploadBox.y,
pillLifted: liftedBox.y < restingBox.y - 10,
};
})
.toEqual({
uploadAboveDockBackdrop: true,
pillAboveUpload: true,
pillLifted: true,
});

await page.getByTestId("composer-upload-cancel").click();
});
Expand Down
4 changes: 2 additions & 2 deletions desktop/tests/e2e/inbox-edit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect, test } from "@playwright/test";

import { waitForAnimations } from "../helpers/animations";
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
import { clearComposer } from "../helpers/composer";

const GENERAL_CHANNEL_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
const CURRENT_PUBKEY = "deadbeef".repeat(8);
Expand Down Expand Up @@ -158,8 +159,7 @@ async function submitEmptyEdit(
await expect(detail.getByTestId("edit-target")).toBeVisible();
const input = detail.getByTestId("message-input");
await expect(input).not.toBeEmpty();
await input.fill("");
await expect(input).toBeEmpty();
await clearComposer(input);
await page.keyboard.press("Enter");
}

Expand Down
5 changes: 3 additions & 2 deletions desktop/tests/e2e/mention-recipients.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect, test, type Page } from "@playwright/test";
import { truncateNpub } from "../../src/shared/lib/pubkey";
import { waitForAnimations } from "../helpers/animations";
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
import { replaceComposerText } from "../helpers/composer";

const FIRST = TEST_IDENTITIES.alice.pubkey;
const SECOND = TEST_IDENTITIES.bob.pubkey;
Expand Down Expand Up @@ -644,7 +645,7 @@ for (const replacement of [
// by filling the still-idle composer before the historical body is loaded.
await expect(panel.getByTestId("edit-target")).toBeVisible();
await expect(input).toHaveText("@Scout historical ambiguity");
await input.fill(replacement);
await replaceComposerText(input, replacement);
await panel.getByTestId("send-message").click();
await expect(row).toContainText("repaired history");
const expected = replacement.startsWith("@")
Expand Down Expand Up @@ -747,7 +748,7 @@ for (const mixed of [false, true]) {
await expect(input).toHaveText(
"@Scout and @Scout Jones historical ambiguity",
);
await input.fill(replacement);
await replaceComposerText(input, replacement);
await panel.getByTestId("send-message").click();
await expect(row).toContainText("repaired history");
const expected = long;
Expand Down
Loading
Loading