From daf5b7c5a1a8f925a7301e42097496fef80c94f9 Mon Sep 17 00:00:00 2001 From: suyao Date: Mon, 22 Dec 2025 13:26:01 +0800 Subject: [PATCH] feat: add tests for YAML front matter tokenizer and enhance parsing logic --- .../__tests__/yaml-front-matter.test.ts | 84 +++++++++++++++++++ .../extensions/yaml-front-matter.ts | 9 +- 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 src/renderer/src/components/RichEditor/extensions/__tests__/yaml-front-matter.test.ts diff --git a/src/renderer/src/components/RichEditor/extensions/__tests__/yaml-front-matter.test.ts b/src/renderer/src/components/RichEditor/extensions/__tests__/yaml-front-matter.test.ts new file mode 100644 index 0000000000..28b816c903 --- /dev/null +++ b/src/renderer/src/components/RichEditor/extensions/__tests__/yaml-front-matter.test.ts @@ -0,0 +1,84 @@ +import type { JSONContent } from '@tiptap/core' +import { MarkdownManager } from '@tiptap/markdown' +import { describe, expect, it } from 'vitest' + +import { YamlFrontMatter } from '../yaml-front-matter' + +const parseFrontMatters = (markdown: string) => { + const manager = new MarkdownManager({ + extensions: [YamlFrontMatter] + }) + const doc = manager.parse(markdown) as JSONContent + const frontMatterNodes = (doc.content || []).filter((node) => node.type === 'yamlFrontMatter') + return frontMatterNodes.map((node) => (node.attrs as { content?: string } | undefined)?.content?.trim()) +} + +describe('YamlFrontMatter markdown tokenizer', () => { + it('only parses the first front matter block at the very start of the document', () => { + const markdown = `--- +title: First +--- + +Body text + +--- +title: Second +--- +More content` + + const contents = parseFrontMatters(markdown) + expect(contents).toHaveLength(1) + expect(contents[0]).toBe('title: First') + }) + + it('ignores a front matter block when it is not at the beginning of the document', () => { + const markdown = `Intro paragraph + +--- +title: Should not parse +---` + + const contents = parseFrontMatters(markdown) + expect(contents).toHaveLength(0) + }) + + it('ignores consecutive front matter blocks after the first one', () => { + const markdown = `--- +first: yes +--- +--- +second: no +---` + + const contents = parseFrontMatters(markdown) + expect(contents).toHaveLength(1) + expect(contents[0]).toBe('first: yes') + }) + + it('does not treat body content containing --- as additional front matter', () => { + const markdown = `--- +title: Only header +--- + +Paragraph text. + +--- + +More text.` + + const contents = parseFrontMatters(markdown) + expect(contents).toHaveLength(1) + expect(contents[0]).toBe('title: Only header') + }) + + it('treats later front matter markers as regular markdown when content already exists', () => { + const markdown = `Intro paragraph + +--- +title: Should not parse +---` + + const contents = parseFrontMatters(markdown) + expect(contents).toHaveLength(0) + }) +}) diff --git a/src/renderer/src/components/RichEditor/extensions/yaml-front-matter.ts b/src/renderer/src/components/RichEditor/extensions/yaml-front-matter.ts index 67c51c0553..ad63cf87ac 100644 --- a/src/renderer/src/components/RichEditor/extensions/yaml-front-matter.ts +++ b/src/renderer/src/components/RichEditor/extensions/yaml-front-matter.ts @@ -1,4 +1,4 @@ -import { mergeAttributes, Node } from '@tiptap/core' +import { type MarkdownToken,mergeAttributes, Node } from '@tiptap/core' import { ReactNodeViewRenderer } from '@tiptap/react' import YamlFrontMatterNodeView from '../components/YamlFrontMatterNodeView' @@ -27,7 +27,12 @@ export const YamlFrontMatter = Node.create({ return result }, // Parse YAML front matter - tokenize(src: string) { + tokenize(src: string, tokens: MarkdownToken[] = []) { + const hasExistingContent = tokens.some((token) => token.type && token.type !== 'space') + if (hasExistingContent) { + return undefined + } + // Match: ---\n...yaml content...\n--- const match = /^---\n([\s\S]*?)\n---(?:\n|$)/.exec(src)