refactor
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
|
||||
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||
|
||||
import * as deesCatalog from '../ts_web';
|
||||
import * as deesCatalog from '../ts_web/index.js';
|
||||
|
||||
tap.test('should create a working button', async () => {
|
||||
const button: deesCatalog.DeesButton = await webhelpers.fixture(
|
||||
@ -9,4 +9,4 @@ tap.test('should create a working button', async () => {
|
||||
expect(button).toBeInstanceOf(deesCatalog.DeesButton);
|
||||
});
|
||||
|
||||
tap.start();
|
||||
export default tap.start();
|
||||
|
175
test/test.shadow-dom-containment.browser.ts
Normal file
175
test/test.shadow-dom-containment.browser.ts
Normal file
@ -0,0 +1,175 @@
|
||||
import { expect, tap, webhelpers } from '@push.rocks/tapbundle';
|
||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
||||
import { WysiwygSelection } from '../ts_web/elements/wysiwyg/wysiwyg.selection.js';
|
||||
|
||||
tap.test('Shadow DOM containment should work correctly', async () => {
|
||||
console.log('=== Testing Shadow DOM Containment ===');
|
||||
|
||||
// Create a WYSIWYG block component
|
||||
const block = await webhelpers.fixture<DeesWysiwygBlock>(
|
||||
'<dees-wysiwyg-block></dees-wysiwyg-block>'
|
||||
);
|
||||
|
||||
// Set the block data
|
||||
block.block = {
|
||||
id: 'test-1',
|
||||
type: 'paragraph',
|
||||
content: 'Hello world test content'
|
||||
};
|
||||
|
||||
block.handlers = {
|
||||
onInput: () => {},
|
||||
onKeyDown: () => {},
|
||||
onFocus: () => {},
|
||||
onBlur: () => {},
|
||||
onCompositionStart: () => {},
|
||||
onCompositionEnd: () => {}
|
||||
};
|
||||
|
||||
await block.updateComplete;
|
||||
|
||||
// Get the paragraph element inside Shadow DOM
|
||||
const container = block.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const paragraphBlock = container?.querySelector('.block.paragraph') as HTMLElement;
|
||||
|
||||
expect(paragraphBlock).toBeTruthy();
|
||||
console.log('Found paragraph block:', paragraphBlock);
|
||||
console.log('Paragraph text content:', paragraphBlock.textContent);
|
||||
|
||||
// Focus the paragraph
|
||||
paragraphBlock.focus();
|
||||
|
||||
// Manually set cursor position
|
||||
const textNode = paragraphBlock.firstChild;
|
||||
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
||||
const range = document.createRange();
|
||||
const selection = window.getSelection();
|
||||
|
||||
// Set cursor at position 11 (after "Hello world")
|
||||
range.setStart(textNode, 11);
|
||||
range.setEnd(textNode, 11);
|
||||
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
|
||||
console.log('Set cursor at position 11');
|
||||
|
||||
// Test the containment check
|
||||
console.log('\n--- Testing containment ---');
|
||||
const currentSelection = window.getSelection();
|
||||
if (currentSelection && currentSelection.rangeCount > 0) {
|
||||
const selRange = currentSelection.getRangeAt(0);
|
||||
console.log('Selection range:', {
|
||||
startContainer: selRange.startContainer,
|
||||
startOffset: selRange.startOffset,
|
||||
containerText: selRange.startContainer.textContent
|
||||
});
|
||||
|
||||
// Test regular contains (should fail across Shadow DOM)
|
||||
const regularContains = paragraphBlock.contains(selRange.startContainer);
|
||||
console.log('Regular contains:', regularContains);
|
||||
|
||||
// Test Shadow DOM-aware contains
|
||||
const shadowDOMContains = WysiwygSelection.containsAcrossShadowDOM(paragraphBlock, selRange.startContainer);
|
||||
console.log('Shadow DOM contains:', shadowDOMContains);
|
||||
|
||||
// Since we're setting selection within the same shadow DOM, both should be true
|
||||
expect(regularContains).toBeTrue();
|
||||
expect(shadowDOMContains).toBeTrue();
|
||||
}
|
||||
|
||||
// Test getSplitContent
|
||||
console.log('\n--- Testing getSplitContent ---');
|
||||
const splitResult = block.getSplitContent();
|
||||
console.log('Split result:', splitResult);
|
||||
|
||||
expect(splitResult).toBeTruthy();
|
||||
if (splitResult) {
|
||||
console.log('Before:', JSON.stringify(splitResult.before));
|
||||
console.log('After:', JSON.stringify(splitResult.after));
|
||||
|
||||
// Expected split at position 11
|
||||
expect(splitResult.before).toEqual('Hello world');
|
||||
expect(splitResult.after).toEqual(' test content');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('Shadow DOM containment across different shadow roots', async () => {
|
||||
console.log('=== Testing Cross Shadow Root Containment ===');
|
||||
|
||||
// Create parent component with WYSIWYG editor
|
||||
const parentDiv = document.createElement('div');
|
||||
parentDiv.innerHTML = `
|
||||
<dees-input-wysiwyg>
|
||||
<dees-wysiwyg-block></dees-wysiwyg-block>
|
||||
</dees-input-wysiwyg>
|
||||
`;
|
||||
document.body.appendChild(parentDiv);
|
||||
|
||||
// Wait for components to be ready
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
const wysiwygInput = parentDiv.querySelector('dees-input-wysiwyg') as any;
|
||||
const blockElement = wysiwygInput?.shadowRoot?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
|
||||
if (blockElement) {
|
||||
// Set block data
|
||||
blockElement.block = {
|
||||
id: 'test-2',
|
||||
type: 'paragraph',
|
||||
content: 'Cross shadow DOM test'
|
||||
};
|
||||
|
||||
blockElement.handlers = {
|
||||
onInput: () => {},
|
||||
onKeyDown: () => {},
|
||||
onFocus: () => {},
|
||||
onBlur: () => {},
|
||||
onCompositionStart: () => {},
|
||||
onCompositionEnd: () => {}
|
||||
};
|
||||
|
||||
await blockElement.updateComplete;
|
||||
|
||||
// Get the paragraph inside the nested shadow DOM
|
||||
const container = blockElement.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const paragraphBlock = container?.querySelector('.block.paragraph') as HTMLElement;
|
||||
|
||||
if (paragraphBlock) {
|
||||
console.log('Found nested paragraph block');
|
||||
|
||||
// Focus and set selection
|
||||
paragraphBlock.focus();
|
||||
const textNode = paragraphBlock.firstChild;
|
||||
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
||||
const range = document.createRange();
|
||||
range.setStart(textNode, 5);
|
||||
range.setEnd(textNode, 5);
|
||||
|
||||
const selection = window.getSelection();
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
|
||||
// Test containment from parent's perspective
|
||||
const selRange = selection?.getRangeAt(0);
|
||||
if (selRange) {
|
||||
// This should fail because it crosses shadow DOM boundary
|
||||
const regularContains = wysiwygInput.contains(selRange.startContainer);
|
||||
console.log('Parent regular contains:', regularContains);
|
||||
expect(regularContains).toBeFalse();
|
||||
|
||||
// This should work with our Shadow DOM-aware method
|
||||
const shadowDOMContains = WysiwygSelection.containsAcrossShadowDOM(wysiwygInput, selRange.startContainer);
|
||||
console.log('Parent shadow DOM contains:', shadowDOMContains);
|
||||
expect(shadowDOMContains).toBeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
document.body.removeChild(parentDiv);
|
||||
});
|
||||
|
||||
export default tap.start();
|
69
test/test.wysiwyg-blocks-debug.browser.ts
Normal file
69
test/test.wysiwyg-blocks-debug.browser.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
|
||||
|
||||
import * as deesCatalog from '../ts_web/index.js';
|
||||
import { BlockRegistry } from '../ts_web/elements/wysiwyg/blocks/block.registry.js';
|
||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
||||
|
||||
// Import block registration to ensure handlers are registered
|
||||
import '../ts_web/elements/wysiwyg/wysiwyg.blockregistration.js';
|
||||
|
||||
tap.test('Debug: should create empty wysiwyg block component', async () => {
|
||||
try {
|
||||
console.log('Creating DeesWysiwygBlock...');
|
||||
const block: DeesWysiwygBlock = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-wysiwyg-block></dees-wysiwyg-block>`
|
||||
);
|
||||
console.log('Block created:', block);
|
||||
expect(block).toBeDefined();
|
||||
expect(block).toBeInstanceOf(DeesWysiwygBlock);
|
||||
console.log('Initial block property:', block.block);
|
||||
console.log('Initial handlers property:', block.handlers);
|
||||
} catch (error) {
|
||||
console.error('Error creating block:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('Debug: should set properties step by step', async () => {
|
||||
try {
|
||||
console.log('Step 1: Creating component...');
|
||||
const block: DeesWysiwygBlock = document.createElement('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
expect(block).toBeDefined();
|
||||
|
||||
console.log('Step 2: Setting handlers...');
|
||||
block.handlers = {
|
||||
onInput: () => console.log('onInput'),
|
||||
onKeyDown: () => console.log('onKeyDown'),
|
||||
onFocus: () => console.log('onFocus'),
|
||||
onBlur: () => console.log('onBlur'),
|
||||
onCompositionStart: () => console.log('onCompositionStart'),
|
||||
onCompositionEnd: () => console.log('onCompositionEnd')
|
||||
};
|
||||
console.log('Handlers set:', block.handlers);
|
||||
|
||||
console.log('Step 3: Setting block data...');
|
||||
block.block = {
|
||||
id: 'test-block',
|
||||
type: 'divider',
|
||||
content: ' '
|
||||
};
|
||||
console.log('Block set:', block.block);
|
||||
|
||||
console.log('Step 4: Appending to body...');
|
||||
document.body.appendChild(block);
|
||||
|
||||
console.log('Step 5: Waiting for update...');
|
||||
await block.updateComplete;
|
||||
console.log('Update complete');
|
||||
|
||||
console.log('Step 6: Checking shadowRoot...');
|
||||
expect(block.shadowRoot).toBeDefined();
|
||||
console.log('ShadowRoot exists');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in step-by-step test:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
205
test/test.wysiwyg-blocks.browser.ts
Normal file
205
test/test.wysiwyg-blocks.browser.ts
Normal file
@ -0,0 +1,205 @@
|
||||
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
|
||||
|
||||
import * as deesCatalog from '../ts_web/index.js';
|
||||
import { BlockRegistry } from '../ts_web/elements/wysiwyg/blocks/block.registry.js';
|
||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
||||
|
||||
// Import block registration to ensure handlers are registered
|
||||
import '../ts_web/elements/wysiwyg/wysiwyg.blockregistration.js';
|
||||
|
||||
tap.test('BlockRegistry should have registered handlers', async () => {
|
||||
// Test divider handler
|
||||
const dividerHandler = BlockRegistry.getHandler('divider');
|
||||
expect(dividerHandler).toBeDefined();
|
||||
expect(dividerHandler?.type).toEqual('divider');
|
||||
|
||||
// Test paragraph handler
|
||||
const paragraphHandler = BlockRegistry.getHandler('paragraph');
|
||||
expect(paragraphHandler).toBeDefined();
|
||||
expect(paragraphHandler?.type).toEqual('paragraph');
|
||||
|
||||
// Test heading handlers
|
||||
const heading1Handler = BlockRegistry.getHandler('heading-1');
|
||||
expect(heading1Handler).toBeDefined();
|
||||
expect(heading1Handler?.type).toEqual('heading-1');
|
||||
|
||||
const heading2Handler = BlockRegistry.getHandler('heading-2');
|
||||
expect(heading2Handler).toBeDefined();
|
||||
expect(heading2Handler?.type).toEqual('heading-2');
|
||||
|
||||
const heading3Handler = BlockRegistry.getHandler('heading-3');
|
||||
expect(heading3Handler).toBeDefined();
|
||||
expect(heading3Handler?.type).toEqual('heading-3');
|
||||
|
||||
// Test that getAllTypes returns all registered types
|
||||
const allTypes = BlockRegistry.getAllTypes();
|
||||
expect(allTypes).toContain('divider');
|
||||
expect(allTypes).toContain('paragraph');
|
||||
expect(allTypes).toContain('heading-1');
|
||||
expect(allTypes).toContain('heading-2');
|
||||
expect(allTypes).toContain('heading-3');
|
||||
});
|
||||
|
||||
tap.test('should render divider block using handler', async () => {
|
||||
const dividerBlock: DeesWysiwygBlock = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-wysiwyg-block></dees-wysiwyg-block>`
|
||||
);
|
||||
|
||||
// Set required handlers
|
||||
dividerBlock.handlers = {
|
||||
onInput: () => {},
|
||||
onKeyDown: () => {},
|
||||
onFocus: () => {},
|
||||
onBlur: () => {},
|
||||
onCompositionStart: () => {},
|
||||
onCompositionEnd: () => {}
|
||||
};
|
||||
|
||||
// Set a divider block
|
||||
dividerBlock.block = {
|
||||
id: 'test-divider',
|
||||
type: 'divider',
|
||||
content: ' '
|
||||
};
|
||||
|
||||
await dividerBlock.updateComplete;
|
||||
|
||||
// Check that the divider is rendered
|
||||
const dividerElement = dividerBlock.shadowRoot?.querySelector('.block.divider');
|
||||
expect(dividerElement).toBeDefined();
|
||||
expect(dividerElement?.getAttribute('tabindex')).toEqual('0');
|
||||
|
||||
// Check for the divider icon
|
||||
const icon = dividerBlock.shadowRoot?.querySelector('.divider-icon');
|
||||
expect(icon).toBeDefined();
|
||||
});
|
||||
|
||||
tap.test('should render paragraph block using handler', async () => {
|
||||
const paragraphBlock: DeesWysiwygBlock = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-wysiwyg-block></dees-wysiwyg-block>`
|
||||
);
|
||||
|
||||
// Set required handlers
|
||||
paragraphBlock.handlers = {
|
||||
onInput: () => {},
|
||||
onKeyDown: () => {},
|
||||
onFocus: () => {},
|
||||
onBlur: () => {},
|
||||
onCompositionStart: () => {},
|
||||
onCompositionEnd: () => {},
|
||||
onMouseUp: () => {}
|
||||
};
|
||||
|
||||
// Set a paragraph block
|
||||
paragraphBlock.block = {
|
||||
id: 'test-paragraph',
|
||||
type: 'paragraph',
|
||||
content: 'Test paragraph content'
|
||||
};
|
||||
|
||||
await paragraphBlock.updateComplete;
|
||||
|
||||
// Check that the paragraph is rendered
|
||||
const paragraphElement = paragraphBlock.shadowRoot?.querySelector('.block.paragraph');
|
||||
expect(paragraphElement).toBeDefined();
|
||||
expect(paragraphElement?.getAttribute('contenteditable')).toEqual('true');
|
||||
expect(paragraphElement?.textContent).toEqual('Test paragraph content');
|
||||
});
|
||||
|
||||
tap.test('should render heading blocks using handler', async () => {
|
||||
// Test heading-1
|
||||
const heading1Block: DeesWysiwygBlock = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-wysiwyg-block></dees-wysiwyg-block>`
|
||||
);
|
||||
|
||||
// Set required handlers
|
||||
heading1Block.handlers = {
|
||||
onInput: () => {},
|
||||
onKeyDown: () => {},
|
||||
onFocus: () => {},
|
||||
onBlur: () => {},
|
||||
onCompositionStart: () => {},
|
||||
onCompositionEnd: () => {},
|
||||
onMouseUp: () => {}
|
||||
};
|
||||
|
||||
heading1Block.block = {
|
||||
id: 'test-h1',
|
||||
type: 'heading-1',
|
||||
content: 'Heading 1 Test'
|
||||
};
|
||||
|
||||
await heading1Block.updateComplete;
|
||||
|
||||
const h1Element = heading1Block.shadowRoot?.querySelector('.block.heading-1');
|
||||
expect(h1Element).toBeDefined();
|
||||
expect(h1Element?.textContent).toEqual('Heading 1 Test');
|
||||
|
||||
// Test heading-2
|
||||
const heading2Block: DeesWysiwygBlock = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-wysiwyg-block></dees-wysiwyg-block>`
|
||||
);
|
||||
|
||||
// Set required handlers
|
||||
heading2Block.handlers = {
|
||||
onInput: () => {},
|
||||
onKeyDown: () => {},
|
||||
onFocus: () => {},
|
||||
onBlur: () => {},
|
||||
onCompositionStart: () => {},
|
||||
onCompositionEnd: () => {},
|
||||
onMouseUp: () => {}
|
||||
};
|
||||
|
||||
heading2Block.block = {
|
||||
id: 'test-h2',
|
||||
type: 'heading-2',
|
||||
content: 'Heading 2 Test'
|
||||
};
|
||||
|
||||
await heading2Block.updateComplete;
|
||||
|
||||
const h2Element = heading2Block.shadowRoot?.querySelector('.block.heading-2');
|
||||
expect(h2Element).toBeDefined();
|
||||
expect(h2Element?.textContent).toEqual('Heading 2 Test');
|
||||
});
|
||||
|
||||
tap.test('paragraph block handler methods should work', async () => {
|
||||
const paragraphBlock: DeesWysiwygBlock = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-wysiwyg-block></dees-wysiwyg-block>`
|
||||
);
|
||||
|
||||
// Set required handlers
|
||||
paragraphBlock.handlers = {
|
||||
onInput: () => {},
|
||||
onKeyDown: () => {},
|
||||
onFocus: () => {},
|
||||
onBlur: () => {},
|
||||
onCompositionStart: () => {},
|
||||
onCompositionEnd: () => {},
|
||||
onMouseUp: () => {}
|
||||
};
|
||||
|
||||
paragraphBlock.block = {
|
||||
id: 'test-methods',
|
||||
type: 'paragraph',
|
||||
content: 'Initial content'
|
||||
};
|
||||
|
||||
await paragraphBlock.updateComplete;
|
||||
|
||||
// Test getContent
|
||||
const content = paragraphBlock.getContent();
|
||||
expect(content).toEqual('Initial content');
|
||||
|
||||
// Test setContent
|
||||
paragraphBlock.setContent('Updated content');
|
||||
await paragraphBlock.updateComplete;
|
||||
expect(paragraphBlock.getContent()).toEqual('Updated content');
|
||||
|
||||
// Test that the DOM is updated
|
||||
const paragraphElement = paragraphBlock.shadowRoot?.querySelector('.block.paragraph');
|
||||
expect(paragraphElement?.textContent).toEqual('Updated content');
|
||||
});
|
||||
|
||||
export default tap.start();
|
341
test/test.wysiwyg-keyboard.browser.ts
Normal file
341
test/test.wysiwyg-keyboard.browser.ts
Normal file
@ -0,0 +1,341 @@
|
||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
||||
|
||||
tap.test('Keyboard: Arrow navigation between blocks', async () => {
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import multiple blocks
|
||||
editor.importBlocks([
|
||||
{ id: 'block-1', type: 'paragraph', content: 'First paragraph' },
|
||||
{ id: 'block-2', type: 'paragraph', content: 'Second paragraph' },
|
||||
{ id: 'block-3', type: 'paragraph', content: 'Third paragraph' }
|
||||
]);
|
||||
|
||||
await editor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Focus first block at end
|
||||
const firstBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="block-1"]');
|
||||
const firstBlockComponent = firstBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const firstBlockContainer = firstBlockComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const firstParagraph = firstBlockContainer?.querySelector('.block.paragraph') as HTMLElement;
|
||||
|
||||
// Focus and set cursor at end of first block
|
||||
firstParagraph.focus();
|
||||
const textNode = firstParagraph.firstChild;
|
||||
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
||||
const range = document.createRange();
|
||||
const selection = window.getSelection();
|
||||
range.setStart(textNode, textNode.textContent?.length || 0);
|
||||
range.setEnd(textNode, textNode.textContent?.length || 0);
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Press ArrowRight to move to second block
|
||||
const arrowRightEvent = new KeyboardEvent('keydown', {
|
||||
key: 'ArrowRight',
|
||||
code: 'ArrowRight',
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true
|
||||
});
|
||||
|
||||
firstParagraph.dispatchEvent(arrowRightEvent);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Check if second block is focused
|
||||
const secondBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="block-2"]');
|
||||
const secondBlockComponent = secondBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const secondBlockContainer = secondBlockComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const secondParagraph = secondBlockContainer?.querySelector('.block.paragraph') as HTMLElement;
|
||||
|
||||
// Check if the second paragraph has focus
|
||||
const activeElement = secondBlockComponent.shadowRoot?.activeElement;
|
||||
expect(activeElement).toEqual(secondParagraph);
|
||||
|
||||
console.log('Arrow navigation test complete');
|
||||
});
|
||||
|
||||
tap.test('Keyboard: Backspace merges blocks', async () => {
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import two blocks
|
||||
editor.importBlocks([
|
||||
{ id: 'merge-1', type: 'paragraph', content: 'First' },
|
||||
{ id: 'merge-2', type: 'paragraph', content: 'Second' }
|
||||
]);
|
||||
|
||||
await editor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Focus second block at beginning
|
||||
const secondBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="merge-2"]');
|
||||
const secondBlockComponent = secondBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const secondBlockContainer = secondBlockComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const secondParagraph = secondBlockContainer?.querySelector('.block.paragraph') as HTMLElement;
|
||||
|
||||
// Focus and set cursor at beginning
|
||||
secondParagraph.focus();
|
||||
const textNode = secondParagraph.firstChild;
|
||||
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
||||
const range = document.createRange();
|
||||
const selection = window.getSelection();
|
||||
range.setStart(textNode, 0);
|
||||
range.setEnd(textNode, 0);
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Press Backspace to merge with previous block
|
||||
const backspaceEvent = new KeyboardEvent('keydown', {
|
||||
key: 'Backspace',
|
||||
code: 'Backspace',
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true
|
||||
});
|
||||
|
||||
secondParagraph.dispatchEvent(backspaceEvent);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Check if blocks were merged
|
||||
expect(editor.blocks.length).toEqual(1);
|
||||
expect(editor.blocks[0].content).toContain('First');
|
||||
expect(editor.blocks[0].content).toContain('Second');
|
||||
|
||||
console.log('Backspace merge test complete');
|
||||
});
|
||||
|
||||
tap.test('Keyboard: Delete key on non-editable blocks', async () => {
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import blocks including a divider
|
||||
editor.importBlocks([
|
||||
{ id: 'para-1', type: 'paragraph', content: 'Before divider' },
|
||||
{ id: 'div-1', type: 'divider', content: '' },
|
||||
{ id: 'para-2', type: 'paragraph', content: 'After divider' }
|
||||
]);
|
||||
|
||||
await editor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Focus the divider block
|
||||
const dividerBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="div-1"]');
|
||||
const dividerBlockComponent = dividerBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const dividerBlockContainer = dividerBlockComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const dividerElement = dividerBlockContainer?.querySelector('.block.divider') as HTMLElement;
|
||||
|
||||
// Non-editable blocks need to be focused differently
|
||||
dividerElement?.focus();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Press Delete to remove the divider
|
||||
const deleteEvent = new KeyboardEvent('keydown', {
|
||||
key: 'Delete',
|
||||
code: 'Delete',
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true
|
||||
});
|
||||
|
||||
dividerElement.dispatchEvent(deleteEvent);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Check if divider was removed
|
||||
expect(editor.blocks.length).toEqual(2);
|
||||
expect(editor.blocks.find(b => b.type === 'divider')).toBeUndefined();
|
||||
|
||||
console.log('Delete key on non-editable block test complete');
|
||||
});
|
||||
|
||||
tap.test('Keyboard: Tab key in code block', async () => {
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import a code block
|
||||
editor.importBlocks([
|
||||
{ id: 'code-1', type: 'code', content: 'function test() {', metadata: { language: 'javascript' } }
|
||||
]);
|
||||
|
||||
await editor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Focus code block
|
||||
const codeBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="code-1"]');
|
||||
const codeBlockComponent = codeBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const codeBlockContainer = codeBlockComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const codeElement = codeBlockContainer?.querySelector('.block.code') as HTMLElement;
|
||||
|
||||
// Focus and set cursor at end
|
||||
codeElement.focus();
|
||||
const textNode = codeElement.firstChild;
|
||||
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
||||
const range = document.createRange();
|
||||
const selection = window.getSelection();
|
||||
range.setStart(textNode, textNode.textContent?.length || 0);
|
||||
range.setEnd(textNode, textNode.textContent?.length || 0);
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Press Tab to insert spaces
|
||||
const tabEvent = new KeyboardEvent('keydown', {
|
||||
key: 'Tab',
|
||||
code: 'Tab',
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true
|
||||
});
|
||||
|
||||
codeElement.dispatchEvent(tabEvent);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Check if spaces were inserted
|
||||
const updatedContent = codeElement.textContent || '';
|
||||
expect(updatedContent).toContain(' '); // Tab should insert 2 spaces
|
||||
|
||||
console.log('Tab in code block test complete');
|
||||
});
|
||||
|
||||
tap.test('Keyboard: ArrowUp/Down navigation', async () => {
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import multiple blocks
|
||||
editor.importBlocks([
|
||||
{ id: 'nav-1', type: 'paragraph', content: 'First line' },
|
||||
{ id: 'nav-2', type: 'paragraph', content: 'Second line' },
|
||||
{ id: 'nav-3', type: 'paragraph', content: 'Third line' }
|
||||
]);
|
||||
|
||||
await editor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Focus second block
|
||||
const secondBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="nav-2"]');
|
||||
const secondBlockComponent = secondBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const secondBlockContainer = secondBlockComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const secondParagraph = secondBlockContainer?.querySelector('.block.paragraph') as HTMLElement;
|
||||
|
||||
secondParagraph.focus();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Press ArrowUp to move to first block
|
||||
const arrowUpEvent = new KeyboardEvent('keydown', {
|
||||
key: 'ArrowUp',
|
||||
code: 'ArrowUp',
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true
|
||||
});
|
||||
|
||||
secondParagraph.dispatchEvent(arrowUpEvent);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Check if first block is focused
|
||||
const firstBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="nav-1"]');
|
||||
const firstBlockComponent = firstBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const firstParagraph = firstBlockComponent?.shadowRoot?.querySelector('.block.paragraph') as HTMLElement;
|
||||
|
||||
expect(firstBlockComponent.shadowRoot?.activeElement).toEqual(firstParagraph);
|
||||
|
||||
// Now press ArrowDown twice to get to third block
|
||||
const arrowDownEvent = new KeyboardEvent('keydown', {
|
||||
key: 'ArrowDown',
|
||||
code: 'ArrowDown',
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true
|
||||
});
|
||||
|
||||
firstParagraph.dispatchEvent(arrowDownEvent);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Second block should be focused, dispatch again
|
||||
const secondActiveElement = secondBlockComponent.shadowRoot?.activeElement;
|
||||
if (secondActiveElement) {
|
||||
secondActiveElement.dispatchEvent(arrowDownEvent);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
}
|
||||
|
||||
// Check if third block is focused
|
||||
const thirdBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="nav-3"]');
|
||||
const thirdBlockComponent = thirdBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const thirdParagraph = thirdBlockComponent?.shadowRoot?.querySelector('.block.paragraph') as HTMLElement;
|
||||
|
||||
expect(thirdBlockComponent.shadowRoot?.activeElement).toEqual(thirdParagraph);
|
||||
|
||||
console.log('ArrowUp/Down navigation test complete');
|
||||
});
|
||||
|
||||
tap.test('Keyboard: Formatting shortcuts', async () => {
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import a paragraph
|
||||
editor.importBlocks([
|
||||
{ id: 'format-1', type: 'paragraph', content: 'Test formatting' }
|
||||
]);
|
||||
|
||||
await editor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Focus and select text
|
||||
const blockWrapper = editor.shadowRoot?.querySelector('[data-block-id="format-1"]');
|
||||
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const blockContainer = blockComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const paragraph = blockContainer?.querySelector('.block.paragraph') as HTMLElement;
|
||||
|
||||
paragraph.focus();
|
||||
|
||||
// Select "formatting"
|
||||
const textNode = paragraph.firstChild;
|
||||
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
||||
const range = document.createRange();
|
||||
const selection = window.getSelection();
|
||||
range.setStart(textNode, 5); // After "Test "
|
||||
range.setEnd(textNode, 15); // After "formatting"
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Press Cmd/Ctrl+B for bold
|
||||
const boldEvent = new KeyboardEvent('keydown', {
|
||||
key: 'b',
|
||||
code: 'KeyB',
|
||||
metaKey: true, // Use metaKey for Mac, ctrlKey for Windows/Linux
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true
|
||||
});
|
||||
|
||||
paragraph.dispatchEvent(boldEvent);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Check if bold was applied
|
||||
const content = paragraph.innerHTML;
|
||||
expect(content).toContain('<strong>') || expect(content).toContain('<b>');
|
||||
|
||||
console.log('Formatting shortcuts test complete');
|
||||
});
|
||||
|
||||
export default tap.start();
|
150
test/test.wysiwyg-phase3.browser.ts
Normal file
150
test/test.wysiwyg-phase3.browser.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
||||
|
||||
tap.test('Phase 3: Quote block should render and work correctly', async () => {
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import a quote block
|
||||
editor.importBlocks([
|
||||
{ id: 'quote-1', type: 'quote', content: 'This is a famous quote' }
|
||||
]);
|
||||
|
||||
await editor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Check if quote block was rendered
|
||||
const quoteBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="quote-1"]');
|
||||
const quoteBlockComponent = quoteBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
expect(quoteBlockComponent).toBeTruthy();
|
||||
|
||||
const quoteContainer = quoteBlockComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const quoteElement = quoteContainer?.querySelector('.block.quote') as HTMLElement;
|
||||
expect(quoteElement).toBeTruthy();
|
||||
expect(quoteElement?.textContent).toEqual('This is a famous quote');
|
||||
|
||||
// Check if styles are applied (border-left for quote)
|
||||
const computedStyle = window.getComputedStyle(quoteElement);
|
||||
expect(computedStyle.borderLeftStyle).toEqual('solid');
|
||||
expect(computedStyle.fontStyle).toEqual('italic');
|
||||
});
|
||||
|
||||
tap.test('Phase 3: Code block should render and handle tab correctly', async () => {
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import a code block
|
||||
editor.importBlocks([
|
||||
{ id: 'code-1', type: 'code', content: 'const x = 42;', metadata: { language: 'javascript' } }
|
||||
]);
|
||||
|
||||
await editor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Check if code block was rendered
|
||||
const codeBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="code-1"]');
|
||||
const codeBlockComponent = codeBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const codeContainer = codeBlockComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const codeElement = codeContainer?.querySelector('.block.code') as HTMLElement;
|
||||
|
||||
expect(codeElement).toBeTruthy();
|
||||
expect(codeElement?.textContent).toEqual('const x = 42;');
|
||||
|
||||
// Check if language label is shown
|
||||
const languageLabel = codeContainer?.querySelector('.code-language');
|
||||
expect(languageLabel?.textContent).toEqual('javascript');
|
||||
|
||||
// Check if monospace font is applied
|
||||
const computedStyle = window.getComputedStyle(codeElement);
|
||||
expect(computedStyle.fontFamily).toContain('monospace');
|
||||
});
|
||||
|
||||
tap.test('Phase 3: List block should render correctly', async () => {
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import a list block
|
||||
editor.importBlocks([
|
||||
{ id: 'list-1', type: 'list', content: 'First item\nSecond item\nThird item' }
|
||||
]);
|
||||
|
||||
await editor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Check if list block was rendered
|
||||
const listBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="list-1"]');
|
||||
const listBlockComponent = listBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const listContainer = listBlockComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const listElement = listContainer?.querySelector('.block.list') as HTMLElement;
|
||||
|
||||
expect(listElement).toBeTruthy();
|
||||
|
||||
// Check if list items were created
|
||||
const listItems = listElement?.querySelectorAll('li');
|
||||
expect(listItems?.length).toEqual(3);
|
||||
expect(listItems?.[0].textContent).toEqual('First item');
|
||||
expect(listItems?.[1].textContent).toEqual('Second item');
|
||||
expect(listItems?.[2].textContent).toEqual('Third item');
|
||||
|
||||
// Check if it's an unordered list by default
|
||||
const ulElement = listElement?.querySelector('ul');
|
||||
expect(ulElement).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('Phase 3: Quote block split should work', async () => {
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import a quote block
|
||||
editor.importBlocks([
|
||||
{ id: 'quote-split', type: 'quote', content: 'To be or not to be' }
|
||||
]);
|
||||
|
||||
await editor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Get the quote block
|
||||
const quoteBlockWrapper = editor.shadowRoot?.querySelector('[data-block-id="quote-split"]');
|
||||
const quoteBlockComponent = quoteBlockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const quoteContainer = quoteBlockComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const quoteElement = quoteContainer?.querySelector('.block.quote') as HTMLElement;
|
||||
|
||||
// Focus and set cursor after "To be"
|
||||
quoteElement.focus();
|
||||
const textNode = quoteElement.firstChild;
|
||||
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
||||
const range = document.createRange();
|
||||
const selection = window.getSelection();
|
||||
range.setStart(textNode, 5); // After "To be"
|
||||
range.setEnd(textNode, 5);
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Press Enter to split
|
||||
const enterEvent = new KeyboardEvent('keydown', {
|
||||
key: 'Enter',
|
||||
code: 'Enter',
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true
|
||||
});
|
||||
|
||||
quoteElement.dispatchEvent(enterEvent);
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Check if split happened correctly
|
||||
expect(editor.blocks.length).toEqual(2);
|
||||
expect(editor.blocks[0].content).toEqual('To be');
|
||||
expect(editor.blocks[1].content).toEqual(' or not to be');
|
||||
expect(editor.blocks[1].type).toEqual('paragraph'); // New block should be paragraph
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
105
test/test.wysiwyg-registry.both.ts
Normal file
105
test/test.wysiwyg-registry.both.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||
|
||||
import { BlockRegistry } from '../ts_web/elements/wysiwyg/blocks/block.registry.js';
|
||||
import { DividerBlockHandler } from '../ts_web/elements/wysiwyg/blocks/content/divider.block.js';
|
||||
import { ParagraphBlockHandler } from '../ts_web/elements/wysiwyg/blocks/text/paragraph.block.js';
|
||||
import { HeadingBlockHandler } from '../ts_web/elements/wysiwyg/blocks/text/heading.block.js';
|
||||
|
||||
// Import block registration to ensure handlers are registered
|
||||
import '../ts_web/elements/wysiwyg/wysiwyg.blockregistration.js';
|
||||
|
||||
tap.test('BlockRegistry should register and retrieve handlers', async () => {
|
||||
// Test divider handler
|
||||
const dividerHandler = BlockRegistry.getHandler('divider');
|
||||
expect(dividerHandler).toBeDefined();
|
||||
expect(dividerHandler).toBeInstanceOf(DividerBlockHandler);
|
||||
expect(dividerHandler?.type).toEqual('divider');
|
||||
|
||||
// Test paragraph handler
|
||||
const paragraphHandler = BlockRegistry.getHandler('paragraph');
|
||||
expect(paragraphHandler).toBeDefined();
|
||||
expect(paragraphHandler).toBeInstanceOf(ParagraphBlockHandler);
|
||||
expect(paragraphHandler?.type).toEqual('paragraph');
|
||||
|
||||
// Test heading handlers
|
||||
const heading1Handler = BlockRegistry.getHandler('heading-1');
|
||||
expect(heading1Handler).toBeDefined();
|
||||
expect(heading1Handler).toBeInstanceOf(HeadingBlockHandler);
|
||||
expect(heading1Handler?.type).toEqual('heading-1');
|
||||
|
||||
const heading2Handler = BlockRegistry.getHandler('heading-2');
|
||||
expect(heading2Handler).toBeDefined();
|
||||
expect(heading2Handler).toBeInstanceOf(HeadingBlockHandler);
|
||||
expect(heading2Handler?.type).toEqual('heading-2');
|
||||
|
||||
const heading3Handler = BlockRegistry.getHandler('heading-3');
|
||||
expect(heading3Handler).toBeDefined();
|
||||
expect(heading3Handler).toBeInstanceOf(HeadingBlockHandler);
|
||||
expect(heading3Handler?.type).toEqual('heading-3');
|
||||
});
|
||||
|
||||
tap.test('Block handlers should render content correctly', async () => {
|
||||
const testBlock = {
|
||||
id: 'test-1',
|
||||
type: 'paragraph' as const,
|
||||
content: 'Test paragraph content'
|
||||
};
|
||||
|
||||
const handler = BlockRegistry.getHandler('paragraph');
|
||||
expect(handler).toBeDefined();
|
||||
|
||||
if (handler) {
|
||||
const rendered = handler.render(testBlock, false);
|
||||
expect(rendered).toContain('contenteditable="true"');
|
||||
expect(rendered).toContain('data-block-type="paragraph"');
|
||||
expect(rendered).toContain('Test paragraph content');
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('Divider handler should render correctly', async () => {
|
||||
const dividerBlock = {
|
||||
id: 'test-divider',
|
||||
type: 'divider' as const,
|
||||
content: ' '
|
||||
};
|
||||
|
||||
const handler = BlockRegistry.getHandler('divider');
|
||||
expect(handler).toBeDefined();
|
||||
|
||||
if (handler) {
|
||||
const rendered = handler.render(dividerBlock, false);
|
||||
expect(rendered).toContain('class="block divider"');
|
||||
expect(rendered).toContain('tabindex="0"');
|
||||
expect(rendered).toContain('divider-icon');
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('Heading handlers should render with correct levels', async () => {
|
||||
const headingBlock = {
|
||||
id: 'test-h1',
|
||||
type: 'heading-1' as const,
|
||||
content: 'Test Heading'
|
||||
};
|
||||
|
||||
const handler = BlockRegistry.getHandler('heading-1');
|
||||
expect(handler).toBeDefined();
|
||||
|
||||
if (handler) {
|
||||
const rendered = handler.render(headingBlock, false);
|
||||
expect(rendered).toContain('class="block heading-1"');
|
||||
expect(rendered).toContain('contenteditable="true"');
|
||||
expect(rendered).toContain('Test Heading');
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('getAllTypes should return all registered types', async () => {
|
||||
const allTypes = BlockRegistry.getAllTypes();
|
||||
expect(allTypes).toContain('divider');
|
||||
expect(allTypes).toContain('paragraph');
|
||||
expect(allTypes).toContain('heading-1');
|
||||
expect(allTypes).toContain('heading-2');
|
||||
expect(allTypes).toContain('heading-3');
|
||||
expect(allTypes.length).toBeGreaterThanOrEqual(5);
|
||||
});
|
||||
|
||||
export default tap.start();
|
98
test/test.wysiwyg-split.browser.ts
Normal file
98
test/test.wysiwyg-split.browser.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
||||
|
||||
tap.test('should split paragraph content on Enter key', async () => {
|
||||
// Create the wysiwyg editor
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import a test paragraph
|
||||
editor.importBlocks([{
|
||||
id: 'test-para-1',
|
||||
type: 'paragraph',
|
||||
content: 'Hello World'
|
||||
}]);
|
||||
|
||||
await editor.updateComplete;
|
||||
|
||||
// Wait for blocks to render
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Get the block wrapper and component
|
||||
const blockWrapper = editor.shadowRoot?.querySelector('[data-block-id="test-para-1"]');
|
||||
expect(blockWrapper).toBeDefined();
|
||||
|
||||
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
expect(blockComponent).toBeDefined();
|
||||
expect(blockComponent.block.type).toEqual('paragraph');
|
||||
|
||||
// Wait for block to render
|
||||
await blockComponent.updateComplete;
|
||||
|
||||
// Test getSplitContent
|
||||
console.log('Testing getSplitContent...');
|
||||
const splitResult = blockComponent.getSplitContent();
|
||||
console.log('Split result:', splitResult);
|
||||
|
||||
// Since we haven't set cursor position, it might return null or split at start
|
||||
// This is just to test if the method is callable
|
||||
expect(typeof blockComponent.getSplitContent).toEqual('function');
|
||||
});
|
||||
|
||||
tap.test('should handle Enter key press in paragraph', async () => {
|
||||
// Create the wysiwyg editor
|
||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||
);
|
||||
|
||||
// Import a test paragraph
|
||||
editor.importBlocks([{
|
||||
id: 'test-enter-1',
|
||||
type: 'paragraph',
|
||||
content: 'First part|Second part' // | marks where we'll simulate cursor
|
||||
}]);
|
||||
|
||||
await editor.updateComplete;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Check initial state
|
||||
expect(editor.blocks.length).toEqual(1);
|
||||
expect(editor.blocks[0].content).toEqual('First part|Second part');
|
||||
|
||||
// Get the block element
|
||||
const blockWrapper = editor.shadowRoot?.querySelector('[data-block-id="test-enter-1"]');
|
||||
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||
const blockElement = blockComponent.shadowRoot?.querySelector('.block.paragraph') as HTMLDivElement;
|
||||
|
||||
expect(blockElement).toBeDefined();
|
||||
|
||||
// Set content without the | marker
|
||||
blockElement.textContent = 'First partSecond part';
|
||||
|
||||
// Focus the block
|
||||
blockElement.focus();
|
||||
|
||||
// Create and dispatch Enter key event
|
||||
const enterEvent = new KeyboardEvent('keydown', {
|
||||
key: 'Enter',
|
||||
code: 'Enter',
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true
|
||||
});
|
||||
|
||||
// Dispatch the event
|
||||
blockElement.dispatchEvent(enterEvent);
|
||||
|
||||
// Wait for processing
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Check if block was split (this might not work perfectly in test environment)
|
||||
console.log('Blocks after Enter:', editor.blocks.length);
|
||||
console.log('Block contents:', editor.blocks.map(b => b.content));
|
||||
});
|
||||
|
||||
export default tap.start();
|
Reference in New Issue
Block a user