mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
* feat(app): add fullscreen editing to the compact composer * fix(app): preserve read-only terminal command previews Static command previews must render their published source; the live editor buffer is updated after render and has no mounted input to repaint read-only content. * fix(app): import React for composer editing scope rendering Workspace pane unit tests use the classic JSX transform, so the editing-scope provider needs React in scope when mounted. * fix(app): preserve fullscreen composer controls and editing Keep the fullscreen frame inside the visual viewport, gate hidden autocomplete keys, apply replacement commands to local line measurement, and return to inline before starting voice so its controls stay accessible. * test(app): cover draft replacement through the production composer Replace the measurement-hook fixture with profile switching and typing through the real composer. Keep measurement state inside its production surface; Android manual red/green evidence covers the native replacement flow.
105 lines
4.0 KiB
TypeScript
105 lines
4.0 KiB
TypeScript
import { expect, test, type Page } from "../support/fixtures";
|
|
import {
|
|
composerLocator,
|
|
expectComposerDraft,
|
|
expectComposerVisible,
|
|
fillComposerDraft,
|
|
enterComposerFullscreen,
|
|
exitComposerFullscreen,
|
|
resizeComposerViewport,
|
|
selectComposerDraftRange,
|
|
} from "../support/helpers/composer";
|
|
import { openAgentRoute, seedMockAgentWorkspace } from "../support/helpers/mock-agent";
|
|
|
|
// The popover has no semantic role; its rows are React Native Pressables.
|
|
function fileSuggestions(page: Page) {
|
|
return page.getByTestId("composer-autocomplete-popover");
|
|
}
|
|
|
|
async function selectReadmeMention(page: Page): Promise<void> {
|
|
await fillComposerDraft(page, "@README");
|
|
await expect(fileSuggestions(page).getByText("README.md", { exact: true })).toBeVisible();
|
|
await expect(fileSuggestions(page).getByText("example.com", { exact: true })).toHaveCount(0);
|
|
await composerLocator(page).press("Enter");
|
|
await expectComposerDraft(page, '"README.md"');
|
|
}
|
|
|
|
async function sendEmailIntact(page: Page, email: string): Promise<void> {
|
|
await fillComposerDraft(page, email);
|
|
await expect(fileSuggestions(page)).not.toBeVisible();
|
|
await composerLocator(page).press("Enter");
|
|
await expectComposerDraft(page, "");
|
|
await expect(page.getByText(email, { exact: true })).toBeVisible();
|
|
}
|
|
|
|
for (const email of [
|
|
"user <12345+user@noreply.example.com>",
|
|
"alice@example.com",
|
|
"noreply+@example.com",
|
|
]) {
|
|
test(`Enter sends ${email} intact and still completes file mentions`, async ({ page }) => {
|
|
const agent = await seedMockAgentWorkspace({
|
|
repoPrefix: "composer-email-",
|
|
title: "Email mention regression",
|
|
repo: { files: [{ path: "example.com", content: "A file matching the email domain.\n" }] },
|
|
});
|
|
try {
|
|
await openAgentRoute(page, agent);
|
|
await expectComposerVisible(page);
|
|
await test.step("complete a workspace file mention with Enter", async () => {
|
|
await selectReadmeMention(page);
|
|
});
|
|
await test.step("send an email without offering or inserting workspace files", async () => {
|
|
await sendEmailIntact(page, email);
|
|
});
|
|
} finally {
|
|
await agent.cleanup();
|
|
}
|
|
});
|
|
}
|
|
|
|
test("fullscreen mention keys edit the draft while inline keys still complete files", async ({
|
|
page,
|
|
}) => {
|
|
const agent = await seedMockAgentWorkspace({
|
|
repoPrefix: "composer-fullscreen-mention-",
|
|
title: "Fullscreen mention keys",
|
|
});
|
|
try {
|
|
await resizeComposerViewport(page, "portrait");
|
|
await openAgentRoute(page, agent);
|
|
await expectComposerVisible(page);
|
|
const draft = "First line\nSecond line\n@README";
|
|
await fillComposerDraft(page, draft);
|
|
await expect(fileSuggestions(page).getByText("README.md", { exact: true })).toBeVisible();
|
|
await enterComposerFullscreen(page);
|
|
await expect(fileSuggestions(page)).not.toBeVisible();
|
|
await selectComposerDraftRange(page, draft.length, draft.length);
|
|
await composerLocator(page).press("ArrowUp");
|
|
await expect
|
|
.poll(() =>
|
|
composerLocator(page).evaluate((element) => {
|
|
const input = element as HTMLTextAreaElement;
|
|
return input.selectionStart === input.selectionEnd && input.selectionStart < 23;
|
|
}),
|
|
)
|
|
.toBe(true);
|
|
await selectComposerDraftRange(page, draft.length, draft.length);
|
|
await composerLocator(page).press("Enter");
|
|
await expectComposerDraft(page, `${draft}\n`);
|
|
await exitComposerFullscreen(page);
|
|
await selectReadmeMention(page);
|
|
const commandDraft = "/cl\nSecond line\nThird line";
|
|
await fillComposerDraft(page, commandDraft);
|
|
await selectComposerDraftRange(page, 2, 2);
|
|
await composerLocator(page).press("ArrowRight");
|
|
await expect(fileSuggestions(page).getByText("/clear", { exact: true }).first()).toBeVisible();
|
|
await enterComposerFullscreen(page);
|
|
await expect(fileSuggestions(page)).not.toBeVisible();
|
|
await composerLocator(page).press("Enter");
|
|
await expectComposerDraft(page, "/cl\n\nSecond line\nThird line");
|
|
} finally {
|
|
await agent.cleanup();
|
|
}
|
|
});
|