diff --git a/src/static/css/iframe_editor.css b/src/static/css/iframe_editor.css index a29c11573ae..703485c2138 100644 --- a/src/static/css/iframe_editor.css +++ b/src/static/css/iframe_editor.css @@ -53,6 +53,9 @@ html.outer-editor, html.inner-editor { #innerdocbody a { color: #2e96f3; + white-space: normal; + word-wrap: break-word; + overflow-wrap: break-word; } #innerdocbody.authorColors [class^='author-'] a { color: inherit; diff --git a/src/tests/frontend-new/specs/url_wrapping.spec.ts b/src/tests/frontend-new/specs/url_wrapping.spec.ts new file mode 100644 index 00000000000..7e02286c3ef --- /dev/null +++ b/src/tests/frontend-new/specs/url_wrapping.spec.ts @@ -0,0 +1,53 @@ +import {expect, test} from "@playwright/test"; +import {clearPadContent, getPadBody, goToNewPad, writeToPad} from "../helper/padHelper"; + +test.beforeEach(async ({page}) => { + await goToNewPad(page); +}); + +// Regression test for https://github.com/ether/etherpad/issues/7894 +test.describe('long URL wrapping in pad editor', function () { + test('long URLs should wrap instead of overflowing the editor', async function ({page}) { + const padBody = await getPadBody(page); + await clearPadContent(page); + + // The fix for #7894 ensures #innerdocbody a has white-space: normal, + // word-wrap: break-word, and overflow-wrap: break-word to override + // the global a { white-space: nowrap } from pad.css. + const longUrl = + 'https://example.com/this/is/a/very/long/test/url/for/etherpad/regression/' + + 'issue/7894/wrapping/behavior/long/urls/should/wrap/instead/of/overflowing/' + + 'to/the/right/and/causing/awkward/rendering'; + + // Write a short word on line 1, then the long URL on line 2 + await writeToPad(page, 'hello\n' + longUrl + ' '); + + // Verify the URL became a clickable link + const link = padBody.locator('a'); + await expect(link).toHaveCount(1); + await expect(link).toHaveAttribute('href', longUrl); + + // Verify wrapping CSS properties are applied (the direct fix for #7894) + const cssProps = await link.evaluate((el) => { + const style = window.getComputedStyle(el); + return { + whiteSpace: style.whiteSpace, + wordWrap: style.wordWrap, + overflowWrap: style.overflowWrap, + }; + }); + + expect(cssProps.whiteSpace).toBe('normal'); + expect(cssProps.wordWrap).toBe('break-word'); + expect(cssProps.overflowWrap).toBe('break-word'); + + // The short line should be one line tall, the wrapped URL line should + // be much taller — this is the observable behavior that wrapping works. + const lines = padBody.locator('> div'); + await expect(lines).toHaveCount(2); + + const shortLineHeight = await lines.nth(0).evaluate((el) => el.getBoundingClientRect().height); + const longLineHeight = await lines.nth(1).evaluate((el) => el.getBoundingClientRect().height); + expect(longLineHeight).toBeGreaterThan(shortLineHeight * 1.5); + }); +});