diff --git a/src/tests/backend/specs/settings.ts b/src/tests/backend/specs/settings.ts index 89f4bc7b1..3f836ae40 100644 --- a/src/tests/backend/specs/settings.ts +++ b/src/tests/backend/specs/settings.ts @@ -89,4 +89,62 @@ describe(__filename, function () { assert.deepEqual(settings!.ep_webrtc, {"enabled": true}); }) }) + + // Regression test for https://github.com/ether/etherpad/issues/7543. + // Plugins (ep_font_color, ep_font_size, ep_plugin_helpers, …) consume + // Settings via CommonJS require(), which under tsx/ESM interop would place + // the default export under .default and leave top-level fields undefined. + // That broke template rendering with: + // TypeError: Cannot read properties of undefined (reading 'indexOf') + // when plugins called settings.toolbar.left / etc. + // + // The CJS compat layer in Settings.ts re-exposes every top-level field on + // module.exports via accessor properties, so require(...). resolves + // even though the source uses `export default`. This test asserts that + // contract so a future refactor can't regress it silently. + describe('CJS compatibility for plugin consumers', function () { + it('exposes top-level fields directly on require() result', function () { + const cjs = require('../../../node/utils/Settings'); + // The three fields most commonly read by first-party plugins. + assert.notStrictEqual(cjs.toolbar, undefined, + 'settings.toolbar must be reachable via CJS require'); + assert.notStrictEqual(cjs.skinName, undefined, + 'settings.skinName must be reachable via CJS require'); + assert.notStrictEqual(cjs.padOptions, undefined, + 'settings.padOptions must be reachable via CJS require'); + }); + + it('toolbar has the shape plugins index into (left/right/timeslider)', function () { + const cjs = require('../../../node/utils/Settings'); + // ep_font_color and friends JSON.stringify(settings.toolbar) then call + // .indexOf on the result, so the object must be present and well-formed. + assert.ok(cjs.toolbar && typeof cjs.toolbar === 'object'); + assert.ok(Array.isArray(cjs.toolbar.left)); + assert.ok(Array.isArray(cjs.toolbar.right)); + assert.ok(Array.isArray(cjs.toolbar.timeslider)); + }); + + it('does not hide the real value under a .default wrapper', function () { + const cjs = require('../../../node/utils/Settings'); + // If export-default handling regresses, consumers end up seeing a + // {default: {...}} wrapper and .toolbar on the wrapper is undefined. + // Either shape is acceptable as long as .toolbar is directly present, + // which is what the CJS compat shim guarantees. + if (cjs.default != null && cjs.default.toolbar != null) { + assert.strictEqual(cjs.toolbar, cjs.default.toolbar, + 'require().toolbar must be the same object as require().default.toolbar'); + } + }); + + it('setters propagate so reloadSettings() changes are visible to plugins', function () { + const cjs = require('../../../node/utils/Settings'); + const original = cjs.title; + try { + cjs.title = 'cjs-shim-test'; + assert.strictEqual(cjs.title, 'cjs-shim-test'); + } finally { + cjs.title = original; + } + }); + }); });