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
8 changes: 8 additions & 0 deletions src/static/js/pad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,14 @@ const pad = {
options,
changedBy: pad.myUserInfo.name || 'unnamed',
});
// The pad creator is never "enforced upon themselves", so their personal
// view overrides (cookies) are always merged on top of the pad-wide value
// in getEffectivePadOptions. A stale personal pref would therefore mask the
// pad-wide value they just set, making the control appear to do nothing
// (#7900). Sync the creator's personal pref to the value they chose so
// their own view adopts it immediately. They can still override it
// afterwards via the "My view" controls.
pad.setMyViewOption(key, value);
},
changeViewOption: (key, value) => {
const effectiveOptions = pad.getEffectivePadOptions();
Expand Down
62 changes: 62 additions & 0 deletions src/tests/frontend-new/specs/rtl_pad_wide_enforce.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {expect, test} from "@playwright/test";
import {goToNewPad} from "../helper/padHelper";
import {showSettings} from "../helper/settingsHelper";

// Regression test for ether/etherpad#7900.
//
// The pad creator is never "enforced upon themselves" (they can always edit
// pad settings), so their personal view overrides (cookies) are always merged
// on top of the pad-wide options in getEffectivePadOptions. A creator who had
// at some point toggled their personal "Read content from right to left"
// carried a stale rtlIsTrue=false cookie that silently masked the pad-wide RTL
// value they later set — the pad content stayed LTR and the pad-wide control
// appeared to "do nothing".
//
// Fix: changePadViewOption syncs the creator's personal pref to the value they
// chose, so their own view adopts the pad-wide setting immediately (while still
// allowing them to override it afterwards via the "My view" controls).
test.beforeEach(async ({page}) => {
// Clear cookies on the context of the page under test (not a throwaway
// context) so the test reliably starts without a stale rtlIsTrue pref.
await page.context().clearCookies();
await goToNewPad(page);
});

test.describe('RTL pad-wide + enforce', function () {
test('pad-wide RTL applies to the creator even with a stale personal setting', {tag: '@feature:rtl-toggle'}, async function ({page}) {
const innerBody = page
.frameLocator('iframe[name="ace_outer"]')
.frameLocator('iframe[name="ace_inner"]')
.locator('#innerdocbody');
const computedDir = () => innerBody.evaluate((el) =>
el.ownerDocument.defaultView!.getComputedStyle(el).direction);

await showSettings(page);

// The checkboxes are visually replaced by styled labels, so drive the UI
// the way a user does — by clicking the labels.
// The creator first toggles their PERSONAL RTL on, then off. This writes a
// personal cookie pref rtlIsTrue=false that used to mask the pad-wide value.
await page.locator('label[for="options-rtlcheck"]').click();
await expect(page.locator('#options-rtlcheck')).toBeChecked();
await expect.poll(computedDir).toBe('rtl');
await page.locator('label[for="options-rtlcheck"]').click();
await expect(page.locator('#options-rtlcheck')).not.toBeChecked();
await expect.poll(computedDir).toBe('ltr');

// Pad-wide settings are visible because the new pad's first user is its
// creator (canEditPadSettings). Setting pad-wide RTL must now flip the
// creator's own content despite the stale personal cookie...
await page.locator('label[for="padsettings-options-rtlcheck"]').click();
await expect(page.locator('#padsettings-options-rtlcheck')).toBeChecked();
await expect.poll(computedDir).toBe('rtl');
await expect(innerBody).toHaveClass(/\brtl\b/);
// ...and the personal control reflects the synced value.
await expect(page.locator('#options-rtlcheck')).toBeChecked();

// Enforcing for other users keeps the creator's content RTL.
await page.locator('label[for="padsettings-enforcecheck"]').click();
await expect(page.locator('#padsettings-enforcecheck')).toBeChecked();
await expect.poll(computedDir).toBe('rtl');
});
});
Loading