@omicronenergy/oscd-shell 0.0.14 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +11 -6
  2. package/dist/landing-page/landing-page.d.ts +3 -3
  3. package/dist/landing-page/landing-page.js.map +1 -1
  4. package/dist/landing-page/landing-page.spec.js +1 -1
  5. package/dist/landing-page/landing-page.spec.js.map +1 -1
  6. package/dist/localization.js +1 -1
  7. package/dist/localization.js.map +1 -1
  8. package/dist/menus/files-menu.d.ts +1 -1
  9. package/dist/menus/plugins-menu.d.ts +5 -5
  10. package/dist/menus/plugins-menu.js +2 -2
  11. package/dist/menus/plugins-menu.js.map +1 -1
  12. package/dist/oscd-shell-design-tokens.d.ts +1 -1
  13. package/dist/oscd-shell-design-tokens.js +12 -1
  14. package/dist/oscd-shell-design-tokens.js.map +1 -1
  15. package/dist/oscd-shell.d.ts +45 -17
  16. package/dist/oscd-shell.js +36 -12
  17. package/dist/oscd-shell.js.map +1 -1
  18. package/dist/side-panel/build-tree-nodes.spec.js.map +1 -1
  19. package/dist/side-panel/editor-plugins-panel.d.ts +6 -6
  20. package/dist/side-panel/editor-plugins-panel.js +6 -0
  21. package/dist/side-panel/editor-plugins-panel.js.map +1 -1
  22. package/dist/side-panel/editor-plugins-panel.spec.js +10 -7
  23. package/dist/side-panel/editor-plugins-panel.spec.js.map +1 -1
  24. package/dist/utils/plugin-utils.d.ts +28 -21
  25. package/dist/utils/plugin-utils.js +12 -19
  26. package/dist/utils/plugin-utils.js.map +1 -1
  27. package/dist/utils/plugin-utils.spec.js +30 -5
  28. package/dist/utils/plugin-utils.spec.js.map +1 -1
  29. package/dist/utils/testing/plugin-helpers.d.ts +6 -15
  30. package/dist/utils/testing/plugin-helpers.js +3 -3
  31. package/dist/utils/testing/plugin-helpers.js.map +1 -1
  32. package/dist/utils/testing/test-plugins.js +1 -1
  33. package/dist/utils/testing/test-plugins.js.map +1 -1
  34. package/package.json +18 -58
@@ -1 +1 @@
1
- {"version":3,"file":"editor-plugins-panel.spec.js","sourceRoot":"","sources":["../../src/side-panel/editor-plugins-panel.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture, html } from '@open-wc/testing';\nimport type { OscdShell } from '../oscd-shell.js';\nimport '../oscd-shell.js';\nimport { EditorPluginsPanel } from './editor-plugins-panel.js';\nimport type { PluginEntry, PluginGroup } from '../oscd-shell.js';\nimport { createTestDocs } from '../utils/testing/test-doc-helpers.js';\nimport { sampleEditorPlugins } from '../utils/testing/plugin-helpers.js';\nimport { TestMenuPlugin1 } from '../utils/testing/test-plugins.js';\nimport type { OscdMenu } from '@omicronenergy/oscd-ui/menu/OscdMenu.js';\nimport type { OscdTree } from '@omicronenergy/oscd-ui/tree/OscdTree.js';\nimport { OscdOutlinedSearchField } from '@omicronenergy/oscd-ui/search-field/OscdOutlinedSearchField.js';\nimport sinon from 'sinon';\n\n// A grouped editor fixture, used to exercise the collapsed rail's group\n// flyout (rendered only when `editors` contains a `PluginGroup`).\nconst groupedEditorPlugins: (PluginEntry | PluginGroup)[] = [\n {\n name: 'Grouped Editors',\n icon: 'folder',\n plugins: [\n {\n name: 'Grouped Editor 1',\n tagName: 'test-grouped-editor-1',\n icon: 'coronavirus',\n },\n {\n name: 'Grouped Editor 2',\n tagName: 'test-grouped-editor-2',\n icon: 'coronavirus',\n },\n ],\n },\n];\n\nconst findPanelToggleButton = (pluginsMenu: EditorPluginsPanel) => {\n const toggleButton = pluginsMenu.shadowRoot?.querySelector(\n '.toggle-button',\n ) as HTMLElement;\n expect(toggleButton).to.exist;\n return toggleButton;\n};\n\nconst isPanelExpanded = (pluginsMenu: EditorPluginsPanel) => {\n return pluginsMenu.hasAttribute('expanded') && pluginsMenu.expanded;\n};\n\ndescribe('editor-plugins-panel', () => {\n let oscdShell: OscdShell;\n let editorPluginsPanel: EditorPluginsPanel;\n let docs: Record<string, XMLDocument>;\n const extraShells: OscdShell[] = [];\n\n const LS_KEYS = {\n expanded: 'editor-plugins-panel:expanded',\n expandedIds: 'editor-plugins-panel:expandedIds',\n pinnedPluginIds: 'editor-plugins-panel:pinnedPluginIds',\n pinnedExpanded: 'editor-plugins-panel:pinnedExpanded',\n };\n\n // Mounts a brand-new shell + panel, simulating a page reload. Any pre-seeded\n // localStorage is therefore read by a freshly constructed panel.\n const mountFreshPanel = async (): Promise<EditorPluginsPanel> => {\n const shell = <OscdShell>(\n await fixture(\n html`<oscd-shell\n .docs=${docs}\n docName=${Object.keys(docs)[0]}\n ></oscd-shell>`,\n )\n );\n shell.plugins = { editor: sampleEditorPlugins };\n const panel = shell.shadowRoot!.querySelector('editor-plugins-panel')!;\n await shell.updateComplete;\n await panel.updateComplete;\n extraShells.push(shell);\n return panel;\n };\n\n beforeEach(async () => {\n docs = createTestDocs(1);\n oscdShell = <OscdShell>(\n await fixture(\n html`<oscd-shell\n .docs=${docs}\n docName=${Object.keys(docs)[0]}\n ></oscd-shell>`,\n )\n );\n if (!oscdShell.registry?.get('test-menu-plugin1')) {\n oscdShell.registry?.define('test-menu-plugin1', TestMenuPlugin1);\n }\n oscdShell.plugins = {\n editor: sampleEditorPlugins,\n };\n editorPluginsPanel = oscdShell.shadowRoot!.querySelector(\n 'editor-plugins-panel',\n )!;\n await oscdShell.updateComplete;\n await editorPluginsPanel.updateComplete;\n });\n\n afterEach(() => {\n oscdShell.remove();\n while (extraShells.length) {\n extraShells.pop()!.remove();\n }\n Object.values(LS_KEYS).forEach(key => localStorage.removeItem(key));\n });\n\n it('collapses on toggle button click when already expanded ', async () => {\n const toggleButton = findPanelToggleButton(editorPluginsPanel);\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n toggleButton.click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.false;\n });\n\n it('expands on toggle button click when already collapsed', async () => {\n findPanelToggleButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.false;\n // The control is a different element in the collapsed rail, so re-query it.\n findPanelToggleButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n });\n\n it('initially appears expanded if no value found in localStorage', async () => {\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n });\n\n it('uses value stored in localstorage initially', async () => {\n localStorage.setItem(LS_KEYS.expanded, JSON.stringify(false));\n const editorPluginsPanel2 = await mountFreshPanel();\n expect(isPanelExpanded(editorPluginsPanel2)).to.be.false;\n });\n\n it('saves expanded/collapsed state (when toggled) in localStorage', async () => {\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n\n findPanelToggleButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.false;\n expect(localStorage.getItem(LS_KEYS.expanded)).to.equal(\n JSON.stringify(false),\n );\n\n // Re-query: the collapsed rail renders a different toggle element.\n findPanelToggleButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n expect(localStorage.getItem(LS_KEYS.expanded)).to.equal(\n JSON.stringify(true),\n );\n });\n\n describe('restores persisted state on reload (fresh mount)', () => {\n it('hydrates expandedIds from localStorage and does not clobber it', async () => {\n const seeded = ['group:0:Communication', 'group:1:Advanced'];\n localStorage.setItem(LS_KEYS.expandedIds, JSON.stringify(seeded));\n\n const panel = await mountFreshPanel();\n\n expect(panel.expandedIds).to.deep.equal(seeded);\n expect(localStorage.getItem(LS_KEYS.expandedIds)).to.equal(\n JSON.stringify(seeded),\n );\n });\n\n it('hydrates pinnedPluginIds from localStorage and does not clobber it', async () => {\n const seeded = ['oscd-example-editor', 'oscd-other-editor'];\n localStorage.setItem(LS_KEYS.pinnedPluginIds, JSON.stringify(seeded));\n\n const panel = await mountFreshPanel();\n\n expect(panel.pinnedPluginIds).to.deep.equal(seeded);\n expect(localStorage.getItem(LS_KEYS.pinnedPluginIds)).to.equal(\n JSON.stringify(seeded),\n );\n });\n\n it('hydrates pinnedExpanded from localStorage and does not clobber it', async () => {\n const seeded = ['pinned'];\n localStorage.setItem(LS_KEYS.pinnedExpanded, JSON.stringify(seeded));\n\n const panel = await mountFreshPanel();\n\n expect(panel.pinnedExpanded).to.deep.equal(seeded);\n expect(localStorage.getItem(LS_KEYS.pinnedExpanded)).to.equal(\n JSON.stringify(seeded),\n );\n });\n\n it('hydrates expanded (collapsed) state from localStorage and does not clobber it', async () => {\n localStorage.setItem(LS_KEYS.expanded, JSON.stringify(false));\n\n const panel = await mountFreshPanel();\n\n expect(panel.expanded).to.be.false;\n expect(localStorage.getItem(LS_KEYS.expanded)).to.equal(\n JSON.stringify(false),\n );\n });\n });\n\n const getSearchField = () =>\n editorPluginsPanel.shadowRoot!.querySelector(\n 'oscd-outlined-search-field',\n ) as OscdOutlinedSearchField;\n\n const setSearch = async (value: string) => {\n const field = getSearchField();\n field.value = value;\n field.dispatchEvent(new Event('input'));\n await editorPluginsPanel.updateComplete;\n };\n\n it('filters editors by their (source) name when searching', async () => {\n await setSearch('Plugin 2');\n expect(editorPluginsPanel.editorTreeNodes).to.have.lengthOf(1);\n expect(editorPluginsPanel.editorTreeNodes[0].name).to.equal(\n 'Test Editor Plugin 2',\n );\n });\n\n it('filters editors by their localized label when a locale is set', async () => {\n editorPluginsPanel.locale = 'de';\n await editorPluginsPanel.updateComplete;\n await setSearch('Erweiterung');\n // Both sample editors share the German label \"…Erweiterung\" only on the\n // first entry; searching the German term must still match it.\n const names = editorPluginsPanel.editorTreeNodes.map(n => n.name);\n expect(names).to.include('Test Editor Plugin');\n });\n\n it('highlights editor nodes with arrow navigation without selecting them', async () => {\n const field = getSearchField();\n field.dispatchEvent(\n new KeyboardEvent('keydown', {\n key: 'ArrowDown',\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.focusedTree).to.equal('pinned');\n expect(\n (editorPluginsPanel.shadowRoot!.querySelector('.pinned-tree') as OscdTree)\n .activeId,\n ).to.equal('pinned');\n expect(editorPluginsPanel.selectedEditor).to.exist;\n });\n\n it('preserves the search query when the field is refocused', async () => {\n await setSearch('Plugin 2');\n const field = getSearchField();\n\n editorPluginsPanel.focusSearch();\n await editorPluginsPanel.updateComplete;\n\n expect(field.value).to.equal('Plugin 2');\n expect(editorPluginsPanel.editorTreeNodes).to.have.lengthOf(1);\n });\n\n it('hands search-field arrow navigation to the editor tree when searching', async () => {\n await setSearch('Plugin');\n const field = getSearchField();\n field.dispatchEvent(\n new KeyboardEvent('keydown', {\n key: 'ArrowDown',\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n expect(editorPluginsPanel.focusedTree).to.equal('editors');\n expect(editorsTree.activeId).to.equal(editorsTree.getFirstNodeId());\n });\n\n it('starts search navigation at the last editor for ArrowUp', async () => {\n await setSearch('Plugin');\n const field = getSearchField();\n field.dispatchEvent(\n new KeyboardEvent('keydown', {\n key: 'ArrowUp',\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n expect(editorsTree.activeId).to.equal(editorsTree.getLastNodeId());\n });\n\n it('selects the only search result when Enter is pressed in the search field', async () => {\n await setSearch('Plugin 2');\n const field = getSearchField();\n let selected: PluginEntry | undefined;\n editorPluginsPanel.addEventListener('editor-select', (event: Event) => {\n selected = (event as CustomEvent).detail.editor;\n });\n field.focus();\n field.dispatchEvent(\n new KeyboardEvent('keydown', {\n key: 'Enter',\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(selected).to.exist;\n });\n\n it('does nothing on search-field Enter when multiple results remain', async () => {\n await setSearch('Plugin');\n const field = getSearchField();\n let selected = false;\n editorPluginsPanel.addEventListener('editor-select', () => {\n selected = true;\n });\n field.focus();\n field.dispatchEvent(\n new KeyboardEvent('keydown', {\n key: 'Enter',\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(selected).to.be.false;\n });\n\n it('updates the focused tree when the tree emits active-changed or receives focus', async () => {\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n editorsTree.dispatchEvent(new Event('focusin', { bubbles: true }));\n expect(editorPluginsPanel.focusedTree).to.equal('editors');\n\n editorsTree.dispatchEvent(\n new CustomEvent('active-changed', {\n detail: { activeId: editorsTree.activeId },\n bubbles: true,\n }),\n );\n expect(editorPluginsPanel.focusedTree).to.equal('editors');\n });\n\n it('does not focus a tree when the handoff has no active node', () => {\n // @ts-expect-error focusTree is private; exercise its null-node guard.\n expect(editorPluginsPanel.focusTree('editors', null)).to.be.false;\n });\n\n it('transfers focus from the pinned tree to the editor tree at its boundary', async () => {\n const pinnedTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.pinned-tree',\n ) as OscdTree;\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n pinnedTree.activeId = pinnedTree.getLastNodeId();\n const focusSpy = sinon.spy(editorsTree, 'focus');\n\n pinnedTree.dispatchEvent(\n new CustomEvent('navigation-boundary', {\n detail: { direction: 'last' },\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.focusedTree).to.equal('editors');\n expect(editorsTree.activeId).to.equal(editorsTree.getFirstNodeId());\n expect(focusSpy.called).to.be.true;\n focusSpy.restore();\n });\n\n it('transfers focus back to the pinned tree at the editor boundary', async () => {\n const pinnedTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.pinned-tree',\n ) as OscdTree;\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n editorPluginsPanel.focusedTree = 'editors';\n editorsTree.activeId = editorsTree.getFirstNodeId();\n\n editorsTree.dispatchEvent(\n new CustomEvent('navigation-boundary', {\n detail: { direction: 'first' },\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.focusedTree).to.equal('pinned');\n expect(pinnedTree.activeId).to.equal(pinnedTree.getLastNodeId());\n });\n\n it('does not transfer across the editor boundary while searching', async () => {\n await setSearch('Plugin');\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n editorPluginsPanel.focusedTree = 'editors';\n editorsTree.dispatchEvent(\n new CustomEvent('navigation-boundary', {\n detail: { direction: 'first' },\n bubbles: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n expect(editorPluginsPanel.focusedTree).to.equal('editors');\n });\n\n it('pins and unpins an editor, persisting the ids to localStorage', async () => {\n const tagName = (oscdShell.plugins.editor[0] as PluginEntry).tagName;\n\n editorPluginsPanel.togglePin(tagName);\n await editorPluginsPanel.updateComplete;\n expect(editorPluginsPanel.pinnedPluginIds).to.include(tagName);\n expect(\n localStorage.getItem('editor-plugins-panel:pinnedPluginIds'),\n ).to.contain(tagName);\n\n editorPluginsPanel.togglePin(tagName);\n await editorPluginsPanel.updateComplete;\n expect(editorPluginsPanel.pinnedPluginIds).to.not.include(tagName);\n });\n\n it('reflects the selected editor into the pinned tree selectedIds', async () => {\n const editor = oscdShell.plugins.editor[0] as PluginEntry;\n\n editorPluginsPanel.togglePin(editor.tagName);\n editorPluginsPanel.selectedEditor = editor;\n await editorPluginsPanel.updateComplete;\n\n const pinnedTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.tree-container oscd-tree:not(.editors-tree)',\n ) as unknown as { selectedIds: string[] };\n expect(pinnedTree.selectedIds).to.deep.equal([editor.tagName]);\n });\n\n it('selects an editor chosen from the pinned tree', async () => {\n const editor = oscdShell.plugins.editor[0] as PluginEntry;\n editorPluginsPanel.togglePin(editor.tagName);\n await editorPluginsPanel.updateComplete;\n\n let selected: PluginEntry | undefined;\n editorPluginsPanel.addEventListener('editor-select', (event: Event) => {\n selected = (event as CustomEvent).detail.editor;\n });\n\n const pinnedTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.tree-container oscd-tree:not(.editors-tree)',\n )!;\n pinnedTree.dispatchEvent(\n new CustomEvent('selected-ids-changed', {\n detail: { selectedIds: [editor.tagName] },\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(selected?.tagName).to.equal(editor.tagName);\n });\n\n it('ignores an editor selection with no id', async () => {\n let dispatched = false;\n editorPluginsPanel.addEventListener('editor-select', () => {\n dispatched = true;\n });\n editorPluginsPanel.selectEditor([]);\n expect(dispatched).to.be.false;\n });\n\n it('ignores an unknown editor selection id', async () => {\n let dispatched = false;\n editorPluginsPanel.addEventListener('editor-select', () => {\n dispatched = true;\n });\n editorPluginsPanel.selectEditor(['unknown-editor']);\n expect(dispatched).to.be.false;\n });\n\n describe('transient search mode (collapsed rail)', () => {\n const collapse = async (panel: EditorPluginsPanel) => {\n findPanelToggleButton(panel).click();\n await panel.updateComplete;\n expect(isPanelExpanded(panel)).to.be.false;\n };\n\n const findRailSearchButton = (panel: EditorPluginsPanel) =>\n panel.shadowRoot!.querySelector(\n '.rail oscd-icon-button.rail-item',\n ) as HTMLElement;\n\n it('opens the panel without persisting `expanded` when the rail search icon is clicked', async () => {\n await collapse(editorPluginsPanel);\n expect(localStorage.getItem('editor-plugins-panel:expanded')).to.equal(\n JSON.stringify(false),\n );\n\n findRailSearchButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.hasAttribute('search-mode')).to.be.true;\n expect(editorPluginsPanel.shadowRoot!.querySelector('.tree-container')).to\n .exist;\n // Still collapsed as far as persisted state is concerned.\n expect(editorPluginsPanel.expanded).to.be.false;\n expect(localStorage.getItem('editor-plugins-panel:expanded')).to.equal(\n JSON.stringify(false),\n );\n });\n\n it('focuses the search field on entering search mode', async () => {\n await collapse(editorPluginsPanel);\n const focusSpy = sinon.spy(OscdOutlinedSearchField.prototype, 'focus');\n\n findRailSearchButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n // The focus() call is chained off `updateComplete.then(...)`; await it\n // again so that microtask has a chance to run.\n await editorPluginsPanel.updateComplete;\n\n expect(focusSpy.called).to.be.true;\n focusSpy.restore();\n });\n\n it('exits search mode (and clears the query) on Escape', async () => {\n await collapse(editorPluginsPanel);\n findRailSearchButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n await setSearch('Plugin 2');\n expect(editorPluginsPanel.searchValue).to.equal('Plugin 2');\n\n editorPluginsPanel.dispatchEvent(\n new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.hasAttribute('search-mode')).to.be.false;\n expect(editorPluginsPanel.searchValue).to.equal('');\n });\n\n it('ignores Escape when not in search mode', async () => {\n await collapse(editorPluginsPanel);\n editorPluginsPanel.dispatchEvent(\n new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }),\n );\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.false;\n });\n\n it('exits search mode on selecting an editor, without persisting expanded', async () => {\n await collapse(editorPluginsPanel);\n findRailSearchButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n\n let selected: PluginEntry | undefined;\n editorPluginsPanel.addEventListener('editor-select', (event: Event) => {\n selected = (event as CustomEvent).detail.editor;\n });\n const tagName = (oscdShell.plugins.editor[0] as PluginEntry).tagName;\n editorPluginsPanel.selectEditor([tagName]);\n await editorPluginsPanel.updateComplete;\n\n expect(selected?.tagName).to.equal(tagName);\n expect(editorPluginsPanel.hasAttribute('search-mode')).to.be.false;\n expect(editorPluginsPanel.expanded).to.be.false;\n });\n\n it('selects a root editor from the collapsed rail', async () => {\n await collapse(editorPluginsPanel);\n\n const editor = oscdShell.plugins.editor[0] as PluginEntry;\n const railEditors = editorPluginsPanel.shadowRoot!.querySelectorAll(\n '.rail > oscd-icon-button.rail-item',\n );\n const railEditor = railEditors[2] as HTMLElement;\n expect(!!railEditor).to.be.true;\n let selected: PluginEntry | undefined;\n editorPluginsPanel.addEventListener('editor-select', (event: Event) => {\n selected = (event as CustomEvent).detail.editor;\n });\n\n railEditor.dispatchEvent(\n new MouseEvent('click', { bubbles: true, composed: true }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(selected?.tagName).to.equal(editor.tagName);\n });\n });\n\n describe('pinned/editors tree expand-state persistence', () => {\n it('persists the pinned tree expanded ids on `expanded-ids-changed`', async () => {\n const tagName = (oscdShell.plugins.editor[0] as PluginEntry).tagName;\n editorPluginsPanel.togglePin(tagName);\n await editorPluginsPanel.updateComplete;\n\n const pinnedTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.tree-container oscd-tree:not(.editors-tree)',\n )!;\n pinnedTree.dispatchEvent(\n new CustomEvent('expanded-ids-changed', {\n detail: { expandedIds: ['pinned'] },\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.pinnedExpanded).to.deep.equal(['pinned']);\n });\n\n it('persists the editors tree expanded ids on `expanded-ids-changed`', async () => {\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.tree-container oscd-tree.editors-tree',\n )!;\n editorsTree.dispatchEvent(\n new CustomEvent('expanded-ids-changed', {\n detail: { expandedIds: ['group:0:Communication'] },\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.expandedIds).to.deep.equal([\n 'group:0:Communication',\n ]);\n });\n });\n\n describe('collapsed rail group flyout', () => {\n let groupedShell: OscdShell;\n let groupedPanel: EditorPluginsPanel;\n\n beforeEach(async () => {\n groupedShell = <OscdShell>(\n await fixture(\n html`<oscd-shell\n .docs=${docs}\n docName=${Object.keys(docs)[0]}\n ></oscd-shell>`,\n )\n );\n groupedShell.plugins = { editor: groupedEditorPlugins };\n groupedPanel = groupedShell.shadowRoot!.querySelector(\n 'editor-plugins-panel',\n )!;\n await groupedShell.updateComplete;\n await groupedPanel.updateComplete;\n // Start from the collapsed rail, where the group flyout lives.\n findPanelToggleButton(groupedPanel).click();\n await groupedPanel.updateComplete;\n extraShells.push(groupedShell);\n });\n\n const findGroupRailButton = () =>\n groupedPanel.shadowRoot!.querySelector('#rail-group-0') as HTMLElement;\n\n const findGroupFlyoutMenu = () =>\n groupedPanel.shadowRoot!.querySelector(\n 'oscd-menu.rail-flyout[data-flyout=\"group-0\"]',\n ) as OscdMenu;\n\n it('shows a disabled placeholder in the empty pinned flyout', () => {\n const pinnedFlyout = groupedPanel.shadowRoot!.querySelector(\n 'oscd-menu.rail-flyout[data-flyout=\"pinned\"]',\n );\n const placeholder = pinnedFlyout?.querySelector('oscd-menu-item');\n\n expect(placeholder?.hasAttribute('disabled')).to.be.true;\n expect(placeholder?.textContent).to.contain(\n 'Items you pin will appear here',\n );\n });\n\n it('opens the group flyout menu on rail icon click', async () => {\n const menu = findGroupFlyoutMenu();\n expect(menu.open).to.be.false;\n\n findGroupRailButton().click();\n await groupedPanel.updateComplete;\n\n expect(menu.open).to.be.true;\n });\n\n it('closes the group flyout menu on a second rail icon click', async () => {\n findGroupRailButton().click();\n await groupedPanel.updateComplete;\n expect(findGroupFlyoutMenu().open).to.be.true;\n\n findGroupRailButton().click();\n await groupedPanel.updateComplete;\n\n expect(findGroupFlyoutMenu().open).to.be.false;\n });\n\n it('does nothing when toggling a flyout with no matching anchor', () => {\n expect(() =>\n // @ts-expect-error toggleFlyout is private; exercised directly to\n // cover the defensive \"no matching anchor\" guard.\n groupedPanel.toggleFlyout('unknown-group'),\n ).to.not.throw();\n });\n\n it('dispatches editor-select when a flyout item is clicked', async () => {\n findGroupRailButton().click();\n await groupedPanel.updateComplete;\n\n let selected: PluginEntry | undefined;\n groupedPanel.addEventListener('editor-select', (event: Event) => {\n selected = (event as CustomEvent).detail.editor;\n });\n\n const flyoutItem = findGroupFlyoutMenu().querySelector('oscd-menu-item');\n flyoutItem!.dispatchEvent(new Event('click', { bubbles: true }));\n await groupedPanel.updateComplete;\n\n expect(selected?.tagName).to.equal('test-grouped-editor-1');\n });\n\n it('toggles a group when its tree selection is committed', async () => {\n findPanelToggleButton(groupedPanel).click();\n await groupedPanel.updateComplete;\n\n const editorsTree = groupedPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n editorsTree.dispatchEvent(\n new CustomEvent('selected-ids-changed', {\n detail: { selectedIds: ['group:0:Grouped Editors'] },\n bubbles: true,\n }),\n );\n await groupedPanel.updateComplete;\n\n expect(editorsTree.expandedIds).to.include('group:0:Grouped Editors');\n });\n\n it('marks the rail group icon active when one of its plugins is selected', async () => {\n groupedPanel.selectedEditor = {\n name: 'Grouped Editor 1',\n tagName: 'test-grouped-editor-1',\n icon: 'coronavirus',\n };\n await groupedPanel.updateComplete;\n\n expect(findGroupRailButton().classList.contains('active')).to.be.true;\n });\n });\n});\n"]}
1
+ {"version":3,"file":"editor-plugins-panel.spec.js","sourceRoot":"","sources":["../../src/side-panel/editor-plugins-panel.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture, html } from '@open-wc/testing';\nimport type { OscdShell } from '../oscd-shell.js';\nimport '../oscd-shell.js';\nimport { EditorPluginsPanel } from './editor-plugins-panel.js';\nimport type { ResolvedPlugin, PluginGroup } from '../oscd-shell.js';\nimport { createTestDocs } from '../utils/testing/test-doc-helpers.js';\nimport { sampleEditorPlugins } from '../utils/testing/plugin-helpers.js';\nimport { TestMenuPlugin1 } from '../utils/testing/test-plugins.js';\nimport type { OscdMenu } from '@omicronenergy/oscd-ui/menu/OscdMenu.js';\nimport type { OscdTree } from '@omicronenergy/oscd-ui/tree/OscdTree.js';\nimport { OscdOutlinedSearchField } from '@omicronenergy/oscd-ui/search-field/OscdOutlinedSearchField.js';\nimport sinon from 'sinon';\n\n// A grouped editor fixture, used to exercise the collapsed rail's group\n// flyout (rendered only when `editors` contains a `PluginGroup<ResolvedPlugin>`).\nconst groupedEditorPlugins: (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[] = [\n {\n name: 'Grouped Editors',\n icon: 'folder',\n plugins: [\n {\n name: 'Grouped Editor 1',\n tagName: 'test-grouped-editor-1',\n icon: 'coronavirus',\n },\n {\n name: 'Grouped Editor 2',\n tagName: 'test-grouped-editor-2',\n icon: 'coronavirus',\n },\n ],\n },\n];\n\nconst findPanelToggleButton = (pluginsMenu: EditorPluginsPanel) => {\n const toggleButton = pluginsMenu.shadowRoot?.querySelector(\n '.toggle-button',\n ) as HTMLElement;\n expect(toggleButton).to.exist;\n return toggleButton;\n};\n\nconst isPanelExpanded = (pluginsMenu: EditorPluginsPanel) => {\n return pluginsMenu.hasAttribute('expanded') && pluginsMenu.expanded;\n};\n\ndescribe('editor-plugins-panel', () => {\n let oscdShell: OscdShell;\n let editorPluginsPanel: EditorPluginsPanel;\n let docs: Record<string, XMLDocument>;\n const extraShells: OscdShell[] = [];\n\n const LS_KEYS = {\n expanded: 'editor-plugins-panel:expanded',\n expandedIds: 'editor-plugins-panel:expandedIds',\n pinnedPluginIds: 'editor-plugins-panel:pinnedPluginIds',\n pinnedExpanded: 'editor-plugins-panel:pinnedExpanded',\n };\n\n // Mounts a brand-new shell + panel, simulating a page reload. Any pre-seeded\n // localStorage is therefore read by a freshly constructed panel.\n const mountFreshPanel = async (): Promise<EditorPluginsPanel> => {\n const shell = <OscdShell>(\n await fixture(\n html`<oscd-shell\n .docs=${docs}\n docName=${Object.keys(docs)[0]}\n ></oscd-shell>`,\n )\n );\n shell.plugins = { editor: sampleEditorPlugins };\n const panel = shell.shadowRoot!.querySelector('editor-plugins-panel')!;\n await shell.updateComplete;\n await panel.updateComplete;\n extraShells.push(shell);\n return panel;\n };\n\n beforeEach(async () => {\n docs = createTestDocs(1);\n oscdShell = <OscdShell>(\n await fixture(\n html`<oscd-shell\n .docs=${docs}\n docName=${Object.keys(docs)[0]}\n ></oscd-shell>`,\n )\n );\n if (!oscdShell.registry?.get('test-menu-plugin1')) {\n oscdShell.registry?.define('test-menu-plugin1', TestMenuPlugin1);\n }\n oscdShell.plugins = {\n editor: sampleEditorPlugins,\n };\n editorPluginsPanel = oscdShell.shadowRoot!.querySelector(\n 'editor-plugins-panel',\n )!;\n await oscdShell.updateComplete;\n await editorPluginsPanel.updateComplete;\n });\n\n afterEach(() => {\n oscdShell.remove();\n while (extraShells.length) {\n extraShells.pop()!.remove();\n }\n Object.values(LS_KEYS).forEach(key => localStorage.removeItem(key));\n });\n\n it('collapses on toggle button click when already expanded ', async () => {\n const toggleButton = findPanelToggleButton(editorPluginsPanel);\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n toggleButton.click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.false;\n });\n\n it('expands on toggle button click when already collapsed', async () => {\n findPanelToggleButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.false;\n // The control is a different element in the collapsed rail, so re-query it.\n findPanelToggleButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n });\n\n it('initially appears expanded if no value found in localStorage', async () => {\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n });\n\n it('uses value stored in localstorage initially', async () => {\n localStorage.setItem(LS_KEYS.expanded, JSON.stringify(false));\n const editorPluginsPanel2 = await mountFreshPanel();\n expect(isPanelExpanded(editorPluginsPanel2)).to.be.false;\n });\n\n it('saves expanded/collapsed state (when toggled) in localStorage', async () => {\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n\n findPanelToggleButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.false;\n expect(localStorage.getItem(LS_KEYS.expanded)).to.equal(\n JSON.stringify(false),\n );\n\n // Re-query: the collapsed rail renders a different toggle element.\n findPanelToggleButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n expect(localStorage.getItem(LS_KEYS.expanded)).to.equal(\n JSON.stringify(true),\n );\n });\n\n describe('restores persisted state on reload (fresh mount)', () => {\n it('hydrates expandedIds from localStorage and does not clobber it', async () => {\n const seeded = ['group:0:Communication', 'group:1:Advanced'];\n localStorage.setItem(LS_KEYS.expandedIds, JSON.stringify(seeded));\n\n const panel = await mountFreshPanel();\n\n expect(panel.expandedIds).to.deep.equal(seeded);\n expect(localStorage.getItem(LS_KEYS.expandedIds)).to.equal(\n JSON.stringify(seeded),\n );\n });\n\n it('hydrates pinnedPluginIds from localStorage and does not clobber it', async () => {\n const seeded = ['oscd-example-editor', 'oscd-other-editor'];\n localStorage.setItem(LS_KEYS.pinnedPluginIds, JSON.stringify(seeded));\n\n const panel = await mountFreshPanel();\n\n expect(panel.pinnedPluginIds).to.deep.equal(seeded);\n expect(localStorage.getItem(LS_KEYS.pinnedPluginIds)).to.equal(\n JSON.stringify(seeded),\n );\n });\n\n it('hydrates pinnedExpanded from localStorage and does not clobber it', async () => {\n const seeded = ['pinned'];\n localStorage.setItem(LS_KEYS.pinnedExpanded, JSON.stringify(seeded));\n\n const panel = await mountFreshPanel();\n\n expect(panel.pinnedExpanded).to.deep.equal(seeded);\n expect(localStorage.getItem(LS_KEYS.pinnedExpanded)).to.equal(\n JSON.stringify(seeded),\n );\n });\n\n it('hydrates expanded (collapsed) state from localStorage and does not clobber it', async () => {\n localStorage.setItem(LS_KEYS.expanded, JSON.stringify(false));\n\n const panel = await mountFreshPanel();\n\n expect(panel.expanded).to.be.false;\n expect(localStorage.getItem(LS_KEYS.expanded)).to.equal(\n JSON.stringify(false),\n );\n });\n });\n\n const getSearchField = () =>\n editorPluginsPanel.shadowRoot!.querySelector(\n 'oscd-outlined-search-field',\n ) as OscdOutlinedSearchField;\n\n const setSearch = async (value: string) => {\n const field = getSearchField();\n field.value = value;\n field.dispatchEvent(new Event('input'));\n await editorPluginsPanel.updateComplete;\n };\n\n it('filters editors by their (source) name when searching', async () => {\n await setSearch('Plugin 2');\n expect(editorPluginsPanel.editorTreeNodes).to.have.lengthOf(1);\n expect(editorPluginsPanel.editorTreeNodes[0].name).to.equal(\n 'Test Editor Plugin 2',\n );\n });\n\n it('filters editors by their localized label when a locale is set', async () => {\n editorPluginsPanel.locale = 'de';\n await editorPluginsPanel.updateComplete;\n await setSearch('Erweiterung');\n // Both sample editors share the German label \"…Erweiterung\" only on the\n // first entry; searching the German term must still match it.\n const names = editorPluginsPanel.editorTreeNodes.map(n => n.name);\n expect(names).to.include('Test Editor Plugin');\n });\n\n it('highlights editor nodes with arrow navigation without selecting them', async () => {\n const field = getSearchField();\n field.dispatchEvent(\n new KeyboardEvent('keydown', {\n key: 'ArrowDown',\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.focusedTree).to.equal('pinned');\n expect(\n (editorPluginsPanel.shadowRoot!.querySelector('.pinned-tree') as OscdTree)\n .activeId,\n ).to.equal('pinned');\n expect(editorPluginsPanel.selectedEditor).to.exist;\n });\n\n it('preserves the search query when the field is refocused', async () => {\n await setSearch('Plugin 2');\n const field = getSearchField();\n\n editorPluginsPanel.focusSearch();\n await editorPluginsPanel.updateComplete;\n\n expect(field.value).to.equal('Plugin 2');\n expect(editorPluginsPanel.editorTreeNodes).to.have.lengthOf(1);\n });\n\n it('hands search-field arrow navigation to the editor tree when searching', async () => {\n await setSearch('Plugin');\n const field = getSearchField();\n field.dispatchEvent(\n new KeyboardEvent('keydown', {\n key: 'ArrowDown',\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n expect(editorPluginsPanel.focusedTree).to.equal('editors');\n expect(editorsTree.activeId).to.equal(editorsTree.getFirstNodeId());\n });\n\n it('starts search navigation at the last editor for ArrowUp', async () => {\n await setSearch('Plugin');\n const field = getSearchField();\n field.dispatchEvent(\n new KeyboardEvent('keydown', {\n key: 'ArrowUp',\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n expect(editorsTree.activeId).to.equal(editorsTree.getLastNodeId());\n });\n\n it('selects the only search result when Enter is pressed in the search field', async () => {\n await setSearch('Plugin 2');\n const field = getSearchField();\n let selected: ResolvedPlugin | undefined;\n editorPluginsPanel.addEventListener('editor-select', (event: Event) => {\n selected = (event as CustomEvent).detail.editor;\n });\n field.focus();\n field.dispatchEvent(\n new KeyboardEvent('keydown', {\n key: 'Enter',\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(selected).to.exist;\n });\n\n it('does nothing on search-field Enter when multiple results remain', async () => {\n await setSearch('Plugin');\n const field = getSearchField();\n let selected = false;\n editorPluginsPanel.addEventListener('editor-select', () => {\n selected = true;\n });\n field.focus();\n field.dispatchEvent(\n new KeyboardEvent('keydown', {\n key: 'Enter',\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(selected).to.be.false;\n });\n\n it('updates the focused tree when the tree emits active-changed or receives focus', async () => {\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n editorsTree.dispatchEvent(new Event('focusin', { bubbles: true }));\n expect(editorPluginsPanel.focusedTree).to.equal('editors');\n\n editorsTree.dispatchEvent(\n new CustomEvent('active-changed', {\n detail: { activeId: editorsTree.activeId },\n bubbles: true,\n }),\n );\n expect(editorPluginsPanel.focusedTree).to.equal('editors');\n });\n\n it('does not focus a tree when the handoff has no active node', () => {\n // @ts-expect-error focusTree is private; exercise its null-node guard.\n expect(editorPluginsPanel.focusTree('editors', null)).to.be.false;\n });\n\n it('transfers focus from the pinned tree to the editor tree at its boundary', async () => {\n const pinnedTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.pinned-tree',\n ) as OscdTree;\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n pinnedTree.activeId = pinnedTree.getLastNodeId();\n const focusSpy = sinon.spy(editorsTree, 'focus');\n\n pinnedTree.dispatchEvent(\n new CustomEvent('navigation-boundary', {\n detail: { direction: 'last' },\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.focusedTree).to.equal('editors');\n expect(editorsTree.activeId).to.equal(editorsTree.getFirstNodeId());\n expect(focusSpy.called).to.be.true;\n focusSpy.restore();\n });\n\n it('transfers focus back to the pinned tree at the editor boundary', async () => {\n const pinnedTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.pinned-tree',\n ) as OscdTree;\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n editorPluginsPanel.focusedTree = 'editors';\n editorsTree.activeId = editorsTree.getFirstNodeId();\n\n editorsTree.dispatchEvent(\n new CustomEvent('navigation-boundary', {\n detail: { direction: 'first' },\n bubbles: true,\n composed: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.focusedTree).to.equal('pinned');\n expect(pinnedTree.activeId).to.equal(pinnedTree.getLastNodeId());\n });\n\n it('does not transfer across the editor boundary while searching', async () => {\n await setSearch('Plugin');\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n editorPluginsPanel.focusedTree = 'editors';\n editorsTree.dispatchEvent(\n new CustomEvent('navigation-boundary', {\n detail: { direction: 'first' },\n bubbles: true,\n }),\n );\n await editorPluginsPanel.updateComplete;\n expect(editorPluginsPanel.focusedTree).to.equal('editors');\n });\n\n it('pins and unpins an editor, persisting the ids to localStorage', async () => {\n const tagName = (oscdShell._resolvedPlugins.editor[0] as ResolvedPlugin)\n .tagName;\n\n editorPluginsPanel.togglePin(tagName);\n await editorPluginsPanel.updateComplete;\n expect(editorPluginsPanel.pinnedPluginIds).to.include(tagName);\n expect(\n localStorage.getItem('editor-plugins-panel:pinnedPluginIds'),\n ).to.contain(tagName);\n\n editorPluginsPanel.togglePin(tagName);\n await editorPluginsPanel.updateComplete;\n expect(editorPluginsPanel.pinnedPluginIds).to.not.include(tagName);\n });\n\n it('reflects the selected editor into the pinned tree selectedIds', async () => {\n const editor = oscdShell._resolvedPlugins.editor[0] as ResolvedPlugin;\n\n editorPluginsPanel.togglePin(editor.tagName);\n editorPluginsPanel.selectedEditor = editor;\n await editorPluginsPanel.updateComplete;\n\n const pinnedTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.tree-container oscd-tree:not(.editors-tree)',\n ) as unknown as { selectedIds: string[] };\n expect(pinnedTree.selectedIds).to.deep.equal([editor.tagName]);\n });\n\n it('selects an editor chosen from the pinned tree', async () => {\n const editor = oscdShell._resolvedPlugins.editor[0] as ResolvedPlugin;\n editorPluginsPanel.togglePin(editor.tagName);\n await editorPluginsPanel.updateComplete;\n\n let selected: ResolvedPlugin | undefined;\n editorPluginsPanel.addEventListener('editor-select', (event: Event) => {\n selected = (event as CustomEvent).detail.editor;\n });\n\n const pinnedTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.tree-container oscd-tree:not(.editors-tree)',\n )!;\n pinnedTree.dispatchEvent(\n new CustomEvent('selected-ids-changed', {\n detail: { selectedIds: [editor.tagName] },\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(selected?.tagName).to.equal(editor.tagName);\n });\n\n it('ignores an editor selection with no id', async () => {\n let dispatched = false;\n editorPluginsPanel.addEventListener('editor-select', () => {\n dispatched = true;\n });\n editorPluginsPanel.selectEditor([]);\n expect(dispatched).to.be.false;\n });\n\n it('ignores an unknown editor selection id', async () => {\n let dispatched = false;\n editorPluginsPanel.addEventListener('editor-select', () => {\n dispatched = true;\n });\n editorPluginsPanel.selectEditor(['unknown-editor']);\n expect(dispatched).to.be.false;\n });\n\n describe('transient search mode (collapsed rail)', () => {\n const collapse = async (panel: EditorPluginsPanel) => {\n findPanelToggleButton(panel).click();\n await panel.updateComplete;\n expect(isPanelExpanded(panel)).to.be.false;\n };\n\n const findRailSearchButton = (panel: EditorPluginsPanel) =>\n panel.shadowRoot!.querySelector(\n '.rail oscd-icon-button.rail-item',\n ) as HTMLElement;\n\n it('opens the panel without persisting `expanded` when the rail search icon is clicked', async () => {\n await collapse(editorPluginsPanel);\n expect(localStorage.getItem('editor-plugins-panel:expanded')).to.equal(\n JSON.stringify(false),\n );\n\n findRailSearchButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.hasAttribute('search-mode')).to.be.true;\n expect(editorPluginsPanel.shadowRoot!.querySelector('.tree-container')).to\n .exist;\n // Still collapsed as far as persisted state is concerned.\n expect(editorPluginsPanel.expanded).to.be.false;\n expect(localStorage.getItem('editor-plugins-panel:expanded')).to.equal(\n JSON.stringify(false),\n );\n });\n\n it('focuses the search field on entering search mode', async () => {\n await collapse(editorPluginsPanel);\n const focusSpy = sinon.spy(OscdOutlinedSearchField.prototype, 'focus');\n\n findRailSearchButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n // The focus() call is chained off `updateComplete.then(...)`; await it\n // again so that microtask has a chance to run.\n await editorPluginsPanel.updateComplete;\n\n expect(focusSpy.called).to.be.true;\n focusSpy.restore();\n });\n\n it('exits search mode (and clears the query) on Escape', async () => {\n await collapse(editorPluginsPanel);\n findRailSearchButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n await setSearch('Plugin 2');\n expect(editorPluginsPanel.searchValue).to.equal('Plugin 2');\n\n editorPluginsPanel.dispatchEvent(\n new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.hasAttribute('search-mode')).to.be.false;\n expect(editorPluginsPanel.searchValue).to.equal('');\n });\n\n it('ignores Escape when not in search mode', async () => {\n await collapse(editorPluginsPanel);\n editorPluginsPanel.dispatchEvent(\n new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }),\n );\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.false;\n });\n\n it('exits search mode on selecting an editor, without persisting expanded', async () => {\n await collapse(editorPluginsPanel);\n findRailSearchButton(editorPluginsPanel).click();\n await editorPluginsPanel.updateComplete;\n\n let selected: ResolvedPlugin | undefined;\n editorPluginsPanel.addEventListener('editor-select', (event: Event) => {\n selected = (event as CustomEvent).detail.editor;\n });\n const tagName = (oscdShell._resolvedPlugins.editor[0] as ResolvedPlugin)\n .tagName;\n editorPluginsPanel.selectEditor([tagName]);\n await editorPluginsPanel.updateComplete;\n\n expect(selected?.tagName).to.equal(tagName);\n expect(editorPluginsPanel.hasAttribute('search-mode')).to.be.false;\n expect(editorPluginsPanel.expanded).to.be.false;\n });\n\n it('selects a root editor from the collapsed rail', async () => {\n await collapse(editorPluginsPanel);\n\n const editor = oscdShell._resolvedPlugins.editor[0] as ResolvedPlugin;\n const railEditors = editorPluginsPanel.shadowRoot!.querySelectorAll(\n '.rail > oscd-icon-button.rail-item',\n );\n const railEditor = railEditors[2] as HTMLElement;\n expect(!!railEditor).to.be.true;\n let selected: ResolvedPlugin | undefined;\n editorPluginsPanel.addEventListener('editor-select', (event: Event) => {\n selected = (event as CustomEvent).detail.editor;\n });\n\n railEditor.dispatchEvent(\n new MouseEvent('click', { bubbles: true, composed: true }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(selected?.tagName).to.equal(editor.tagName);\n });\n });\n\n describe('pinned/editors tree expand-state persistence', () => {\n it('persists the pinned tree expanded ids on `expanded-ids-changed`', async () => {\n const tagName = (oscdShell._resolvedPlugins.editor[0] as ResolvedPlugin)\n .tagName;\n editorPluginsPanel.togglePin(tagName);\n await editorPluginsPanel.updateComplete;\n\n const pinnedTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.tree-container oscd-tree:not(.editors-tree)',\n )!;\n pinnedTree.dispatchEvent(\n new CustomEvent('expanded-ids-changed', {\n detail: { expandedIds: ['pinned'] },\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.pinnedExpanded).to.deep.equal(['pinned']);\n });\n\n it('persists the editors tree expanded ids on `expanded-ids-changed`', async () => {\n const editorsTree = editorPluginsPanel.shadowRoot!.querySelector(\n '.tree-container oscd-tree.editors-tree',\n )!;\n editorsTree.dispatchEvent(\n new CustomEvent('expanded-ids-changed', {\n detail: { expandedIds: ['group:0:Communication'] },\n }),\n );\n await editorPluginsPanel.updateComplete;\n\n expect(editorPluginsPanel.expandedIds).to.deep.equal([\n 'group:0:Communication',\n ]);\n });\n });\n\n describe('collapsed rail group flyout', () => {\n let groupedShell: OscdShell;\n let groupedPanel: EditorPluginsPanel;\n\n beforeEach(async () => {\n groupedShell = <OscdShell>(\n await fixture(\n html`<oscd-shell\n .docs=${docs}\n docName=${Object.keys(docs)[0]}\n ></oscd-shell>`,\n )\n );\n groupedShell.plugins = { editor: groupedEditorPlugins };\n groupedPanel = groupedShell.shadowRoot!.querySelector(\n 'editor-plugins-panel',\n )!;\n await groupedShell.updateComplete;\n await groupedPanel.updateComplete;\n // Start from the collapsed rail, where the group flyout lives.\n findPanelToggleButton(groupedPanel).click();\n await groupedPanel.updateComplete;\n extraShells.push(groupedShell);\n });\n\n const findGroupRailButton = () =>\n groupedPanel.shadowRoot!.querySelector('#rail-group-0') as HTMLElement;\n\n const findGroupFlyoutMenu = () =>\n groupedPanel.shadowRoot!.querySelector(\n 'oscd-menu.rail-flyout[data-flyout=\"group-0\"]',\n ) as OscdMenu;\n\n it('shows a disabled placeholder in the empty pinned flyout', () => {\n const pinnedFlyout = groupedPanel.shadowRoot!.querySelector(\n 'oscd-menu.rail-flyout[data-flyout=\"pinned\"]',\n );\n const placeholder = pinnedFlyout?.querySelector('oscd-menu-item');\n\n expect(placeholder?.hasAttribute('disabled')).to.be.true;\n expect(placeholder?.textContent).to.contain(\n 'Items you pin will appear here',\n );\n });\n\n it('opens the group flyout menu on rail icon click', async () => {\n const menu = findGroupFlyoutMenu();\n expect(menu.open).to.be.false;\n\n findGroupRailButton().click();\n await groupedPanel.updateComplete;\n\n expect(menu.open).to.be.true;\n });\n\n it('closes the group flyout menu on a second rail icon click', async () => {\n findGroupRailButton().click();\n await groupedPanel.updateComplete;\n expect(findGroupFlyoutMenu().open).to.be.true;\n\n findGroupRailButton().click();\n await groupedPanel.updateComplete;\n\n expect(findGroupFlyoutMenu().open).to.be.false;\n });\n\n it('does nothing when toggling a flyout with no matching anchor', () => {\n expect(() =>\n // @ts-expect-error toggleFlyout is private; exercised directly to\n // cover the defensive \"no matching anchor\" guard.\n groupedPanel.toggleFlyout('unknown-group'),\n ).to.not.throw();\n });\n\n it('dispatches editor-select when a flyout item is clicked', async () => {\n findGroupRailButton().click();\n await groupedPanel.updateComplete;\n\n let selected: ResolvedPlugin | undefined;\n groupedPanel.addEventListener('editor-select', (event: Event) => {\n selected = (event as CustomEvent).detail.editor;\n });\n\n const flyoutItem = findGroupFlyoutMenu().querySelector('oscd-menu-item');\n flyoutItem!.dispatchEvent(new Event('click', { bubbles: true }));\n await groupedPanel.updateComplete;\n\n expect(selected?.tagName).to.equal('test-grouped-editor-1');\n });\n\n it('toggles a group when its tree selection is committed', async () => {\n findPanelToggleButton(groupedPanel).click();\n await groupedPanel.updateComplete;\n\n const editorsTree = groupedPanel.shadowRoot!.querySelector(\n '.editors-tree',\n ) as OscdTree;\n editorsTree.dispatchEvent(\n new CustomEvent('selected-ids-changed', {\n detail: { selectedIds: ['group:0:Grouped Editors'] },\n bubbles: true,\n }),\n );\n await groupedPanel.updateComplete;\n\n expect(editorsTree.expandedIds).to.include('group:0:Grouped Editors');\n });\n\n it('marks the rail group icon active when one of its plugins is selected', async () => {\n groupedPanel.selectedEditor = {\n name: 'Grouped Editor 1',\n tagName: 'test-grouped-editor-1',\n icon: 'coronavirus',\n };\n await groupedPanel.updateComplete;\n\n expect(findGroupRailButton().classList.contains('active')).to.be.true;\n });\n });\n});\n"]}
@@ -1,13 +1,12 @@
1
- import { PluginBase, PluginEntry, PluginGroup, SourcedPluginEntry } from '../oscd-shell.js';
2
- export type AnyPluginEntry = PluginEntry | SourcedPluginEntry;
1
+ import { PluginMetadata, PluginGroup, ResolvedPlugin, OscdPlugin, SourcedPlugin, TaggedPlugin } from '../oscd-shell.js';
3
2
  /**
4
3
  * Helper fn to filter root plugins and grouped plugins, whilst preserving the structure.
5
4
  */
6
- export declare function filterPlugins(pluginItems: (PluginEntry | PluginGroup<PluginEntry>)[], predicate: (plugin: PluginEntry) => boolean): (PluginEntry | PluginGroup<PluginEntry>)[];
5
+ export declare function filterPlugins(pluginItems: (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[], predicate: (plugin: ResolvedPlugin) => boolean): (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[];
7
6
  /**
8
7
  * Returns a flattened array of all plugin entries from a given PluginSet, including those nested within PluginGroups.
9
8
  */
10
- export declare function flattenPluginEntries<P extends PluginBase = PluginEntry>(pluginSet: (P | PluginGroup<P>)[]): P[];
9
+ export declare function flattenPluginEntries<P extends PluginMetadata = ResolvedPlugin>(pluginSet: (P | PluginGroup<P>)[]): P[];
11
10
  /**
12
11
  * Filters plugins by a search term, matching (case-insensitively) against the
13
12
  * leaf plugin names only - group names are intentionally not matched. When a
@@ -16,38 +15,46 @@ export declare function flattenPluginEntries<P extends PluginBase = PluginEntry>
16
15
  * is preserved and empty groups are dropped. An empty or whitespace-only term
17
16
  * returns the plugins unchanged.
18
17
  */
19
- export declare function filterBySearchTerm(editors: (PluginEntry | PluginGroup<PluginEntry>)[], searchTerm: string, locale?: string): (PluginEntry | PluginGroup<PluginEntry>)[];
18
+ export declare function filterBySearchTerm(editors: (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[], searchTerm: string, locale?: string): (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[];
20
19
  /**
21
20
  * Flattens plugins (including those nested within groups) and returns the leaf
22
21
  * entries whose tagName is included in the given pinnedIds.
23
22
  */
24
- export declare function filterByPinned(editors: (PluginEntry | PluginGroup<PluginEntry>)[], pinnedIds: string[]): PluginEntry[];
25
- export declare function isPluginGroup<P extends PluginBase = PluginEntry>(item: unknown): item is PluginGroup<P>;
23
+ export declare function filterByPinned(editors: (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[], pinnedIds: string[]): ResolvedPlugin[];
24
+ export declare function isPluginGroup<P extends PluginMetadata = ResolvedPlugin>(item: unknown): item is PluginGroup<P>;
26
25
  /**
27
- * Checks if the given object is a valid Plugin.
26
+ * Checks whether the given object carries a `tagName`, i.e. names an element
27
+ * the shell does not have to load itself.
28
28
  * @param item - The object to check.
29
- * @returns true if the object is a Plugin, false otherwise.
29
+ * @returns true if the object is a TaggedPlugin, false otherwise.
30
30
  */
31
- export declare function isPluginEntry(item: unknown): item is PluginEntry;
31
+ export declare function isTaggedPlugin(item: unknown): item is TaggedPlugin;
32
32
  /**
33
33
  * Checks if the given object is a SourcedPlugin.
34
34
  * @param item - The object to check.
35
35
  * @returns true if the object is a SourcedPlugin, false otherwise.
36
36
  */
37
- export declare function isSourcedPlugin(item: unknown): item is SourcedPluginEntry;
37
+ export declare function isSourcedPlugin(item: unknown): item is SourcedPlugin;
38
38
  /**
39
39
  * Validates a Plugin object, checking for required fields and types.
40
40
  * If the plugin is invalid, it logs an error and returns undefined.
41
41
  * @param plugin - The plugin object to validate.
42
42
  * @returns The validated Plugin object or undefined if invalid.
43
43
  */
44
- export declare function validatePlugin(plugin: unknown): PluginEntry | undefined;
45
- /**
46
- * Goes through all the plugins in the PluginSet and loads any sourced plugins, replacing the src field with a tagName.
47
- * If a plugin does not have a tagName, it will be generated based on its src.
48
- * All plugins returned are validated for required fields.
49
- * If a sourced plugin fails to load (bad src), it will be replaced with an Error Web Component.
50
- * @param plugins - Array of plugins to convert.
51
- * @returns Array of plugins with tagName included.
52
- */
53
- export declare function loadSourcedPlugins(plugins: Partial<PluginEntry | SourcedPluginEntry>[], registry: CustomElementRegistry): PluginEntry[];
44
+ export declare function validatePlugin(plugin: unknown): ResolvedPlugin | undefined;
45
+ /**
46
+ * Resolves a declared plugin list into its render form: every entry is
47
+ * validated and comes back with a `tagName`, sourced entries having been
48
+ * imported into the registry under a tag derived from their `src`.
49
+ *
50
+ * `src` is checked before `tagName`, so an entry carrying both is re-resolved
51
+ * from source rather than trusting a tag that may be stale. `src` is dropped
52
+ * from the result - the declared set remains the record of where a plugin
53
+ * came from.
54
+ *
55
+ * If a sourced plugin fails to load (bad src), it is replaced with an Error Web Component.
56
+ * @param plugins - Array of declared plugins to resolve.
57
+ * @returns Array of resolved plugins, each with a tagName.
58
+ */
59
+ export declare function loadSourcedPlugins(plugins: OscdPlugin[], registry: CustomElementRegistry): ResolvedPlugin[];
60
+ export declare function loadSourcedPlugins(plugins: (OscdPlugin | PluginGroup<OscdPlugin>)[], registry: CustomElementRegistry): (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[];
@@ -46,7 +46,7 @@ export function filterBySearchTerm(editors, searchTerm, locale) {
46
46
  if (!term) {
47
47
  return editors;
48
48
  }
49
- return filterPlugins(editors, plugin => {
49
+ return filterPlugins(editors, (plugin) => {
50
50
  const localizedName = locale ? plugin.translations?.[locale] : undefined;
51
51
  return (plugin.name.toLowerCase().includes(term) ||
52
52
  !!localizedName?.toLowerCase().includes(term));
@@ -88,11 +88,12 @@ export function isPluginGroup(item) {
88
88
  Array.isArray(item.plugins));
89
89
  }
90
90
  /**
91
- * Checks if the given object is a valid Plugin.
91
+ * Checks whether the given object carries a `tagName`, i.e. names an element
92
+ * the shell does not have to load itself.
92
93
  * @param item - The object to check.
93
- * @returns true if the object is a Plugin, false otherwise.
94
+ * @returns true if the object is a TaggedPlugin, false otherwise.
94
95
  */
95
- export function isPluginEntry(item) {
96
+ export function isTaggedPlugin(item) {
96
97
  return (typeof item === 'object' &&
97
98
  item !== null &&
98
99
  'tagName' in item &&
@@ -117,7 +118,7 @@ export function isSourcedPlugin(item) {
117
118
  */
118
119
  export function validatePlugin(plugin) {
119
120
  const missingFields = [];
120
- if (!isPluginEntry(plugin)) {
121
+ if (!isTaggedPlugin(plugin)) {
121
122
  missingFields.push('tagName');
122
123
  }
123
124
  const _plugin = plugin;
@@ -137,27 +138,19 @@ export function validatePlugin(plugin) {
137
138
  }
138
139
  return _plugin;
139
140
  }
140
- /**
141
- * Goes through all the plugins in the PluginSet and loads any sourced plugins, replacing the src field with a tagName.
142
- * If a plugin does not have a tagName, it will be generated based on its src.
143
- * All plugins returned are validated for required fields.
144
- * If a sourced plugin fails to load (bad src), it will be replaced with an Error Web Component.
145
- * @param plugins - Array of plugins to convert.
146
- * @returns Array of plugins with tagName included.
147
- */
148
141
  export function loadSourcedPlugins(plugins, registry) {
149
142
  return plugins
150
- .map(plugin => {
143
+ .map((plugin) => {
151
144
  if (isPluginGroup(plugin)) {
152
145
  return {
153
146
  ...plugin,
154
147
  plugins: loadSourcedPlugins(plugin.plugins, registry),
155
148
  };
156
149
  }
157
- if (isPluginEntry(plugin)) {
158
- return validatePlugin(plugin);
159
- }
160
150
  if (!isSourcedPlugin(plugin)) {
151
+ if (isTaggedPlugin(plugin)) {
152
+ return validatePlugin(plugin);
153
+ }
161
154
  console.error(`[Invalid Plugin] Requires a tagName or src - skipping. ${JSON.stringify(plugin)}`);
162
155
  return undefined;
163
156
  }
@@ -175,13 +168,13 @@ export function loadSourcedPlugins(plugins, registry) {
175
168
  }
176
169
  const url = new URL(src, window.location.href).toString();
177
170
  import(/* @vite-ignore */ url)
178
- .then(mod => {
171
+ .then((mod) => {
179
172
  // Because this is async, we need to check (again) if the element is already defined.
180
173
  if (!registry?.get(hashedTagName)) {
181
174
  registry.define(hashedTagName, mod.default);
182
175
  }
183
176
  })
184
- .catch(err => {
177
+ .catch((err) => {
185
178
  // Log this as a warning because we load an Error WC in place of the plugin.
186
179
  console.warn(`[Invalid Plugin] Failed to load plugin ${plugin.name} <${hashedTagName}/> from ${url}`, err);
187
180
  const ErrWc = generateErrorWcClass(plugin);
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-utils.js","sourceRoot":"","sources":["../../src/utils/plugin-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAQjD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;AAE7C;;;;;GAKG;AACH,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,UAAU,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;AAC9B,CAAC;AAID;;GAEG;AACH,MAAM,UAAU,aAAa,CAC3B,WAAuD,EACvD,SAA2C;IAE3C,OAAO,WAAW;SACf,GAAG,CAAC,IAAI,CAAC,EAAE,CACV,aAAa,CAAC,IAAI,CAAC;QACjB,CAAC,CAAC;YACE,GAAG,IAAI;YACP,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;SACxC;QACH,CAAC,CAAC,IAAI,CACT;SACA,MAAM,CAAC,IAAI,CAAC,EAAE;IACb,gEAAgE;IAChE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAChE,CAAC;AACN,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAAiC;IAEjC,OAAO,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAC9B,aAAa,CAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAC/C,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAmD,EACnD,UAAkB,EAClB,MAAe;IAEf,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE;QACrC,MAAM,aAAa,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzE,OAAO,CACL,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;YACxC,CAAC,CAAC,aAAa,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAC9C,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAmD,EACnD,SAAmB;IAEnB,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CACnD,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CACnC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,oBAAoB,CAAC,MAA4B;IACxD,MAAM,KAAK,GAAG,+BAA+B,CAAC;IAC9C,MAAM,OAAO,GAAG,WAAW,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;IACpD,MAAM,WAAW,GAAG;;;;8BAIQ,KAAK,WAAW,OAAO;;;;eAItC,KAAK,UAAU,OAAO;;IAEjC,CAAC;IAEH,OAAO,IAAI,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,IAAa;IAEb,OAAO,CACL,OAAO,IAAI,KAAK,QAAQ;QACxB,IAAI,KAAK,IAAI;QACb,SAAS,IAAI,IAAI;QACjB,KAAK,CAAC,OAAO,CAAE,IAAuB,CAAC,OAAO,CAAC,CAChD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAa;IACzC,OAAO,CACL,OAAO,IAAI,KAAK,QAAQ;QACxB,IAAI,KAAK,IAAI;QACb,SAAS,IAAI,IAAI;QACjB,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CACjC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,OAAO,CACL,OAAO,IAAI,KAAK,QAAQ;QACxB,IAAI,KAAK,IAAI;QACb,KAAK,IAAI,IAAI;QACb,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ,CAC7B,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,MAAe;IAC5C,MAAM,aAAa,GAAG,EAAE,CAAC;IACzB,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAChC,CAAC;IAED,MAAM,OAAO,GAAG,MAAqB,CAAC;IACtC,aAAa,CAAC,IAAI,CAChB,GAAI,CAAC,MAAM,EAAE,MAAM,CAAW,CAAC,MAAM,CACnC,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,QAAQ,CAC/D,CACF,CAAC;IACF,IACE,OAAO,OAAO,CAAC,UAAU,KAAK,WAAW;QACzC,OAAO,OAAO,CAAC,UAAU,KAAK,SAAS,EACvC,CAAC;QACD,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACnC,CAAC;IACD,IACE,OAAO,OAAO,CAAC,YAAY,KAAK,WAAW;QAC3C,CAAC,OAAO,OAAO,CAAC,YAAY,KAAK,QAAQ;YACvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,EACvE,CAAC;QACD,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CACX,qBAAqB,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,6BAA6B,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CACxH,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAoD,EACpD,QAA+B;IAE/B,OAAO,OAAO;SACX,GAAG,CAAC,MAAM,CAAC,EAAE;QACZ,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO;gBACL,GAAG,MAAM;gBACT,OAAO,EAAE,kBAAkB,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC;aACtD,CAAC;QACJ,CAAC;QAED,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,KAAK,CACX,0DAA0D,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CACnF,CAAC;YACF,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,MAA4B,CAAC;QACtD,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,eAAe,GAAG,cAAc,CAAC;YACrC,GAAG,IAAI;YACP,OAAO,EAAE,aAAa;SACvB,CAAC,CAAC;QAEH,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,IAAI,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;YAChC,OAAO,eAAe,CAAC;QACzB,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC1D,MAAM,CAAC,kBAAkB,CAAC,GAAG,CAAC;aAC3B,IAAI,CAAC,GAAG,CAAC,EAAE;YACV,qFAAqF;YACrF,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;gBAClC,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,CAAC,EAAE;YACX,4EAA4E;YAC5E,OAAO,CAAC,IAAI,CACV,0CAA0C,MAAM,CAAC,IAAI,KAAK,aAAa,WAAW,GAAG,EAAE,EACvF,GAAG,CACJ,CAAC;YACF,MAAM,KAAK,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;YAC3C,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;QACL,OAAO,eAAe,CAAC;IACzB,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,MAAM,EAAyB,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;AACrE,CAAC","sourcesContent":["import { cyrb64 } from '../foundation/cyrb64.js';\nimport {\n PluginBase,\n PluginEntry,\n PluginGroup,\n SourcedPluginEntry,\n} from '../oscd-shell.js';\n\nconst pluginTags = new Map<string, string>();\n\n/*\n * Generates a unique tag name for a plugin based on its source URI.\n * This is used to ensure that each plugin has a unique identifier in the CustomElements registry.\n * @param uri - The source URI of the plugin.\n * @returns A unique tag name for the plugin.\n */\nfunction pluginTag(uri: string): string {\n if (!pluginTags.has(uri)) {\n pluginTags.set(uri, `oscd-p${cyrb64(uri)}`);\n }\n return pluginTags.get(uri)!;\n}\n\nexport type AnyPluginEntry = PluginEntry | SourcedPluginEntry;\n\n/**\n * Helper fn to filter root plugins and grouped plugins, whilst preserving the structure.\n */\nexport function filterPlugins(\n pluginItems: (PluginEntry | PluginGroup<PluginEntry>)[],\n predicate: (plugin: PluginEntry) => boolean,\n): (PluginEntry | PluginGroup<PluginEntry>)[] {\n return pluginItems\n .map(item =>\n isPluginGroup(item)\n ? {\n ...item,\n plugins: item.plugins.filter(predicate),\n }\n : item,\n )\n .filter(item =>\n //remove empty groups or plugins which don't match the predicate\n isPluginGroup(item) ? item.plugins.length > 0 : predicate(item),\n );\n}\n\n/**\n * Returns a flattened array of all plugin entries from a given PluginSet, including those nested within PluginGroups.\n */\nexport function flattenPluginEntries<P extends PluginBase = PluginEntry>(\n pluginSet: (P | PluginGroup<P>)[],\n): P[] {\n return pluginSet.flatMap(item =>\n isPluginGroup<P>(item) ? item.plugins : [item],\n );\n}\n\n/**\n * Filters plugins by a search term, matching (case-insensitively) against the\n * leaf plugin names only - group names are intentionally not matched. When a\n * `locale` is given, the plugin's localized label (`translations[locale]`) is\n * also matched, so users can search by the label they see. The group structure\n * is preserved and empty groups are dropped. An empty or whitespace-only term\n * returns the plugins unchanged.\n */\nexport function filterBySearchTerm(\n editors: (PluginEntry | PluginGroup<PluginEntry>)[],\n searchTerm: string,\n locale?: string,\n): (PluginEntry | PluginGroup<PluginEntry>)[] {\n const term = searchTerm.trim().toLowerCase();\n if (!term) {\n return editors;\n }\n return filterPlugins(editors, plugin => {\n const localizedName = locale ? plugin.translations?.[locale] : undefined;\n return (\n plugin.name.toLowerCase().includes(term) ||\n !!localizedName?.toLowerCase().includes(term)\n );\n });\n}\n\n/**\n * Flattens plugins (including those nested within groups) and returns the leaf\n * entries whose tagName is included in the given pinnedIds.\n */\nexport function filterByPinned(\n editors: (PluginEntry | PluginGroup<PluginEntry>)[],\n pinnedIds: string[],\n): PluginEntry[] {\n return flattenPluginEntries(editors).filter(plugin =>\n pinnedIds.includes(plugin.tagName),\n );\n}\n\n/**\n * Generates a Web Component class that displays an error message when a plugin fails to load.\n * This is used to provide feedback to the Distro developer when a plugin cannot be loaded due to an error.\n * @param plugin - The plugin object that failed to load.\n * @returns A Web Component class that displays the error message.\n */\nfunction generateErrorWcClass(plugin: Partial<PluginEntry>) {\n const title = 'Error: Plugin failed to load.';\n const details = `Plugin: ${JSON.stringify(plugin)}`;\n const classString = `\n return class extends HTMLElement {\n\n connectedCallback() {\n this.innerHTML = '<h1>${title}</h1><p>${details}</p><emphasis>Check your plugins.json</emphasis>';\n }\n\n async run() {\n alert('${title}\\\\n\\\\n ${details}; \\\\n\\\\n Check your plugins.json');\n }\n }`;\n\n return new Function(classString)();\n}\n\nexport function isPluginGroup<P extends PluginBase = PluginEntry>(\n item: unknown,\n): item is PluginGroup<P> {\n return (\n typeof item === 'object' &&\n item !== null &&\n 'plugins' in item &&\n Array.isArray((item as PluginGroup<P>).plugins)\n );\n}\n\n/**\n * Checks if the given object is a valid Plugin.\n * @param item - The object to check.\n * @returns true if the object is a Plugin, false otherwise.\n */\nexport function isPluginEntry(item: unknown): item is PluginEntry {\n return (\n typeof item === 'object' &&\n item !== null &&\n 'tagName' in item &&\n typeof item.tagName === 'string'\n );\n}\n\n/**\n * Checks if the given object is a SourcedPlugin.\n * @param item - The object to check.\n * @returns true if the object is a SourcedPlugin, false otherwise.\n */\nexport function isSourcedPlugin(item: unknown): item is SourcedPluginEntry {\n return (\n typeof item === 'object' &&\n item !== null &&\n 'src' in item &&\n typeof item.src === 'string'\n );\n}\n\n/**\n * Validates a Plugin object, checking for required fields and types.\n * If the plugin is invalid, it logs an error and returns undefined.\n * @param plugin - The plugin object to validate.\n * @returns The validated Plugin object or undefined if invalid.\n */\nexport function validatePlugin(plugin: unknown): PluginEntry | undefined {\n const missingFields = [];\n if (!isPluginEntry(plugin)) {\n missingFields.push('tagName');\n }\n\n const _plugin = plugin as PluginEntry;\n missingFields.push(\n ...(['name', 'icon'] as const).filter(\n field => !_plugin[field] || typeof _plugin[field] !== 'string',\n ),\n );\n if (\n typeof _plugin.requireDoc !== 'undefined' &&\n typeof _plugin.requireDoc !== 'boolean'\n ) {\n missingFields.push('requireDoc');\n }\n if (\n typeof _plugin.translations !== 'undefined' &&\n (typeof _plugin.translations !== 'object' ||\n Object.values(_plugin.translations).some(t => typeof t !== 'string'))\n ) {\n missingFields.push('translations');\n }\n\n if (missingFields.length > 0) {\n console.error(\n `[Invalid Plugin]\\n${JSON.stringify(plugin, null, 2)}\\nMissing/Invalid fields [${missingFields.join(',')}] - skipping.`,\n );\n return undefined;\n }\n return _plugin;\n}\n\n/**\n * Goes through all the plugins in the PluginSet and loads any sourced plugins, replacing the src field with a tagName.\n * If a plugin does not have a tagName, it will be generated based on its src.\n * All plugins returned are validated for required fields.\n * If a sourced plugin fails to load (bad src), it will be replaced with an Error Web Component.\n * @param plugins - Array of plugins to convert.\n * @returns Array of plugins with tagName included.\n */\nexport function loadSourcedPlugins(\n plugins: Partial<PluginEntry | SourcedPluginEntry>[],\n registry: CustomElementRegistry,\n): PluginEntry[] {\n return plugins\n .map(plugin => {\n if (isPluginGroup(plugin)) {\n return {\n ...plugin,\n plugins: loadSourcedPlugins(plugin.plugins, registry),\n };\n }\n\n if (isPluginEntry(plugin)) {\n return validatePlugin(plugin);\n }\n if (!isSourcedPlugin(plugin)) {\n console.error(\n `[Invalid Plugin] Requires a tagName or src - skipping. ${JSON.stringify(plugin)}`,\n );\n return undefined;\n }\n const { src, ...rest } = plugin as SourcedPluginEntry;\n const hashedTagName = pluginTag(src);\n const validatedPlugin = validatePlugin({\n ...rest,\n tagName: hashedTagName,\n });\n\n if (!validatedPlugin) {\n return undefined;\n }\n\n if (registry.get(hashedTagName)) {\n return validatedPlugin;\n }\n\n const url = new URL(src, window.location.href).toString();\n import(/* @vite-ignore */ url)\n .then(mod => {\n // Because this is async, we need to check (again) if the element is already defined.\n if (!registry?.get(hashedTagName)) {\n registry.define(hashedTagName, mod.default);\n }\n })\n .catch(err => {\n // Log this as a warning because we load an Error WC in place of the plugin.\n console.warn(\n `[Invalid Plugin] Failed to load plugin ${plugin.name} <${hashedTagName}/> from ${url}`,\n err,\n );\n const ErrWc = generateErrorWcClass(plugin);\n registry.define(hashedTagName, ErrWc);\n });\n return validatedPlugin;\n })\n .filter((plugin): plugin is PluginEntry => plugin !== undefined);\n}\n"]}
1
+ {"version":3,"file":"plugin-utils.js","sourceRoot":"","sources":["../../src/utils/plugin-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { cyrb64 } from '../foundation/cyrb64.js';\nimport {\n PluginMetadata,\n PluginGroup,\n ResolvedPlugin,\n OscdPlugin,\n SourcedPlugin,\n TaggedPlugin,\n} from '../oscd-shell.js';\n\nconst pluginTags = new Map<string, string>();\n\n/*\n * Generates a unique tag name for a plugin based on its source URI.\n * This is used to ensure that each plugin has a unique identifier in the CustomElements registry.\n * @param uri - The source URI of the plugin.\n * @returns A unique tag name for the plugin.\n */\nfunction pluginTag(uri: string): string {\n if (!pluginTags.has(uri)) {\n pluginTags.set(uri, `oscd-p${cyrb64(uri)}`);\n }\n return pluginTags.get(uri)!;\n}\n\n/**\n * Helper fn to filter root plugins and grouped plugins, whilst preserving the structure.\n */\nexport function filterPlugins(\n pluginItems: (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[],\n predicate: (plugin: ResolvedPlugin) => boolean,\n): (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[] {\n return pluginItems\n .map(item =>\n isPluginGroup(item)\n ? {\n ...item,\n plugins: item.plugins.filter(predicate),\n }\n : item,\n )\n .filter(item =>\n //remove empty groups or plugins which don't match the predicate\n isPluginGroup(item) ? item.plugins.length > 0 : predicate(item),\n );\n}\n\n/**\n * Returns a flattened array of all plugin entries from a given PluginSet, including those nested within PluginGroups.\n */\nexport function flattenPluginEntries<P extends PluginMetadata = ResolvedPlugin>(\n pluginSet: (P | PluginGroup<P>)[],\n): P[] {\n return pluginSet.flatMap(item =>\n isPluginGroup<P>(item) ? item.plugins : [item],\n );\n}\n\n/**\n * Filters plugins by a search term, matching (case-insensitively) against the\n * leaf plugin names only - group names are intentionally not matched. When a\n * `locale` is given, the plugin's localized label (`translations[locale]`) is\n * also matched, so users can search by the label they see. The group structure\n * is preserved and empty groups are dropped. An empty or whitespace-only term\n * returns the plugins unchanged.\n */\nexport function filterBySearchTerm(\n editors: (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[],\n searchTerm: string,\n locale?: string,\n): (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[] {\n const term = searchTerm.trim().toLowerCase();\n if (!term) {\n return editors;\n }\n return filterPlugins(editors, (plugin) => {\n const localizedName = locale ? plugin.translations?.[locale] : undefined;\n return (\n plugin.name.toLowerCase().includes(term) ||\n !!localizedName?.toLowerCase().includes(term)\n );\n });\n}\n\n/**\n * Flattens plugins (including those nested within groups) and returns the leaf\n * entries whose tagName is included in the given pinnedIds.\n */\nexport function filterByPinned(\n editors: (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[],\n pinnedIds: string[],\n): ResolvedPlugin[] {\n return flattenPluginEntries(editors).filter(plugin =>\n pinnedIds.includes(plugin.tagName),\n );\n}\n\n/**\n * Generates a Web Component class that displays an error message when a plugin fails to load.\n * This is used to provide feedback to the Distro developer when a plugin cannot be loaded due to an error.\n * @param plugin - The plugin object that failed to load.\n * @returns A Web Component class that displays the error message.\n */\nfunction generateErrorWcClass(plugin: OscdPlugin) {\n const title = 'Error: Plugin failed to load.';\n const details = `Plugin: ${JSON.stringify(plugin)}`;\n const classString = `\n return class extends HTMLElement {\n\n connectedCallback() {\n this.innerHTML = '<h1>${title}</h1><p>${details}</p><emphasis>Check your plugins.json</emphasis>';\n }\n\n async run() {\n alert('${title}\\\\n\\\\n ${details}; \\\\n\\\\n Check your plugins.json');\n }\n }`;\n\n return new Function(classString)();\n}\n\nexport function isPluginGroup<P extends PluginMetadata = ResolvedPlugin>(\n item: unknown,\n): item is PluginGroup<P> {\n return (\n typeof item === 'object' &&\n item !== null &&\n 'plugins' in item &&\n Array.isArray((item as PluginGroup<P>).plugins)\n );\n}\n\n/**\n * Checks whether the given object carries a `tagName`, i.e. names an element\n * the shell does not have to load itself.\n * @param item - The object to check.\n * @returns true if the object is a TaggedPlugin, false otherwise.\n */\nexport function isTaggedPlugin(item: unknown): item is TaggedPlugin {\n return (\n typeof item === 'object' &&\n item !== null &&\n 'tagName' in item &&\n typeof item.tagName === 'string'\n );\n}\n\n/**\n * Checks if the given object is a SourcedPlugin.\n * @param item - The object to check.\n * @returns true if the object is a SourcedPlugin, false otherwise.\n */\nexport function isSourcedPlugin(item: unknown): item is SourcedPlugin {\n return (\n typeof item === 'object' &&\n item !== null &&\n 'src' in item &&\n typeof item.src === 'string'\n );\n}\n\n/**\n * Validates a Plugin object, checking for required fields and types.\n * If the plugin is invalid, it logs an error and returns undefined.\n * @param plugin - The plugin object to validate.\n * @returns The validated Plugin object or undefined if invalid.\n */\nexport function validatePlugin(plugin: unknown): ResolvedPlugin | undefined {\n const missingFields = [];\n if (!isTaggedPlugin(plugin)) {\n missingFields.push('tagName');\n }\n\n const _plugin = plugin as ResolvedPlugin;\n missingFields.push(\n ...(['name', 'icon'] as const).filter(\n field => !_plugin[field] || typeof _plugin[field] !== 'string',\n ),\n );\n if (\n typeof _plugin.requireDoc !== 'undefined' &&\n typeof _plugin.requireDoc !== 'boolean'\n ) {\n missingFields.push('requireDoc');\n }\n if (\n typeof _plugin.translations !== 'undefined' &&\n (typeof _plugin.translations !== 'object' ||\n Object.values(_plugin.translations).some(t => typeof t !== 'string'))\n ) {\n missingFields.push('translations');\n }\n\n if (missingFields.length > 0) {\n console.error(\n `[Invalid Plugin]\\n${JSON.stringify(plugin, null, 2)}\\nMissing/Invalid fields [${missingFields.join(',')}] - skipping.`,\n );\n return undefined;\n }\n return _plugin;\n}\n\n/**\n * Resolves a declared plugin list into its render form: every entry is\n * validated and comes back with a `tagName`, sourced entries having been\n * imported into the registry under a tag derived from their `src`.\n *\n * `src` is checked before `tagName`, so an entry carrying both is re-resolved\n * from source rather than trusting a tag that may be stale. `src` is dropped\n * from the result - the declared set remains the record of where a plugin\n * came from.\n *\n * If a sourced plugin fails to load (bad src), it is replaced with an Error Web Component.\n * @param plugins - Array of declared plugins to resolve.\n * @returns Array of resolved plugins, each with a tagName.\n */\nexport function loadSourcedPlugins(\n plugins: OscdPlugin[],\n registry: CustomElementRegistry,\n): ResolvedPlugin[];\nexport function loadSourcedPlugins(\n plugins: (OscdPlugin | PluginGroup<OscdPlugin>)[],\n registry: CustomElementRegistry,\n): (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[];\nexport function loadSourcedPlugins(\n plugins: (OscdPlugin | PluginGroup<OscdPlugin>)[],\n registry: CustomElementRegistry,\n): (ResolvedPlugin | PluginGroup<ResolvedPlugin>)[] {\n return plugins\n .map((plugin) => {\n if (isPluginGroup(plugin)) {\n return {\n ...plugin,\n plugins: loadSourcedPlugins(plugin.plugins, registry),\n };\n }\n\n if (!isSourcedPlugin(plugin)) {\n if (isTaggedPlugin(plugin)) {\n return validatePlugin(plugin);\n }\n console.error(\n `[Invalid Plugin] Requires a tagName or src - skipping. ${JSON.stringify(plugin)}`,\n );\n return undefined;\n }\n\n const { src, ...rest } = plugin as SourcedPlugin;\n const hashedTagName = pluginTag(src);\n const validatedPlugin = validatePlugin({\n ...rest,\n tagName: hashedTagName,\n });\n\n if (!validatedPlugin) {\n return undefined;\n }\n\n if (registry.get(hashedTagName)) {\n return validatedPlugin;\n }\n\n const url = new URL(src, window.location.href).toString();\n import(/* @vite-ignore */ url)\n .then((mod) => {\n // Because this is async, we need to check (again) if the element is already defined.\n if (!registry?.get(hashedTagName)) {\n registry.define(hashedTagName, mod.default);\n }\n })\n .catch((err) => {\n // Log this as a warning because we load an Error WC in place of the plugin.\n console.warn(\n `[Invalid Plugin] Failed to load plugin ${plugin.name} <${hashedTagName}/> from ${url}`,\n err,\n );\n const ErrWc = generateErrorWcClass(plugin);\n registry.define(hashedTagName, ErrWc);\n });\n return validatedPlugin;\n })\n .filter(\n (plugin): plugin is ResolvedPlugin | PluginGroup<ResolvedPlugin> =>\n plugin !== undefined,\n );\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { expect } from '@open-wc/testing';
2
- import { isPluginEntry, isSourcedPlugin, validatePlugin, filterBySearchTerm, filterByPinned, } from './plugin-utils.js';
2
+ import { isTaggedPlugin, isSourcedPlugin, validatePlugin, filterBySearchTerm, filterByPinned, loadSourcedPlugins, } from './plugin-utils.js';
3
3
  describe('Plugin Utils', () => {
4
- describe('isPlugin', () => {
4
+ describe('isTaggedPlugin', () => {
5
5
  it('should return true for a valid Plugin object', () => {
6
6
  const plugin = {
7
7
  tagName: 'test-plugin',
@@ -9,7 +9,7 @@ describe('Plugin Utils', () => {
9
9
  icon: 'test-icon',
10
10
  requireDoc: false,
11
11
  };
12
- expect(plugin).satisfies(isPluginEntry);
12
+ expect(plugin).satisfies(isTaggedPlugin);
13
13
  });
14
14
  it('should return false for an object without tagName', () => {
15
15
  const plugin = {
@@ -17,7 +17,7 @@ describe('Plugin Utils', () => {
17
17
  icon: 'test-icon',
18
18
  requireDoc: false,
19
19
  };
20
- expect(plugin).to.not.satisfy(isPluginEntry);
20
+ expect(plugin).to.not.satisfy(isTaggedPlugin);
21
21
  });
22
22
  it('should return false for a SourcePlugin', () => {
23
23
  const plugin = {
@@ -25,7 +25,7 @@ describe('Plugin Utils', () => {
25
25
  icon: 'test-icon',
26
26
  src: 'data:text/javascript;charset=utf-8,import%20%7B%20default%20as%20TestPlugin%20%7D%20from%20"./test-plugin.js";',
27
27
  };
28
- expect(plugin).to.not.satisfy(isPluginEntry);
28
+ expect(plugin).to.not.satisfy(isTaggedPlugin);
29
29
  });
30
30
  });
31
31
  describe('isSourcePlugin', () => {
@@ -221,4 +221,29 @@ describe('filterByPinned', () => {
221
221
  expect(filterByPinned(editors, ['does-not-exist'])).to.be.empty;
222
222
  });
223
223
  });
224
+ describe('loadSourcedPlugins', () => {
225
+ const src = 'data:text/javascript;charset=utf-8,export%20default%20class%20extends%20HTMLElement%20%7B%7D';
226
+ it('derives a tagName from src and drops src from the render form', () => {
227
+ const [resolved] = loadSourcedPlugins([{ name: 'Sourced', icon: 'icon', src }], customElements);
228
+ expect(resolved.tagName).to.match(/^oscd-p/);
229
+ expect(resolved).to.not.have.property('src');
230
+ });
231
+ it('derives the same tagName for the same src, so repeated loads are stable', () => {
232
+ const [first] = loadSourcedPlugins([{ name: 'Sourced', icon: 'icon', src }], customElements);
233
+ const [second] = loadSourcedPlugins([{ name: 'Sourced', icon: 'icon', src }], customElements);
234
+ expect(second).to.deep.equal(first);
235
+ });
236
+ it('resolves from src when an entry carries both src and a stale tagName', () => {
237
+ const [resolved] = loadSourcedPlugins([{ name: 'Sourced', icon: 'icon', src, tagName: 'oscd-pSTALE' }], customElements);
238
+ expect(resolved.tagName).to.not.equal('oscd-pSTALE');
239
+ });
240
+ it('passes a tagName-only entry through unchanged', () => {
241
+ const [resolved] = loadSourcedPlugins([{ name: 'Tagged', icon: 'icon', tagName: 'oscd-tagged' }], customElements);
242
+ expect(resolved).to.deep.equal({
243
+ name: 'Tagged',
244
+ icon: 'icon',
245
+ tagName: 'oscd-tagged',
246
+ });
247
+ });
248
+ });
224
249
  //# sourceMappingURL=plugin-utils.spec.js.map