UNPKG

claritykit-svelte

Version:

A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility

382 lines (381 loc) 11.9 kB
/** * Editor test utilities for TipTap/ProseMirror components * Provides factory functions and test helpers for editor testing */ import { vi } from 'vitest'; import { editorMocks } from './browser-mocks'; /** * Mock Editor class that mimics TipTap Editor behavior */ export class MockEditor { constructor(config = {}) { Object.defineProperty(this, "element", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "extensions", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "content", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "editable", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "callbacks", { enumerable: true, configurable: true, writable: true, value: {} }); this.element = config.element || document.createElement('div'); this.extensions = config.extensions || []; this.content = config.content || ''; this.editable = config.editable !== false; // Store callbacks if (config.onTransaction) this.callbacks.onTransaction = [config.onTransaction]; if (config.onUpdate) this.callbacks.onUpdate = [config.onUpdate]; if (config.onFocus) this.callbacks.onFocus = [config.onFocus]; if (config.onBlur) this.callbacks.onBlur = [config.onBlur]; if (config.onCreate) this.callbacks.onCreate = [config.onCreate]; if (config.onDestroy) this.callbacks.onDestroy = [config.onDestroy]; // Initialize the mock DOM structure this._initializeDOMStructure(); // Call onCreate if provided if (config.onCreate) { config.onCreate({ editor: this }); } } _initializeDOMStructure() { // Create ProseMirror-like DOM structure this.element.classList.add('ProseMirror'); this.element.contentEditable = this.editable ? 'true' : 'false'; // Add some initial content if provided if (this.content) { if (typeof this.content === 'string' && this.content.startsWith('<')) { this.element.innerHTML = this.content; } else { this.element.textContent = this.content; } } else { // Add empty paragraph as default const p = document.createElement('p'); p.setAttribute('data-placeholder', 'Start typing...'); p.className = 'is-editor-empty'; this.element.appendChild(p); } } // Mock editor methods getHTML() { return this.element.innerHTML || '<p></p>'; } getJSON() { return { type: 'doc', content: [ { type: 'paragraph', content: this.element.textContent ? [ { type: 'text', text: this.element.textContent } ] : [] } ] }; } getText() { return this.element.textContent || ''; } setEditable(editable) { this.editable = editable; this.element.contentEditable = editable ? 'true' : 'false'; } isActive(name) { // Simple mock implementation return name === 'bold' ? this.element.querySelector('strong, b') !== null : name === 'italic' ? this.element.querySelector('em, i') !== null : false; } // Mock command chain chain() { const self = this; return { focus() { self.element.focus(); self._triggerCallback('onFocus'); return this; }, toggleBold() { // Mock bold toggle const selection = window.getSelection(); if (selection && selection.rangeCount > 0) { const range = selection.getRangeAt(0); const strong = document.createElement('strong'); try { range.surroundContents(strong); } catch (e) { // Ignore errors in test environment } } self._triggerCallback('onUpdate'); return this; }, toggleItalic() { // Mock italic toggle const selection = window.getSelection(); if (selection && selection.rangeCount > 0) { const range = selection.getRangeAt(0); const em = document.createElement('em'); try { range.surroundContents(em); } catch (e) { // Ignore errors in test environment } } self._triggerCallback('onUpdate'); return this; }, run() { // End of chain - trigger any pending callbacks return true; } }; } // Mock commands object get commands() { const self = this; return { setContent: (content) => { self.content = content; if (typeof content === 'string' && content.startsWith('<')) { self.element.innerHTML = content; } else { self.element.textContent = content; } self._triggerCallback('onUpdate'); return true; }, focus: () => { self.element.focus(); self._triggerCallback('onFocus'); return true; }, blur: () => { self.element.blur(); self._triggerCallback('onBlur'); return true; } }; } destroy() { this._triggerCallback('onDestroy'); // Clean up element if (this.element.parentNode) { this.element.parentNode.removeChild(this.element); } } focus() { this.element.focus(); this._triggerCallback('onFocus'); } blur() { this.element.blur(); this._triggerCallback('onBlur'); } _triggerCallback(eventName, data) { const callbacks = this.callbacks[eventName]; if (callbacks) { callbacks.forEach(callback => { try { if (eventName === 'onUpdate') { callback({ editor: this }); } else if (eventName === 'onFocus' || eventName === 'onBlur') { callback(); } else { callback(data || { editor: this }); } } catch (e) { // Ignore callback errors in tests } }); } } // Simulate user typing simulateTyping(text) { this.element.textContent = text; this.content = text; this._triggerCallback('onUpdate'); this._triggerCallback('onTransaction'); } // Simulate user focus simulateFocus() { this.element.focus(); this._triggerCallback('onFocus'); } // Simulate user blur simulateBlur() { this.element.blur(); this._triggerCallback('onBlur'); } } /** * Editor factory for creating consistent test setups */ export class EditorTestFactory { /** * Sets up the test environment for editor testing */ static setup() { if (!this.mocks) { this.mocks = editorMocks.setup(); } return this.mocks; } /** * Creates a mock editor instance */ static createEditor(config = {}) { this.setup(); return new MockEditor(config); } /** * Creates a mock editor element ready for mounting */ static createEditorElement() { this.setup(); return editorMocks.createEditorElement(); } /** * Creates mock editor configuration */ static createEditorConfig(overrides = {}) { this.setup(); return { element: this.createEditorElement(), extensions: [], content: '', editable: true, onTransaction: vi.fn(), onUpdate: vi.fn(), onFocus: vi.fn(), onBlur: vi.fn(), onCreate: vi.fn(), onDestroy: vi.fn(), ...overrides }; } /** * Mock the TipTap Editor constructor */ static mockTipTapEditor() { this.setup(); // Mock the Editor import return vi.fn().mockImplementation((config) => { return this.createEditor(config); }); } /** * Creates a test helper for editor components */ static createTestHelper() { const mocks = this.setup(); return { /** * Simulates text input in an editor */ simulateTextInput: (element, text) => { element.textContent = text; // Dispatch input event const inputEvent = new Event('input', { bubbles: true }); element.dispatchEvent(inputEvent); }, /** * Simulates a key press in an editor */ simulateKeyPress: (element, key, options = {}) => { const keyEvent = new KeyboardEvent('keydown', { key, code: key, bubbles: true, ...options }); element.dispatchEvent(keyEvent); }, /** * Simulates focus on an editor element */ simulateFocus: (element) => { element.focus(); const focusEvent = new Event('focus', { bubbles: false }); element.dispatchEvent(focusEvent); }, /** * Simulates blur on an editor element */ simulateBlur: (element) => { element.blur(); const blurEvent = new Event('blur', { bubbles: false }); element.dispatchEvent(blurEvent); }, /** * Waits for editor to be ready */ waitForEditor: async () => { // Simple delay to allow editor initialization await new Promise(resolve => setTimeout(resolve, 10)); }, /** * Cleans up editor mocks */ cleanup: () => { if (mocks.cleanup) { mocks.cleanup(); } this.mocks = null; } }; } /** * Cleans up all editor mocks */ static cleanup() { if (this.mocks && this.mocks.cleanup) { this.mocks.cleanup(); } this.mocks = null; } } Object.defineProperty(EditorTestFactory, "mocks", { enumerable: true, configurable: true, writable: true, value: null }); /** * Default export for convenience */ export const editorTestUtils = EditorTestFactory;