@joinezco/markdown-editor 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/dist/editor/extensions/codeblock.js +82 -31
- package/dist/editor/index.js +23 -0
- package/dist/editor/styles.js +89 -0
- package/package.json +3 -3
- package/public/fonts/UbuntuMonoNerdFont-Regular.ttf +0 -0
- package/public/snapshot.bin +0 -0
- package/src/App.css +0 -1
- package/src/lib/editor/extensions/codeblock.ts +91 -36
- package/src/lib/editor/index.ts +23 -0
- package/src/lib/editor/styles.ts +93 -0
- package/src/test/multiview-sync.test.ts +137 -0
- package/TEST_README.md +0 -359
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-be-focusable-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-create-an-editor-instance-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-have-initial-content-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Basic-Editor-Functionality-should-render-in-the-DOM-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Browser-specific-Features-should-handle-copy-and-paste-operations-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Browser-specific-Features-should-handle-undo-and-redo-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Editor-State-and-Updates-should-maintain-state-across-content-changes-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Editor-State-and-Updates-should-trigger-update-callbacks-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Error-Handling-should-handle-invalid-markdown-gracefully-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Error-Handling-should-handle-very-long-content-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Keyboard-Shortcuts-should-handle-Ctrl-B-for-bold-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Keyboard-Shortcuts-should-handle-Ctrl-I-for-italic-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Content-Management-should-get-markdown-content-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Content-Management-should-handle-empty-content-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Content-Management-should-set-markdown-content-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-bold-text-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-code-blocks-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-headings-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-inline-code-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-italic-text-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-lists-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Markdown-Formatting-should-handle-task-lists-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Selection-and-Cursor-Management-should-handle-cursor-positioning-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Selection-and-Cursor-Management-should-set-and-get-selection-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Text-Input-and-Editing-should-handle-line-breaks-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Text-Input-and-Editing-should-handle-typing-at-different-positions-1.png +0 -0
- package/src/test/__screenshots__/editor.test.ts/MarkdownEditor-Text-Input-and-Editing-should-insert-text-at-cursor-position-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-handle-code-blocks-without-language-specification-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-handle-different-programming-languages-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-handle-inline-code-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Code-Block-Extension-should-render-code-blocks-with-syntax-highlighting-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Extension-Interactions-should-handle-multiple-extensions-working-together-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Extension-Interactions-should-maintain-editor-state-across-complex-operations-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-File-System-Integration-should-handle-file-references-in-code-blocks-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-File-System-Integration-should-maintain-file-system-state-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Link-Extension-should-auto-detect-URLs-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Link-Extension-should-handle-email-links-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Link-Extension-should-render-links-correctly-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Markdown-Storage-should-provide-markdown-storage-interface-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Markdown-Storage-should-sync-markdown-content-with-storage-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Slash-Commands-Extension-should-handle-heading-commands-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Slash-Commands-Extension-should-trigger-slash-commands-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Table-Extension-should-handle-table-navigation-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Table-Extension-should-render-tables-correctly-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Task-List-Extension-should-handle-nested-task-lists-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Task-List-Extension-should-render-task-lists-correctly-1.png +0 -0
- package/src/test/__screenshots__/extensions.test.ts/MarkdownEditor-Extensions-Task-List-Extension-should-toggle-task-completion-1.png +0 -0
package/src/lib/editor/styles.ts
CHANGED
|
@@ -19,6 +19,29 @@ export const styleModule: StyleModule = new StyleModule({
|
|
|
19
19
|
'--ezco-mde-link-color': '#5861ff',
|
|
20
20
|
'--ezco-mde-link-color-hover': '#383ea3',
|
|
21
21
|
|
|
22
|
+
// Typography scale based on perfect fourth ratio (1.333)
|
|
23
|
+
'--ezco-mde-type-ratio': '1.25',
|
|
24
|
+
'--ezco-mde-base-font-size': '1rem',
|
|
25
|
+
'--ezco-mde-base-line-height': '1.5',
|
|
26
|
+
|
|
27
|
+
// Font sizes using modular scale
|
|
28
|
+
'--ezco-mde-text-xs': 'calc(var(--ezco-mde-base-font-size) / var(--ezco-mde-type-ratio))',
|
|
29
|
+
'--ezco-mde-text-sm': 'calc(var(--ezco-mde-text-xs) * var(--ezco-mde-type-ratio))',
|
|
30
|
+
'--ezco-mde-text-base': 'var(--ezco-mde-base-font-size)',
|
|
31
|
+
'--ezco-mde-text-lg': 'calc(var(--ezco-mde-text-base) * var(--ezco-mde-type-ratio))',
|
|
32
|
+
'--ezco-mde-text-xl': 'calc(var(--ezco-mde-text-lg) * var(--ezco-mde-type-ratio))',
|
|
33
|
+
'--ezco-mde-text-2xl': 'calc(var(--ezco-mde-text-xl) * var(--ezco-mde-type-ratio))',
|
|
34
|
+
'--ezco-mde-text-3xl': 'calc(var(--ezco-mde-text-2xl) * var(--ezco-mde-type-ratio))',
|
|
35
|
+
'--ezco-mde-text-4xl': 'calc(var(--ezco-mde-text-3xl) * var(--ezco-mde-type-ratio))',
|
|
36
|
+
|
|
37
|
+
// Line heights based on modular scale - inversely related to font size for better readability
|
|
38
|
+
'--ezco-mde-line-ratio': '1', // Smaller ratio for line height progression
|
|
39
|
+
'--ezco-mde-leading-loose': 'calc(var(--ezco-mde-base-line-height) * var(--ezco-mde-line-ratio))',
|
|
40
|
+
'--ezco-mde-leading-relaxed': 'var(--ezco-mde-base-line-height)',
|
|
41
|
+
'--ezco-mde-leading-normal': 'calc(var(--ezco-mde-base-line-height) / var(--ezco-mde-line-ratio))',
|
|
42
|
+
'--ezco-mde-leading-snug': 'calc(var(--ezco-mde-leading-normal) / var(--ezco-mde-line-ratio))',
|
|
43
|
+
'--ezco-mde-leading-tight': 'calc(var(--ezco-mde-leading-snug) / var(--ezco-mde-line-ratio))',
|
|
44
|
+
|
|
22
45
|
// Default to light mode, overridden by media query
|
|
23
46
|
'--ezco-mde-code-bg': 'var(--ezco-mde-code-bg-light)',
|
|
24
47
|
'--ezco-mde-bg': 'var(--ezco-mde-bg-light)',
|
|
@@ -39,6 +62,60 @@ export const styleModule: StyleModule = new StyleModule({
|
|
|
39
62
|
cursor: 'pointer',
|
|
40
63
|
},
|
|
41
64
|
|
|
65
|
+
// Typography styles for Markdown elements using modular scale
|
|
66
|
+
'& h1': {
|
|
67
|
+
'font-size': 'var(--ezco-mde-text-4xl)',
|
|
68
|
+
'line-height': 'var(--ezco-mde-leading-tight)',
|
|
69
|
+
'margin': '0.67em 0',
|
|
70
|
+
'font-weight': 'bold',
|
|
71
|
+
},
|
|
72
|
+
'& h2': {
|
|
73
|
+
'font-size': 'var(--ezco-mde-text-3xl)',
|
|
74
|
+
'line-height': 'var(--ezco-mde-leading-tight)',
|
|
75
|
+
'margin': '0.75em 0 0.5em 0',
|
|
76
|
+
'font-weight': 'bold',
|
|
77
|
+
},
|
|
78
|
+
'& h3': {
|
|
79
|
+
'font-size': 'var(--ezco-mde-text-2xl)',
|
|
80
|
+
'line-height': 'var(--ezco-mde-leading-snug)',
|
|
81
|
+
'margin': '0.83em 0 0.5em 0',
|
|
82
|
+
'font-weight': 'bold',
|
|
83
|
+
},
|
|
84
|
+
'& h4': {
|
|
85
|
+
'font-size': 'var(--ezco-mde-text-xl)',
|
|
86
|
+
'line-height': 'var(--ezco-mde-leading-snug)',
|
|
87
|
+
'margin': '1em 0 0.5em 0',
|
|
88
|
+
'font-weight': 'bold',
|
|
89
|
+
},
|
|
90
|
+
'& h5': {
|
|
91
|
+
'font-size': 'var(--ezco-mde-text-lg)',
|
|
92
|
+
'line-height': 'var(--ezco-mde-leading-normal)',
|
|
93
|
+
'margin': '1.17em 0 0.5em 0',
|
|
94
|
+
'font-weight': 'bold',
|
|
95
|
+
},
|
|
96
|
+
'& h6': {
|
|
97
|
+
'font-size': 'var(--ezco-mde-text-base)',
|
|
98
|
+
'line-height': 'var(--ezco-mde-leading-normal)',
|
|
99
|
+
'margin': '1.33em 0 0.5em 0',
|
|
100
|
+
'font-weight': 'bold',
|
|
101
|
+
},
|
|
102
|
+
'& p': {
|
|
103
|
+
'font-size': 'var(--ezco-mde-text-base)',
|
|
104
|
+
'line-height': 'var(--ezco-mde-leading-relaxed)',
|
|
105
|
+
'margin': '1em 0',
|
|
106
|
+
},
|
|
107
|
+
'& blockquote': {
|
|
108
|
+
'font-size': 'var(--ezco-mde-text-base)',
|
|
109
|
+
'line-height': 'var(--ezco-mde-leading-relaxed)',
|
|
110
|
+
'margin': '1.5em 0',
|
|
111
|
+
'padding': '0 1em',
|
|
112
|
+
'border-left': '4px solid #ddd',
|
|
113
|
+
},
|
|
114
|
+
'& small': {
|
|
115
|
+
'font-size': 'var(--ezco-mde-text-sm)',
|
|
116
|
+
'line-height': 'var(--ezco-mde-leading-normal)',
|
|
117
|
+
},
|
|
118
|
+
|
|
42
119
|
// Codeblock styles
|
|
43
120
|
'& .cm-editor': {
|
|
44
121
|
margin: '2rem 0',
|
|
@@ -51,6 +128,8 @@ export const styleModule: StyleModule = new StyleModule({
|
|
|
51
128
|
background: 'var(--ezco-mde-code-bg)',
|
|
52
129
|
padding: '0.1em 0.3em',
|
|
53
130
|
'border-radius': '3px',
|
|
131
|
+
'-webkit-box-decoration-break': 'clone',
|
|
132
|
+
'box-decoration-break': 'clone',
|
|
54
133
|
},
|
|
55
134
|
// Table styles
|
|
56
135
|
'&.tableWrapper': {
|
|
@@ -121,6 +200,12 @@ export const styleModule: StyleModule = new StyleModule({
|
|
|
121
200
|
'& li > p': {
|
|
122
201
|
'margin-top': 0,
|
|
123
202
|
'margin-bottom': 0,
|
|
203
|
+
'font-size': 'var(--ezco-mde-text-base)',
|
|
204
|
+
'line-height': 'var(--ezco-mde-leading-relaxed)',
|
|
205
|
+
},
|
|
206
|
+
'& ol > li > p, & ul > li > p': {
|
|
207
|
+
'font-size': 'var(--ezco-mde-text-base)',
|
|
208
|
+
'line-height': 'var(--ezco-mde-leading-relaxed)',
|
|
124
209
|
},
|
|
125
210
|
// Task list styles
|
|
126
211
|
'& li[data-checked="true"]>div>p': {
|
|
@@ -162,5 +247,13 @@ export const styleModule: StyleModule = new StyleModule({
|
|
|
162
247
|
flex: 1
|
|
163
248
|
}
|
|
164
249
|
},
|
|
250
|
+
// Make task checkboxes visible when selected (Ctrl-A)
|
|
251
|
+
// Checkboxes don't natively show selection highlighting,
|
|
252
|
+
// so add an outline using the system Highlight color
|
|
253
|
+
'& ul[data-type="taskList"] li > label > input[type="checkbox"]': {
|
|
254
|
+
'&::selection': {
|
|
255
|
+
background: 'Highlight',
|
|
256
|
+
},
|
|
257
|
+
},
|
|
165
258
|
}
|
|
166
259
|
})
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { fileChangeBus } from '@joinezco/codeblock'
|
|
3
|
+
import { EditorView } from '@codemirror/view'
|
|
4
|
+
import { EditorState } from '@codemirror/state'
|
|
5
|
+
|
|
6
|
+
describe('FileChangeBus', () => {
|
|
7
|
+
let viewA: EditorView
|
|
8
|
+
let viewB: EditorView
|
|
9
|
+
let containerA: HTMLElement
|
|
10
|
+
let containerB: HTMLElement
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
containerA = document.createElement('div')
|
|
14
|
+
containerB = document.createElement('div')
|
|
15
|
+
document.body.appendChild(containerA)
|
|
16
|
+
document.body.appendChild(containerB)
|
|
17
|
+
|
|
18
|
+
viewA = new EditorView({
|
|
19
|
+
state: EditorState.create({ doc: 'initial' }),
|
|
20
|
+
parent: containerA,
|
|
21
|
+
})
|
|
22
|
+
viewB = new EditorView({
|
|
23
|
+
state: EditorState.create({ doc: 'initial' }),
|
|
24
|
+
parent: containerB,
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
viewA.destroy()
|
|
30
|
+
viewB.destroy()
|
|
31
|
+
containerA.remove()
|
|
32
|
+
containerB.remove()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should notify other subscribers but not the source', () => {
|
|
36
|
+
const received: { view: string; content: string }[] = []
|
|
37
|
+
|
|
38
|
+
fileChangeBus.subscribe('test.txt', viewA, (content) => {
|
|
39
|
+
received.push({ view: 'A', content })
|
|
40
|
+
})
|
|
41
|
+
fileChangeBus.subscribe('test.txt', viewB, (content) => {
|
|
42
|
+
received.push({ view: 'B', content })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Notify from view A — only B should receive
|
|
46
|
+
fileChangeBus.notify('test.txt', 'hello from A', viewA)
|
|
47
|
+
|
|
48
|
+
expect(received).toEqual([{ view: 'B', content: 'hello from A' }])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should not notify after unsubscribe', () => {
|
|
52
|
+
const received: string[] = []
|
|
53
|
+
|
|
54
|
+
const unsub = fileChangeBus.subscribe('test.txt', viewA, (content) => {
|
|
55
|
+
received.push(content)
|
|
56
|
+
})
|
|
57
|
+
fileChangeBus.subscribe('test.txt', viewB, () => {})
|
|
58
|
+
|
|
59
|
+
unsub()
|
|
60
|
+
fileChangeBus.notify('test.txt', 'hello', viewB)
|
|
61
|
+
|
|
62
|
+
expect(received).toEqual([])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should handle multiple files independently', () => {
|
|
66
|
+
const received: string[] = []
|
|
67
|
+
|
|
68
|
+
fileChangeBus.subscribe('a.txt', viewA, (content) => {
|
|
69
|
+
received.push('a:' + content)
|
|
70
|
+
})
|
|
71
|
+
fileChangeBus.subscribe('b.txt', viewA, (content) => {
|
|
72
|
+
received.push('b:' + content)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
fileChangeBus.notify('a.txt', 'one', viewB)
|
|
76
|
+
fileChangeBus.notify('b.txt', 'two', viewB)
|
|
77
|
+
|
|
78
|
+
expect(received).toEqual(['a:one', 'b:two'])
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should sync document content between views via the bus', () => {
|
|
82
|
+
// Simulate two views on the same file using the bus to sync
|
|
83
|
+
|
|
84
|
+
const unsubA = fileChangeBus.subscribe('shared.txt', viewA, (content) => {
|
|
85
|
+
if (viewA.state.doc.toString() !== content) {
|
|
86
|
+
viewA.dispatch({ changes: { from: 0, to: viewA.state.doc.length, insert: content } })
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
const unsubB = fileChangeBus.subscribe('shared.txt', viewB, (content) => {
|
|
90
|
+
if (viewB.state.doc.toString() !== content) {
|
|
91
|
+
viewB.dispatch({ changes: { from: 0, to: viewB.state.doc.length, insert: content } })
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// Edit view A and "save" (notify the bus)
|
|
96
|
+
viewA.dispatch({ changes: { from: 0, to: viewA.state.doc.length, insert: 'updated content' } })
|
|
97
|
+
fileChangeBus.notify('shared.txt', 'updated content', viewA)
|
|
98
|
+
|
|
99
|
+
// View B should have received the update
|
|
100
|
+
expect(viewB.state.doc.toString()).toBe('updated content')
|
|
101
|
+
// View A should NOT have been re-dispatched (it was the source)
|
|
102
|
+
expect(viewA.state.doc.toString()).toBe('updated content')
|
|
103
|
+
|
|
104
|
+
unsubA()
|
|
105
|
+
unsubB()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('should not create infinite loops when both views subscribe', () => {
|
|
109
|
+
let dispatchCountA = 0
|
|
110
|
+
let dispatchCountB = 0
|
|
111
|
+
|
|
112
|
+
fileChangeBus.subscribe('shared.txt', viewA, (content) => {
|
|
113
|
+
if (viewA.state.doc.toString() !== content) {
|
|
114
|
+
dispatchCountA++
|
|
115
|
+
viewA.dispatch({ changes: { from: 0, to: viewA.state.doc.length, insert: content } })
|
|
116
|
+
// In the real codeblockView, this dispatch would NOT trigger save because
|
|
117
|
+
// receivingExternalUpdate is true. So we do NOT re-notify.
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
fileChangeBus.subscribe('shared.txt', viewB, (content) => {
|
|
121
|
+
if (viewB.state.doc.toString() !== content) {
|
|
122
|
+
dispatchCountB++
|
|
123
|
+
viewB.dispatch({ changes: { from: 0, to: viewB.state.doc.length, insert: content } })
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Simulate save from A
|
|
128
|
+
viewA.dispatch({ changes: { from: 0, to: viewA.state.doc.length, insert: 'final' } })
|
|
129
|
+
fileChangeBus.notify('shared.txt', 'final', viewA)
|
|
130
|
+
|
|
131
|
+
// Only B should have dispatched once
|
|
132
|
+
expect(dispatchCountA).toBe(0)
|
|
133
|
+
expect(dispatchCountB).toBe(1)
|
|
134
|
+
expect(viewA.state.doc.toString()).toBe('final')
|
|
135
|
+
expect(viewB.state.doc.toString()).toBe('final')
|
|
136
|
+
})
|
|
137
|
+
})
|
package/TEST_README.md
DELETED
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
# Markdown Editor Testing Guide
|
|
2
|
-
|
|
3
|
-
This document explains how to use the testing setup for the `@joinezco/markdown-editor` library.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The testing setup uses **Vitest** with **browser testing capabilities** to test the markdown editor in a real browser environment. This allows us to test DOM interactions, keyboard events, and the actual rendering behavior of the editor.
|
|
8
|
-
|
|
9
|
-
## Setup
|
|
10
|
-
|
|
11
|
-
### Dependencies
|
|
12
|
-
|
|
13
|
-
The following testing dependencies are included:
|
|
14
|
-
|
|
15
|
-
- `vitest` - Fast unit test framework
|
|
16
|
-
- `@vitest/browser` - Browser testing support
|
|
17
|
-
- `@vitest/ui` - Web UI for test results
|
|
18
|
-
- `playwright` - Browser automation for testing
|
|
19
|
-
- `jsdom` - DOM implementation for Node.js
|
|
20
|
-
- `webdriverio` - WebDriver implementation
|
|
21
|
-
|
|
22
|
-
### Configuration
|
|
23
|
-
|
|
24
|
-
The testing is configured in [`vitest.config.ts`](./vitest.config.ts) with:
|
|
25
|
-
|
|
26
|
-
- **Browser testing enabled** using Playwright with Chromium
|
|
27
|
-
- **Test environment** set up with proper DOM mocking
|
|
28
|
-
- **Coverage reporting** with v8 provider
|
|
29
|
-
- **Custom setup file** for browser environment preparation
|
|
30
|
-
|
|
31
|
-
## Running Tests
|
|
32
|
-
|
|
33
|
-
### Available Scripts
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
# Run tests in watch mode
|
|
37
|
-
npm run test
|
|
38
|
-
|
|
39
|
-
# Run tests with UI
|
|
40
|
-
npm run test:ui
|
|
41
|
-
|
|
42
|
-
# Run tests in browser mode
|
|
43
|
-
npm run test:browser
|
|
44
|
-
|
|
45
|
-
# Run tests once and exit
|
|
46
|
-
npm run test:run
|
|
47
|
-
|
|
48
|
-
# Run tests with coverage
|
|
49
|
-
npm run test:coverage
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Test Files
|
|
53
|
-
|
|
54
|
-
Tests are located in the `src/test/` directory:
|
|
55
|
-
|
|
56
|
-
- [`setup.ts`](./src/test/setup.ts) - Global test setup and browser mocks
|
|
57
|
-
- [`utils.ts`](./src/test/utils.ts) - Testing utilities for markdown editor
|
|
58
|
-
- [`editor.test.ts`](./src/test/editor.test.ts) - Core editor functionality tests
|
|
59
|
-
- [`extensions.test.ts`](./src/test/extensions.test.ts) - Extension-specific tests
|
|
60
|
-
|
|
61
|
-
## Testing Utilities
|
|
62
|
-
|
|
63
|
-
The [`utils.ts`](./src/test/utils.ts) file provides comprehensive utilities for testing the markdown editor:
|
|
64
|
-
|
|
65
|
-
### DOM Management
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
// Create a test container
|
|
69
|
-
const container = createTestContainer()
|
|
70
|
-
|
|
71
|
-
// Create and initialize editor
|
|
72
|
-
const editor = await createTestEditor(container)
|
|
73
|
-
await waitForEditor(editor)
|
|
74
|
-
|
|
75
|
-
// Cleanup after test
|
|
76
|
-
cleanupEditor(editor, container)
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Content Management
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
// Get current markdown content
|
|
83
|
-
const content = getMarkdownContent(editor)
|
|
84
|
-
|
|
85
|
-
// Set new markdown content
|
|
86
|
-
setMarkdownContent(editor, '# New Content')
|
|
87
|
-
|
|
88
|
-
// Get HTML output
|
|
89
|
-
const html = getHTMLContent(editor)
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### User Interactions
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
// Focus the editor
|
|
96
|
-
focusEditor(editor)
|
|
97
|
-
|
|
98
|
-
// Type text
|
|
99
|
-
typeText(editor, 'Hello, World!')
|
|
100
|
-
|
|
101
|
-
// Simulate key presses
|
|
102
|
-
pressKey(editor, 'b', { ctrl: true }) // Ctrl+B for bold
|
|
103
|
-
|
|
104
|
-
// Set cursor position
|
|
105
|
-
setSelection(editor, 0, 10) // Select characters 0-10
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Async Operations
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
// Wait for conditions
|
|
112
|
-
await waitFor(() => editor.isFocused, 5000)
|
|
113
|
-
|
|
114
|
-
// Wait for editor to be ready
|
|
115
|
-
await waitForEditor(editor)
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Test Categories
|
|
119
|
-
|
|
120
|
-
### Basic Editor Functionality
|
|
121
|
-
|
|
122
|
-
Tests core editor features:
|
|
123
|
-
- Editor initialization and DOM rendering
|
|
124
|
-
- Content getting/setting
|
|
125
|
-
- Focus management
|
|
126
|
-
- Selection handling
|
|
127
|
-
|
|
128
|
-
### Markdown Content Management
|
|
129
|
-
|
|
130
|
-
Tests markdown processing:
|
|
131
|
-
- Content conversion between markdown and HTML
|
|
132
|
-
- Handling of various markdown syntax
|
|
133
|
-
- Content validation and error handling
|
|
134
|
-
|
|
135
|
-
### Text Input and Editing
|
|
136
|
-
|
|
137
|
-
Tests user input:
|
|
138
|
-
- Text insertion at cursor position
|
|
139
|
-
- Line breaks and formatting
|
|
140
|
-
- Keyboard shortcuts (Ctrl+B, Ctrl+I, etc.)
|
|
141
|
-
|
|
142
|
-
### Extension Testing
|
|
143
|
-
|
|
144
|
-
Tests specific editor extensions:
|
|
145
|
-
- **Task Lists** - Checkbox rendering and interaction
|
|
146
|
-
- **Tables** - Table rendering and navigation
|
|
147
|
-
- **Links** - Link detection and rendering
|
|
148
|
-
- **Code Blocks** - Syntax highlighting and language support
|
|
149
|
-
- **Slash Commands** - Command menu functionality
|
|
150
|
-
|
|
151
|
-
### Browser-specific Features
|
|
152
|
-
|
|
153
|
-
Tests browser interactions:
|
|
154
|
-
- Copy/paste operations
|
|
155
|
-
- Undo/redo functionality
|
|
156
|
-
- Keyboard event handling
|
|
157
|
-
- DOM event simulation
|
|
158
|
-
|
|
159
|
-
## Writing New Tests
|
|
160
|
-
|
|
161
|
-
### Basic Test Structure
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
165
|
-
import { MarkdownEditor } from '../lib/editor'
|
|
166
|
-
import {
|
|
167
|
-
createTestContainer,
|
|
168
|
-
createTestEditor,
|
|
169
|
-
waitForEditor,
|
|
170
|
-
cleanupEditor,
|
|
171
|
-
} from './utils'
|
|
172
|
-
|
|
173
|
-
describe('My Feature', () => {
|
|
174
|
-
let container: HTMLElement
|
|
175
|
-
let editor: MarkdownEditor
|
|
176
|
-
|
|
177
|
-
beforeEach(async () => {
|
|
178
|
-
container = createTestContainer()
|
|
179
|
-
editor = await createTestEditor(container)
|
|
180
|
-
await waitForEditor(editor)
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
afterEach(() => {
|
|
184
|
-
cleanupEditor(editor, container)
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('should do something', () => {
|
|
188
|
-
// Your test code here
|
|
189
|
-
expect(editor).toBeDefined()
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Testing Editor State
|
|
195
|
-
|
|
196
|
-
```typescript
|
|
197
|
-
it('should update content correctly', () => {
|
|
198
|
-
setMarkdownContent(editor, '# Test Heading')
|
|
199
|
-
const content = getMarkdownContent(editor)
|
|
200
|
-
expect(content).toContain('# Test Heading')
|
|
201
|
-
|
|
202
|
-
const html = getHTMLContent(editor)
|
|
203
|
-
expect(html).toContain('<h1>Test Heading</h1>')
|
|
204
|
-
})
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Testing User Interactions
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
it('should handle keyboard shortcuts', () => {
|
|
211
|
-
focusEditor(editor)
|
|
212
|
-
typeText(editor, 'bold text')
|
|
213
|
-
setSelection(editor, 0, 9) // Select "bold text"
|
|
214
|
-
|
|
215
|
-
pressKey(editor, 'b', { ctrl: true })
|
|
216
|
-
|
|
217
|
-
const content = getMarkdownContent(editor)
|
|
218
|
-
expect(content).toContain('**bold text**')
|
|
219
|
-
})
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### Testing Async Operations
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
it('should handle async updates', async () => {
|
|
226
|
-
let updateTriggered = false
|
|
227
|
-
|
|
228
|
-
const testEditor = await createTestEditor(container, {
|
|
229
|
-
onUpdate: () => { updateTriggered = true }
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
typeText(testEditor, 'New content')
|
|
233
|
-
|
|
234
|
-
await waitFor(() => updateTriggered, 2000)
|
|
235
|
-
expect(updateTriggered).toBe(true)
|
|
236
|
-
})
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
## Browser Testing Features
|
|
240
|
-
|
|
241
|
-
### Real DOM Environment
|
|
242
|
-
|
|
243
|
-
Tests run in a real browser environment (Chromium via Playwright), providing:
|
|
244
|
-
- Accurate DOM rendering
|
|
245
|
-
- Real event handling
|
|
246
|
-
- Proper CSS layout
|
|
247
|
-
- Browser-specific behaviors
|
|
248
|
-
|
|
249
|
-
### Visual Testing
|
|
250
|
-
|
|
251
|
-
While not implemented in the current setup, the browser environment supports:
|
|
252
|
-
- Screenshot comparison testing
|
|
253
|
-
- Visual regression testing
|
|
254
|
-
- Layout testing
|
|
255
|
-
|
|
256
|
-
### Performance Testing
|
|
257
|
-
|
|
258
|
-
The browser environment allows for:
|
|
259
|
-
- Measuring render times
|
|
260
|
-
- Testing with large documents
|
|
261
|
-
- Memory usage monitoring
|
|
262
|
-
|
|
263
|
-
## Debugging Tests
|
|
264
|
-
|
|
265
|
-
### Using the UI
|
|
266
|
-
|
|
267
|
-
Run tests with the UI for better debugging:
|
|
268
|
-
|
|
269
|
-
```bash
|
|
270
|
-
npm run test:ui
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
This opens a web interface showing:
|
|
274
|
-
- Test results and failures
|
|
275
|
-
- Test execution timeline
|
|
276
|
-
- Code coverage reports
|
|
277
|
-
- Interactive test running
|
|
278
|
-
|
|
279
|
-
### Browser DevTools
|
|
280
|
-
|
|
281
|
-
When running browser tests, you can:
|
|
282
|
-
- Set `headless: false` in `vitest.config.ts`
|
|
283
|
-
- Use browser DevTools for debugging
|
|
284
|
-
- Inspect the actual DOM during tests
|
|
285
|
-
|
|
286
|
-
### Console Logging
|
|
287
|
-
|
|
288
|
-
Add debug logging in tests:
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
it('should debug editor state', () => {
|
|
292
|
-
console.log('Editor state:', editor.state)
|
|
293
|
-
console.log('Current content:', getMarkdownContent(editor))
|
|
294
|
-
// ... test code
|
|
295
|
-
})
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
## Best Practices
|
|
299
|
-
|
|
300
|
-
### Test Isolation
|
|
301
|
-
|
|
302
|
-
- Always use `beforeEach`/`afterEach` for setup/cleanup
|
|
303
|
-
- Create fresh editor instances for each test
|
|
304
|
-
- Clean up DOM elements after tests
|
|
305
|
-
|
|
306
|
-
### Async Handling
|
|
307
|
-
|
|
308
|
-
- Use `await waitForEditor()` after creating editors
|
|
309
|
-
- Use `waitFor()` for conditional waiting
|
|
310
|
-
- Handle async operations properly
|
|
311
|
-
|
|
312
|
-
### Realistic Testing
|
|
313
|
-
|
|
314
|
-
- Test actual user interactions (typing, clicking)
|
|
315
|
-
- Use real markdown content in tests
|
|
316
|
-
- Test edge cases and error conditions
|
|
317
|
-
|
|
318
|
-
### Performance
|
|
319
|
-
|
|
320
|
-
- Keep tests focused and fast
|
|
321
|
-
- Use appropriate timeouts
|
|
322
|
-
- Clean up resources properly
|
|
323
|
-
|
|
324
|
-
## Troubleshooting
|
|
325
|
-
|
|
326
|
-
### Common Issues
|
|
327
|
-
|
|
328
|
-
1. **Editor not ready**: Always use `await waitForEditor(editor)` after creation
|
|
329
|
-
2. **DOM not found**: Ensure container is created and editor is initialized
|
|
330
|
-
3. **Async timing**: Use `waitFor()` for conditions that may take time
|
|
331
|
-
4. **Memory leaks**: Always call `cleanupEditor()` in `afterEach`
|
|
332
|
-
|
|
333
|
-
### Browser Issues
|
|
334
|
-
|
|
335
|
-
1. **Headless failures**: Set `headless: false` for debugging
|
|
336
|
-
2. **Timeout errors**: Increase timeout values in config
|
|
337
|
-
3. **Worker issues**: Check that worker files are accessible
|
|
338
|
-
|
|
339
|
-
## Contributing
|
|
340
|
-
|
|
341
|
-
When adding new tests:
|
|
342
|
-
|
|
343
|
-
1. Follow the existing test structure
|
|
344
|
-
2. Add utilities to `utils.ts` for reusable functionality
|
|
345
|
-
3. Group related tests in describe blocks
|
|
346
|
-
4. Use descriptive test names
|
|
347
|
-
5. Include both positive and negative test cases
|
|
348
|
-
6. Test browser-specific behaviors when relevant
|
|
349
|
-
|
|
350
|
-
## Future Enhancements
|
|
351
|
-
|
|
352
|
-
Potential improvements to the testing setup:
|
|
353
|
-
|
|
354
|
-
- Visual regression testing with screenshot comparison
|
|
355
|
-
- Performance benchmarking tests
|
|
356
|
-
- Accessibility testing integration
|
|
357
|
-
- Cross-browser testing support
|
|
358
|
-
- Integration with CI/CD pipelines
|
|
359
|
-
- Test data generation utilities
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|