@omicronenergy/oscd-shell 0.0.13 → 0.0.15
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.
- package/dist/landing-page/landing-page.js +3 -2
- package/dist/landing-page/landing-page.js.map +1 -1
- package/dist/landing-page/landing-page.spec.js +4 -2
- package/dist/landing-page/landing-page.spec.js.map +1 -1
- package/dist/locales/de.d.ts +8 -0
- package/dist/locales/de.js +9 -0
- package/dist/locales/de.js.map +1 -1
- package/dist/menus/files-menu.js +4 -5
- package/dist/menus/files-menu.js.map +1 -1
- package/dist/menus/plugins-menu.d.ts +6 -3
- package/dist/menus/plugins-menu.js +40 -6
- package/dist/menus/plugins-menu.js.map +1 -1
- package/dist/menus/plugins-menu.spec.js +1 -0
- package/dist/menus/plugins-menu.spec.js.map +1 -1
- package/dist/oscd-shell-design-tokens.js +139 -64
- package/dist/oscd-shell-design-tokens.js.map +1 -1
- package/dist/oscd-shell.d.ts +31 -22
- package/dist/oscd-shell.js +136 -73
- package/dist/oscd-shell.js.map +1 -1
- package/dist/side-panel/build-tree-nodes.spec.d.ts +1 -0
- package/dist/side-panel/build-tree-nodes.spec.js +50 -0
- package/dist/side-panel/build-tree-nodes.spec.js.map +1 -0
- package/dist/side-panel/editor-plugins-panel.d.ts +79 -7
- package/dist/side-panel/editor-plugins-panel.js +829 -92
- package/dist/side-panel/editor-plugins-panel.js.map +1 -1
- package/dist/side-panel/editor-plugins-panel.spec.js +469 -24
- package/dist/side-panel/editor-plugins-panel.spec.js.map +1 -1
- package/dist/utils/plugin-utils.d.ts +29 -5
- package/dist/utils/plugin-utils.js +73 -14
- package/dist/utils/plugin-utils.js.map +1 -1
- package/dist/utils/plugin-utils.spec.js +113 -4
- package/dist/utils/plugin-utils.spec.js.map +1 -1
- package/dist/utils/testing/plugin-helpers.d.ts +7 -3
- package/dist/utils/testing/plugin-helpers.js +21 -9
- package/dist/utils/testing/plugin-helpers.js.map +1 -1
- package/package.json +37 -38
|
@@ -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 { createTestDocs } from '../utils/testing/test-doc-helpers.js';\nimport { OscdFilledIconButton } from '@omicronenergy/oscd-ui/iconbutton/OscdFilledIconButton.js';\nimport { sampleEditorPlugins } from '../utils/testing/plugin-helpers.js';\nimport { TestMenuPlugin1 } from '../utils/testing/test-plugins.js';\n\nconst findPanelToggleButton = (pluginsMenu: EditorPluginsPanel) => {\n const toggleButton = pluginsMenu.shadowRoot?.querySelector(\n 'oscd-icon-button.toggle-button',\n ) as OscdFilledIconButton;\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\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 localStorage.removeItem('editorsPanel.expanded');\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 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 toggleButton.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('editorsPanel.expanded', 'false');\n const oscdShell2 = <OscdShell>(\n await fixture(\n html`<oscd-shell\n .docs=${docs}\n docName=${Object.keys(docs)[0]}\n ></oscd-shell>`,\n )\n );\n oscdShell2.plugins = {\n editor: sampleEditorPlugins,\n };\n const editorPluginsPanel2 = oscdShell2.shadowRoot!.querySelector(\n 'editor-plugins-panel',\n )!;\n await oscdShell2.updateComplete;\n await editorPluginsPanel2.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel2)).to.be.false;\n oscdShell2.remove();\n });\n\n it('saves expanded/collapsed state (when toggled) in localStorage', async () => {\n const toggleButton = findPanelToggleButton(editorPluginsPanel);\n expect(localStorage.getItem('editorsPanel.expanded')).to.be.null;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n\n toggleButton.click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.false;\n expect(localStorage.getItem('editorsPanel.expanded')).to.equal('false');\n\n toggleButton.click();\n await editorPluginsPanel.updateComplete;\n expect(isPanelExpanded(editorPluginsPanel)).to.be.true;\n expect(localStorage.getItem('editorsPanel.expanded')).to.equal('true');\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 { 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,16 +1,40 @@
|
|
|
1
|
-
import { PluginEntry, SourcedPluginEntry } from '../oscd-shell.js';
|
|
1
|
+
import { PluginBase, PluginEntry, PluginGroup, SourcedPluginEntry } from '../oscd-shell.js';
|
|
2
|
+
export type AnyPluginEntry = PluginEntry | SourcedPluginEntry;
|
|
3
|
+
/**
|
|
4
|
+
* Helper fn to filter root plugins and grouped plugins, whilst preserving the structure.
|
|
5
|
+
*/
|
|
6
|
+
export declare function filterPlugins(pluginItems: (PluginEntry | PluginGroup<PluginEntry>)[], predicate: (plugin: PluginEntry) => boolean): (PluginEntry | PluginGroup<PluginEntry>)[];
|
|
7
|
+
/**
|
|
8
|
+
* Returns a flattened array of all plugin entries from a given PluginSet, including those nested within PluginGroups.
|
|
9
|
+
*/
|
|
10
|
+
export declare function flattenPluginEntries<P extends PluginBase = PluginEntry>(pluginSet: (P | PluginGroup<P>)[]): P[];
|
|
11
|
+
/**
|
|
12
|
+
* Filters plugins by a search term, matching (case-insensitively) against the
|
|
13
|
+
* leaf plugin names only - group names are intentionally not matched. When a
|
|
14
|
+
* `locale` is given, the plugin's localized label (`translations[locale]`) is
|
|
15
|
+
* also matched, so users can search by the label they see. The group structure
|
|
16
|
+
* is preserved and empty groups are dropped. An empty or whitespace-only term
|
|
17
|
+
* returns the plugins unchanged.
|
|
18
|
+
*/
|
|
19
|
+
export declare function filterBySearchTerm(editors: (PluginEntry | PluginGroup<PluginEntry>)[], searchTerm: string, locale?: string): (PluginEntry | PluginGroup<PluginEntry>)[];
|
|
20
|
+
/**
|
|
21
|
+
* Flattens plugins (including those nested within groups) and returns the leaf
|
|
22
|
+
* entries whose tagName is included in the given pinnedIds.
|
|
23
|
+
*/
|
|
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>;
|
|
2
26
|
/**
|
|
3
27
|
* Checks if the given object is a valid Plugin.
|
|
4
|
-
* @param
|
|
28
|
+
* @param item - The object to check.
|
|
5
29
|
* @returns true if the object is a Plugin, false otherwise.
|
|
6
30
|
*/
|
|
7
|
-
export declare function
|
|
31
|
+
export declare function isPluginEntry(item: unknown): item is PluginEntry;
|
|
8
32
|
/**
|
|
9
33
|
* Checks if the given object is a SourcedPlugin.
|
|
10
|
-
* @param
|
|
34
|
+
* @param item - The object to check.
|
|
11
35
|
* @returns true if the object is a SourcedPlugin, false otherwise.
|
|
12
36
|
*/
|
|
13
|
-
export declare function isSourcedPlugin(
|
|
37
|
+
export declare function isSourcedPlugin(item: unknown): item is SourcedPluginEntry;
|
|
14
38
|
/**
|
|
15
39
|
* Validates a Plugin object, checking for required fields and types.
|
|
16
40
|
* If the plugin is invalid, it logs an error and returns undefined.
|
|
@@ -12,6 +12,53 @@ function pluginTag(uri) {
|
|
|
12
12
|
}
|
|
13
13
|
return pluginTags.get(uri);
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Helper fn to filter root plugins and grouped plugins, whilst preserving the structure.
|
|
17
|
+
*/
|
|
18
|
+
export function filterPlugins(pluginItems, predicate) {
|
|
19
|
+
return pluginItems
|
|
20
|
+
.map(item => isPluginGroup(item)
|
|
21
|
+
? {
|
|
22
|
+
...item,
|
|
23
|
+
plugins: item.plugins.filter(predicate),
|
|
24
|
+
}
|
|
25
|
+
: item)
|
|
26
|
+
.filter(item =>
|
|
27
|
+
//remove empty groups or plugins which don't match the predicate
|
|
28
|
+
isPluginGroup(item) ? item.plugins.length > 0 : predicate(item));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns a flattened array of all plugin entries from a given PluginSet, including those nested within PluginGroups.
|
|
32
|
+
*/
|
|
33
|
+
export function flattenPluginEntries(pluginSet) {
|
|
34
|
+
return pluginSet.flatMap(item => isPluginGroup(item) ? item.plugins : [item]);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Filters plugins by a search term, matching (case-insensitively) against the
|
|
38
|
+
* leaf plugin names only - group names are intentionally not matched. When a
|
|
39
|
+
* `locale` is given, the plugin's localized label (`translations[locale]`) is
|
|
40
|
+
* also matched, so users can search by the label they see. The group structure
|
|
41
|
+
* is preserved and empty groups are dropped. An empty or whitespace-only term
|
|
42
|
+
* returns the plugins unchanged.
|
|
43
|
+
*/
|
|
44
|
+
export function filterBySearchTerm(editors, searchTerm, locale) {
|
|
45
|
+
const term = searchTerm.trim().toLowerCase();
|
|
46
|
+
if (!term) {
|
|
47
|
+
return editors;
|
|
48
|
+
}
|
|
49
|
+
return filterPlugins(editors, plugin => {
|
|
50
|
+
const localizedName = locale ? plugin.translations?.[locale] : undefined;
|
|
51
|
+
return (plugin.name.toLowerCase().includes(term) ||
|
|
52
|
+
!!localizedName?.toLowerCase().includes(term));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Flattens plugins (including those nested within groups) and returns the leaf
|
|
57
|
+
* entries whose tagName is included in the given pinnedIds.
|
|
58
|
+
*/
|
|
59
|
+
export function filterByPinned(editors, pinnedIds) {
|
|
60
|
+
return flattenPluginEntries(editors).filter(plugin => pinnedIds.includes(plugin.tagName));
|
|
61
|
+
}
|
|
15
62
|
/**
|
|
16
63
|
* Generates a Web Component class that displays an error message when a plugin fails to load.
|
|
17
64
|
* This is used to provide feedback to the Distro developer when a plugin cannot be loaded due to an error.
|
|
@@ -34,27 +81,33 @@ function generateErrorWcClass(plugin) {
|
|
|
34
81
|
}`;
|
|
35
82
|
return new Function(classString)();
|
|
36
83
|
}
|
|
84
|
+
export function isPluginGroup(item) {
|
|
85
|
+
return (typeof item === 'object' &&
|
|
86
|
+
item !== null &&
|
|
87
|
+
'plugins' in item &&
|
|
88
|
+
Array.isArray(item.plugins));
|
|
89
|
+
}
|
|
37
90
|
/**
|
|
38
91
|
* Checks if the given object is a valid Plugin.
|
|
39
|
-
* @param
|
|
92
|
+
* @param item - The object to check.
|
|
40
93
|
* @returns true if the object is a Plugin, false otherwise.
|
|
41
94
|
*/
|
|
42
|
-
export function
|
|
43
|
-
return (typeof
|
|
44
|
-
|
|
45
|
-
'tagName' in
|
|
46
|
-
typeof
|
|
95
|
+
export function isPluginEntry(item) {
|
|
96
|
+
return (typeof item === 'object' &&
|
|
97
|
+
item !== null &&
|
|
98
|
+
'tagName' in item &&
|
|
99
|
+
typeof item.tagName === 'string');
|
|
47
100
|
}
|
|
48
101
|
/**
|
|
49
102
|
* Checks if the given object is a SourcedPlugin.
|
|
50
|
-
* @param
|
|
103
|
+
* @param item - The object to check.
|
|
51
104
|
* @returns true if the object is a SourcedPlugin, false otherwise.
|
|
52
105
|
*/
|
|
53
|
-
export function isSourcedPlugin(
|
|
54
|
-
return (typeof
|
|
55
|
-
|
|
56
|
-
'src' in
|
|
57
|
-
typeof
|
|
106
|
+
export function isSourcedPlugin(item) {
|
|
107
|
+
return (typeof item === 'object' &&
|
|
108
|
+
item !== null &&
|
|
109
|
+
'src' in item &&
|
|
110
|
+
typeof item.src === 'string');
|
|
58
111
|
}
|
|
59
112
|
/**
|
|
60
113
|
* Validates a Plugin object, checking for required fields and types.
|
|
@@ -64,7 +117,7 @@ export function isSourcedPlugin(plugin) {
|
|
|
64
117
|
*/
|
|
65
118
|
export function validatePlugin(plugin) {
|
|
66
119
|
const missingFields = [];
|
|
67
|
-
if (!
|
|
120
|
+
if (!isPluginEntry(plugin)) {
|
|
68
121
|
missingFields.push('tagName');
|
|
69
122
|
}
|
|
70
123
|
const _plugin = plugin;
|
|
@@ -95,7 +148,13 @@ export function validatePlugin(plugin) {
|
|
|
95
148
|
export function loadSourcedPlugins(plugins, registry) {
|
|
96
149
|
return plugins
|
|
97
150
|
.map(plugin => {
|
|
98
|
-
if (
|
|
151
|
+
if (isPluginGroup(plugin)) {
|
|
152
|
+
return {
|
|
153
|
+
...plugin,
|
|
154
|
+
plugins: loadSourcedPlugins(plugin.plugins, registry),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (isPluginEntry(plugin)) {
|
|
99
158
|
return validatePlugin(plugin);
|
|
100
159
|
}
|
|
101
160
|
if (!isSourcedPlugin(plugin)) {
|
|
@@ -1 +1 @@
|
|
|
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 { PluginEntry, SourcedPluginEntry } 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 * 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\n/**\n * Checks if the given object is a valid Plugin.\n * @param plugin - The object to check.\n * @returns true if the object is a Plugin, false otherwise.\n */\nexport function isPlugin(plugin: unknown): plugin is PluginEntry {\n return (\n typeof plugin === 'object' &&\n plugin !== null &&\n 'tagName' in plugin &&\n typeof plugin.tagName === 'string'\n );\n}\n\n/**\n * Checks if the given object is a SourcedPlugin.\n * @param plugin - The object to check.\n * @returns true if the object is a SourcedPlugin, false otherwise.\n */\nexport function isSourcedPlugin(plugin: unknown): plugin is SourcedPluginEntry {\n return (\n typeof plugin === 'object' &&\n plugin !== null &&\n 'src' in plugin &&\n typeof plugin.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 (!isPlugin(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 (isPlugin(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 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,5 +1,5 @@
|
|
|
1
1
|
import { expect } from '@open-wc/testing';
|
|
2
|
-
import {
|
|
2
|
+
import { isPluginEntry, isSourcedPlugin, validatePlugin, filterBySearchTerm, filterByPinned, } from './plugin-utils.js';
|
|
3
3
|
describe('Plugin Utils', () => {
|
|
4
4
|
describe('isPlugin', () => {
|
|
5
5
|
it('should return true for a valid Plugin object', () => {
|
|
@@ -9,7 +9,7 @@ describe('Plugin Utils', () => {
|
|
|
9
9
|
icon: 'test-icon',
|
|
10
10
|
requireDoc: false,
|
|
11
11
|
};
|
|
12
|
-
expect(plugin).satisfies(
|
|
12
|
+
expect(plugin).satisfies(isPluginEntry);
|
|
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(
|
|
20
|
+
expect(plugin).to.not.satisfy(isPluginEntry);
|
|
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(
|
|
28
|
+
expect(plugin).to.not.satisfy(isPluginEntry);
|
|
29
29
|
});
|
|
30
30
|
});
|
|
31
31
|
describe('isSourcePlugin', () => {
|
|
@@ -112,4 +112,113 @@ describe('validatePlugin', () => {
|
|
|
112
112
|
expect(plugin).not.to.satisfy(validatePlugin);
|
|
113
113
|
});
|
|
114
114
|
});
|
|
115
|
+
describe('filterBySearchTerm', () => {
|
|
116
|
+
const leaf = (name, tagName) => ({
|
|
117
|
+
name,
|
|
118
|
+
tagName,
|
|
119
|
+
icon: 'margin',
|
|
120
|
+
});
|
|
121
|
+
const group = (name, plugins) => ({
|
|
122
|
+
name,
|
|
123
|
+
icon: 'folder',
|
|
124
|
+
plugins,
|
|
125
|
+
});
|
|
126
|
+
const editors = [
|
|
127
|
+
leaf('Substation Editor', 'oscd-substation'),
|
|
128
|
+
leaf('Single Line Diagram', 'oscd-sld'),
|
|
129
|
+
group('Communication', [
|
|
130
|
+
leaf('GOOSE Editor', 'oscd-goose'),
|
|
131
|
+
leaf('Sampled Values', 'oscd-smv'),
|
|
132
|
+
]),
|
|
133
|
+
];
|
|
134
|
+
it('returns the plugins unchanged for an empty term', () => {
|
|
135
|
+
expect(filterBySearchTerm(editors, '')).to.equal(editors);
|
|
136
|
+
});
|
|
137
|
+
it('returns the plugins unchanged for a whitespace-only term', () => {
|
|
138
|
+
expect(filterBySearchTerm(editors, ' ')).to.equal(editors);
|
|
139
|
+
});
|
|
140
|
+
it('matches leaf plugin names case-insensitively', () => {
|
|
141
|
+
const result = filterBySearchTerm(editors, 'EDITOR');
|
|
142
|
+
const names = result.flatMap(item => 'plugins' in item ? item.plugins.map(p => p.name) : [item.name]);
|
|
143
|
+
expect(names).to.have.members(['Substation Editor', 'GOOSE Editor']);
|
|
144
|
+
});
|
|
145
|
+
it('matches anywhere within a plugin name, not just the start', () => {
|
|
146
|
+
const result = filterBySearchTerm(editors, 'line');
|
|
147
|
+
expect(result).to.have.lengthOf(1);
|
|
148
|
+
expect(result[0].name).to.equal('Single Line Diagram');
|
|
149
|
+
});
|
|
150
|
+
it('preserves group structure and keeps only matching children', () => {
|
|
151
|
+
const result = filterBySearchTerm(editors, 'goose');
|
|
152
|
+
expect(result).to.have.lengthOf(1);
|
|
153
|
+
const communication = result[0];
|
|
154
|
+
expect(communication.name).to.equal('Communication');
|
|
155
|
+
expect(communication.plugins.map(p => p.name)).to.deep.equal([
|
|
156
|
+
'GOOSE Editor',
|
|
157
|
+
]);
|
|
158
|
+
});
|
|
159
|
+
it('drops groups whose children do not match', () => {
|
|
160
|
+
const result = filterBySearchTerm(editors, 'substation');
|
|
161
|
+
expect(result.some(item => 'plugins' in item && item.name === 'Communication')).to.be.false;
|
|
162
|
+
});
|
|
163
|
+
it('does not match against group names', () => {
|
|
164
|
+
expect(filterBySearchTerm(editors, 'Communication')).to.be.empty;
|
|
165
|
+
});
|
|
166
|
+
it('matches the localized label when a locale is given', () => {
|
|
167
|
+
const localized = [
|
|
168
|
+
{
|
|
169
|
+
...leaf('Substation Editor', 'oscd-substation'),
|
|
170
|
+
translations: { de: 'Unterstation' },
|
|
171
|
+
},
|
|
172
|
+
leaf('Single Line Diagram', 'oscd-sld'),
|
|
173
|
+
];
|
|
174
|
+
const result = filterBySearchTerm(localized, 'unterstation', 'de');
|
|
175
|
+
expect(result).to.have.lengthOf(1);
|
|
176
|
+
expect(result[0].tagName).to.equal('oscd-substation');
|
|
177
|
+
});
|
|
178
|
+
it('does not match the localized label when no locale is given', () => {
|
|
179
|
+
const localized = [
|
|
180
|
+
{
|
|
181
|
+
...leaf('Substation Editor', 'oscd-substation'),
|
|
182
|
+
translations: { de: 'Unterstation' },
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
expect(filterBySearchTerm(localized, 'unterstation')).to.be.empty;
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
describe('filterByPinned', () => {
|
|
189
|
+
const leaf = (name, tagName) => ({
|
|
190
|
+
name,
|
|
191
|
+
tagName,
|
|
192
|
+
icon: 'margin',
|
|
193
|
+
});
|
|
194
|
+
const editors = [
|
|
195
|
+
leaf('Substation Editor', 'oscd-substation'),
|
|
196
|
+
{
|
|
197
|
+
name: 'Communication',
|
|
198
|
+
icon: 'folder',
|
|
199
|
+
plugins: [
|
|
200
|
+
leaf('GOOSE Editor', 'oscd-goose'),
|
|
201
|
+
leaf('Sampled Values', 'oscd-smv'),
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
it('returns flattened leaf entries whose tagName is pinned', () => {
|
|
206
|
+
const result = filterByPinned(editors, ['oscd-substation', 'oscd-smv']);
|
|
207
|
+
expect(result.map(p => p.tagName)).to.deep.equal([
|
|
208
|
+
'oscd-substation',
|
|
209
|
+
'oscd-smv',
|
|
210
|
+
]);
|
|
211
|
+
});
|
|
212
|
+
it('finds pinned plugins nested within groups', () => {
|
|
213
|
+
const result = filterByPinned(editors, ['oscd-goose']);
|
|
214
|
+
expect(result).to.have.lengthOf(1);
|
|
215
|
+
expect(result[0].name).to.equal('GOOSE Editor');
|
|
216
|
+
});
|
|
217
|
+
it('returns an empty array when nothing is pinned', () => {
|
|
218
|
+
expect(filterByPinned(editors, [])).to.be.empty;
|
|
219
|
+
});
|
|
220
|
+
it('ignores pinned ids that do not match any plugin', () => {
|
|
221
|
+
expect(filterByPinned(editors, ['does-not-exist'])).to.be.empty;
|
|
222
|
+
});
|
|
223
|
+
});
|
|
115
224
|
//# sourceMappingURL=plugin-utils.spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-utils.spec.js","sourceRoot":"","sources":["../../src/utils/plugin-utils.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { isPlugin, isSourcedPlugin, validatePlugin } from './plugin-utils.js';\n\ndescribe('Plugin Utils', () => {\n describe('isPlugin', () => {\n it('should return true for a valid Plugin object', () => {\n const plugin = {\n tagName: 'test-plugin',\n name: 'Test Plugin',\n icon: 'test-icon',\n requireDoc: false,\n };\n expect(plugin).satisfies(isPlugin);\n });\n\n it('should return false for an object without tagName', () => {\n const plugin = {\n name: 'Test Plugin',\n icon: 'test-icon',\n requireDoc: false,\n };\n expect(plugin).to.not.satisfy(isPlugin);\n });\n\n it('should return false for a SourcePlugin', () => {\n const plugin = {\n name: 'Test Plugin',\n icon: 'test-icon',\n src: 'data:text/javascript;charset=utf-8,import%20%7B%20default%20as%20TestPlugin%20%7D%20from%20\"./test-plugin.js\";',\n };\n expect(plugin).to.not.satisfy(isPlugin);\n });\n });\n\n describe('isSourcePlugin', () => {\n it('should return true for a valid SourcePlugin object', () => {\n const plugin = {\n name: 'Test Plugin',\n src: 'data:text/javascript;charset=utf-8,import%20%7B%20default%20as%20TestPlugin%20%7D%20from%20\"./test-plugin.js\";',\n icon: 'test-icon',\n };\n expect(plugin).satisfies(isSourcedPlugin);\n });\n\n it('should return false for an object without a src field', () => {\n const plugin = {\n name: 'Test Plugin',\n icon: 'test-icon',\n };\n expect(plugin).not.to.satisfy(isSourcedPlugin);\n });\n\n it('should return false for an Plugin object (has tagName, but no src)', () => {\n const plugin = {\n name: 'Test Plugin',\n icon: 'test-icon',\n tagName: 'test-plugin',\n };\n expect(plugin).not.to.satisfy(isSourcedPlugin);\n });\n });\n});\n\ndescribe('validatePlugin', () => {\n it('returns a Plugin object, for a valid plugin', async () => {\n const plugin = {\n name: 'Tagless, Sourceless, Hopeless Plugin',\n icon: 'coronavirus',\n tagName: 'test-tagless-plugin',\n };\n\n expect(plugin).satisfies(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition missing its \"tagName\" field', async () => {\n const plugin = {\n icon: 'coronavirus',\n name: 'Tagless, Sourceless, Hopeless Plugin',\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition missing its \"name\" field', async () => {\n const plugin = {\n icon: 'coronavirus',\n tagName: 'test-tagless-plugin',\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition missing its \"icon\" field', async () => {\n const plugin = {\n name: 'Tagless, Sourceless, Hopeless Plugin',\n tagName: 'test-tagless-plugin',\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition with invalid requireDoc field', async () => {\n const plugin = {\n name: 'Tagless, Sourceless, Hopeless Plugin',\n tagName: 'test-tagless-plugin',\n requireDoc: 'not-a-boolean',\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition with invalid translations field', async () => {\n const plugin = {\n name: 'Tagless, Sourceless, Hopeless Plugin',\n tagName: 'test-tagless-plugin',\n translations: 'ops',\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition with invalid translations', async () => {\n const plugin = {\n name: 'Tagless, Sourceless, Hopeless Plugin',\n tagName: 'test-tagless-plugin',\n translations: {\n en: 'Tagless, Sourceless, Hopeless Plugin',\n fr: 123, // Invalid translation (not a string)\n },\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n});\n"]}
|
|
1
|
+
{"version":3,"file":"plugin-utils.spec.js","sourceRoot":"","sources":["../../src/utils/plugin-utils.spec.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport {\n isPluginEntry,\n isSourcedPlugin,\n validatePlugin,\n filterBySearchTerm,\n filterByPinned,\n} from './plugin-utils.js';\nimport { PluginEntry, PluginGroup } from '../oscd-shell.js';\n\ndescribe('Plugin Utils', () => {\n describe('isPlugin', () => {\n it('should return true for a valid Plugin object', () => {\n const plugin = {\n tagName: 'test-plugin',\n name: 'Test Plugin',\n icon: 'test-icon',\n requireDoc: false,\n };\n expect(plugin).satisfies(isPluginEntry);\n });\n\n it('should return false for an object without tagName', () => {\n const plugin = {\n name: 'Test Plugin',\n icon: 'test-icon',\n requireDoc: false,\n };\n expect(plugin).to.not.satisfy(isPluginEntry);\n });\n\n it('should return false for a SourcePlugin', () => {\n const plugin = {\n name: 'Test Plugin',\n icon: 'test-icon',\n src: 'data:text/javascript;charset=utf-8,import%20%7B%20default%20as%20TestPlugin%20%7D%20from%20\"./test-plugin.js\";',\n };\n expect(plugin).to.not.satisfy(isPluginEntry);\n });\n });\n\n describe('isSourcePlugin', () => {\n it('should return true for a valid SourcePlugin object', () => {\n const plugin = {\n name: 'Test Plugin',\n src: 'data:text/javascript;charset=utf-8,import%20%7B%20default%20as%20TestPlugin%20%7D%20from%20\"./test-plugin.js\";',\n icon: 'test-icon',\n };\n expect(plugin).satisfies(isSourcedPlugin);\n });\n\n it('should return false for an object without a src field', () => {\n const plugin = {\n name: 'Test Plugin',\n icon: 'test-icon',\n };\n expect(plugin).not.to.satisfy(isSourcedPlugin);\n });\n\n it('should return false for an Plugin object (has tagName, but no src)', () => {\n const plugin = {\n name: 'Test Plugin',\n icon: 'test-icon',\n tagName: 'test-plugin',\n };\n expect(plugin).not.to.satisfy(isSourcedPlugin);\n });\n });\n});\n\ndescribe('validatePlugin', () => {\n it('returns a Plugin object, for a valid plugin', async () => {\n const plugin = {\n name: 'Tagless, Sourceless, Hopeless Plugin',\n icon: 'coronavirus',\n tagName: 'test-tagless-plugin',\n };\n\n expect(plugin).satisfies(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition missing its \"tagName\" field', async () => {\n const plugin = {\n icon: 'coronavirus',\n name: 'Tagless, Sourceless, Hopeless Plugin',\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition missing its \"name\" field', async () => {\n const plugin = {\n icon: 'coronavirus',\n tagName: 'test-tagless-plugin',\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition missing its \"icon\" field', async () => {\n const plugin = {\n name: 'Tagless, Sourceless, Hopeless Plugin',\n tagName: 'test-tagless-plugin',\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition with invalid requireDoc field', async () => {\n const plugin = {\n name: 'Tagless, Sourceless, Hopeless Plugin',\n tagName: 'test-tagless-plugin',\n requireDoc: 'not-a-boolean',\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition with invalid translations field', async () => {\n const plugin = {\n name: 'Tagless, Sourceless, Hopeless Plugin',\n tagName: 'test-tagless-plugin',\n translations: 'ops',\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n\n it('returns undefined, for a plugin definition with invalid translations', async () => {\n const plugin = {\n name: 'Tagless, Sourceless, Hopeless Plugin',\n tagName: 'test-tagless-plugin',\n translations: {\n en: 'Tagless, Sourceless, Hopeless Plugin',\n fr: 123, // Invalid translation (not a string)\n },\n };\n\n expect(plugin).not.to.satisfy(validatePlugin);\n });\n});\n\ndescribe('filterBySearchTerm', () => {\n const leaf = (name: string, tagName: string): PluginEntry => ({\n name,\n tagName,\n icon: 'margin',\n });\n const group = (name: string, plugins: PluginEntry[]): PluginGroup => ({\n name,\n icon: 'folder',\n plugins,\n });\n\n const editors: (PluginEntry | PluginGroup)[] = [\n leaf('Substation Editor', 'oscd-substation'),\n leaf('Single Line Diagram', 'oscd-sld'),\n group('Communication', [\n leaf('GOOSE Editor', 'oscd-goose'),\n leaf('Sampled Values', 'oscd-smv'),\n ]),\n ];\n\n it('returns the plugins unchanged for an empty term', () => {\n expect(filterBySearchTerm(editors, '')).to.equal(editors);\n });\n\n it('returns the plugins unchanged for a whitespace-only term', () => {\n expect(filterBySearchTerm(editors, ' ')).to.equal(editors);\n });\n\n it('matches leaf plugin names case-insensitively', () => {\n const result = filterBySearchTerm(editors, 'EDITOR');\n const names = result.flatMap(item =>\n 'plugins' in item ? item.plugins.map(p => p.name) : [item.name],\n );\n expect(names).to.have.members(['Substation Editor', 'GOOSE Editor']);\n });\n\n it('matches anywhere within a plugin name, not just the start', () => {\n const result = filterBySearchTerm(editors, 'line');\n expect(result).to.have.lengthOf(1);\n expect((result[0] as PluginEntry).name).to.equal('Single Line Diagram');\n });\n\n it('preserves group structure and keeps only matching children', () => {\n const result = filterBySearchTerm(editors, 'goose');\n expect(result).to.have.lengthOf(1);\n const communication = result[0] as PluginGroup;\n expect(communication.name).to.equal('Communication');\n expect(communication.plugins.map(p => p.name)).to.deep.equal([\n 'GOOSE Editor',\n ]);\n });\n\n it('drops groups whose children do not match', () => {\n const result = filterBySearchTerm(editors, 'substation');\n expect(\n result.some(item => 'plugins' in item && item.name === 'Communication'),\n ).to.be.false;\n });\n\n it('does not match against group names', () => {\n expect(filterBySearchTerm(editors, 'Communication')).to.be.empty;\n });\n\n it('matches the localized label when a locale is given', () => {\n const localized: (PluginEntry | PluginGroup)[] = [\n {\n ...leaf('Substation Editor', 'oscd-substation'),\n translations: { de: 'Unterstation' },\n },\n leaf('Single Line Diagram', 'oscd-sld'),\n ];\n const result = filterBySearchTerm(localized, 'unterstation', 'de');\n expect(result).to.have.lengthOf(1);\n expect((result[0] as PluginEntry).tagName).to.equal('oscd-substation');\n });\n\n it('does not match the localized label when no locale is given', () => {\n const localized: (PluginEntry | PluginGroup)[] = [\n {\n ...leaf('Substation Editor', 'oscd-substation'),\n translations: { de: 'Unterstation' },\n },\n ];\n expect(filterBySearchTerm(localized, 'unterstation')).to.be.empty;\n });\n});\n\ndescribe('filterByPinned', () => {\n const leaf = (name: string, tagName: string): PluginEntry => ({\n name,\n tagName,\n icon: 'margin',\n });\n const editors: (PluginEntry | PluginGroup)[] = [\n leaf('Substation Editor', 'oscd-substation'),\n {\n name: 'Communication',\n icon: 'folder',\n plugins: [\n leaf('GOOSE Editor', 'oscd-goose'),\n leaf('Sampled Values', 'oscd-smv'),\n ],\n },\n ];\n\n it('returns flattened leaf entries whose tagName is pinned', () => {\n const result = filterByPinned(editors, ['oscd-substation', 'oscd-smv']);\n expect(result.map(p => p.tagName)).to.deep.equal([\n 'oscd-substation',\n 'oscd-smv',\n ]);\n });\n\n it('finds pinned plugins nested within groups', () => {\n const result = filterByPinned(editors, ['oscd-goose']);\n expect(result).to.have.lengthOf(1);\n expect(result[0].name).to.equal('GOOSE Editor');\n });\n\n it('returns an empty array when nothing is pinned', () => {\n expect(filterByPinned(editors, [])).to.be.empty;\n });\n\n it('ignores pinned ids that do not match any plugin', () => {\n expect(filterByPinned(editors, ['does-not-exist'])).to.be.empty;\n });\n});\n"]}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { OscdShell, PluginEntry } from '../../oscd-shell.js';
|
|
2
|
-
export declare const sampleMenuPlugins: (Omit<PluginEntry, 'tagName'> & {
|
|
1
|
+
import { OscdShell, PluginEntry, PluginGroup } from '../../oscd-shell.js';
|
|
2
|
+
export declare const sampleMenuPlugins: (PluginGroup<Omit<PluginEntry, 'tagName'> & {
|
|
3
3
|
tagName?: string;
|
|
4
4
|
src?: string;
|
|
5
|
-
}
|
|
5
|
+
}> | (Omit<PluginEntry, 'tagName'> & {
|
|
6
|
+
tagName?: string;
|
|
7
|
+
src?: string;
|
|
8
|
+
}))[];
|
|
6
9
|
export declare const sampleEditorPlugins: (Omit<PluginEntry, 'tagName'> & {
|
|
7
10
|
tagName?: string;
|
|
8
11
|
src?: string;
|
|
9
12
|
})[];
|
|
13
|
+
export declare function findPluginByTagName(pluginSet: (PluginEntry | PluginGroup<PluginEntry>)[], tagName: string): PluginEntry | undefined;
|
|
10
14
|
export declare const isPluginInstanciated: (pluginTagName: string, shell: OscdShell) => boolean;
|
|
11
15
|
export declare const waitForPluginInstanciation: (plugin: PluginEntry, shell: OscdShell) => Promise<void>;
|
|
12
16
|
export declare const waitForPluginsToInstantiate: (plugins: PluginEntry[], shell: OscdShell) => Promise<void[]>;
|