update wysiwyg
This commit is contained in:
35
test/test.contextmenu-demo.browser.ts
Normal file
35
test/test.contextmenu-demo.browser.ts
Normal file
@ -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();
|
93
test/test.contextmenu-nested-close.browser.ts
Normal file
93
test/test.contextmenu-nested-close.browser.ts
Normal file
@ -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();
|
71
test/test.contextmenu-shadowdom.browser.ts
Normal file
71
test/test.contextmenu-shadowdom.browser.ts
Normal file
@ -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`
|
||||
<div style="padding: 40px; background: #eee; border-radius: 8px;">
|
||||
<h3>Shadow DOM Content</h3>
|
||||
<p>Right-click anywhere inside this shadow DOM</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
tap.test('should show context menu when right-clicking inside shadow DOM', async () => {
|
||||
// Create the shadow DOM element
|
||||
const shadowElement = document.createElement('test-shadow-element');
|
||||
document.body.appendChild(shadowElement);
|
||||
|
||||
// Wait for element to be ready
|
||||
await shadowElement.updateComplete;
|
||||
|
||||
// Get the content inside shadow DOM
|
||||
const shadowContent = shadowElement.shadowRoot!.querySelector('div');
|
||||
expect(shadowContent).toBeTruthy();
|
||||
|
||||
// Simulate right-click on content inside shadow DOM
|
||||
const contextMenuEvent = new MouseEvent('contextmenu', {
|
||||
clientX: 100,
|
||||
clientY: 100,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true // Important for shadow DOM
|
||||
});
|
||||
|
||||
shadowContent!.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);
|
||||
|
||||
// Check if menu items from shadow element are rendered
|
||||
const menuItems = contextMenu!.shadowRoot!.querySelectorAll('.menuitem');
|
||||
expect(menuItems.length).toBeGreaterThanOrEqual(2);
|
||||
|
||||
// Check menu item text
|
||||
const menuTexts = Array.from(menuItems).map(item =>
|
||||
item.querySelector('.menuitem-text')?.textContent
|
||||
);
|
||||
expect(menuTexts).toContain('Shadow Item 1');
|
||||
expect(menuTexts).toContain('Shadow Item 2');
|
||||
|
||||
// Clean up
|
||||
contextMenu!.remove();
|
||||
shadowElement.remove();
|
||||
});
|
||||
|
||||
export default tap.start();
|
77
test/test.contextmenu.browser.ts
Normal file
77
test/test.contextmenu.browser.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
||||
|
||||
tap.test('should show context menu with nested submenu', async () => {
|
||||
// Create a test element with context menu items
|
||||
const testDiv = document.createElement('div');
|
||||
testDiv.style.width = '200px';
|
||||
testDiv.style.height = '200px';
|
||||
testDiv.style.background = '#eee';
|
||||
testDiv.innerHTML = 'Right-click me';
|
||||
|
||||
// Add getContextMenuItems method
|
||||
(testDiv as any).getContextMenuItems = () => {
|
||||
return [
|
||||
{
|
||||
name: 'Change Type',
|
||||
iconName: 'type',
|
||||
submenu: [
|
||||
{ name: 'Paragraph', iconName: 'text', action: () => console.log('Paragraph') },
|
||||
{ name: 'Heading 1', iconName: 'heading1', action: () => console.log('Heading 1') },
|
||||
{ name: 'Heading 2', iconName: 'heading2', action: () => console.log('Heading 2') },
|
||||
{ divider: true },
|
||||
{ name: 'Code Block', iconName: 'fileCode', action: () => console.log('Code') },
|
||||
{ name: 'Quote', iconName: 'quote', action: () => console.log('Quote') }
|
||||
]
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
name: 'Delete',
|
||||
iconName: 'trash2',
|
||||
action: () => console.log('Delete')
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
document.body.appendChild(testDiv);
|
||||
|
||||
// Simulate right-click
|
||||
const contextMenuEvent = new MouseEvent('contextmenu', {
|
||||
clientX: 100,
|
||||
clientY: 100,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
|
||||
testDiv.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);
|
||||
|
||||
// Check if menu items are rendered
|
||||
const menuItems = contextMenu!.shadowRoot!.querySelectorAll('.menuitem');
|
||||
expect(menuItems.length).toEqual(2); // "Change Type" and "Delete"
|
||||
|
||||
// Hover over "Change Type" to trigger submenu
|
||||
const changeTypeItem = menuItems[0] as HTMLElement;
|
||||
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 submenus = document.querySelectorAll('dees-contextmenu');
|
||||
expect(submenus.length).toEqual(2); // Main menu and submenu
|
||||
|
||||
// Clean up
|
||||
contextMenu!.remove();
|
||||
const submenu = submenus[1];
|
||||
if (submenu) submenu.remove();
|
||||
testDiv.remove();
|
||||
});
|
||||
|
||||
export default tap.start();
|
109
test/test.wysiwyg-blocktype-change.browser.ts
Normal file
109
test/test.wysiwyg-blocktype-change.browser.ts
Normal file
@ -0,0 +1,109 @@
|
||||
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 change block type via context menu', async () => {
|
||||
// Create WYSIWYG editor with a paragraph
|
||||
const wysiwygEditor = new DeesInputWysiwyg();
|
||||
wysiwygEditor.value = '<p>This is a test paragraph</p>';
|
||||
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();
|
68
test/test.wysiwyg-contextmenu.browser.ts
Normal file
68
test/test.wysiwyg-contextmenu.browser.ts
Normal file
@ -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 = '<p>Test paragraph</p><h1>Test heading</h1>';
|
||||
document.body.appendChild(wysiwygEditor);
|
||||
|
||||
// Wait for editor to be ready
|
||||
await wysiwygEditor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Get the first block element
|
||||
const firstBlockWrapper = wysiwygEditor.shadowRoot!.querySelector('.block-wrapper');
|
||||
expect(firstBlockWrapper).toBeTruthy();
|
||||
|
||||
const blockComponent = firstBlockWrapper!.querySelector('dees-wysiwyg-block') as any;
|
||||
expect(blockComponent).toBeTruthy();
|
||||
|
||||
// Wait for block to be ready
|
||||
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 // Important for shadow DOM
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
// Check if menu items from WYSIWYG block are rendered
|
||||
const menuItems = contextMenu!.shadowRoot!.querySelectorAll('.menuitem');
|
||||
const menuTexts = Array.from(menuItems).map(item =>
|
||||
item.querySelector('.menuitem-text')?.textContent?.trim()
|
||||
);
|
||||
|
||||
// Should have "Change Type" and "Delete Block" items
|
||||
expect(menuTexts).toContain('Change Type');
|
||||
expect(menuTexts).toContain('Delete Block');
|
||||
|
||||
// Check if "Change Type" has submenu indicator
|
||||
const changeTypeItem = Array.from(menuItems).find(item =>
|
||||
item.querySelector('.menuitem-text')?.textContent?.trim() === 'Change Type'
|
||||
);
|
||||
expect(changeTypeItem?.classList.contains('has-submenu')).toEqual(true);
|
||||
|
||||
// Clean up
|
||||
contextMenu!.remove();
|
||||
wysiwygEditor.remove();
|
||||
});
|
||||
|
||||
export default tap.start();
|
Reference in New Issue
Block a user