diff --git a/test/test.contextmenu-demo.browser.ts b/test/test.contextmenu-demo.browser.ts new file mode 100644 index 0000000..26e0610 --- /dev/null +++ b/test/test.contextmenu-demo.browser.ts @@ -0,0 +1,35 @@ +import { expect, tap } from '@git.zone/tstest/tapbundle'; +import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js'; +import { demoFunc } from '../ts_web/elements/dees-contextmenu.demo.js'; + +tap.test('should render context menu demo', async () => { + // Create demo container + const demoContainer = document.createElement('div'); + document.body.appendChild(demoContainer); + + // Render the demo + const demoContent = demoFunc(); + + // Create a temporary element to hold the rendered template + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = demoContent.strings.join(''); + + // Check that panels are rendered + const panels = tempDiv.querySelectorAll('dees-panel'); + expect(panels.length).toEqual(4); + + // Check panel headings + expect(panels[0].getAttribute('heading')).toEqual('Basic Context Menu with Nested Submenus'); + expect(panels[1].getAttribute('heading')).toEqual('Component-Specific Context Menu'); + expect(panels[2].getAttribute('heading')).toEqual('Advanced Context Menu Example'); + expect(panels[3].getAttribute('heading')).toEqual('Static Context Menu (Always Visible)'); + + // Check that static context menu exists + const staticMenu = tempDiv.querySelector('dees-contextmenu'); + expect(staticMenu).toBeTruthy(); + + // Clean up + demoContainer.remove(); +}); + +export default tap.start(); \ No newline at end of file diff --git a/test/test.contextmenu-nested-close.browser.ts b/test/test.contextmenu-nested-close.browser.ts new file mode 100644 index 0000000..e76e69a --- /dev/null +++ b/test/test.contextmenu-nested-close.browser.ts @@ -0,0 +1,93 @@ +import { expect, tap } from '@git.zone/tstest/tapbundle'; +import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js'; + +tap.test('should close all parent menus when clicking action in nested submenu', async () => { + let actionCalled = false; + + // Create a test element + const testDiv = document.createElement('div'); + testDiv.style.width = '300px'; + testDiv.style.height = '300px'; + testDiv.style.background = '#f0f0f0'; + testDiv.innerHTML = 'Right-click for nested menu test'; + document.body.appendChild(testDiv); + + // Simulate right-click to open context menu + const contextMenuEvent = new MouseEvent('contextmenu', { + clientX: 150, + clientY: 150, + bubbles: true, + cancelable: true + }); + + // Open context menu with nested structure + DeesContextmenu.openContextMenuWithOptions(contextMenuEvent, [ + { + name: 'Parent Item', + iconName: 'folder', + action: async () => {}, // Parent items with submenus need an action + submenu: [ + { + name: 'Child Item', + iconName: 'file', + action: async () => { + actionCalled = true; + console.log('Child action called'); + } + }, + { + name: 'Another Child', + iconName: 'fileText', + action: async () => console.log('Another child') + } + ] + }, + { + name: 'Regular Item', + iconName: 'box', + action: async () => console.log('Regular item') + } + ]); + + // Wait for main menu to appear + await new Promise(resolve => setTimeout(resolve, 150)); + + // Check main menu exists + const mainMenu = document.querySelector('dees-contextmenu'); + expect(mainMenu).toBeInstanceOf(DeesContextmenu); + + // Hover over "Parent Item" to trigger submenu + const parentItem = mainMenu!.shadowRoot!.querySelector('.menuitem'); + expect(parentItem).toBeTruthy(); + parentItem!.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true })); + + // Wait for submenu to appear + await new Promise(resolve => setTimeout(resolve, 300)); + + // Check submenu exists + const allMenus = document.querySelectorAll('dees-contextmenu'); + expect(allMenus.length).toEqual(2); // Main menu and submenu + + const submenu = allMenus[1]; + expect(submenu).toBeTruthy(); + + // Click on "Child Item" in submenu + const childItem = submenu.shadowRoot!.querySelector('.menuitem'); + expect(childItem).toBeTruthy(); + childItem!.click(); + + // Wait for menus to close + await new Promise(resolve => setTimeout(resolve, 200)); + + // Verify action was called + expect(actionCalled).toEqual(true); + + // Verify all menus are closed + const remainingMenus = document.querySelectorAll('dees-contextmenu'); + expect(remainingMenus.length).toEqual(0); + + // Clean up + testDiv.remove(); +}); + +export default tap.start(); \ No newline at end of file diff --git a/test/test.contextmenu-shadowdom.browser.ts b/test/test.contextmenu-shadowdom.browser.ts new file mode 100644 index 0000000..28f1489 --- /dev/null +++ b/test/test.contextmenu-shadowdom.browser.ts @@ -0,0 +1,71 @@ +import { expect, tap } from '@git.zone/tstest/tapbundle'; +import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js'; +import { DeesElement, customElement, html } from '@design.estate/dees-element'; + +// Create a test element with shadow DOM +@customElement('test-shadow-element') +class TestShadowElement extends DeesElement { + public getContextMenuItems() { + return [ + { name: 'Shadow Item 1', iconName: 'box', action: async () => console.log('Shadow 1') }, + { name: 'Shadow Item 2', iconName: 'package', action: async () => console.log('Shadow 2') } + ]; + } + + render() { + return html` +
Right-click anywhere inside this shadow DOM
+This is a test paragraph
'; + document.body.appendChild(wysiwygEditor); + + // Wait for editor to be ready + await wysiwygEditor.updateComplete; + await new Promise(resolve => setTimeout(resolve, 100)); + + // Get the first block + const firstBlock = wysiwygEditor.blocks[0]; + expect(firstBlock.type).toEqual('paragraph'); + + // Get the block element + const firstBlockWrapper = wysiwygEditor.shadowRoot!.querySelector('.block-wrapper'); + expect(firstBlockWrapper).toBeTruthy(); + + const blockComponent = firstBlockWrapper!.querySelector('dees-wysiwyg-block') as any; + expect(blockComponent).toBeTruthy(); + await blockComponent.updateComplete; + + // Get the editable content inside the block's shadow DOM + const editableBlock = blockComponent.shadowRoot!.querySelector('.block'); + expect(editableBlock).toBeTruthy(); + + // Simulate right-click on the editable block + const contextMenuEvent = new MouseEvent('contextmenu', { + clientX: 200, + clientY: 200, + bubbles: true, + cancelable: true, + composed: true + }); + + editableBlock!.dispatchEvent(contextMenuEvent); + + // Wait for context menu to appear + await new Promise(resolve => setTimeout(resolve, 100)); + + // Check if context menu is created + const contextMenu = document.querySelector('dees-contextmenu'); + expect(contextMenu).toBeInstanceOf(DeesContextmenu); + + // Find "Change Type" menu item + const menuItems = Array.from(contextMenu!.shadowRoot!.querySelectorAll('.menuitem')); + const changeTypeItem = menuItems.find(item => + item.querySelector('.menuitem-text')?.textContent?.trim() === 'Change Type' + ); + expect(changeTypeItem).toBeTruthy(); + + // Hover over "Change Type" to trigger submenu + changeTypeItem!.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true })); + + // Wait for submenu to appear + await new Promise(resolve => setTimeout(resolve, 300)); + + // Check if submenu is created + const allMenus = document.querySelectorAll('dees-contextmenu'); + expect(allMenus.length).toEqual(2); + + const submenu = allMenus[1]; + const submenuItems = Array.from(submenu.shadowRoot!.querySelectorAll('.menuitem')); + + // Find "Heading 1" option + const heading1Item = submenuItems.find(item => + item.querySelector('.menuitem-text')?.textContent?.trim() === 'Heading 1' + ); + expect(heading1Item).toBeTruthy(); + + // Add debug logging + console.log('Before click:', { + blockType: wysiwygEditor.blocks[0].type, + blockId: wysiwygEditor.blocks[0].id + }); + + // Click on "Heading 1" + (heading1Item as HTMLElement).click(); + + // Wait for menu to close and block to update + await new Promise(resolve => setTimeout(resolve, 500)); + + console.log('After click:', { + blockType: wysiwygEditor.blocks[0].type, + blockId: wysiwygEditor.blocks[0].id + }); + + // Verify block type has changed + expect(wysiwygEditor.blocks[0].type).toEqual('heading-1'); + + // Verify DOM has been updated + const updatedBlockComponent = wysiwygEditor.shadowRoot! + .querySelector('.block-wrapper')! + .querySelector('dees-wysiwyg-block') as any; + + await updatedBlockComponent.updateComplete; + + const updatedBlock = updatedBlockComponent.shadowRoot!.querySelector('.block'); + expect(updatedBlock?.classList.contains('heading-1')).toEqual(true); + + // Clean up + wysiwygEditor.remove(); +}); + +export default tap.start(); \ No newline at end of file diff --git a/test/test.wysiwyg-contextmenu.browser.ts b/test/test.wysiwyg-contextmenu.browser.ts new file mode 100644 index 0000000..6f781cd --- /dev/null +++ b/test/test.wysiwyg-contextmenu.browser.ts @@ -0,0 +1,68 @@ +import { expect, tap } from '@git.zone/tstest/tapbundle'; +import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js'; +import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js'; + +tap.test('should show context menu on WYSIWYG blocks', async () => { + // Create WYSIWYG editor + const wysiwygEditor = new DeesInputWysiwyg(); + wysiwygEditor.value = 'Test paragraph
A context menu with nested submenus will appear
+A context menu will appear with various options
-This shows deeply nested submenus and various formatting options
+