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`` ); // 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`` ); // 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`` ); // 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`` ); // 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`` ); // 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`` ); // 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('') || expect(content).toContain(''); console.log('Formatting shortcuts test complete'); }); export default tap.start();