From dd0a4f14707464b05f61c7f05ce71839310a6ea5 Mon Sep 17 00:00:00 2001
From: imeepos
Date: Tue, 22 Jul 2025 18:57:18 +0800
Subject: [PATCH] =?UTF-8?q?fix:=20system=20=E7=B3=BB=E7=BB=9F=E6=8F=90?=
=?UTF-8?q?=E7=A4=BA=E8=AF=8D=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/infrastructure/gemini_service.rs | 4 +-
.../src/components/ChatMaterialCard.tsx | 5 -
apps/desktop/src/components/GroundedText.tsx | 5 +-
.../src/components/MarkdownParserRenderer.tsx | 12 +-
.../__tests__/EnhancedChatMessageV2.test.tsx | 1 -
.../EnhancedMarkdownRendererExample.tsx | 287 ------------
.../src/examples/GroundingAnalysisExample.tsx | 186 --------
.../src/examples/MarkdownParserExample.tsx | 202 --------
.../EnhancedMarkdownRenderer.test.tsx | 347 --------------
.../MarkdownParserRenderer.test.tsx | 395 ----------------
.../markdownParser.integration.test.ts | 2 +-
.../tests/services/markdownService.test.ts | 436 ------------------
12 files changed, 11 insertions(+), 1871 deletions(-)
delete mode 100644 apps/desktop/src/examples/EnhancedMarkdownRendererExample.tsx
delete mode 100644 apps/desktop/src/examples/GroundingAnalysisExample.tsx
delete mode 100644 apps/desktop/src/examples/MarkdownParserExample.tsx
delete mode 100644 apps/desktop/src/tests/components/EnhancedMarkdownRenderer.test.tsx
delete mode 100644 apps/desktop/src/tests/components/MarkdownParserRenderer.test.tsx
delete mode 100644 apps/desktop/src/tests/services/markdownService.test.ts
diff --git a/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs b/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs
index a8a5830..6992b49 100644
--- a/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs
+++ b/apps/desktop/src-tauri/src/infrastructure/gemini_service.rs
@@ -175,8 +175,8 @@ impl Default for RagGroundingConfig {
data_store_id: "searchable-model-images_1752827560253".to_string(), // 使用存在的数据存储
model_id: "gemini-2.5-flash".to_string(),
temperature: 1.0,
- max_output_tokens: 8192,
- system_prompt: Some("你是一个短视频情景穿搭分析专家, 根据用户预想的情景输出符合逻辑的情景和模特穿搭描述,必须依据已知的数据返回可能的方案, 并且给出参照的依据;如果没有匹配的数据支持,返回空结果;".to_string()),
+ max_output_tokens: 1000 * 10,
+ system_prompt: Some("你是一个短视频情景穿搭分析专家, 根据用户预想的情景检索RAG,然后输出符合逻辑的情景和模特穿搭描述,必须依据已知的数据返回可能的方案, 并且给出参照的依据;如果没有匹配的数据支持,返回空结果;".to_string()),
}
}
}
diff --git a/apps/desktop/src/components/ChatMaterialCard.tsx b/apps/desktop/src/components/ChatMaterialCard.tsx
index b9c09e6..1255b0c 100644
--- a/apps/desktop/src/components/ChatMaterialCard.tsx
+++ b/apps/desktop/src/components/ChatMaterialCard.tsx
@@ -5,16 +5,11 @@ import {
FileImage,
Clock,
ExternalLink,
- Eye,
Play,
Volume2,
- Image as ImageIcon,
- Calendar,
- Hash,
HardDrive
} from 'lucide-react';
import { GroundingSource } from '../types/ragGrounding';
-import { MaterialThumbnail } from './MaterialThumbnail';
/**
* 聊天素材卡片属性接口
diff --git a/apps/desktop/src/components/GroundedText.tsx b/apps/desktop/src/components/GroundedText.tsx
index c9b6eee..18ccb44 100644
--- a/apps/desktop/src/components/GroundedText.tsx
+++ b/apps/desktop/src/components/GroundedText.tsx
@@ -5,7 +5,6 @@ import { GroundingMetadata, GroundingSupport, GroundingSource } from '../types/r
import ImageCard from './ImageCard';
import ImagePreviewModal from './ImagePreviewModal';
import useImageDownload from '../hooks/useImageDownload';
-import { ReferenceFootnote } from './ReferenceFootnote';
/**
* 文字片段信息
@@ -87,12 +86,12 @@ export const GroundedText: React.FC = ({
const sources = groundingMetadata.sources;
// 按开始位置排序
- const sortedSupports = [...supports].sort((a, b) => a.segment.start_index - b.segment.start_index);
+ const sortedSupports = [...supports].sort((a, b) => a.segment.endIndex - b.segment.startIndex);
let currentIndex = 0;
for (const support of sortedSupports) {
- const { start_index, end_index, text: segmentText } = support.segment;
+ const { startIndex: start_index, endIndex: end_index, text: segmentText } = support.segment;
// 添加前面的普通文本
if (currentIndex < start_index) {
diff --git a/apps/desktop/src/components/MarkdownParserRenderer.tsx b/apps/desktop/src/components/MarkdownParserRenderer.tsx
index 59def70..a56cf0f 100644
--- a/apps/desktop/src/components/MarkdownParserRenderer.tsx
+++ b/apps/desktop/src/components/MarkdownParserRenderer.tsx
@@ -173,7 +173,7 @@ export const MarkdownParserRenderer: React.FC = ({
onClick={handleNodeClick}
style={{ marginLeft: `${depth * 20}px` }}
>
- {node.children.map((child, index) => renderNode(child, depth + 1))}
+ {node.children.map((child, _index) => renderNode(child, depth + 1))}
{positionInfo}
);
@@ -237,7 +237,7 @@ export const MarkdownParserRenderer: React.FC = ({
onClick={handleNodeClick}
style={{ marginLeft: `${depth * 20}px` }}
>
- {node.children.map((child, index) => renderNode(child, depth + 1))}
+ {node.children.map((child, _index) => renderNode(child, depth + 1))}
{positionInfo}
);
@@ -250,7 +250,7 @@ export const MarkdownParserRenderer: React.FC = ({
onClick={handleNodeClick}
style={{ marginLeft: `${depth * 20}px` }}
>
- {node.children.map((child, index) => renderNode(child, depth + 1))}
+ {node.children.map((child, _index) => renderNode(child, depth + 1))}
{positionInfo}
);
@@ -262,7 +262,7 @@ export const MarkdownParserRenderer: React.FC = ({
className={nodeClasses}
onClick={handleNodeClick}
>
- {node.children.map((child, index) => renderNode(child, depth + 1))}
+ {node.children.map((child, _index) => renderNode(child, depth + 1))}
{positionInfo}
);
@@ -274,7 +274,7 @@ export const MarkdownParserRenderer: React.FC = ({
className={nodeClasses}
onClick={handleNodeClick}
>
- {node.children.map((child, index) => renderNode(child, depth + 1))}
+ {node.children.map((child, _index) => renderNode(child, depth + 1))}
{positionInfo}
);
@@ -299,7 +299,7 @@ export const MarkdownParserRenderer: React.FC = ({
onClick={handleNodeClick}
style={{ marginLeft: `${depth * 20}px` }}
>
- {node.children.map((child, index) => renderNode(child, depth + 1))}
+ {node.children.map((child, _index) => renderNode(child, depth + 1))}
{positionInfo}
);
diff --git a/apps/desktop/src/components/__tests__/EnhancedChatMessageV2.test.tsx b/apps/desktop/src/components/__tests__/EnhancedChatMessageV2.test.tsx
index b74d3bf..0f9bb2e 100644
--- a/apps/desktop/src/components/__tests__/EnhancedChatMessageV2.test.tsx
+++ b/apps/desktop/src/components/__tests__/EnhancedChatMessageV2.test.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { describe, test, expect, vi } from 'vitest';
diff --git a/apps/desktop/src/examples/EnhancedMarkdownRendererExample.tsx b/apps/desktop/src/examples/EnhancedMarkdownRendererExample.tsx
deleted file mode 100644
index 384d1a4..0000000
--- a/apps/desktop/src/examples/EnhancedMarkdownRendererExample.tsx
+++ /dev/null
@@ -1,287 +0,0 @@
-import React, { useState } from 'react';
-import { EnhancedMarkdownRenderer } from '../components/EnhancedMarkdownRenderer';
-import { GroundingMetadata } from '../types/ragGrounding';
-
-/**
- * EnhancedMarkdownRenderer使用示例
- * 展示基于MarkdownService的增强Markdown渲染功能
- */
-const EnhancedMarkdownRendererExample: React.FC = () => {
- const [content, setContent] = useState(`# 增强Markdown渲染器示例
-
-这是一个基于**Tree-sitter MarkdownService**的增强Markdown渲染器,替代了传统的ReactMarkdown。
-
-## 🚀 新功能特性
-
-### 1. 基于Tree-sitter的精确解析
-- 使用pulldown-cmark进行语法树解析
-- 保留完整的位置信息和原文引用
-- 更准确的语法分析和错误恢复
-
-### 2. 实时解析和验证
-- 支持实时解析模式
-- 文档结构验证
-- 解析统计信息显示
-
-### 3. 增强的渲染功能
-- 支持所有标准Markdown语法
-- 自定义样式和主题
-- 错误状态和加载状态处理
-
-## 📝 语法支持
-
-### 文本格式
-- **粗体文本**
-- *斜体文本*
-- \`内联代码\`
-- ~~删除线~~(如果支持)
-
-### 链接和图片
-- [普通链接](https://example.com)
-- [带标题的链接](https://example.com "这是标题")
-- 
-
-### 列表
-
-#### 无序列表
-- 项目1
-- 项目2
- - 子项目2.1
- - 子项目2.2
-- 项目3
-
-#### 有序列表
-1. 第一项
-2. 第二项
-3. 第三项
-
-### 代码块
-
-\`\`\`typescript
-// TypeScript代码示例
-interface MarkdownRendererProps {
- content: string;
- enableMarkdown?: boolean;
- showStatistics?: boolean;
-}
-
-const renderer = new MarkdownRenderer(props);
-renderer.render();
-\`\`\`
-
-\`\`\`javascript
-// JavaScript代码示例
-function parseMarkdown(content) {
- return markdownService.parseMarkdown(content);
-}
-\`\`\`
-
-### 引用块
-
-> 这是一个引用块。
->
-> 它可以包含多行内容,并且支持**格式化**文本。
-
-### 表格(如果支持)
-
-| 功能 | 状态 | 描述 |
-|------|------|------|
-| 解析 | ✅ | Tree-sitter解析 |
-| 渲染 | ✅ | React组件渲染 |
-| 验证 | ✅ | 文档结构验证 |
-
----
-
-## 🔧 技术实现
-
-这个渲染器的核心技术栈:
-
-1. **后端解析**: Rust + pulldown-cmark
-2. **前端渲染**: React + TypeScript
-3. **通信层**: Tauri命令系统
-4. **样式**: TailwindCSS
-
-### 性能优势
-- 更快的解析速度
-- 更低的内存占用
-- 更好的错误处理
-- 实时解析支持
-
-## 📊 使用统计
-
-当启用统计信息时,您可以看到:
-- 解析的节点总数
-- 错误节点数量
-- 解析耗时
-- 文档最大深度
-
-这些信息有助于优化文档结构和性能调试。
-`);
-
- const [enableMarkdown, setEnableMarkdown] = useState(true);
- const [showStatistics, setShowStatistics] = useState(false);
- const [enableRealTimeParsing, setEnableRealTimeParsing] = useState(false);
- const [enableReferences, setEnableReferences] = useState(true);
-
- // 模拟grounding metadata
- const mockGroundingMetadata: GroundingMetadata = {
- sources: [
- {
- title: 'Markdown语法指南',
- uri: 'https://markdown-guide.org',
- content: { snippet: 'Markdown是一种轻量级标记语言...' },
- },
- {
- title: 'Tree-sitter文档',
- uri: 'https://tree-sitter.github.io',
- content: { snippet: 'Tree-sitter是一个解析器生成工具...' },
- },
- {
- title: 'React组件开发',
- uri: 'https://react.dev',
- content: { snippet: 'React是一个用于构建用户界面的库...' },
- },
- ],
- search_queries: ['markdown parser', 'tree-sitter', 'react components'],
- };
-
- return (
-
-
增强Markdown渲染器示例
-
- {/* 控制面板 */}
-
-
-
- {/* 编辑器 */}
-
-
- {/* 渲染结果 */}
-
-
-
- {/* 功能对比 */}
-
-
🔄 从ReactMarkdown到MarkdownService的升级
-
-
-
❌ 旧版本 (ReactMarkdown)
-
- - • 基于正则表达式解析
- - • 有限的位置信息
- - • 较慢的解析速度
- - • 有限的错误恢复
- - • 依赖外部库
-
-
-
-
✅ 新版本 (MarkdownService)
-
- - • 基于Tree-sitter语法树解析
- - • 完整的位置信息和原文引用
- - • 更快的解析速度
- - • 强大的错误恢复能力
- - • 原生Rust实现
- - • 实时解析和验证
- - • 详细的解析统计
-
-
-
-
-
- {/* 使用说明 */}
-
-
💡 使用说明
-
- - • 修改左侧编辑器内容查看实时渲染效果
- - • 使用控制面板切换不同功能选项
- - • 启用"显示统计"查看解析性能信息
- - • 启用"实时解析"体验防抖解析功能
- - • 查看底部的引用来源信息
-
-
-
- {/* 技术细节 */}
-
-
🔧 技术实现细节
-
-
解析流程: Markdown文本 → Tauri命令 → Rust解析器 → pulldown-cmark → AST → React渲染
-
位置跟踪: 每个节点都包含精确的行号、列号和字符偏移量信息
-
错误处理: 优雅的错误恢复和用户友好的错误提示
-
性能优化: 防抖解析、缓存机制和增量更新支持
-
-
-
- );
-};
-
-export default EnhancedMarkdownRendererExample;
diff --git a/apps/desktop/src/examples/GroundingAnalysisExample.tsx b/apps/desktop/src/examples/GroundingAnalysisExample.tsx
deleted file mode 100644
index d7f5603..0000000
--- a/apps/desktop/src/examples/GroundingAnalysisExample.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import React, { useState } from 'react';
-import { EnhancedMarkdownRenderer } from '../components/EnhancedMarkdownRenderer';
-import { GroundingMetadata } from '../types/ragGrounding';
-
-/**
- * Grounding分析示例
- * 展示如何将Markdown节点与引用资源进行关联分析
- */
-const GroundingAnalysisExample: React.FC = () => {
- const [content] = useState(`# AI技术发展报告
-
-人工智能技术在近年来取得了显著进展。深度学习算法的突破使得机器学习模型在图像识别、自然语言处理等领域达到了前所未有的精度。
-
-## 主要技术突破
-
-### 大语言模型
-大语言模型如GPT系列展现了强大的文本生成和理解能力,这些模型通过在海量文本数据上进行预训练,学习到了丰富的语言知识和常识。
-
-### 计算机视觉
-在计算机视觉领域,卷积神经网络和Transformer架构的结合产生了新的突破,使得图像分类、目标检测等任务的准确率大幅提升。
-
-## 应用前景
-
-AI技术的应用前景广阔,从自动驾驶到医疗诊断,从智能客服到内容创作,人工智能正在改变我们的生活和工作方式。`);
-
- // 模拟包含grounding支持信息的metadata
- const mockGroundingMetadata: GroundingMetadata = {
- sources: [
- {
- title: 'AI技术发展白皮书2024',
- uri: 'https://ai-research.org/whitepaper-2024',
- content: {
- snippet: '人工智能技术在深度学习、自然语言处理等领域取得重大突破...',
- summary: '详细分析了AI技术的最新发展趋势'
- }
- },
- {
- title: 'GPT模型技术解析',
- uri: 'https://openai.com/research/gpt-analysis',
- content: {
- snippet: 'GPT系列模型通过Transformer架构实现了强大的文本生成能力...',
- summary: 'GPT模型的技术原理和应用分析'
- }
- },
- {
- title: '计算机视觉前沿技术',
- uri: 'https://cv-research.com/latest-trends',
- content: {
- snippet: '卷积神经网络与Transformer的结合为计算机视觉带来新突破...',
- summary: '计算机视觉领域的最新技术发展'
- }
- },
- {
- title: 'AI应用场景研究报告',
- uri: 'https://ai-applications.org/report',
- content: {
- snippet: 'AI技术在自动驾驶、医疗、客服等领域的应用前景分析...',
- summary: 'AI技术的实际应用场景和发展前景'
- }
- }
- ],
- search_queries: ['AI技术发展', '深度学习', 'GPT模型', '计算机视觉', 'AI应用'],
- grounding_supports: [
- {
- groundingChunkIndices: [0],
- segment: {
- start_index: 15, // "人工智能技术在近年来取得了显著进展"
- end_index: 85
- }
- },
- {
- groundingChunkIndices: [1],
- segment: {
- start_index: 180, // "大语言模型如GPT系列展现了强大的文本生成和理解能力"
- end_index: 280
- }
- },
- {
- groundingChunkIndices: [2],
- segment: {
- start_index: 350, // "在计算机视觉领域,卷积神经网络和Transformer架构的结合"
- end_index: 420
- }
- },
- {
- groundingChunkIndices: [3],
- segment: {
- start_index: 480, // "AI技术的应用前景广阔,从自动驾驶到医疗诊断"
- end_index: 550
- }
- }
- ]
- };
-
- return (
-
-
Grounding分析示例
-
-
-
🔍 功能说明
-
- 这个示例展示了如何将Markdown节点与引用资源进行关联分析:
-
-
- - • 📚 蓝色标记表示该节点有引用支持
- - • 点击标记可以在控制台查看详细的引用分析
- - • 数字表示引用的来源数量
- - • 打开浏览器控制台查看详细的分析日志
-
-
-
- {/* Grounding Metadata 信息展示 */}
-
-
📋 Grounding Metadata 信息
-
-
-
引用来源 ({mockGroundingMetadata.sources.length})
-
- {mockGroundingMetadata.sources.map((source, index) => (
-
-
{source.title}
-
{source.uri}
-
- ))}
-
-
-
-
支持片段 ({mockGroundingMetadata.grounding_supports?.length || 0})
-
- {mockGroundingMetadata.grounding_supports?.map((support, index) => (
-
-
位置: {support.segment.start_index} - {support.segment.end_index}
-
来源索引: [{support.groundingChunkIndices.join(', ')}]
-
- ))}
-
-
-
-
-
- {/* Markdown渲染 */}
-
-
-
渲染结果(带引用分析)
-
-
-
-
-
-
- {/* 分析说明 */}
-
-
🧠 分析逻辑说明
-
-
1. 位置匹配: 通过比较Markdown节点的字符偏移位置与grounding片段的位置范围,找出重叠的部分。
-
2. 来源关联: 根据grounding支持信息中的索引,关联到具体的引用来源。
-
3. 可视化指示: 在有引用支持的节点旁边显示📚标记,点击可查看详细信息。
-
4. 控制台输出: 详细的分析结果会输出到浏览器控制台,包括节点信息和关联的引用资源。
-
-
-
- {/* 技术实现 */}
-
-
⚙️ 技术实现
-
-
类型分析:
-
- - •
MarkdownNode: 包含节点类型、内容、位置范围等信息
- - •
GroundingMetadata: 包含引用来源和支持片段信息
- - •
GroundingSupport: 关联文字片段与来源索引
-
-
关联算法: 通过字符偏移位置的重叠检测来建立节点与引用的关联关系
-
可视化: 使用React组件动态显示引用指示器和详细信息
-
-
-
- );
-};
-
-export default GroundingAnalysisExample;
diff --git a/apps/desktop/src/examples/MarkdownParserExample.tsx b/apps/desktop/src/examples/MarkdownParserExample.tsx
deleted file mode 100644
index 9f8eb6a..0000000
--- a/apps/desktop/src/examples/MarkdownParserExample.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import React, { useState } from 'react';
-import MarkdownParserRenderer from '../components/MarkdownParserRenderer';
-
-/**
- * Markdown解析器使用示例
- * 展示基于Tree-sitter的Markdown解析功能
- */
-const MarkdownParserExample: React.FC = () => {
- const [markdown, setMarkdown] = useState(`# Tree-sitter Markdown解析器示例
-
-这是一个基于Tree-sitter的Markdown解析器示例,展示了原文引用和位置信息保留功能。
-
-## 功能特性
-
-### 1. 精确解析
-- 基于Tree-sitter的语法树解析
-- 比传统正则表达式更准确
-- 支持错误恢复
-
-### 2. 原文引用
-- 保留每个节点的精确位置信息
-- 行号、列号、字符偏移量
-- 支持位置查询和导航
-
-### 3. 多种查询
-- **标题查询**: 提取文档大纲
-- **链接查询**: 查找所有链接和图片
-- **代码查询**: 查找代码块和内联代码
-
-## 示例内容
-
-### 链接和图片
-- [Google](https://google.com "Google搜索")
-- [GitHub](https://github.com)
-- 
-
-### 代码示例
-
-\`\`\`typescript
-// TypeScript代码示例
-interface MarkdownNode {
- node_type: MarkdownNodeType;
- content: string;
- range: Range;
- children: MarkdownNode[];
- attributes: Record;
-}
-
-function parseMarkdown(text: string): MarkdownParseResult {
- // 解析逻辑
- return result;
-}
-\`\`\`
-
-\`\`\`rust
-// Rust代码示例
-use pulldown_cmark::{Parser, Event, Tag};
-
-pub fn parse_markdown(text: &str) -> Result {
- let parser = Parser::new(text);
- // 解析逻辑
- Ok(result)
-}
-\`\`\`
-
-### 列表示例
-
-#### 无序列表
-- 项目1
-- 项目2
- - 子项目2.1
- - 子项目2.2
-- 项目3
-
-#### 有序列表
-1. 第一项
-2. 第二项
-3. 第三项
-
-### 引用和强调
-
-> 这是一个引用块。
->
-> 它可以包含**粗体**和*斜体*文本。
-
-### 表格
-
-| 功能 | 描述 | 状态 |
-|------|------|------|
-| 解析 | Markdown解析 | ✅ |
-| 查询 | 节点查询 | ✅ |
-| 验证 | 文档验证 | ✅ |
-| 位置 | 位置信息 | ✅ |
-
----
-
-## 技术实现
-
-这个解析器使用了以下技术:
-
-1. **后端**: Rust + pulldown-cmark
-2. **前端**: React + TypeScript
-3. **通信**: Tauri命令系统
-4. **位置跟踪**: 自定义位置计算算法
-
-### 内联代码
-使用 \`markdownService.parseMarkdown()\` 方法进行解析。
-
-## 总结
-
-这个基于Tree-sitter的Markdown解析器提供了:
-- 精确的语法解析
-- 完整的位置信息
-- 灵活的查询接口
-- 实时解析支持
-`);
-
- const handleNodeClick = (node: any) => {
- console.log('点击的节点:', node);
- alert(`节点类型: ${node.node_type}\n位置: ${node.range.start.line + 1}:${node.range.start.column + 1}\n内容长度: ${node.content.length} 字符`);
- };
-
- const handleParseComplete = (result: any) => {
- console.log('解析完成:', result);
- };
-
- return (
-
-
Tree-sitter Markdown解析器示例
-
-
- {/* 编辑器 */}
-
-
- {/* 解析结果 */}
-
-
-
- {/* 功能说明 */}
-
-
功能说明
-
-
-
🎯 精确解析
-
基于pulldown-cmark的语法树解析,提供准确的结构分析
-
-
-
📍 位置信息
-
保留每个节点的行号、列号和字符偏移量信息
-
-
-
🔍 智能查询
-
支持标题、链接、代码块等特定节点类型的查询
-
-
-
✅ 文档验证
-
检查文档结构一致性,发现潜在问题
-
-
-
-
- {/* 使用提示 */}
-
-
💡 使用提示
-
- - • 点击解析结果中的任意节点查看详细信息
- - • 修改左侧编辑器内容可实时查看解析结果
- - • 大纲、链接和验证信息会自动显示在解析结果上方
- - • 位置信息显示在每个节点后面的方括号中
-
-
-
- );
-};
-
-export default MarkdownParserExample;
diff --git a/apps/desktop/src/tests/components/EnhancedMarkdownRenderer.test.tsx b/apps/desktop/src/tests/components/EnhancedMarkdownRenderer.test.tsx
deleted file mode 100644
index a54ba96..0000000
--- a/apps/desktop/src/tests/components/EnhancedMarkdownRenderer.test.tsx
+++ /dev/null
@@ -1,347 +0,0 @@
-import React from 'react';
-import { render, screen, waitFor } from '@testing-library/react';
-import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
-import { EnhancedMarkdownRenderer } from '../../components/EnhancedMarkdownRenderer';
-import { markdownService } from '../../services/markdownService';
-import {
- MarkdownParseResult,
- MarkdownNode,
- MarkdownNodeType,
- ValidationResult,
-} from '../../types/markdown';
-
-// Mock the markdown service
-vi.mock('../../services/markdownService', () => ({
- markdownService: {
- parseMarkdown: vi.fn(),
- validateMarkdown: vi.fn(),
- extractTextContent: vi.fn(),
- },
-}));
-
-const mockMarkdownService = markdownService as any;
-
-describe('EnhancedMarkdownRenderer', () => {
- const mockParseResult: MarkdownParseResult = {
- root: {
- node_type: MarkdownNodeType.Document,
- content: '# Test\n\nHello **world**',
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 2, column: 12, offset: 19 },
- },
- children: [
- {
- node_type: MarkdownNodeType.Heading,
- content: '# Test',
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 0, column: 6, offset: 6 },
- },
- children: [
- {
- node_type: MarkdownNodeType.Text,
- content: 'Test',
- range: {
- start: { line: 0, column: 2, offset: 2 },
- end: { line: 0, column: 6, offset: 6 },
- },
- children: [],
- attributes: {},
- },
- ],
- attributes: { level: '1' },
- },
- {
- node_type: MarkdownNodeType.Paragraph,
- content: 'Hello **world**',
- range: {
- start: { line: 2, column: 0, offset: 8 },
- end: { line: 2, column: 15, offset: 23 },
- },
- children: [
- {
- node_type: MarkdownNodeType.Text,
- content: 'Hello ',
- range: {
- start: { line: 2, column: 0, offset: 8 },
- end: { line: 2, column: 6, offset: 14 },
- },
- children: [],
- attributes: {},
- },
- {
- node_type: MarkdownNodeType.Strong,
- content: '**world**',
- range: {
- start: { line: 2, column: 6, offset: 14 },
- end: { line: 2, column: 15, offset: 23 },
- },
- children: [
- {
- node_type: MarkdownNodeType.Text,
- content: 'world',
- range: {
- start: { line: 2, column: 8, offset: 16 },
- end: { line: 2, column: 13, offset: 21 },
- },
- children: [],
- attributes: {},
- },
- ],
- attributes: {},
- },
- ],
- attributes: {},
- },
- ],
- attributes: {},
- },
- statistics: {
- total_nodes: 5,
- error_nodes: 0,
- parse_time_ms: 3,
- document_length: 19,
- max_depth: 3,
- },
- source_text: '# Test\n\nHello **world**',
- };
-
- const mockValidation: ValidationResult = {
- is_valid: true,
- issues: [],
- statistics: {
- total_nodes: 5,
- error_nodes: 0,
- parse_time_ms: 3,
- document_length: 19,
- max_depth: 3,
- },
- };
-
- beforeEach(() => {
- mockMarkdownService.parseMarkdown.mockResolvedValue(mockParseResult);
- mockMarkdownService.validateMarkdown.mockResolvedValue(mockValidation);
- mockMarkdownService.extractTextContent.mockImplementation((node: MarkdownNode) => {
- if (node.node_type === MarkdownNodeType.Text) {
- return node.content;
- }
- return node.children.map(child => mockMarkdownService.extractTextContent(child)).join('');
- });
- });
-
- afterEach(() => {
- vi.clearAllMocks();
- });
-
- it('renders markdown content using MarkdownService', async () => {
- render(
-
- );
-
- await waitFor(() => {
- expect(mockMarkdownService.parseMarkdown).toHaveBeenCalledWith(expect.stringContaining('# Test'));
- });
-
- await waitFor(() => {
- expect(screen.getByText('Test')).toBeInTheDocument();
- expect(screen.getByText('Hello')).toBeInTheDocument();
- expect(screen.getByText('world')).toBeInTheDocument();
- });
- });
-
- it('renders plain text when markdown is disabled', () => {
- render(
-
- );
-
- expect(screen.getByText(/# Test.*Hello \*\*world\*\*/s)).toBeInTheDocument();
- expect(mockMarkdownService.parseMarkdown).not.toHaveBeenCalled();
- });
-
- it('shows loading state during parsing', () => {
- // Make parseMarkdown return a pending promise
- mockMarkdownService.parseMarkdown.mockReturnValue(new Promise(() => {}));
-
- render(
-
- );
-
- expect(screen.getByText('解析中...')).toBeInTheDocument();
- });
-
- it('shows error state when parsing fails', async () => {
- mockMarkdownService.parseMarkdown.mockRejectedValue(new Error('Parse error'));
-
- render(
-
- );
-
- await waitFor(() => {
- expect(screen.getByText('解析错误')).toBeInTheDocument();
- expect(screen.getByText('Parse error')).toBeInTheDocument();
- });
- });
-
- it('shows statistics when enabled', async () => {
- render(
-
- );
-
- await waitFor(() => {
- expect(screen.getByText('解析统计')).toBeInTheDocument();
- expect(screen.getByText('节点数: 5')).toBeInTheDocument();
- expect(screen.getByText('解析时间: 3ms')).toBeInTheDocument();
- });
- });
-
- it('shows validation warnings when document is invalid', async () => {
- const invalidValidation: ValidationResult = {
- is_valid: false,
- issues: [
- {
- issue_type: 'SkippedHeadingLevel' as any,
- message: 'Heading level jumps from 1 to 3',
- range: {
- start: { line: 2, column: 0, offset: 10 },
- end: { line: 2, column: 10, offset: 20 },
- },
- severity: 'Warning' as any,
- },
- ],
- statistics: mockValidation.statistics,
- };
-
- mockMarkdownService.validateMarkdown.mockResolvedValue(invalidValidation);
-
- render(
-
- );
-
- await waitFor(() => {
- expect(screen.getByText('文档验证警告')).toBeInTheDocument();
- expect(screen.getByText('• Heading level jumps from 1 to 3')).toBeInTheDocument();
- });
- });
-
- it('renders grounding metadata when provided', async () => {
- const groundingMetadata = {
- sources: [
- { title: 'Source 1', url: 'https://example.com/1' },
- { title: 'Source 2', url: 'https://example.com/2' },
- ],
- };
-
- render(
-
- );
-
- await waitFor(() => {
- expect(screen.getByText('基于 2 个来源的信息')).toBeInTheDocument();
- expect(screen.getByText('1')).toBeInTheDocument();
- expect(screen.getByText('2')).toBeInTheDocument();
- });
- });
-
- it('handles real-time parsing when enabled', async () => {
- const { rerender } = render(
-
- );
-
- // Change content
- rerender(
-
- );
-
- // Wait for debounced parsing
- await waitFor(() => {
- expect(mockMarkdownService.parseMarkdown).toHaveBeenCalledWith('# Updated Test');
- }, { timeout: 1000 });
- });
-
- it('applies custom className', () => {
- const { container } = render(
-
- );
-
- expect(container.firstChild).toHaveClass('custom-class');
- });
-
- it('renders different heading levels correctly', async () => {
- const headingResult: MarkdownParseResult = {
- ...mockParseResult,
- root: {
- ...mockParseResult.root,
- children: [
- {
- node_type: MarkdownNodeType.Heading,
- content: '# H1',
- range: { start: { line: 0, column: 0, offset: 0 }, end: { line: 0, column: 4, offset: 4 } },
- children: [{ node_type: MarkdownNodeType.Text, content: 'H1', range: { start: { line: 0, column: 2, offset: 2 }, end: { line: 0, column: 4, offset: 4 } }, children: [], attributes: {} }],
- attributes: { level: '1' },
- },
- {
- node_type: MarkdownNodeType.Heading,
- content: '## H2',
- range: { start: { line: 1, column: 0, offset: 5 }, end: { line: 1, column: 5, offset: 10 } },
- children: [{ node_type: MarkdownNodeType.Text, content: 'H2', range: { start: { line: 1, column: 3, offset: 8 }, end: { line: 1, column: 5, offset: 10 } }, children: [], attributes: {} }],
- attributes: { level: '2' },
- },
- ],
- },
- };
-
- mockMarkdownService.parseMarkdown.mockResolvedValue(headingResult);
-
- render(
-
- );
-
- await waitFor(() => {
- const h1 = screen.getByRole('heading', { level: 1 });
- const h2 = screen.getByRole('heading', { level: 2 });
-
- expect(h1.textContent).toBe('H1');
- expect(h2.textContent).toBe('H2');
- });
- });
-});
diff --git a/apps/desktop/src/tests/components/MarkdownParserRenderer.test.tsx b/apps/desktop/src/tests/components/MarkdownParserRenderer.test.tsx
deleted file mode 100644
index 96409e1..0000000
--- a/apps/desktop/src/tests/components/MarkdownParserRenderer.test.tsx
+++ /dev/null
@@ -1,395 +0,0 @@
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
-import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
-import MarkdownParserRenderer from '../../components/MarkdownParserRenderer';
-import { markdownService } from '../../services/markdownService';
-import {
- MarkdownParseResult,
- MarkdownNode,
- MarkdownNodeType,
- OutlineItem,
- LinkInfo,
- ValidationResult,
- ValidationSeverity,
- ValidationIssueType,
- LinkType,
-} from '../../types/markdown';
-
-// Mock the markdown service
-vi.mock('../../services/markdownService', () => ({
- markdownService: {
- parseMarkdown: vi.fn(),
- extractOutline: vi.fn(),
- extractLinks: vi.fn(),
- validateMarkdown: vi.fn(),
- extractTextContent: vi.fn(),
- formatRange: vi.fn(),
- },
-}));
-
-const mockMarkdownService = markdownService as any;
-
-describe('MarkdownParserRenderer', () => {
- const mockParseResult: MarkdownParseResult = {
- root: {
- node_type: MarkdownNodeType.Document,
- content: '# Test\n\nHello world',
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 2, column: 11, offset: 18 },
- },
- children: [
- {
- node_type: MarkdownNodeType.Heading,
- content: '# Test',
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 0, column: 6, offset: 6 },
- },
- children: [],
- attributes: { level: '1' },
- },
- {
- node_type: MarkdownNodeType.Paragraph,
- content: 'Hello world',
- range: {
- start: { line: 2, column: 0, offset: 8 },
- end: { line: 2, column: 11, offset: 18 },
- },
- children: [],
- attributes: {},
- },
- ],
- attributes: {},
- },
- statistics: {
- total_nodes: 3,
- error_nodes: 0,
- parse_time_ms: 5,
- document_length: 18,
- max_depth: 2,
- },
- source_text: '# Test\n\nHello world',
- };
-
- const mockOutline: OutlineItem[] = [
- {
- title: 'Test',
- level: 1,
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 0, column: 6, offset: 6 },
- },
- },
- ];
-
- const mockLinks: LinkInfo[] = [
- {
- text: 'Google',
- url: 'https://google.com',
- title: undefined,
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 0, column: 20, offset: 20 },
- },
- link_type: LinkType.Link,
- },
- ];
-
- const mockValidation: ValidationResult = {
- is_valid: true,
- issues: [],
- statistics: {
- total_nodes: 3,
- error_nodes: 0,
- parse_time_ms: 5,
- document_length: 18,
- max_depth: 2,
- },
- };
-
- beforeEach(() => {
- mockMarkdownService.parseMarkdown.mockResolvedValue(mockParseResult);
- mockMarkdownService.extractOutline.mockResolvedValue(mockOutline);
- mockMarkdownService.extractLinks.mockResolvedValue(mockLinks);
- mockMarkdownService.validateMarkdown.mockResolvedValue(mockValidation);
- mockMarkdownService.extractTextContent.mockImplementation((node: MarkdownNode) => {
- if (node.node_type === MarkdownNodeType.Text) {
- return node.content;
- }
- return node.children.map(child => mockMarkdownService.extractTextContent(child)).join('');
- });
- mockMarkdownService.formatRange.mockImplementation((start: any, end: any) => {
- return `${start.line + 1}:${start.column + 1}-${end.line + 1}:${end.column + 1}`;
- });
- });
-
- afterEach(() => {
- vi.clearAllMocks();
- });
-
- it('renders loading state initially', () => {
- render();
- expect(screen.getByText('解析中...')).toBeInTheDocument();
- });
-
- it('renders parsed markdown content', async () => {
- render();
-
- await waitFor(() => {
- expect(mockMarkdownService.parseMarkdown).toHaveBeenCalledWith(
- '# Test\n\nHello world',
- undefined
- );
- });
-
- await waitFor(() => {
- expect(screen.queryByText('解析中...')).not.toBeInTheDocument();
- });
- });
-
- it('renders outline when showOutline is true', async () => {
- render(
-
- );
-
- await waitFor(() => {
- expect(mockMarkdownService.extractOutline).toHaveBeenCalledWith('# Test\n\nHello world');
- });
-
- await waitFor(() => {
- expect(screen.getByText('目录')).toBeInTheDocument();
- });
- });
-
- it('renders links when showLinks is true', async () => {
- render(
-
- );
-
- await waitFor(() => {
- expect(mockMarkdownService.extractLinks).toHaveBeenCalledWith('[Google](https://google.com)');
- });
-
- await waitFor(() => {
- expect(screen.getByText('链接')).toBeInTheDocument();
- });
- });
-
- it('renders validation results when showValidation is true', async () => {
- render(
-
- );
-
- await waitFor(() => {
- expect(mockMarkdownService.validateMarkdown).toHaveBeenCalledWith('# Test');
- });
-
- await waitFor(() => {
- expect(screen.getByText('验证结果 ✅')).toBeInTheDocument();
- });
- });
-
- it('renders validation issues when document is invalid', async () => {
- const invalidValidation: ValidationResult = {
- is_valid: false,
- issues: [
- {
- issue_type: ValidationIssueType.SkippedHeadingLevel,
- message: 'Heading level jumps from 1 to 3',
- range: {
- start: { line: 2, column: 0, offset: 10 },
- end: { line: 2, column: 10, offset: 20 },
- },
- severity: ValidationSeverity.Warning,
- },
- ],
- statistics: mockValidation.statistics,
- };
-
- mockMarkdownService.validateMarkdown.mockResolvedValue(invalidValidation);
-
- render(
-
- );
-
- await waitFor(() => {
- expect(screen.getByText('验证结果 ❌')).toBeInTheDocument();
- expect(screen.getByText('Heading level jumps from 1 to 3')).toBeInTheDocument();
- });
- });
-
- it('handles parsing errors gracefully', async () => {
- mockMarkdownService.parseMarkdown.mockRejectedValue(new Error('Parse error'));
-
- render();
-
- await waitFor(() => {
- expect(screen.getByText('解析错误')).toBeInTheDocument();
- expect(screen.getByText('Parse error')).toBeInTheDocument();
- });
- });
-
- it('calls onParseComplete when parsing is successful', async () => {
- const onParseComplete = vi.fn();
-
- render(
-
- );
-
- await waitFor(() => {
- expect(onParseComplete).toHaveBeenCalledWith(mockParseResult);
- });
- });
-
- it('calls onNodeClick when a node is clicked', async () => {
- const onNodeClick = vi.fn();
-
- render(
-
- );
-
- await waitFor(() => {
- expect(screen.queryByText('解析中...')).not.toBeInTheDocument();
- });
-
- // Find and click a node (this is a simplified test)
- const nodes = screen.getAllByRole('button', { hidden: true });
- if (nodes.length > 0) {
- fireEvent.click(nodes[0]);
- expect(onNodeClick).toHaveBeenCalled();
- }
- });
-
- it('shows position info when showPositionInfo is true', async () => {
- render(
-
- );
-
- await waitFor(() => {
- expect(screen.queryByText('解析中...')).not.toBeInTheDocument();
- });
-
- // Check if position info is displayed
- expect(mockMarkdownService.formatRange).toHaveBeenCalled();
- });
-
- it('handles empty content', async () => {
- render();
-
- await waitFor(() => {
- expect(screen.getByText('暂无内容')).toBeInTheDocument();
- });
- });
-
- it('enables real-time parsing when enableRealTimeParsing is true', async () => {
- const { rerender } = render(
-
- );
-
- // Change content
- rerender(
-
- );
-
- // Wait for debounced parsing
- await waitFor(() => {
- expect(mockMarkdownService.parseMarkdown).toHaveBeenCalledWith(
- '# Updated Test',
- undefined
- );
- }, { timeout: 1000 });
- });
-
- it('applies custom className', () => {
- const { container } = render(
-
- );
-
- expect(container.firstChild).toHaveClass('custom-class');
- });
-
- it('uses custom parser config', async () => {
- const config = {
- preserve_whitespace: true,
- parse_inline_html: false,
- max_depth: 50,
- timeout_ms: 5000,
- };
-
- render(
-
- );
-
- await waitFor(() => {
- expect(mockMarkdownService.parseMarkdown).toHaveBeenCalledWith(
- '# Test',
- config
- );
- });
- });
-
- it('displays parse statistics', async () => {
- render();
-
- await waitFor(() => {
- expect(screen.getByText('解析统计')).toBeInTheDocument();
- expect(screen.getByText('总节点数: 3')).toBeInTheDocument();
- expect(screen.getByText('错误节点数: 0')).toBeInTheDocument();
- expect(screen.getByText('解析耗时: 5ms')).toBeInTheDocument();
- expect(screen.getByText('最大深度: 2')).toBeInTheDocument();
- });
- });
-
- it('retries parsing when retry button is clicked', async () => {
- mockMarkdownService.parseMarkdown.mockRejectedValueOnce(new Error('Parse error'));
-
- render();
-
- await waitFor(() => {
- expect(screen.getByText('解析错误')).toBeInTheDocument();
- });
-
- // Reset mock to succeed on retry
- mockMarkdownService.parseMarkdown.mockResolvedValue(mockParseResult);
-
- const retryButton = screen.getByText('重试');
- fireEvent.click(retryButton);
-
- await waitFor(() => {
- expect(mockMarkdownService.parseMarkdown).toHaveBeenCalledTimes(2);
- });
- });
-});
diff --git a/apps/desktop/src/tests/integration/markdownParser.integration.test.ts b/apps/desktop/src/tests/integration/markdownParser.integration.test.ts
index 9850f5a..a7f4f90 100644
--- a/apps/desktop/src/tests/integration/markdownParser.integration.test.ts
+++ b/apps/desktop/src/tests/integration/markdownParser.integration.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect, beforeAll, afterAll } from 'vitest';
+import { describe, it, expect, beforeAll } from 'vitest';
import { markdownService } from '../../services/markdownService';
import {
MarkdownNodeType,
diff --git a/apps/desktop/src/tests/services/markdownService.test.ts b/apps/desktop/src/tests/services/markdownService.test.ts
deleted file mode 100644
index 985c752..0000000
--- a/apps/desktop/src/tests/services/markdownService.test.ts
+++ /dev/null
@@ -1,436 +0,0 @@
-import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
-import { invoke } from '@tauri-apps/api/core';
-import { markdownService } from '../../services/markdownService';
-import {
- MarkdownParseResult,
- MarkdownNode,
- MarkdownNodeType,
- OutlineItem,
- LinkInfo,
- ValidationResult,
- LinkType,
- QueryType,
-} from '../../types/markdown';
-
-// Mock Tauri invoke
-vi.mock('@tauri-apps/api/core', () => ({
- invoke: vi.fn(),
-}));
-
-const mockInvoke = invoke as any;
-
-describe('MarkdownService', () => {
- const mockParseResult: MarkdownParseResult = {
- root: {
- node_type: MarkdownNodeType.Document,
- content: '# Test\n\nHello world',
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 2, column: 11, offset: 18 },
- },
- children: [
- {
- node_type: MarkdownNodeType.Heading,
- content: '# Test',
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 0, column: 6, offset: 6 },
- },
- children: [
- {
- node_type: MarkdownNodeType.Text,
- content: 'Test',
- range: {
- start: { line: 0, column: 2, offset: 2 },
- end: { line: 0, column: 6, offset: 6 },
- },
- children: [],
- attributes: {},
- },
- ],
- attributes: { level: '1' },
- },
- ],
- attributes: {},
- },
- statistics: {
- total_nodes: 3,
- error_nodes: 0,
- parse_time_ms: 5,
- document_length: 18,
- max_depth: 2,
- },
- source_text: '# Test\n\nHello world',
- };
-
- const mockOutline: OutlineItem[] = [
- {
- title: 'Test',
- level: 1,
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 0, column: 6, offset: 6 },
- },
- },
- ];
-
- const mockLinks: LinkInfo[] = [
- {
- text: 'Google',
- url: 'https://google.com',
- title: undefined,
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 0, column: 20, offset: 20 },
- },
- link_type: LinkType.Link,
- },
- ];
-
- const mockValidation: ValidationResult = {
- is_valid: true,
- issues: [],
- statistics: {
- total_nodes: 3,
- error_nodes: 0,
- parse_time_ms: 5,
- document_length: 18,
- max_depth: 2,
- },
- };
-
- beforeEach(() => {
- vi.clearAllMocks();
- });
-
- afterEach(() => {
- vi.clearAllMocks();
- });
-
- describe('parseMarkdown', () => {
- it('should parse markdown successfully', async () => {
- mockInvoke.mockResolvedValue({
- success: true,
- result: mockParseResult,
- });
-
- const result = await markdownService.parseMarkdown('# Test\n\nHello world');
-
- expect(mockInvoke).toHaveBeenCalledWith('parse_markdown', {
- request: {
- text: '# Test\n\nHello world',
- config: undefined,
- },
- });
- expect(result).toEqual(mockParseResult);
- });
-
- it('should parse markdown with config', async () => {
- const config = {
- preserve_whitespace: true,
- parse_inline_html: false,
- };
-
- mockInvoke.mockResolvedValue({
- success: true,
- result: mockParseResult,
- });
-
- await markdownService.parseMarkdown('# Test', config);
-
- expect(mockInvoke).toHaveBeenCalledWith('parse_markdown', {
- request: {
- text: '# Test',
- config,
- },
- });
- });
-
- it('should throw error when parsing fails', async () => {
- mockInvoke.mockResolvedValue({
- success: false,
- error: 'Parse error',
- });
-
- await expect(markdownService.parseMarkdown('# Test')).rejects.toThrow('Parse error');
- });
-
- it('should throw error when result is missing', async () => {
- mockInvoke.mockResolvedValue({
- success: true,
- result: null,
- });
-
- await expect(markdownService.parseMarkdown('# Test')).rejects.toThrow('Failed to parse markdown');
- });
- });
-
- describe('queryNodes', () => {
- it('should query nodes successfully', async () => {
- const mockNodes: MarkdownNode[] = [mockParseResult.root.children[0]];
-
- mockInvoke.mockResolvedValue({
- success: true,
- nodes: mockNodes,
- });
-
- const result = await markdownService.queryNodes('# Test', QueryType.Headings);
-
- expect(mockInvoke).toHaveBeenCalledWith('query_markdown_nodes', {
- request: {
- text: '# Test',
- query_name: QueryType.Headings,
- },
- });
- expect(result).toEqual(mockNodes);
- });
-
- it('should throw error when query fails', async () => {
- mockInvoke.mockResolvedValue({
- success: false,
- error: 'Query error',
- });
-
- await expect(markdownService.queryNodes('# Test', QueryType.Headings)).rejects.toThrow('Query error');
- });
- });
-
- describe('findNodeAtPosition', () => {
- it('should find node at position successfully', async () => {
- const mockNode = mockParseResult.root.children[0];
-
- mockInvoke.mockResolvedValue({
- success: true,
- node: mockNode,
- });
-
- const result = await markdownService.findNodeAtPosition('# Test', 0, 0);
-
- expect(mockInvoke).toHaveBeenCalledWith('find_markdown_node_at_position', {
- request: {
- text: '# Test',
- line: 0,
- column: 0,
- },
- });
- expect(result).toEqual(mockNode);
- });
-
- it('should return null when no node found', async () => {
- mockInvoke.mockResolvedValue({
- success: true,
- node: null,
- });
-
- const result = await markdownService.findNodeAtPosition('# Test', 10, 10);
- expect(result).toBeNull();
- });
-
- it('should throw error when find fails', async () => {
- mockInvoke.mockResolvedValue({
- success: false,
- error: 'Find error',
- });
-
- await expect(markdownService.findNodeAtPosition('# Test', 0, 0)).rejects.toThrow('Find error');
- });
- });
-
- describe('extractOutline', () => {
- it('should extract outline successfully', async () => {
- mockInvoke.mockResolvedValue({
- success: true,
- outline: mockOutline,
- });
-
- const result = await markdownService.extractOutline('# Test');
-
- expect(mockInvoke).toHaveBeenCalledWith('extract_markdown_outline', {
- request: {
- text: '# Test',
- },
- });
- expect(result).toEqual(mockOutline);
- });
-
- it('should throw error when extraction fails', async () => {
- mockInvoke.mockResolvedValue({
- success: false,
- error: 'Extraction error',
- });
-
- await expect(markdownService.extractOutline('# Test')).rejects.toThrow('Extraction error');
- });
- });
-
- describe('extractLinks', () => {
- it('should extract links successfully', async () => {
- mockInvoke.mockResolvedValue({
- success: true,
- links: mockLinks,
- });
-
- const result = await markdownService.extractLinks('[Google](https://google.com)');
-
- expect(mockInvoke).toHaveBeenCalledWith('extract_markdown_links', {
- request: {
- text: '[Google](https://google.com)',
- },
- });
- expect(result).toEqual(mockLinks);
- });
-
- it('should throw error when extraction fails', async () => {
- mockInvoke.mockResolvedValue({
- success: false,
- error: 'Extraction error',
- });
-
- await expect(markdownService.extractLinks('[Google](https://google.com)')).rejects.toThrow('Extraction error');
- });
- });
-
- describe('validateMarkdown', () => {
- it('should validate markdown successfully', async () => {
- mockInvoke.mockResolvedValue({
- success: true,
- validation: mockValidation,
- });
-
- const result = await markdownService.validateMarkdown('# Test');
-
- expect(mockInvoke).toHaveBeenCalledWith('validate_markdown', {
- request: {
- text: '# Test',
- },
- });
- expect(result).toEqual(mockValidation);
- });
-
- it('should throw error when validation fails', async () => {
- mockInvoke.mockResolvedValue({
- success: false,
- error: 'Validation error',
- });
-
- await expect(markdownService.validateMarkdown('# Test')).rejects.toThrow('Validation error');
- });
- });
-
- describe('convenience methods', () => {
- beforeEach(() => {
- mockInvoke.mockResolvedValue({
- success: true,
- nodes: [mockParseResult.root.children[0]],
- });
- });
-
- it('should query headings', async () => {
- await markdownService.queryHeadings('# Test');
- expect(mockInvoke).toHaveBeenCalledWith('query_markdown_nodes', {
- request: {
- text: '# Test',
- query_name: QueryType.Headings,
- },
- });
- });
-
- it('should query link nodes', async () => {
- await markdownService.queryLinkNodes('[Link](url)');
- expect(mockInvoke).toHaveBeenCalledWith('query_markdown_nodes', {
- request: {
- text: '[Link](url)',
- query_name: QueryType.Links,
- },
- });
- });
-
- it('should query code blocks', async () => {
- await markdownService.queryCodeBlocks('```js\ncode\n```');
- expect(mockInvoke).toHaveBeenCalledWith('query_markdown_nodes', {
- request: {
- text: '```js\ncode\n```',
- query_name: QueryType.Code,
- },
- });
- });
- });
-
- describe('utility methods', () => {
- const textNode: MarkdownNode = {
- node_type: MarkdownNodeType.Text,
- content: 'Hello world',
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 0, column: 11, offset: 11 },
- },
- children: [],
- attributes: {},
- };
-
- const headingNode: MarkdownNode = {
- node_type: MarkdownNodeType.Heading,
- content: '# Test',
- range: {
- start: { line: 0, column: 0, offset: 0 },
- end: { line: 0, column: 6, offset: 6 },
- },
- children: [textNode],
- attributes: { level: '1' },
- };
-
- it('should extract text content from text node', () => {
- const result = markdownService.extractTextContent(textNode);
- expect(result).toBe('Hello world');
- });
-
- it('should extract text content from node with children', () => {
- const result = markdownService.extractTextContent(headingNode);
- expect(result).toBe('Hello world');
- });
-
- it('should check if node is in range', () => {
- const result = markdownService.isNodeInRange(textNode, 0, 1);
- expect(result).toBe(true);
-
- const result2 = markdownService.isNodeInRange(textNode, 1, 2);
- expect(result2).toBe(false);
- });
-
- it('should get node line count', () => {
- const result = markdownService.getNodeLineCount(textNode);
- expect(result).toBe(1);
- });
-
- it('should find nodes containing text', () => {
- const nodes = [textNode, headingNode];
- const result = markdownService.findNodesContainingText(nodes, 'Hello');
- expect(result).toHaveLength(2); // Both nodes contain "Hello"
- });
-
- it('should get node depth', () => {
- const result = markdownService.getNodeDepth(headingNode);
- expect(result).toBe(1); // Has one level of children
- });
-
- it('should format position', () => {
- const result = markdownService.formatPosition(0, 0);
- expect(result).toBe('1:1'); // 1-based indexing
- });
-
- it('should format range', () => {
- const start = { line: 0, column: 0 };
- const end = { line: 1, column: 5 };
- const result = markdownService.formatRange(start, end);
- expect(result).toBe('1:1-2:6'); // 1-based indexing
- });
- });
-
- describe('singleton pattern', () => {
- it('should return the same instance', () => {
- const instance1 = markdownService;
- const instance2 = markdownService;
- expect(instance1).toBe(instance2);
- });
- });
-});