diff --git a/src/pat/contentbrowser/README.md b/src/pat/contentbrowser/README.md index 261ce3863..e21f6fe1e 100644 --- a/src/pat/contentbrowser/README.md +++ b/src/pat/contentbrowser/README.md @@ -113,6 +113,9 @@ Note: the `name` of the registered component must match the `componentRegistryKe It can be any unique identifier. If no custom component is registered for the configured key, the pattern falls back to the default component registered as `pat-contentbrowser.SelectedItem`. +To replace the component site-wide without any pattern configuration, register your component under the default key `pat-contentbrowser.SelectedItem` instead. +The pattern only registers its own default component if nothing is registered under that key yet, so an add-on registration is kept no matter whether the add-on bundle initializes before or after the pattern. + ```javascript ... import plone_registry from "@plone/registry"; diff --git a/src/pat/contentbrowser/contentbrowser.js b/src/pat/contentbrowser/contentbrowser.js index 336b4ebf3..d16ffc0a7 100644 --- a/src/pat/contentbrowser/contentbrowser.js +++ b/src/pat/contentbrowser/contentbrowser.js @@ -56,16 +56,25 @@ class Pattern extends BasePattern { static trigger = ".pat-contentbrowser"; static parser = parser; + static async register_default_components() { + // Register the default components in @plone/registry — but only if + // nothing is registered under that key yet. This lets add-ons + // replace a component site-wide by registering their own under the + // default key, regardless of whether their bundle initializes before + // or after this pattern (registerComponent overwrites silently). + if (!plone_registry.getComponent("pat-contentbrowser.SelectedItem").component) { + const SelectedItem = (await import("./src/SelectedItem.svelte")).default; + plone_registry.registerComponent({ + name: "pat-contentbrowser.SelectedItem", + component: SelectedItem, + }); + } + } + async init() { this.el.style.display = "none"; - // register default components in @plone/registry - const SelectedItem = (await import("./src/SelectedItem.svelte")).default; - - plone_registry.registerComponent({ - name: "pat-contentbrowser.SelectedItem", - component: SelectedItem, - }); + await Pattern.register_default_components(); // ensure an id on our element (TinyMCE doesn't have one) let nodeId = this.el.getAttribute("id"); diff --git a/src/pat/contentbrowser/contentbrowser.test.js b/src/pat/contentbrowser/contentbrowser.test.js index 1e9997c90..4c4a3bd1a 100644 --- a/src/pat/contentbrowser/contentbrowser.test.js +++ b/src/pat/contentbrowser/contentbrowser.test.js @@ -1,7 +1,18 @@ -import "./contentbrowser"; +import Pattern from "./contentbrowser"; +import plone_registry from "@plone/registry"; import registry from "@patternslib/patternslib/src/core/registry"; import utils from "@patternslib/patternslib/src/core/utils"; +const DEFAULT_KEY = "pat-contentbrowser.SelectedItem"; + +// The jest setup cannot compile Svelte components, so stand in for the +// default component with a plain function. +const mockDefaultSelectedItem = () => "default SelectedItem"; +jest.mock("./src/SelectedItem.svelte", () => ({ + __esModule: true, + default: mockDefaultSelectedItem, +})); + describe("Content Browser", () => { beforeEach(() => { @@ -29,3 +40,38 @@ describe("Content Browser", () => { }); }); + +describe("Content Browser default components", () => { + afterEach(() => { + delete plone_registry.components[DEFAULT_KEY]; + }); + + it("registers the default SelectedItem component", async function () { + expect(plone_registry.getComponent(DEFAULT_KEY).component).toBeUndefined(); + + await Pattern.register_default_components(); + + expect(plone_registry.getComponent(DEFAULT_KEY).component).toBe( + mockDefaultSelectedItem, + ); + }); + + it("keeps a component an add-on registered under the default key", async function () { + const custom = () => "custom SelectedItem"; + plone_registry.registerComponent({ name: DEFAULT_KEY, component: custom }); + + await Pattern.register_default_components(); + + expect(plone_registry.getComponent(DEFAULT_KEY).component).toBe(custom); + }); + + it("does not overwrite the add-on component on repeated initialization", async function () { + const custom = () => "custom SelectedItem"; + plone_registry.registerComponent({ name: DEFAULT_KEY, component: custom }); + + await Pattern.register_default_components(); + await Pattern.register_default_components(); + + expect(plone_registry.getComponent(DEFAULT_KEY).component).toBe(custom); + }); +});