Skip to content

Commit 0cb24a1

Browse files
authored
Merge pull request #213 from refactor-group/189-feature-enable-full-markdown-interpretation-in-coaching-notes
Add markdown support to coaching notes.
2 parents 661168a + 6fb18fc commit 0cb24a1

File tree

10 files changed

+981
-139
lines changed

10 files changed

+981
-139
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { describe, it, expect, beforeEach, vi } from 'vitest'
2+
import { EditorContent, useEditor } from '@tiptap/react'
3+
import { render, waitFor } from '@testing-library/react'
4+
import * as Y from 'yjs'
5+
import { Extensions } from '@/components/ui/coaching-sessions/coaching-notes/extensions'
6+
7+
describe('Coaching Notes Markdown Extensions', () => {
8+
let yDoc: Y.Doc
9+
10+
beforeEach(() => {
11+
yDoc = new Y.Doc()
12+
})
13+
14+
describe('Link Support', () => {
15+
it('should handle autolink for plain URLs', async () => {
16+
const TestEditor = () => {
17+
const editor = useEditor({
18+
extensions: Extensions(yDoc, null),
19+
content: '',
20+
})
21+
22+
if (!editor) return null
23+
24+
// Test autolink feature - plain URL should become link
25+
editor.commands.insertContent('https://google.com ')
26+
27+
return <EditorContent editor={editor} />
28+
}
29+
30+
const { container } = render(<TestEditor />)
31+
32+
await waitFor(() => {
33+
const link = container.querySelector('a[href="https://google.com"]')
34+
expect(link).toBeTruthy()
35+
})
36+
})
37+
})
38+
39+
describe('Markdown Table Paste', () => {
40+
it('should convert pasted markdown table to rich text table', async () => {
41+
const TestEditor = () => {
42+
const editor = useEditor({
43+
extensions: Extensions(yDoc, null),
44+
content: '',
45+
})
46+
47+
if (!editor) return null
48+
49+
const markdownTable = `| Name | Age |
50+
| --- | --- |
51+
| Alice | 30 |
52+
| Bob | 25 |`
53+
54+
editor.commands.insertContent(markdownTable, { contentType: 'markdown' })
55+
56+
return <EditorContent editor={editor} />
57+
}
58+
59+
const { container } = render(<TestEditor />)
60+
61+
await waitFor(() => {
62+
const table = container.querySelector('table')
63+
expect(table).toBeTruthy()
64+
65+
// Check header cells
66+
const headerCells = container.querySelectorAll('th')
67+
expect(headerCells.length).toBe(2)
68+
expect(headerCells[0]?.textContent).toBe('Name')
69+
expect(headerCells[1]?.textContent).toBe('Age')
70+
71+
// Check body cells
72+
const bodyCells = container.querySelectorAll('td')
73+
expect(bodyCells.length).toBe(4)
74+
expect(bodyCells[0]?.textContent).toBe('Alice')
75+
expect(bodyCells[1]?.textContent).toBe('30')
76+
expect(bodyCells[2]?.textContent).toBe('Bob')
77+
expect(bodyCells[3]?.textContent).toBe('25')
78+
})
79+
})
80+
81+
it('should handle tables with different column counts', async () => {
82+
const TestEditor = () => {
83+
const editor = useEditor({
84+
extensions: Extensions(yDoc, null),
85+
content: '',
86+
})
87+
88+
if (!editor) return null
89+
90+
const markdownTable = `| Product | Price | Stock | Category |
91+
| --- | --- | --- | --- |
92+
| Widget | $10 | 100 | Tools |
93+
| Gadget | $20 | 50 | Electronics |`
94+
95+
editor.commands.insertContent(markdownTable, { contentType: 'markdown' })
96+
97+
return <EditorContent editor={editor} />
98+
}
99+
100+
const { container } = render(<TestEditor />)
101+
102+
await waitFor(() => {
103+
const table = container.querySelector('table')
104+
expect(table).toBeTruthy()
105+
106+
// Check we have 4 columns
107+
const headerCells = container.querySelectorAll('th')
108+
expect(headerCells.length).toBe(4)
109+
110+
// Check we have 2 rows × 4 columns = 8 body cells
111+
const bodyCells = container.querySelectorAll('td')
112+
expect(bodyCells.length).toBe(8)
113+
})
114+
})
115+
116+
it('should handle malformed markdown tables gracefully', async () => {
117+
const TestEditor = () => {
118+
const editor = useEditor({
119+
extensions: Extensions(yDoc, null),
120+
content: '',
121+
})
122+
123+
if (!editor) return null
124+
125+
// Missing separator row - should not crash
126+
const malformedTable = `| Name | Age |
127+
| Alice | 30 |`
128+
129+
editor.commands.insertContent(malformedTable, { contentType: 'markdown' })
130+
131+
return <EditorContent editor={editor} />
132+
}
133+
134+
const { container } = render(<TestEditor />)
135+
136+
// Should not crash - might render as paragraph or incomplete table
137+
await waitFor(() => {
138+
expect(container).toBeTruthy()
139+
})
140+
})
141+
142+
it('should handle tables with leading spaces', async () => {
143+
const TestEditor = () => {
144+
const editor = useEditor({
145+
extensions: Extensions(yDoc, null),
146+
content: '',
147+
})
148+
149+
if (!editor) return null
150+
151+
const tableWithSpaces = ` | Name | Age |
152+
| --- | --- |
153+
| Alice | 30 |`
154+
155+
editor.commands.insertContent(tableWithSpaces, { contentType: 'markdown' })
156+
157+
return <EditorContent editor={editor} />
158+
}
159+
160+
const { container } = render(<TestEditor />)
161+
162+
await waitFor(() => {
163+
const table = container.querySelector('table')
164+
expect(table).toBeTruthy()
165+
166+
const headerCells = container.querySelectorAll('th')
167+
expect(headerCells.length).toBe(2)
168+
})
169+
})
170+
171+
it('should handle empty table cells', async () => {
172+
const TestEditor = () => {
173+
const editor = useEditor({
174+
extensions: Extensions(yDoc, null),
175+
content: '',
176+
})
177+
178+
if (!editor) return null
179+
180+
const emptyTable = `| Name | Age |
181+
| --- | --- |
182+
| | |`
183+
184+
editor.commands.insertContent(emptyTable, { contentType: 'markdown' })
185+
186+
return <EditorContent editor={editor} />
187+
}
188+
189+
const { container } = render(<TestEditor />)
190+
191+
await waitFor(() => {
192+
const table = container.querySelector('table')
193+
expect(table).toBeTruthy()
194+
195+
const bodyCells = container.querySelectorAll('td')
196+
expect(bodyCells.length).toBe(2)
197+
})
198+
})
199+
})
200+
})

package-lock.json

Lines changed: 59 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@
5050
"@tiptap/extension-collaboration-cursor": "^3.0.0",
5151
"@tiptap/extension-highlight": "^3.0.0",
5252
"@tiptap/extension-placeholder": "^3.0.0",
53+
"@tiptap/extension-table": "^3.7.2",
5354
"@tiptap/extension-task-item": "^3.0.0",
5455
"@tiptap/extension-task-list": "^3.0.0",
5556
"@tiptap/extension-text-style": "^3.0.0",
5657
"@tiptap/extension-underline": "^3.0.0",
58+
"@tiptap/markdown": "^3.7.2",
5759
"@tiptap/pm": "^3.0.0",
5860
"@tiptap/react": "^3.0.0",
5961
"@tiptap/starter-kit": "^3.0.0",
@@ -63,6 +65,7 @@
6365
"clsx": "^2.1.1",
6466
"cmdk": "^1.0.4",
6567
"date-fns": "^2.28.0",
68+
"just-debounce-it": "^3.2.0",
6669
"lucide-react": "^0.469.0",
6770
"next": "15.4.7",
6871
"next-themes": "^0.4.6",

0 commit comments

Comments
 (0)