From 186bdb486fcfb065ed90ed8ffe3bb9c2780e131e Mon Sep 17 00:00:00 2001 From: luoxu1314 <32211456+luoxu1314@users.noreply.github.com> Date: Sat, 12 Jul 2025 22:45:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BB=8E=E6=9C=AA?= =?UTF-8?q?=E6=89=93=E5=BC=80=E8=BF=87=E7=9A=84=E8=AF=9D=E9=A2=98=E5=AF=BC?= =?UTF-8?q?=E5=87=BAmarkdown=E4=B8=BA=E7=A9=BA=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#8103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 优化导出功能使用 TopicManager 确保消息正确加载 - 移除对 db 的直接依赖,改用 TopicManager.getTopicMessages - 修复从未打开过的话题导出为空的问题 Signed-off-by: luoxu1314 * Update export.test.ts 修复相关测试mock使用TopicManager而非db.topics.get,确保测试与新的消息加载方式兼容 * Update export.ts * Update export.test.ts 完善测试 * style(test): 移除多余空行 --------- Signed-off-by: luoxu1314 Co-authored-by: GeorgeDong32 --- .../src/utils/__tests__/export.test.ts | 65 ++++++++++++++----- src/renderer/src/utils/export.ts | 38 ++++++----- 2 files changed, 72 insertions(+), 31 deletions(-) diff --git a/src/renderer/src/utils/__tests__/export.test.ts b/src/renderer/src/utils/__tests__/export.test.ts index 463313078b..5f39ea696d 100644 --- a/src/renderer/src/utils/__tests__/export.test.ts +++ b/src/renderer/src/utils/__tests__/export.test.ts @@ -59,11 +59,10 @@ vi.mock('@renderer/utils/messageUtils/find', () => ({ }) })) -vi.mock('@renderer/databases', () => ({ - default: { - topics: { - get: vi.fn() - } +// Mock TopicManager for dynamic import +vi.mock('@renderer/hooks/useTopic', () => ({ + TopicManager: { + getTopicMessages: vi.fn() } })) @@ -76,7 +75,6 @@ vi.mock('@renderer/utils/markdown', async (importOriginal) => { }) // Import the functions to test AFTER setting up mocks -import db from '@renderer/databases' import { Topic } from '@renderer/types' import { markdownToPlainText } from '@renderer/utils/markdown' @@ -415,6 +413,18 @@ describe('export', () => { }) describe('formatMessageAsPlainText (via topicToPlainText)', () => { + beforeEach(async () => { + vi.clearAllMocks() + vi.resetModules() + + // Re-mock TopicManager for this test suite + vi.doMock('@renderer/hooks/useTopic', () => ({ + TopicManager: { + getTopicMessages: vi.fn() + } + })) + }) + it('should format user and assistant messages correctly to plain text with roles', async () => { const userMsg = createMessage({ role: 'user', id: 'u_plain_formatted' }, [ { type: MessageBlockType.MAIN_TEXT, content: '# User Content Formatted' } @@ -430,7 +440,9 @@ describe('export', () => { createdAt: '', updatedAt: '' } - ;(db.topics.get as any).mockResolvedValue({ messages: [userMsg, assistantMsg] }) + // Mock TopicManager.getTopicMessages to return the expected messages + const { TopicManager } = await import('@renderer/hooks/useTopic') + ;(TopicManager.getTopicMessages as any).mockResolvedValue([userMsg, assistantMsg]) // Specific mock for this test to check formatting ;(markdownToPlainText as any).mockImplementation((str: string) => str.replace(/[#*]/g, '')) @@ -501,6 +513,18 @@ describe('export', () => { }) describe('messagesToPlainText (via topicToPlainText)', () => { + beforeEach(async () => { + vi.clearAllMocks() // Clear mocks before each test in this suite + vi.resetModules() // Reset module cache + + // Re-import and re-mock TopicManager to ensure clean state + vi.doMock('@renderer/hooks/useTopic', () => ({ + TopicManager: { + getTopicMessages: vi.fn() + } + })) + }) + it('should join multiple formatted plain text messages with double newlines', async () => { const msg1 = createMessage({ role: 'user', id: 'm_plain1_formatted' }, [ { type: MessageBlockType.MAIN_TEXT, content: 'Msg1 Formatted' } @@ -516,7 +540,9 @@ describe('export', () => { createdAt: '', updatedAt: '' } - ;(db.topics.get as any).mockResolvedValue({ messages: [msg1, msg2] }) + // Mock TopicManager.getTopicMessages to return the expected messages + const { TopicManager } = await import('@renderer/hooks/useTopic') + ;(TopicManager.getTopicMessages as any).mockResolvedValue([msg1, msg2]) ;(markdownToPlainText as any).mockImplementation((str: string) => str) // Pass-through const plainText = await topicToPlainText(testTopic) @@ -549,7 +575,9 @@ describe('export', () => { createdAt: '', updatedAt: '' } - ;(db.topics.get as any).mockResolvedValue({ messages: [msgWithEmpty] }) + // Mock TopicManager.getTopicMessages to return the expected messages + const { TopicManager } = await import('@renderer/hooks/useTopic') + ;(TopicManager.getTopicMessages as any).mockResolvedValue([msgWithEmpty]) ;(markdownToPlainText as any).mockImplementation((str: string) => str) const result = await topicToPlainText(testTopic) @@ -568,7 +596,9 @@ describe('export', () => { createdAt: '', updatedAt: '' } - ;(db.topics.get as any).mockResolvedValue({ messages: [msgWithSpecial] }) + // Mock TopicManager.getTopicMessages to return the expected messages + const { TopicManager } = await import('@renderer/hooks/useTopic') + ;(TopicManager.getTopicMessages as any).mockResolvedValue([msgWithSpecial]) ;(markdownToPlainText as any).mockImplementation((str: string) => str) const result = await topicToPlainText(testTopic) @@ -592,11 +622,12 @@ describe('export', () => { createdAt: '', updatedAt: '' } - ;(db.topics.get as any).mockResolvedValue({ messages: [msg1, msg2] }) + // Mock TopicManager.getTopicMessages to return the expected messages + const { TopicManager } = await import('@renderer/hooks/useTopic') + ;(TopicManager.getTopicMessages as any).mockResolvedValue([msg1, msg2]) ;(markdownToPlainText as any).mockImplementation((str: string) => str.replace(/[#*_]/g, '')) const result = await topicToPlainText(testTopic) - expect(db.topics.get).toHaveBeenCalledWith('topic1_plain') expect(markdownToPlainText).toHaveBeenCalledWith('# Topic One') expect(markdownToPlainText).toHaveBeenCalledWith('**Hello**') expect(markdownToPlainText).toHaveBeenCalledWith('_World_') @@ -612,7 +643,9 @@ describe('export', () => { createdAt: '', updatedAt: '' } - ;(db.topics.get as any).mockResolvedValue({ messages: [] }) + // Mock TopicManager.getTopicMessages to return empty array + const { TopicManager } = await import('@renderer/hooks/useTopic') + ;(TopicManager.getTopicMessages as any).mockResolvedValue([]) ;(markdownToPlainText as any).mockImplementation((str: string) => str.replace(/[#*_]/g, '')) const result = await topicToPlainText(testTopic) @@ -629,10 +662,12 @@ describe('export', () => { createdAt: '', updatedAt: '' } - ;(db.topics.get as any).mockResolvedValue(null) + // Mock TopicManager.getTopicMessages to return empty array for null case + const { TopicManager } = await import('@renderer/hooks/useTopic') + ;(TopicManager.getTopicMessages as any).mockResolvedValue([]) const result = await topicToPlainText(testTopic) - expect(result).toBe('') + expect(result).toBe('Null Messages Topic') }) }) diff --git a/src/renderer/src/utils/export.ts b/src/renderer/src/utils/export.ts index 6c0004d90b..61b9696f33 100644 --- a/src/renderer/src/utils/export.ts +++ b/src/renderer/src/utils/export.ts @@ -1,5 +1,4 @@ import { Client } from '@notionhq/client' -import db from '@renderer/databases' import i18n from '@renderer/i18n' import { getMessageTitle } from '@renderer/services/MessagesService' import store from '@renderer/store' @@ -13,6 +12,17 @@ import { markdownToBlocks } from '@tryfabric/martian' import dayjs from 'dayjs' import { appendBlocks } from 'notion-helper' // 引入 notion-helper 的 appendBlocks 函数 +/** + * 获取话题的消息列表,使用TopicManager确保消息被正确加载 + * 这样可以避免从未打开过的话题导出为空的问题 + * @param topicId 话题ID + * @returns 话题消息列表 + */ +async function fetchTopicMessages(topicId: string): Promise { + const { TopicManager } = await import('@renderer/hooks/useTopic') + return await TopicManager.getTopicMessages(topicId) +} + /** * 从消息内容中提取标题,限制长度并处理换行和标点符号。用于导出功能。 * @param {string} str 输入字符串 @@ -143,28 +153,26 @@ const messagesToPlainText = (messages: Message[]): string => { export const topicToMarkdown = async (topic: Topic, exportReasoning?: boolean) => { const topicName = `# ${topic.name}` - const topicMessages = await db.topics.get(topic.id) - if (topicMessages) { - return topicName + '\n\n' + messagesToMarkdown(topicMessages.messages, exportReasoning) + const messages = await fetchTopicMessages(topic.id) + + if (messages && messages.length > 0) { + return topicName + '\n\n' + messagesToMarkdown(messages, exportReasoning) } - return '' + return topicName } export const topicToPlainText = async (topic: Topic): Promise => { const topicName = markdownToPlainText(topic.name).trim() - const topicMessages = await db.topics.get(topic.id) - if (topicMessages && topicMessages.messages.length > 0) { - return topicName + '\n\n' + messagesToPlainText(topicMessages.messages) + const topicMessages = await fetchTopicMessages(topic.id) + + if (topicMessages && topicMessages.length > 0) { + return topicName + '\n\n' + messagesToPlainText(topicMessages) } - if (topicMessages && topicMessages.messages.length === 0) { - return topicName - } - - return '' + return topicName } export const exportTopicAsMarkdown = async (topic: Topic, exportReasoning?: boolean) => { @@ -365,9 +373,7 @@ export const exportMessageToNotion = async (title: string, content: string, mess export const exportTopicToNotion = async (topic: Topic) => { const { notionExportReasoning } = store.getState().settings - // 获取话题消息 - const topicRecord = await db.topics.get(topic.id) - const topicMessages = topicRecord?.messages || [] + const topicMessages = await fetchTopicMessages(topic.id) // 创建话题标题块 const titleBlocks = await convertMarkdownToNotionBlocks(`# ${topic.name}`)