fix: 修复从未打开过的话题导出markdown为空的问题 (#8103)

* feat: 优化导出功能使用 TopicManager 确保消息正确加载

- 移除对 db 的直接依赖,改用 TopicManager.getTopicMessages
- 修复从未打开过的话题导出为空的问题

Signed-off-by: luoxu1314 <xiaoluoxu@163.com>

* Update export.test.ts

修复相关测试mock使用TopicManager而非db.topics.get,确保测试与新的消息加载方式兼容

* Update export.ts

* Update export.test.ts

完善测试

* style(test): 移除多余空行

---------

Signed-off-by: luoxu1314 <xiaoluoxu@163.com>
Co-authored-by: GeorgeDong32 <georgedong32@qq.com>
This commit is contained in:
luoxu1314 2025-07-12 22:45:01 +08:00 committed by GitHub
parent ea40cc7692
commit 186bdb486f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 72 additions and 31 deletions

View File

@ -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')
})
})

View File

@ -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<Message[]> {
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<string> => {
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}`)