Skip to content

Commit 8413966

Browse files
committed
test(e2e): cover mobile terminal long-press line copy
1 parent 542ab3d commit 8413966

1 file changed

Lines changed: 87 additions & 111 deletions

File tree

e2e/specs/settings/mobile-copy-on-select.spec.ts

Lines changed: 87 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -163,27 +163,29 @@ async function seedLongTerminalLine(page: Page): Promise<void> {
163163
);
164164
}
165165

166-
async function longPressTerminalRows(page: Page): Promise<void> {
166+
async function longPressTerminalRows(page: Page, rowIndex = 1): Promise<void> {
167167
const rows = page.locator(".mobile-sheet--terminal .xterm-rows").first();
168-
const host = page.locator(".mobile-sheet--terminal .xterm-host").first();
169168
await expect(rows).toBeVisible({ timeout: 10000 });
170-
await expect(host).toBeVisible({ timeout: 10000 });
171-
const box = await rows.boundingBox();
169+
170+
const targetRow = rows.locator(":scope > div").nth(rowIndex);
171+
await expect(targetRow).toBeVisible({ timeout: 10000 });
172+
173+
const box = await targetRow.boundingBox();
172174
expect(box).toBeTruthy();
173175
if (!box) {
174-
throw new Error("xterm rows bounding box missing");
176+
throw new Error("xterm row bounding box missing");
175177
}
176178

177179
const x = box.x + Math.min(48, Math.max(16, box.width / 4));
178-
const y = box.y + Math.min(32, Math.max(12, box.height / 3));
180+
const y = box.y + Math.min(16, Math.max(8, box.height / 2));
179181

180-
await host.evaluate(
182+
await targetRow.evaluate(
181183
(node, { clientX, clientY }) => {
182184
if (!(node instanceof HTMLElement)) {
183-
throw new Error("xterm host missing");
185+
throw new Error("xterm row missing");
184186
}
185187

186-
const touches = [{ identifier: 1, clientX, clientY }];
188+
const touches = [{ identifier: 1, clientX, clientY, target: node }];
187189
const buildEvent = (
188190
type: string,
189191
activeTouches: typeof touches,
@@ -205,6 +207,13 @@ async function longPressTerminalRows(page: Page): Promise<void> {
205207
);
206208
}
207209

210+
async function findPrintedLineRowIndex(page: Page): Promise<number> {
211+
const rowTexts = await page
212+
.locator(".mobile-sheet--terminal .xterm-rows > div")
213+
.allTextContents();
214+
return rowTexts.findIndex((text) => text.startsWith("MOBILE_COPY_MODE_LONG_LINE"));
215+
}
216+
208217
async function setMobileCopyOnSelect(page: Page, enabled: boolean): Promise<void> {
209218
await page.goto("/settings");
210219
await expect(page.locator(".settings-page")).toBeVisible({ timeout: 15000 });
@@ -228,10 +237,37 @@ test.describe("mobile copy on select", () => {
228237
test.beforeEach(async ({ page }) => {
229238
await page.addInitScript(() => {
230239
window.localStorage.setItem("ui.locale", JSON.stringify("en"));
240+
241+
let copiedText = "";
242+
Object.defineProperty(window, "__mobileCopiedText", {
243+
configurable: true,
244+
get() {
245+
return copiedText;
246+
},
247+
});
248+
249+
if (navigator.clipboard) {
250+
Object.defineProperty(navigator.clipboard, "writeText", {
251+
configurable: true,
252+
value: async (text: string) => {
253+
copiedText = text;
254+
},
255+
});
256+
return;
257+
}
258+
259+
Object.defineProperty(navigator, "clipboard", {
260+
configurable: true,
261+
value: {
262+
writeText: async (text: string) => {
263+
copiedText = text;
264+
},
265+
},
266+
});
231267
});
232268
});
233269

234-
test("mobile copy mode respects the setting and does not expand page or sheet layout", async ({
270+
test("mobile long press copies the wrapped logical line without opening a copy overlay", async ({
235271
page,
236272
}) => {
237273
await setMobileCopyOnSelect(page, true);
@@ -240,119 +276,59 @@ test.describe("mobile copy on select", () => {
240276
await ensureTerminalExists(page);
241277
await seedLongTerminalLine(page);
242278

243-
const beforeMetrics = await page.evaluate(() => {
244-
const sheet = document.querySelector(".mobile-sheet--terminal");
245-
const terminalSheet = document.querySelector(".mobile-terminal-sheet");
246-
const body = document.body;
247-
const doc = document.documentElement;
248-
const viewportWidth = window.innerWidth;
249-
const viewportHeight = window.innerHeight;
250-
251-
return {
252-
viewportWidth,
253-
viewportHeight,
254-
docScrollWidth: doc.scrollWidth,
255-
docScrollHeight: doc.scrollHeight,
256-
bodyScrollWidth: body.scrollWidth,
257-
bodyScrollHeight: body.scrollHeight,
258-
sheetRect: sheet?.getBoundingClientRect().toJSON() ?? null,
259-
terminalSheetRect: terminalSheet?.getBoundingClientRect().toJSON() ?? null,
260-
};
261-
});
262-
263-
await longPressTerminalRows(page);
264-
265-
const copyMode = page.locator(".mobile-terminal-copy-mode");
266-
await expect(copyMode).toBeVisible({ timeout: 5000 });
267-
await expect(
268-
copyMode.getByText(translateForE2E("terminal.copy_mode_title", "en"))
269-
).toBeVisible();
270-
271-
const afterMetrics = await page.evaluate(() => {
272-
const body = document.body;
273-
const doc = document.documentElement;
274-
const overlay = document.querySelector(".mobile-terminal-copy-mode");
275-
const toolbar = document.querySelector(".mobile-terminal-copy-mode__toolbar");
276-
const content = document.querySelector(".mobile-terminal-copy-mode__content");
277-
const text = document.querySelector(".mobile-terminal-copy-mode__text");
278-
const sheet = document.querySelector(".mobile-sheet--terminal");
279-
const terminalSheet = document.querySelector(".mobile-terminal-sheet");
280-
const xtermViewport = document.querySelector(".mobile-sheet--terminal .xterm-viewport");
281-
const xtermRows = document.querySelector(".mobile-sheet--terminal .xterm-rows");
282-
const viewportWidth = window.innerWidth;
283-
const viewportHeight = window.innerHeight;
284-
285-
const rect = (node: Element | null) => node?.getBoundingClientRect().toJSON() ?? null;
286-
287-
return {
288-
viewportWidth,
289-
viewportHeight,
290-
docScrollWidth: doc.scrollWidth,
291-
docScrollHeight: doc.scrollHeight,
292-
bodyScrollWidth: body.scrollWidth,
293-
bodyScrollHeight: body.scrollHeight,
294-
overlayRect: rect(overlay),
295-
toolbarRect: rect(toolbar),
296-
contentRect: rect(content),
297-
textRect: rect(text),
298-
sheetRect: rect(sheet),
299-
terminalSheetRect: rect(terminalSheet),
300-
xtermViewportRect: rect(xtermViewport),
301-
xtermRowsRect: rect(xtermRows),
302-
contentClientHeight: content instanceof HTMLElement ? content.clientHeight : null,
303-
contentScrollHeight: content instanceof HTMLElement ? content.scrollHeight : null,
304-
textClientHeight: text instanceof HTMLElement ? text.clientHeight : null,
305-
textScrollHeight: text instanceof HTMLElement ? text.scrollHeight : null,
306-
};
307-
});
279+
await expect
280+
.poll(async () => {
281+
return page.locator(".mobile-sheet--terminal .xterm-rows > div").count();
282+
})
283+
.toBeGreaterThan(1);
308284

309-
expect(afterMetrics.docScrollWidth).toBeLessThanOrEqual(afterMetrics.viewportWidth);
310-
expect(afterMetrics.bodyScrollWidth).toBeLessThanOrEqual(afterMetrics.viewportWidth);
311-
expect(afterMetrics.docScrollHeight).toBeLessThanOrEqual(afterMetrics.viewportHeight);
312-
expect(afterMetrics.bodyScrollHeight).toBeLessThanOrEqual(afterMetrics.viewportHeight);
285+
await expect
286+
.poll(async () => {
287+
return findPrintedLineRowIndex(page);
288+
})
289+
.not.toBe(-1);
290+
const printedLineRowIndex = await findPrintedLineRowIndex(page);
291+
await longPressTerminalRows(page, printedLineRowIndex);
313292

314-
expect(afterMetrics.overlayRect?.width ?? 0).toBeLessThanOrEqual(afterMetrics.viewportWidth);
315-
expect(afterMetrics.overlayRect?.height ?? 0).toBeLessThanOrEqual(afterMetrics.viewportHeight);
316-
expect(afterMetrics.contentRect?.width ?? 0).toBeLessThanOrEqual(
317-
(afterMetrics.overlayRect?.width ?? afterMetrics.viewportWidth) + 1
293+
await expect(page.getByText(translateForE2E("terminal.copied_current_line", "en"))).toBeVisible(
294+
{ timeout: 5000 }
318295
);
319-
expect(afterMetrics.sheetRect?.width ?? 0).toBeLessThanOrEqual(afterMetrics.viewportWidth);
320-
expect(afterMetrics.sheetRect?.height ?? 0).toBeLessThanOrEqual(afterMetrics.viewportHeight);
321-
322-
expect(
323-
Math.abs((afterMetrics.sheetRect?.width ?? 0) - (beforeMetrics.sheetRect?.width ?? 0))
324-
).toBeLessThanOrEqual(1);
325-
expect(
326-
Math.abs((afterMetrics.sheetRect?.height ?? 0) - (beforeMetrics.sheetRect?.height ?? 0))
327-
).toBeLessThanOrEqual(1);
328-
expect(
329-
Math.abs(
330-
(afterMetrics.terminalSheetRect?.width ?? 0) - (beforeMetrics.terminalSheetRect?.width ?? 0)
331-
)
332-
).toBeLessThanOrEqual(1);
333-
expect(
334-
Math.abs(
335-
(afterMetrics.terminalSheetRect?.height ?? 0) -
336-
(beforeMetrics.terminalSheetRect?.height ?? 0)
337-
)
338-
).toBeLessThanOrEqual(1);
339-
expect(
340-
afterMetrics.contentScrollHeight,
341-
`copy mode content should not overflow by default:\n${JSON.stringify(afterMetrics, null, 2)}`
342-
).toBeLessThanOrEqual((afterMetrics.contentClientHeight ?? 0) + 1);
296+
await expect(page.locator(".mobile-terminal-copy-mode")).toHaveCount(0);
297+
298+
await expect
299+
.poll(async () => {
300+
return page.evaluate(() => {
301+
return (window as Window & { __mobileCopiedText?: string }).__mobileCopiedText ?? "";
302+
});
303+
})
304+
.toBe(LONG_LINE_TEXT);
343305
});
344306

345-
test("mobile long press does not enter copy mode when copy on select is disabled", async ({
346-
page,
347-
}) => {
307+
test("mobile long press does not copy when copy on select is disabled", async ({ page }) => {
348308
await setMobileCopyOnSelect(page, false);
349309
await openMobileWorkspace(page);
350310
await openMobileTerminalSheet(page);
351311
await ensureTerminalExists(page);
352312
await seedLongTerminalLine(page);
353313

354-
await longPressTerminalRows(page);
314+
await expect
315+
.poll(async () => {
316+
return findPrintedLineRowIndex(page);
317+
})
318+
.not.toBe(-1);
319+
const printedLineRowIndex = await findPrintedLineRowIndex(page);
320+
await longPressTerminalRows(page, printedLineRowIndex);
355321

356322
await expect(page.locator(".mobile-terminal-copy-mode")).toHaveCount(0);
323+
await expect(page.getByText(translateForE2E("terminal.copied_current_line", "en"))).toHaveCount(
324+
0
325+
);
326+
await expect
327+
.poll(async () => {
328+
return page.evaluate(() => {
329+
return (window as Window & { __mobileCopiedText?: string }).__mobileCopiedText ?? "";
330+
});
331+
})
332+
.toBe("");
357333
});
358334
});

0 commit comments

Comments
 (0)