|
3 | 3 | */ |
4 | 4 | import { Editor } from '@tiptap/core' |
5 | 5 | import { afterEach, describe, expect, it } from 'vitest' |
6 | | -import { createMarkdownContentExtensions } from './extensions' |
7 | | -import { findHeadingPos } from './heading-anchors' |
| 6 | +import { createMarkdownContentExtensions } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/extensions' |
| 7 | +import { scrollToHeading } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/heading-anchors' |
8 | 8 |
|
9 | 9 | let editor: Editor | null = null |
10 | 10 | afterEach(() => { |
11 | 11 | editor?.destroy() |
12 | 12 | editor = null |
13 | 13 | }) |
14 | 14 |
|
15 | | -/** A ProseMirror doc parsed from markdown, for the position-resolution tests. */ |
16 | | -function docOf(markdown: string) { |
| 15 | +function editorOf(markdown: string) { |
17 | 16 | editor = new Editor({ extensions: createMarkdownContentExtensions() }) |
18 | 17 | editor.commands.setContent(markdown, { contentType: 'markdown' }) |
19 | | - return editor.state.doc |
| 18 | + return editor |
20 | 19 | } |
21 | 20 |
|
22 | | -describe('findHeadingPos', () => { |
23 | | - it('disambiguates duplicate slugs GitHub-style (foo, foo-1, foo-2)', () => { |
24 | | - const doc = docOf('# Notes\n\na\n\n# Notes\n\nb\n\n# Notes\n\nc') |
25 | | - const first = findHeadingPos(doc, 'notes') |
26 | | - const second = findHeadingPos(doc, 'notes-1') |
27 | | - const third = findHeadingPos(doc, 'notes-2') |
28 | | - expect(first).toBeGreaterThanOrEqual(0) |
29 | | - expect(second).toBeGreaterThan(first) |
30 | | - expect(third).toBeGreaterThan(second) |
| 21 | +describe('Markdown heading navigation', () => { |
| 22 | + it.each([ |
| 23 | + ['Unicode headings', '# Café 你好', 'café-你好', 0], |
| 24 | + ['punctuation spacing', '# Network & data', 'network--data', 0], |
| 25 | + ['existing suffixes', '# Notes\n\n# Notes-1\n\n# Notes', 'notes-2', 2], |
| 26 | + ] as const)('scrolls to GitHub fragments for %s', (_name, markdown, slug, index) => { |
| 27 | + const editor = editorOf(markdown) |
| 28 | + const headings = editor.view.dom.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6') |
| 29 | + let scrolledTo: HTMLElement | null = null |
| 30 | + for (const heading of headings) { |
| 31 | + heading.scrollIntoView = () => { |
| 32 | + scrolledTo = heading |
| 33 | + } |
| 34 | + } |
| 35 | + expect(scrollToHeading(editor.view, `#${encodeURIComponent(slug)}`)).toBe(true) |
| 36 | + expect(scrolledTo).toBe(headings[index]) |
| 37 | + }) |
| 38 | + |
| 39 | + it('scrolls to duplicate headings in document order', () => { |
| 40 | + const editor = editorOf('# Notes\n\na\n\n# Notes\n\nb\n\n# Notes\n\nc') |
| 41 | + const headings = editor.view.dom.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6') |
| 42 | + let scrolledTo: HTMLElement | null = null |
| 43 | + for (const heading of headings) { |
| 44 | + heading.scrollIntoView = () => { |
| 45 | + scrolledTo = heading |
| 46 | + } |
| 47 | + } |
| 48 | + for (const [index, fragment] of ['#notes', '#notes-1', '#notes-2'].entries()) { |
| 49 | + expect(scrollToHeading(editor.view, fragment)).toBe(true) |
| 50 | + expect(scrolledTo).toBe(headings[index]) |
| 51 | + } |
31 | 52 | }) |
32 | 53 | }) |
0 commit comments