-
-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(editor): PageDown advances on consecutive long wrapped lines #7555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import {expect, test} from "@playwright/test"; | ||
| import {clearPadContent, goToNewPad} from "../helper/padHelper"; | ||
|
|
||
| test.beforeEach(async ({page}) => { | ||
| await goToNewPad(page); | ||
| }); | ||
|
|
||
| // Regression test for https://github.com/ether/etherpad/issues/4562 | ||
| // PageDown failed to scroll when the cursor was on a very long wrapped line and | ||
| // the following lines were also very long, because getVisibleLineRange returns | ||
| // indices into rep.lines (logical lines) and collapsed to [n, n] — so the | ||
| // advance count was 0 and both caret and scroll stayed put. | ||
| test.describe('PageDown on consecutive long wrapped lines (#4562)', function () { | ||
| test.describe.configure({retries: 2}); | ||
|
|
||
| test('PageDown scrolls when three very long lines fill the viewport', async function ({page}) { | ||
| await clearPadContent(page); | ||
|
|
||
| const innerFrame = page.frame('ace_inner')!; | ||
|
|
||
| // Insert three long lines via the editor directly — each ~2000 chars, which | ||
| // wraps to many visual rows in the viewport. | ||
| await innerFrame.evaluate(() => { | ||
| const body = document.getElementById('innerdocbody')!; | ||
| const longText = 'invisible '.repeat(200).trim(); | ||
| body.innerHTML = ''; | ||
| for (let i = 0; i < 3; i++) { | ||
| const div = document.createElement('div'); | ||
| div.textContent = `${i + 1} ${longText}`; | ||
| body.appendChild(div); | ||
| } | ||
| // Trigger the editor to pick up the content | ||
| body.dispatchEvent(new Event('input', {bubbles: true})); | ||
| }); | ||
|
|
||
| // Type a character at the end to make the editor register the long content | ||
| // via its normal input path (the raw innerHTML edit above is just a scaffold). | ||
| await page.keyboard.press('End'); | ||
| await page.keyboard.type('!'); | ||
| await page.waitForTimeout(300); | ||
|
|
||
| // Move caret to start of pad | ||
| await page.keyboard.down('Control'); | ||
| await page.keyboard.press('Home'); | ||
| await page.keyboard.up('Control'); | ||
| await page.waitForTimeout(200); | ||
|
|
||
| // Capture initial scroll position of the outer (scrollable) frame | ||
| const outerFrame = page.frame('ace_outer')!; | ||
| const before = await outerFrame.evaluate( | ||
| () => (document.getElementById('outerdocbody') as HTMLElement).scrollTop || | ||
| document.scrollingElement?.scrollTop || 0); | ||
|
|
||
| // Press PageDown — the ace handler uses a 200ms setTimeout internally. | ||
| await page.keyboard.press('PageDown'); | ||
| await page.waitForTimeout(800); | ||
|
Comment on lines
+40
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. Fixed sleeps cause flakiness The test uses multiple waitForTimeout() sleeps around async editor incorporation and PageDown handling, so it can observe scroll/caret state before it has settled under slower CI conditions. This risks intermittent failures and makes the regression signal less deterministic. Agent Prompt
|
||
|
|
||
| const after = await outerFrame.evaluate( | ||
| () => (document.getElementById('outerdocbody') as HTMLElement).scrollTop || | ||
| document.scrollingElement?.scrollTop || 0); | ||
|
Comment on lines
+50
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. Inconsistent scroll sampling The spec samples scroll as outerdocbody.scrollTop || document.scrollingElement?.scrollTop, which can read from different scrolling containers depending on which one happens to be non-zero, making before/after comparisons less deterministic. Etherpad’s scrolling code scrolls the outer iframe window and (for animation) manipulates both #outerdocbody and its parent, so the test should measure a single consistent metric (e.g., window.scrollY in ace_outer, or Math.max(document.documentElement.scrollTop, document.body.scrollTop)). Agent Prompt
Comment on lines
+48
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. Misreads outer scroll position The new test measures scroll using #outerdocbody.scrollTop, but Etherpad’s scroll position is tracked via the outer iframe window/pageYOffset or the documentElement/parent scroll container, so the test can miss real scrolling. This can weaken the assertion (or fail it) depending on which element actually owns the scroll offset in the runtime/browser. Agent Prompt
|
||
|
|
||
| // Either the viewport scrolled, or the caret advanced to a later logical line. | ||
| const caretLine = await innerFrame.evaluate(() => { | ||
| const sel = document.getSelection(); | ||
| if (!sel || !sel.focusNode) return 0; | ||
| let node = sel.focusNode as HTMLElement; | ||
| while (node && node.tagName !== 'DIV') node = node.parentElement!; | ||
| if (!node) return 0; | ||
| const divs = Array.from(document.getElementById('innerdocbody')!.children); | ||
| return divs.indexOf(node); | ||
| }); | ||
|
|
||
| // Pre-fix behavior (#4562): after == before AND caretLine === 0. | ||
| // Fixed behavior: caret advances at least 1 logical line, or the viewport scrolls. | ||
| expect(after > before || caretLine > 0).toBe(true); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. Flaky fixed sleeps
🐞 Bug☼ ReliabilityAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools