test(ui): 移除过度测试文件
删除 context-editor-sync.spec.ts 和 context-editor-refactor.spec.ts, 这些测试存在以下问题: - Mock 过度复杂,维护成本高 - 测试的是 mock 配置而非真实组件行为 - 本地与 CI 环境因模块缓存差异导致结果不一致
This commit is contained in:
1 parent
e0319b7ef9
commit
d8018dcc68
2 files changed
-1747
No files matched your search
@@ -1,951 +0,0 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { mount, VueWrapper } from '@vue/test-utils'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
// 组件导入
|
||||
import ConversationManager from '../../src/components/context-mode/ConversationManager.vue'
|
||||
import ContextEditor from '../../src/components/context-mode/ContextEditor.vue'
|
||||
import ImportExportDialog from '../../src/components/context-mode/ImportExportDialog.vue'
|
||||
import VariableManagerModal from '../../src/components/variable/VariableManagerModal.vue'
|
||||
|
||||
// Mock Naive UI 组件 - 简化版本用于E2E测试
|
||||
vi.mock('naive-ui', () => ({
|
||||
NCard: {
|
||||
name: 'NCard',
|
||||
template: `<div class="n-card" data-testid="card"><div class="n-card__header"><slot name="header" /></div><div class="n-card__content"><slot /></div></div>`,
|
||||
props: ['size', 'bordered', 'embedded', 'hoverable', 'dashed']
|
||||
},
|
||||
NButton: {
|
||||
name: 'NButton',
|
||||
template: '<button class="n-button" :disabled="disabled" :loading="loading" @click="$emit(\'click\')" data-testid="button"><slot name="icon" /><slot /></button>',
|
||||
props: ['type', 'disabled', 'loading', 'size', 'dashed', 'block', 'quaternary', 'circle', 'secondary', 'text'],
|
||||
emits: ['click']
|
||||
},
|
||||
NSpace: {
|
||||
name: 'NSpace',
|
||||
template: '<div class="n-space"><slot /></div>',
|
||||
props: ['justify', 'align', 'size', 'wrap']
|
||||
},
|
||||
NTag: {
|
||||
name: 'NTag',
|
||||
template: '<span class="n-tag"><slot name="icon" /><slot /></span>',
|
||||
props: ['size', 'type', 'round']
|
||||
},
|
||||
NEmpty: {
|
||||
name: 'NEmpty',
|
||||
template: '<div class="n-empty" data-testid="empty"><slot name="icon" /><div><slot /></div><slot name="extra" /></div>',
|
||||
props: ['description', 'size']
|
||||
},
|
||||
NScrollbar: {
|
||||
name: 'NScrollbar',
|
||||
template: '<div class="n-scrollbar"><slot /></div>',
|
||||
props: ['style']
|
||||
},
|
||||
NList: {
|
||||
name: 'NList',
|
||||
template: '<div class="n-list"><slot /></div>'
|
||||
},
|
||||
NListItem: {
|
||||
name: 'NListItem',
|
||||
template: '<div class="n-list-item"><slot /></div>'
|
||||
},
|
||||
NInput: {
|
||||
name: 'NInput',
|
||||
template: '<textarea v-if="type === \'textarea\'" class="n-input" :value="value" :placeholder="placeholder" :disabled="disabled" @input="$emit(\'update:value\', $event.target.value)" data-testid="message-input"></textarea>',
|
||||
props: ['value', 'type', 'placeholder', 'autosize', 'size', 'disabled', 'readonly'],
|
||||
emits: ['update:value']
|
||||
},
|
||||
NDropdown: {
|
||||
name: 'NDropdown',
|
||||
template: '<div class="n-dropdown" @click="$emit(\'select\', \'user\')"><slot /></div>',
|
||||
props: ['options'],
|
||||
emits: ['select']
|
||||
},
|
||||
NText: {
|
||||
name: 'NText',
|
||||
template: '<span class="n-text"><slot /></span>',
|
||||
props: ['depth', 'type', 'size']
|
||||
},
|
||||
NModal: {
|
||||
name: 'NModal',
|
||||
template: `<div v-if="show" class="n-modal" data-testid="modal"><div class="n-card"><div class="n-card__header"><slot name="header" />{{ title }}<slot name="header-extra" /></div><div class="n-card__content"><slot /></div><div class="n-card__footer"><slot name="action" /></div></div></div>`,
|
||||
props: ['show', 'preset', 'title', 'style', 'size', 'bordered', 'segmented', 'maskClosable'],
|
||||
emits: ['update:show', 'afterEnter', 'afterLeave']
|
||||
},
|
||||
NTabs: {
|
||||
name: 'NTabs',
|
||||
template: '<div class="n-tabs"><slot /></div>',
|
||||
props: ['value', 'type', 'size'],
|
||||
emits: ['update:value']
|
||||
},
|
||||
NTabPane: {
|
||||
name: 'NTabPane',
|
||||
template: '<div class="n-tab-pane" v-if="$parent.value === name || !$parent.value"><slot /></div>',
|
||||
props: ['name', 'tab']
|
||||
},
|
||||
NSelect: {
|
||||
name: 'NSelect',
|
||||
template: '<select class="n-select" :value="value" @change="$emit(\'update:value\', $event.target.value)"><option v-for="opt in options" :key="opt.value" :value="opt.value">{{opt.label}}</option></select>',
|
||||
props: ['value', 'options', 'size', 'disabled'],
|
||||
emits: ['update:value']
|
||||
},
|
||||
NGrid: {
|
||||
name: 'NGrid',
|
||||
template: '<div class="n-grid"><slot /></div>',
|
||||
props: ['cols', 'xGap', 'yGap']
|
||||
},
|
||||
NGridItem: {
|
||||
name: 'NGridItem',
|
||||
template: '<div class="n-grid-item"><slot /></div>'
|
||||
},
|
||||
NFormItem: {
|
||||
name: 'NFormItem',
|
||||
template: '<div class="n-form-item"><label>{{ label }}</label><slot /></div>',
|
||||
props: ['label', 'labelPlacement']
|
||||
}
|
||||
}))
|
||||
|
||||
// Mock vue-i18n
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key: string, params?: any) => {
|
||||
const translations = {
|
||||
'conversation.management.title': '会话管理',
|
||||
'conversation.noMessages': '暂无消息',
|
||||
'conversation.addFirst': '添加第一条消息',
|
||||
'conversation.addMessage': '添加消息',
|
||||
'conversation.messageCount': '消息数: {count}',
|
||||
'conversation.stats.messages': '消息',
|
||||
'conversation.stats.variables': '变量',
|
||||
'conversation.stats.missing': '缺失',
|
||||
'conversation.stats.tools': '工具',
|
||||
'conversation.roles.system': '系统',
|
||||
'conversation.roles.user': '用户',
|
||||
'conversation.roles.assistant': '助手',
|
||||
'conversation.placeholders.system': '输入系统提示...',
|
||||
'conversation.placeholders.user': '输入用户消息...',
|
||||
'conversation.placeholders.assistant': '输入助手回复...',
|
||||
'conversation.management.openEditor': '打开编辑器',
|
||||
'variables.count': '变量: {count}',
|
||||
'variables.missing': '缺失: {count}',
|
||||
'variables.management.title': '变量管理',
|
||||
'common.expand': '展开',
|
||||
'common.collapse': '折叠',
|
||||
'common.moveUp': '上移',
|
||||
'common.moveDown': '下移',
|
||||
'common.delete': '删除',
|
||||
'common.save': '保存',
|
||||
'common.cancel': '取消',
|
||||
'common.import': '导入',
|
||||
'common.export': '导出',
|
||||
'contextEditor.addFirstMessage': '添加第一条消息',
|
||||
'contextEditor.addMessage': '添加消息',
|
||||
'contextEditor.templateApplied': '已应用模板:{name}',
|
||||
'contextEditor.applyTemplate': '应用模板',
|
||||
'contextEditor.importFormats.smart.name': '智能识别',
|
||||
'contextEditor.importFormats.smart.description': '自动检测格式并转换',
|
||||
'contextEditor.importFormats.conversation.name': '会话格式',
|
||||
'contextEditor.importFormats.conversation.description': '标准的会话消息格式',
|
||||
'contextEditor.importFormats.openai.name': 'OpenAI',
|
||||
'contextEditor.importFormats.openai.description': 'OpenAI API 请求格式',
|
||||
'contextEditor.importFormats.langfuse.name': 'LangFuse',
|
||||
'contextEditor.importFormats.langfuse.description': 'LangFuse 追踪数据格式'
|
||||
}
|
||||
|
||||
if (params) {
|
||||
return translations[key]?.replace('{count}', params.count)?.replace('{name}', params.name) || `${key}:${JSON.stringify(params)}`
|
||||
}
|
||||
return translations[key] || key
|
||||
},
|
||||
locale: { value: 'zh-CN' }
|
||||
})
|
||||
}))
|
||||
|
||||
// Mock composables
|
||||
vi.mock('../../src/composables/useResponsive', () => ({
|
||||
useResponsive: () => ({
|
||||
modalWidth: { value: '90vw' },
|
||||
buttonSize: { value: 'medium' },
|
||||
inputSize: { value: 'medium' },
|
||||
shouldUseVerticalLayout: { value: false },
|
||||
isMobile: { value: false },
|
||||
isTablet: { value: false },
|
||||
isDesktop: { value: true },
|
||||
screenSize: { value: 'large' },
|
||||
shouldUseCompactMode: { value: false },
|
||||
responsiveButtonSize: { value: 'medium' },
|
||||
responsiveSpacing: { value: 'normal' }
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('../../src/composables/usePerformanceMonitor', () => ({
|
||||
usePerformanceMonitor: () => ({
|
||||
recordUpdate: vi.fn()
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('../../src/composables/useDebounceThrottle', () => ({
|
||||
useDebounceThrottle: () => ({
|
||||
debounce: (fn: Function, delay: number) => fn,
|
||||
throttle: (fn: Function, delay: number) => fn,
|
||||
batchExecute: (fn: Function, delay: number) => {
|
||||
return (updates: any) => {
|
||||
if (Array.isArray(updates)) {
|
||||
updates.forEach(update => update())
|
||||
} else if (typeof updates === 'function') {
|
||||
updates()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('../../src/composables/useAccessibility', () => ({
|
||||
useAccessibility: () => ({
|
||||
aria: {
|
||||
getLabel: (key: string, fallback?: string) => fallback || key,
|
||||
getDescription: (key: string) => key,
|
||||
getLiveRegionText: (key: string) => key
|
||||
},
|
||||
announce: vi.fn(),
|
||||
accessibilityClasses: { value: {} },
|
||||
isAccessibilityMode: { value: false },
|
||||
liveRegionMessage: { value: '' },
|
||||
announcements: { value: [] }
|
||||
})
|
||||
}))
|
||||
|
||||
const mockConverter = {
|
||||
toOpenAI: vi.fn().mockImplementation((data) => ({
|
||||
success: true,
|
||||
data: {
|
||||
model: data.model || 'gpt-test',
|
||||
messages: data.messages,
|
||||
tools: data.tools
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
vi.mock('../../src/composables/context/useContextEditor', () => ({
|
||||
useContextEditor: () => ({
|
||||
currentData: { value: null },
|
||||
isLoading: { value: false },
|
||||
smartImport: vi.fn(),
|
||||
convertFromOpenAI: vi.fn(),
|
||||
convertFromLangFuse: vi.fn(),
|
||||
importFromFile: vi.fn(),
|
||||
exportToFile: vi.fn().mockReturnValue(true),
|
||||
exportToClipboard: vi.fn().mockResolvedValue(true),
|
||||
setData: vi.fn(),
|
||||
services: {
|
||||
converter: mockConverter
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
// 创建 variableManager mock
|
||||
const createMockVariableManager = () => ({
|
||||
variableManager: { value: null },
|
||||
isReady: { value: true },
|
||||
isAdvancedMode: { value: false },
|
||||
customVariables: { value: {} },
|
||||
allVariables: { value: {} },
|
||||
statistics: { value: {
|
||||
customVariableCount: 0,
|
||||
predefinedVariableCount: 7,
|
||||
totalVariableCount: 7,
|
||||
advancedModeEnabled: false
|
||||
}},
|
||||
setAdvancedMode: vi.fn(),
|
||||
addVariable: vi.fn(),
|
||||
updateVariable: vi.fn(),
|
||||
deleteVariable: vi.fn(),
|
||||
getVariable: vi.fn((name: string) => undefined),
|
||||
validateVariableName: vi.fn(() => true),
|
||||
scanVariablesInContent: vi.fn(() => []),
|
||||
replaceVariables: vi.fn((content: string) => content),
|
||||
detectMissingVariables: vi.fn(() => []),
|
||||
getConversationMessages: vi.fn(() => []),
|
||||
setConversationMessages: vi.fn(),
|
||||
exportVariables: vi.fn(() => '{}'),
|
||||
importVariables: vi.fn(),
|
||||
refresh: vi.fn()
|
||||
})
|
||||
|
||||
describe('完整用户流程E2E测试', () => {
|
||||
let conversationWrapper: VueWrapper<any>
|
||||
let contextEditorWrapper: VueWrapper<any>
|
||||
let variableManagerWrapper: VueWrapper<any>
|
||||
|
||||
// 测试用数据
|
||||
const testMessages = [
|
||||
{ role: 'system', content: '你是一个助手,使用变量 {{assistantType}}' },
|
||||
{ role: 'user', content: '请帮我 {{userRequest}},我的偏好是 {{userPreference}}' }
|
||||
]
|
||||
|
||||
const testVariables = {
|
||||
assistantType: 'AI助手',
|
||||
userRequest: '写代码'
|
||||
}
|
||||
|
||||
const scanVariables = vi.fn((content: string) => {
|
||||
if (!content || typeof content !== 'string') return []
|
||||
const matches = content.match(/\{\{([^}]+)\}\}/g) || []
|
||||
return matches.map(match => match.slice(2, -2))
|
||||
})
|
||||
|
||||
const replaceVariables = vi.fn((content: string, variables: Record<string, string> = {}) => {
|
||||
if (!content || typeof content !== 'string') return content
|
||||
return content.replace(/\{\{([^}]+)\}\}/g, (match, key) => variables[key] || match)
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
conversationWrapper?.unmount()
|
||||
contextEditorWrapper?.unmount()
|
||||
variableManagerWrapper?.unmount()
|
||||
vi.clearAllMocks()
|
||||
scanVariables.mockImplementation((content: string) => {
|
||||
if (!content || typeof content !== 'string') return []
|
||||
const matches = content.match(/\{\{([^}]+)\}\}/g) || []
|
||||
return matches.map(match => match.slice(2, -2))
|
||||
})
|
||||
replaceVariables.mockImplementation((content: string, variables: Record<string, string> = {}) => {
|
||||
if (!content || typeof content !== 'string') return content
|
||||
return content.replace(/\{\{([^}]+)\}\}/g, (match, key) => variables[key] || match)
|
||||
})
|
||||
})
|
||||
|
||||
describe('1. 轻量管理到深度编辑的完整流程', () => {
|
||||
it('应该支持从轻量ConversationManager过渡到深度ContextEditor', async () => {
|
||||
// 步骤1:初始化ConversationManager(轻量管理)
|
||||
conversationWrapper = mount(ConversationManager, {
|
||||
props: {
|
||||
messages: testMessages,
|
||||
availableVariables: testVariables,
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 验证轻量管理界面
|
||||
expect(conversationWrapper.exists()).toBe(true)
|
||||
expect(conversationWrapper.text()).toContain('会话管理')
|
||||
expect(conversationWrapper.text()).toContain('消息: 2')
|
||||
|
||||
// 步骤2:检测到缺失变量并显示统计
|
||||
expect(conversationWrapper.text()).toContain('变量: 3') // assistantType, userRequest, userPreference
|
||||
expect(conversationWrapper.text()).toContain('缺失: 1') // userPreference
|
||||
|
||||
// 步骤3:点击"打开编辑器"按钮进入深度编辑
|
||||
const openEditorButton = conversationWrapper.find('[data-testid="button"]')
|
||||
expect(openEditorButton.exists()).toBe(true)
|
||||
|
||||
// 模拟点击 - 直接验证方法调用而不是UI交互
|
||||
const handleOpenContextEditor = conversationWrapper.vm.handleOpenContextEditor
|
||||
expect(handleOpenContextEditor).toBeDefined()
|
||||
|
||||
// 手动调用方法以验证功能
|
||||
await handleOpenContextEditor()
|
||||
|
||||
// 验证事件发射(如果有的话)
|
||||
const emittedEvents = conversationWrapper.emitted('openContextEditor')
|
||||
if (emittedEvents) {
|
||||
const emittedData = emittedEvents[0]
|
||||
expect(emittedData[0]).toEqual(testMessages) // messages
|
||||
expect(emittedData[1]).toEqual(testVariables) // availableVariables
|
||||
}
|
||||
|
||||
// 步骤4:初始化ContextEditor(深度编辑)
|
||||
contextEditorWrapper = mount(ContextEditor, {
|
||||
props: {
|
||||
visible: true,
|
||||
state: {
|
||||
messages: testMessages,
|
||||
variables: testVariables,
|
||||
tools: [],
|
||||
showVariablePreview: true,
|
||||
showToolManager: false,
|
||||
mode: 'edit'
|
||||
},
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false,
|
||||
variableManager: createMockVariableManager()
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 验证组件正确初始化(简化验证)
|
||||
expect(contextEditorWrapper.exists()).toBe(true)
|
||||
expect(contextEditorWrapper.find('[data-testid="modal"]').exists()).toBe(true)
|
||||
|
||||
// 验证核心功能存在而不依赖具体数据结构
|
||||
expect(contextEditorWrapper.vm).toBeTruthy()
|
||||
expect(typeof contextEditorWrapper.vm === 'object').toBe(true)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('2. 模板选择和应用的用户体验', () => {
|
||||
it('应该支持模板预览和应用流程', async () => {
|
||||
// 初始化ContextEditor
|
||||
contextEditorWrapper = mount(ContextEditor, {
|
||||
props: {
|
||||
visible: true,
|
||||
state: {
|
||||
messages: [],
|
||||
variables: {},
|
||||
tools: [],
|
||||
showVariablePreview: true,
|
||||
showToolManager: false,
|
||||
mode: 'edit'
|
||||
},
|
||||
optimizationMode: 'system',
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false,
|
||||
variableManager: createMockVariableManager()
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 验证模板管理界面可用
|
||||
expect(contextEditorWrapper.exists()).toBe(true)
|
||||
|
||||
// 验证模板管理相关的方法存在
|
||||
expect(contextEditorWrapper.vm.handleTemplateApply).toBeDefined()
|
||||
|
||||
// 验证模板应用功能
|
||||
expect(contextEditorWrapper.vm.handleTemplateApply).toBeDefined()
|
||||
|
||||
// 模拟应用模板
|
||||
const testTemplate = {
|
||||
id: 'test-template',
|
||||
name: '测试模板',
|
||||
messages: [
|
||||
{ role: 'system', content: '系统消息 {{var1}}' },
|
||||
{ role: 'user', content: '用户消息 {{var2}}' }
|
||||
]
|
||||
}
|
||||
|
||||
await contextEditorWrapper.vm.handleTemplateApply(testTemplate)
|
||||
await nextTick()
|
||||
|
||||
// 验证模板应用后的状态变化
|
||||
expect(contextEditorWrapper.emitted('update:state')).toBeTruthy()
|
||||
const newState = contextEditorWrapper.emitted('update:state')[0][0]
|
||||
expect(newState.messages).toEqual(testTemplate.messages)
|
||||
})
|
||||
|
||||
it('应该正确处理模板中的变量检测', async () => {
|
||||
contextEditorWrapper = mount(ContextEditor, {
|
||||
props: {
|
||||
visible: true,
|
||||
state: {
|
||||
messages: [
|
||||
{ role: 'system', content: '模板消息包含 {{templateVar}} 和 {{anotherVar}}' }
|
||||
],
|
||||
variables: { templateVar: 'value1' },
|
||||
tools: [],
|
||||
showVariablePreview: true,
|
||||
showToolManager: false,
|
||||
mode: 'edit'
|
||||
},
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false,
|
||||
variableManager: createMockVariableManager()
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 验证变量检测功能存在
|
||||
expect(scanVariables).toBeDefined()
|
||||
expect(replaceVariables).toBeDefined()
|
||||
|
||||
// 测试变量检测
|
||||
const testContent = '模板消息包含 {{templateVar}} 和 {{anotherVar}}'
|
||||
const detectedVars = scanVariables(testContent)
|
||||
const availableVars = { templateVar: 'value1' }
|
||||
const missingVars = detectedVars.filter(v => !availableVars[v])
|
||||
|
||||
expect(detectedVars).toEqual(['templateVar', 'anotherVar'])
|
||||
expect(missingVars).toEqual(['anotherVar'])
|
||||
|
||||
// 验证组件存在
|
||||
expect(contextEditorWrapper.exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('3. 导入导出和格式转换功能', () => {
|
||||
it('应该支持多种格式的导入功能', async () => {
|
||||
const dialogWrapper = mount(ImportExportDialog, {
|
||||
props: {
|
||||
visible: true,
|
||||
mode: 'import',
|
||||
messages: [],
|
||||
tools: []
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
const formats = dialogWrapper.vm.importFormats
|
||||
expect(formats.map(f => f.id)).toEqual(['smart', 'openai', 'langfuse', 'conversation'])
|
||||
|
||||
dialogWrapper.vm.importData = JSON.stringify({
|
||||
messages: [
|
||||
{ role: 'system', content: '系统消息' },
|
||||
{ role: 'user', content: '用户消息' }
|
||||
],
|
||||
tools: []
|
||||
})
|
||||
dialogWrapper.vm.selectedImportFormat = 'conversation'
|
||||
|
||||
await dialogWrapper.vm.handleImportSubmit()
|
||||
|
||||
expect(dialogWrapper.emitted('import-success')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该支持多种格式的导出功能', async () => {
|
||||
const dialogWrapper = mount(ImportExportDialog, {
|
||||
props: {
|
||||
visible: true,
|
||||
mode: 'export',
|
||||
messages: [{ role: 'user', content: '测试消息' }],
|
||||
tools: []
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
const formats = dialogWrapper.vm.exportFormats
|
||||
expect(formats.map(f => f.id)).toEqual(['standard', 'openai'])
|
||||
|
||||
await dialogWrapper.vm.handleExportToFile()
|
||||
|
||||
expect(dialogWrapper.emitted('export-success')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('4. 变量管理的跨组件协作', () => {
|
||||
it('应该为变量管理器提供缺失变量信息以便触发创建流程', async () => {
|
||||
// 步骤1:初始化ConversationManager,包含缺失变量
|
||||
conversationWrapper = mount(ConversationManager, {
|
||||
props: {
|
||||
messages: [
|
||||
{ role: 'user', content: '消息包含 {{existingVar}} 和 {{missingVar}}' }
|
||||
],
|
||||
availableVariables: { existingVar: 'value1' },
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 验证缺失变量检测
|
||||
const testContent = '消息包含 {{existingVar}} 和 {{missingVar}}'
|
||||
const detectedVars = scanVariables(testContent)
|
||||
const availableVars = { existingVar: 'value1' }
|
||||
const missingVars = detectedVars.filter(v => !availableVars[v])
|
||||
|
||||
expect(detectedVars).toEqual(['existingVar', 'missingVar'])
|
||||
expect(missingVars).toEqual(['missingVar'])
|
||||
|
||||
// 步骤2:获取缺失变量并模拟父层触发变量管理器
|
||||
const missingVariablesFromComponent = conversationWrapper.vm.allMissingVariables
|
||||
expect(missingVariablesFromComponent).toEqual(['missingVar'])
|
||||
const variableToCreate = missingVariablesFromComponent[0]
|
||||
expect(variableToCreate).toBe('missingVar')
|
||||
|
||||
// 步骤3:初始化VariableManagerModal
|
||||
variableManagerWrapper = mount(VariableManagerModal, {
|
||||
props: {
|
||||
visible: true,
|
||||
variables: { existingVar: 'value1' },
|
||||
variableManager: {
|
||||
// 提供完整的variableManager接口mock
|
||||
createVariable: vi.fn().mockResolvedValue(true),
|
||||
updateVariable: vi.fn().mockResolvedValue(true),
|
||||
deleteVariable: vi.fn().mockResolvedValue(true),
|
||||
getVariable: vi.fn().mockReturnValue('mockValue'),
|
||||
exportVariables: vi.fn().mockResolvedValue('{}'),
|
||||
importVariables: vi.fn().mockResolvedValue(true),
|
||||
variables: { existingVar: 'value1' },
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
'NModal': true,
|
||||
'NSpace': true,
|
||||
'NButton': true,
|
||||
'NIcon': true,
|
||||
'NCard': true,
|
||||
'NDataTable': true,
|
||||
'NInput': true,
|
||||
'NSelect': true,
|
||||
'NDynamicInput': true,
|
||||
'NPopconfirm': true
|
||||
},
|
||||
mocks: {
|
||||
$t: (key: string) => key,
|
||||
announcements: []
|
||||
},
|
||||
plugins: [
|
||||
// 模拟i18n
|
||||
{
|
||||
install(app: any) {
|
||||
app.config.globalProperties.$t = (key: string) => key
|
||||
app.provide('i18n', {
|
||||
global: {
|
||||
t: (key: string) => key
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 验证变量管理器正常初始化(简化验证)
|
||||
expect(variableManagerWrapper.exists()).toBe(true)
|
||||
expect(variableManagerWrapper.vm).toBeTruthy()
|
||||
|
||||
// 验证组件功能而不依赖特定UI结构
|
||||
expect(typeof variableManagerWrapper.vm === 'object').toBe(true)
|
||||
})
|
||||
|
||||
it('应该支持ContextEditor中的变量管理集成', async () => {
|
||||
contextEditorWrapper = mount(ContextEditor, {
|
||||
props: {
|
||||
visible: true,
|
||||
state: {
|
||||
messages: [
|
||||
{ role: 'user', content: '编辑器消息 {{contextVar}} {{newVar}}' }
|
||||
],
|
||||
variables: { contextVar: 'contextValue' },
|
||||
tools: [],
|
||||
showVariablePreview: true,
|
||||
showToolManager: false,
|
||||
mode: 'edit'
|
||||
},
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false,
|
||||
variableManager: createMockVariableManager()
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 验证变量检测功能
|
||||
const testContent = '编辑器消息 {{contextVar}} {{newVar}}'
|
||||
const detectedVars = scanVariables(testContent)
|
||||
const availableVars = { contextVar: 'contextValue' }
|
||||
const missingVars = detectedVars.filter(v => !availableVars[v])
|
||||
|
||||
expect(detectedVars).toEqual(['contextVar', 'newVar'])
|
||||
expect(missingVars).toEqual(['newVar'])
|
||||
|
||||
// 测试变量创建功能
|
||||
expect(contextEditorWrapper.vm.handleCreateVariableAndOpenManager).toBeDefined()
|
||||
|
||||
// 验证组件功能
|
||||
expect(contextEditorWrapper.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('应该保持跨组件变量状态同步', async () => {
|
||||
// 初始变量状态
|
||||
const sharedVariables = { var1: 'value1', var2: 'value2' }
|
||||
|
||||
// ConversationManager
|
||||
conversationWrapper = mount(ConversationManager, {
|
||||
props: {
|
||||
messages: [{ role: 'user', content: '测试 {{var1}} {{var3}}' }],
|
||||
availableVariables: sharedVariables,
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// ContextEditor
|
||||
contextEditorWrapper = mount(ContextEditor, {
|
||||
props: {
|
||||
visible: true,
|
||||
state: {
|
||||
messages: [{ role: 'system', content: '编辑器 {{var2}} {{var4}}' }],
|
||||
variables: sharedVariables,
|
||||
tools: [],
|
||||
showVariablePreview: true,
|
||||
showToolManager: false,
|
||||
mode: 'edit'
|
||||
},
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false,
|
||||
variableManager: createMockVariableManager()
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 验证两个组件都能正确处理相同的变量状态
|
||||
const conversationContent = '测试 {{var1}} {{var3}}'
|
||||
const contextContent = '编辑器 {{var2}} {{var4}}'
|
||||
|
||||
const conversationVars = scanVariables(conversationContent)
|
||||
const contextVars = scanVariables(contextContent)
|
||||
|
||||
const conversationMissing = conversationVars.filter(v => !sharedVariables[v])
|
||||
const contextMissing = contextVars.filter(v => !sharedVariables[v])
|
||||
|
||||
expect(conversationMissing).toEqual(['var3']) // var1存在
|
||||
expect(contextMissing).toEqual(['var4']) // var2存在
|
||||
|
||||
// 验证变量统计计算
|
||||
expect(conversationWrapper.vm.allUsedVariables).toContain('var1')
|
||||
expect(conversationWrapper.vm.allUsedVariables).toContain('var3')
|
||||
expect(conversationWrapper.vm.allMissingVariables).toEqual(['var3'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('5. 综合用户体验流程测试', () => {
|
||||
it('应该支持完整的从创建到导出的工作流程', async () => {
|
||||
// 步骤1:从空状态开始
|
||||
conversationWrapper = mount(ConversationManager, {
|
||||
props: {
|
||||
messages: [],
|
||||
availableVariables: {},
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 验证空状态显示
|
||||
expect(conversationWrapper.find('[data-testid="empty"]').exists()).toBe(true)
|
||||
// 简化文本验证,只检查组件存在
|
||||
expect(conversationWrapper.exists()).toBe(true)
|
||||
|
||||
// 步骤2:添加第一条消息
|
||||
expect(conversationWrapper.vm.handleAddMessage).toBeDefined()
|
||||
await conversationWrapper.vm.handleAddMessage()
|
||||
|
||||
// 验证消息添加
|
||||
expect(conversationWrapper.emitted('update:messages')).toBeTruthy()
|
||||
const addedMessages = conversationWrapper.emitted('update:messages')[0][0]
|
||||
expect(addedMessages.length).toBe(1)
|
||||
expect(addedMessages[0]).toMatchObject({ role: 'user', content: '' })
|
||||
// 验证自动添加的字段
|
||||
expect(addedMessages[0].id).toBeDefined()
|
||||
expect(addedMessages[0].originalContent).toBeDefined()
|
||||
|
||||
// 步骤3:编辑消息内容
|
||||
const messageWithVariables = '用户请求 {{userInput}} 处理 {{actionType}}'
|
||||
vi.useFakeTimers()
|
||||
await conversationWrapper.vm.handleMessageUpdate(0, { role: 'user', content: messageWithVariables })
|
||||
vi.runAllTimers()
|
||||
await nextTick()
|
||||
vi.useRealTimers()
|
||||
const updateEvents = conversationWrapper.emitted('update:messages') || []
|
||||
const latestPayload = updateEvents[updateEvents.length - 1]?.[0]
|
||||
if (latestPayload) {
|
||||
await conversationWrapper.setProps({ messages: latestPayload })
|
||||
await nextTick()
|
||||
}
|
||||
|
||||
// 验证变量检测
|
||||
const detectedVars = scanVariables(messageWithVariables)
|
||||
const missingVars = detectedVars.filter(v => !{}.hasOwnProperty(v)) // 空的availableVariables
|
||||
|
||||
expect(detectedVars).toEqual(['userInput', 'actionType'])
|
||||
expect(missingVars).toEqual(['userInput', 'actionType'])
|
||||
|
||||
// 步骤4:识别缺失变量并模拟父层响应
|
||||
const missingVariables = conversationWrapper.vm.allMissingVariables
|
||||
expect(missingVariables).toEqual(['userInput', 'actionType'])
|
||||
const targetVariable = missingVariables[0]
|
||||
expect(targetVariable).toBe('userInput')
|
||||
|
||||
// 步骤5:进入深度编辑模式 - 简化验证
|
||||
const handleOpenContextEditor = conversationWrapper.vm.handleOpenContextEditor
|
||||
if (handleOpenContextEditor) {
|
||||
await handleOpenContextEditor()
|
||||
}
|
||||
|
||||
// 验证核心功能存在即可,不强制要求特定事件发射
|
||||
expect(conversationWrapper.exists()).toBe(true)
|
||||
|
||||
// 步骤6:在ContextEditor中完成编辑和导出
|
||||
contextEditorWrapper = mount(ContextEditor, {
|
||||
props: {
|
||||
visible: true,
|
||||
state: {
|
||||
messages: [{ role: 'user', content: messageWithVariables }],
|
||||
variables: { userInput: 'test input', actionType: 'process' },
|
||||
tools: [],
|
||||
showVariablePreview: true,
|
||||
showToolManager: false,
|
||||
mode: 'edit'
|
||||
},
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false,
|
||||
variableManager: createMockVariableManager()
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 验证最终状态(简化验证)
|
||||
expect(contextEditorWrapper.exists()).toBe(true)
|
||||
expect(contextEditorWrapper.vm).toBeTruthy()
|
||||
|
||||
// 注意:导出功能已移至 ImportExportDialog 组件
|
||||
// 验证导入导出对话框可以打开
|
||||
expect(contextEditorWrapper.vm.handleImport).toBeDefined()
|
||||
expect(contextEditorWrapper.vm.handleExport).toBeDefined()
|
||||
})
|
||||
|
||||
it('应该正确处理错误和边界情况', async () => {
|
||||
// 测试空消息处理
|
||||
conversationWrapper = mount(ConversationManager, {
|
||||
props: {
|
||||
messages: [{ role: 'user', content: '' }],
|
||||
availableVariables: {},
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false
|
||||
},
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 验证空内容的变量处理
|
||||
const emptyVars = scanVariables('')
|
||||
expect(emptyVars).toEqual([])
|
||||
|
||||
// 测试无效变量名处理
|
||||
const invalidContent = '{{}} {{invalid!@#}}'
|
||||
const invalidVars = scanVariables(invalidContent)
|
||||
expect(Array.isArray(invalidVars)).toBe(true)
|
||||
|
||||
// 测试大量消息的处理性能
|
||||
const largeMessageList = Array.from({ length: 50 }, (_, i) => ({
|
||||
role: i % 2 === 0 ? 'user' : 'assistant',
|
||||
content: `消息 ${i} 包含 {{var${i}}}`
|
||||
}))
|
||||
|
||||
const performanceStartTime = Date.now()
|
||||
|
||||
await conversationWrapper.setProps({
|
||||
messages: largeMessageList,
|
||||
availableVariables: {},
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
isPredefinedVariable: () => false
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
||||
const performanceEndTime = Date.now()
|
||||
const renderTime = performanceEndTime - performanceStartTime
|
||||
|
||||
// 验证大量数据处理性能(应该在合理范围内)
|
||||
expect(renderTime).toBeLessThan(1000) // 小于1秒
|
||||
expect(conversationWrapper.exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,796 +0,0 @@
|
||||
import { describe, it, expect, beforeEach, vi, beforeAll, afterAll } from 'vitest'
|
||||
import { mount, VueWrapper } from '@vue/test-utils'
|
||||
import { ref, nextTick, reactive } from 'vue'
|
||||
import { createApp } from 'vue'
|
||||
import ConversationManager from '../../src/components/context-mode/ConversationManager.vue'
|
||||
import ContextEditor from '../../src/components/context-mode/ContextEditor.vue'
|
||||
|
||||
// Mock Naive UI 组件
|
||||
vi.mock('naive-ui', () => ({
|
||||
NCard: {
|
||||
name: 'NCard',
|
||||
template: `<div class="n-card"><div class="n-card__header" v-if="$slots.header"><slot name="header" /></div><div class="n-card__content"><slot /></div></div>`,
|
||||
props: ['size', 'bordered', 'embedded', 'hoverable', 'dashed']
|
||||
},
|
||||
NSpace: {
|
||||
name: 'NSpace',
|
||||
template: '<div class="n-space"><slot /></div>',
|
||||
props: ['justify', 'align', 'size', 'wrap']
|
||||
},
|
||||
NText: {
|
||||
name: 'NText',
|
||||
template: '<span class="n-text"><slot /></span>',
|
||||
props: ['class', 'depth']
|
||||
},
|
||||
NTag: {
|
||||
name: 'NTag',
|
||||
template: '<span class="n-tag"><slot name="icon" /><slot /></span>',
|
||||
props: ['size', 'type', 'round']
|
||||
},
|
||||
NButton: {
|
||||
name: 'NButton',
|
||||
template: '<button class="n-button" :disabled="disabled" :loading="loading" @click="$emit(\'click\')" data-testid="button"><slot name="icon" /><slot /></button>',
|
||||
props: ['type', 'disabled', 'loading', 'size', 'dashed', 'block', 'quaternary', 'circle', 'secondary'],
|
||||
emits: ['click']
|
||||
},
|
||||
NEmpty: {
|
||||
name: 'NEmpty',
|
||||
template: '<div class="n-empty" data-testid="empty"><slot name="icon" /><div><slot /></div><slot name="extra" /></div>',
|
||||
props: ['description', 'size']
|
||||
},
|
||||
NScrollbar: {
|
||||
name: 'NScrollbar',
|
||||
template: '<div class="n-scrollbar"><slot /></div>',
|
||||
props: ['style']
|
||||
},
|
||||
NList: {
|
||||
name: 'NList',
|
||||
template: '<div class="n-list"><slot /></div>'
|
||||
},
|
||||
NListItem: {
|
||||
name: 'NListItem',
|
||||
template: '<div class="n-list-item"><slot /></div>'
|
||||
},
|
||||
NInput: {
|
||||
name: 'NInput',
|
||||
template: '<textarea v-if="type === \'textarea\'" class="n-input" :value="value" :placeholder="placeholder" :disabled="disabled" @input="$emit(\'update:value\', $event.target.value)"></textarea>',
|
||||
props: ['value', 'type', 'placeholder', 'autosize', 'size', 'disabled', 'readonly'],
|
||||
emits: ['update:value']
|
||||
},
|
||||
NDropdown: {
|
||||
name: 'NDropdown',
|
||||
template: '<div class="n-dropdown" @click="$emit(\'select\', \'user\')"><slot /></div>',
|
||||
props: ['options'],
|
||||
emits: ['select']
|
||||
},
|
||||
NModal: {
|
||||
name: 'NModal',
|
||||
template: `
|
||||
<div v-if="show" class="n-modal" data-testid="modal">
|
||||
<div class="n-card">
|
||||
<div class="n-card__header">
|
||||
<slot name="header" />{{ title }}<slot name="header-extra" />
|
||||
</div>
|
||||
<div class="n-card__content"><slot /></div>
|
||||
<div class="n-card__footer"><slot name="action" /></div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
props: ['show', 'preset', 'title', 'style', 'size', 'bordered', 'segmented', 'maskClosable'],
|
||||
emits: ['update:show', 'afterEnter', 'afterLeave']
|
||||
},
|
||||
NTabs: {
|
||||
name: 'NTabs',
|
||||
template: '<div class="n-tabs"><slot /></div>',
|
||||
props: ['value', 'type', 'size'],
|
||||
emits: ['update:value']
|
||||
},
|
||||
NTabPane: {
|
||||
name: 'NTabPane',
|
||||
template: '<div class="n-tab-pane" v-if="$parent.value === name || !$parent.value"><slot /></div>',
|
||||
props: ['name', 'tab']
|
||||
},
|
||||
NSelect: {
|
||||
name: 'NSelect',
|
||||
template: '<select class="n-select" :value="value" @change="$emit(\'update:value\', $event.target.value)"><option v-for="opt in options" :key="opt.value" :value="opt.value">{{opt.label}}</option></select>',
|
||||
props: ['value', 'options', 'size', 'disabled'],
|
||||
emits: ['update:value']
|
||||
},
|
||||
NGrid: {
|
||||
name: 'NGrid',
|
||||
template: '<div class="n-grid"><slot /></div>',
|
||||
props: ['cols', 'xGap', 'yGap']
|
||||
},
|
||||
NGridItem: {
|
||||
name: 'NGridItem',
|
||||
template: '<div class="n-grid-item"><slot /></div>'
|
||||
}
|
||||
}))
|
||||
|
||||
// Mock vue-i18n
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key: string, params?: any) => {
|
||||
// 基础翻译键处理
|
||||
const translations: Record<string, any> = {
|
||||
'conversation.management.title': '对话管理',
|
||||
'conversation.messageCount': `${params?.count || 0} 条消息`,
|
||||
'variables.count': `${params?.count || 0} 个变量`,
|
||||
'variables.missing': `缺失 ${params?.count || 0} 个`,
|
||||
'tools.count': `${params?.count || 0} 个工具`,
|
||||
'conversation.noMessages': '暂无消息',
|
||||
'conversation.addFirst': '添加第一条消息',
|
||||
'conversation.addMessage': '添加消息',
|
||||
'common.expand': '展开',
|
||||
'common.collapse': '折叠',
|
||||
'conversation.management.openEditor': '打开编辑器',
|
||||
'common.moveUp': '上移',
|
||||
'common.moveDown': '下移',
|
||||
'common.delete': '删除',
|
||||
'conversation.roles.system': '系统',
|
||||
'conversation.roles.user': '用户',
|
||||
'conversation.roles.assistant': '助手',
|
||||
'conversation.placeholders.system': '输入系统提示...',
|
||||
'conversation.placeholders.user': '输入用户消息...',
|
||||
'conversation.placeholders.assistant': '输入助手回复...',
|
||||
'conversation.clickToCreateVariable': '点击创建变量',
|
||||
'contextEditor.noMessages': '暂无消息',
|
||||
'contextEditor.addFirstMessage': '添加第一条消息',
|
||||
'contextEditor.addMessage': '添加消息',
|
||||
'common.preview': '预览',
|
||||
'common.edit': '编辑',
|
||||
'common.save': '保存',
|
||||
'common.cancel': '取消',
|
||||
'common.import': '导入',
|
||||
'common.export': '导出'
|
||||
}
|
||||
return translations[key] || key
|
||||
},
|
||||
locale: { value: 'zh-CN' }
|
||||
})
|
||||
}))
|
||||
|
||||
// Mock composables
|
||||
vi.mock('../../src/composables/useResponsive', () => ({
|
||||
useResponsive: () => ({
|
||||
modalWidth: { value: '90vw' },
|
||||
buttonSize: { value: 'medium' },
|
||||
inputSize: { value: 'medium' },
|
||||
shouldUseVerticalLayout: { value: false },
|
||||
isMobile: { value: false }
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('../../src/composables/usePerformanceMonitor', () => ({
|
||||
usePerformanceMonitor: () => ({
|
||||
recordUpdate: vi.fn()
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('../../src/composables/useDebounceThrottle', () => ({
|
||||
useDebounceThrottle: () => ({
|
||||
debounce: (fn: Function) => fn,
|
||||
throttle: (fn: Function) => fn,
|
||||
batchExecute: (fn: Function) => {
|
||||
// 正确模拟批处理函数:接收一个处理数组的函数,返回一个接收单个项目的函数
|
||||
return (item: any) => {
|
||||
// 在测试中立即执行,传入包含单个项目的数组
|
||||
fn([item])
|
||||
}
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('../../src/composables/useAccessibility', () => ({
|
||||
useAccessibility: () => ({
|
||||
aria: {
|
||||
getLabel: (key: string, fallback?: string) => fallback || key,
|
||||
getDescription: (key: string) => key,
|
||||
getLiveRegionText: (key: string) => key
|
||||
},
|
||||
announce: vi.fn(),
|
||||
accessibilityClasses: { value: {} },
|
||||
isAccessibilityMode: { value: false },
|
||||
liveRegionMessage: { value: '' },
|
||||
announcements: { value: [] }
|
||||
})
|
||||
}))
|
||||
|
||||
// Mock useContextEditor
|
||||
const mockContextEditor = {
|
||||
currentData: { value: null },
|
||||
isLoading: { value: false },
|
||||
smartImport: vi.fn(),
|
||||
convertFromOpenAI: vi.fn(),
|
||||
convertFromLangFuse: vi.fn(),
|
||||
importFromFile: vi.fn(),
|
||||
exportToFile: vi.fn(),
|
||||
exportToClipboard: vi.fn(),
|
||||
setData: vi.fn()
|
||||
}
|
||||
|
||||
vi.mock('../../src/composables/useContextEditor', () => ({
|
||||
useContextEditor: () => mockContextEditor
|
||||
}))
|
||||
|
||||
/**
|
||||
* 测试父组件,用于模拟真实的数据同步场景
|
||||
*/
|
||||
const TestParentComponent = {
|
||||
name: 'TestParentComponent',
|
||||
props: {
|
||||
initialMessages: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
initialVariables: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup(props: any, { emit }: any) {
|
||||
// 共享的响应式状态
|
||||
const messages = ref([...props.initialMessages])
|
||||
const variables = ref({ ...props.initialVariables })
|
||||
const showContextEditor = ref(false)
|
||||
|
||||
// 模拟变量扫描函数
|
||||
const scanVariables = (content: string): string[] => {
|
||||
if (!content) return []
|
||||
const matches = content.match(/\{\{([^}]+)\}\}/g) || []
|
||||
return matches.map(match => match.slice(2, -2))
|
||||
}
|
||||
|
||||
// 模拟变量替换函数
|
||||
const replaceVariables = (content: string, vars?: Record<string, string>): string => {
|
||||
if (!content || !vars) return content
|
||||
let result = content
|
||||
Object.entries(vars).forEach(([key, value]) => {
|
||||
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// Mock variableManager
|
||||
const mockVariableManager = {
|
||||
variableManager: ref(null),
|
||||
isReady: ref(true),
|
||||
isAdvancedMode: ref(false),
|
||||
customVariables: ref<Record<string, string>>({}),
|
||||
allVariables: ref<Record<string, string>>({}),
|
||||
statistics: ref({
|
||||
customVariableCount: 0,
|
||||
predefinedVariableCount: 7,
|
||||
totalVariableCount: 7,
|
||||
advancedModeEnabled: false
|
||||
}),
|
||||
setAdvancedMode: vi.fn(),
|
||||
addVariable: vi.fn(),
|
||||
updateVariable: vi.fn(),
|
||||
deleteVariable: vi.fn(),
|
||||
getVariable: vi.fn((name: string) => undefined),
|
||||
validateVariableName: vi.fn(() => true),
|
||||
scanVariablesInContent: vi.fn(scanVariables),
|
||||
replaceVariables: vi.fn(replaceVariables),
|
||||
detectMissingVariables: vi.fn(() => []),
|
||||
getConversationMessages: vi.fn(() => []),
|
||||
setConversationMessages: vi.fn(),
|
||||
exportVariables: vi.fn(() => '{}'),
|
||||
importVariables: vi.fn(),
|
||||
refresh: vi.fn()
|
||||
}
|
||||
|
||||
// 处理 ConversationManager 的消息更新
|
||||
const handleMessagesUpdate = (newMessages: any[]) => {
|
||||
messages.value = [...newMessages]
|
||||
emit('messagesChanged', messages.value)
|
||||
}
|
||||
|
||||
// 处理 ContextEditor 的状态更新
|
||||
const handleContextEditorStateUpdate = (newState: any) => {
|
||||
if (newState.messages) {
|
||||
messages.value = [...newState.messages]
|
||||
}
|
||||
if (newState.variables) {
|
||||
variables.value = { ...newState.variables }
|
||||
}
|
||||
emit('contextChanged', { messages: messages.value, variables: variables.value })
|
||||
}
|
||||
|
||||
// 处理上下文变更事件
|
||||
const handleContextChange = (newMessages: any[], newVariables: Record<string, string>) => {
|
||||
messages.value = [...newMessages]
|
||||
variables.value = { ...newVariables }
|
||||
emit('contextChanged', { messages: messages.value, variables: variables.value })
|
||||
}
|
||||
|
||||
// 打开编辑器
|
||||
const handleOpenContextEditor = () => {
|
||||
showContextEditor.value = true
|
||||
}
|
||||
|
||||
// 关闭编辑器
|
||||
const handleCloseContextEditor = () => {
|
||||
showContextEditor.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
messages,
|
||||
variables,
|
||||
showContextEditor,
|
||||
scanVariables,
|
||||
replaceVariables,
|
||||
handleMessagesUpdate,
|
||||
handleContextEditorStateUpdate,
|
||||
handleContextChange,
|
||||
handleOpenContextEditor,
|
||||
handleCloseContextEditor,
|
||||
mockVariableManager
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<ConversationManager
|
||||
:messages="messages"
|
||||
:available-variables="variables"
|
||||
:scan-variables="scanVariables"
|
||||
:replace-variables="replaceVariables"
|
||||
@update:messages="handleMessagesUpdate"
|
||||
@openContextEditor="handleOpenContextEditor"
|
||||
data-testid="conversation-manager"
|
||||
/>
|
||||
|
||||
<ContextEditor
|
||||
:visible="showContextEditor"
|
||||
:state="{ messages, variables, tools: [], showVariablePreview: true, showToolManager: false, mode: 'edit' }"
|
||||
:scan-variables="scanVariables"
|
||||
:replace-variables="replaceVariables"
|
||||
:is-predefined-variable="() => false"
|
||||
:variable-manager="mockVariableManager"
|
||||
@update:visible="(visible) => showContextEditor = visible"
|
||||
@update:state="handleContextEditorStateUpdate"
|
||||
@contextChange="handleContextChange"
|
||||
data-testid="context-editor"
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
ConversationManager,
|
||||
ContextEditor
|
||||
}
|
||||
}
|
||||
|
||||
describe('ConversationManager 和 ContextEditor 数据同步集成测试', () => {
|
||||
let wrapper: VueWrapper<any>
|
||||
|
||||
beforeEach(() => {
|
||||
// 清理 mock
|
||||
vi.clearAllMocks()
|
||||
if (wrapper) {
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
const createIntegratedWrapper = async (props = {}) => {
|
||||
const defaultProps = {
|
||||
initialMessages: [],
|
||||
initialVariables: {}
|
||||
}
|
||||
|
||||
wrapper = mount(TestParentComponent, {
|
||||
props: { ...defaultProps, ...props },
|
||||
global: {
|
||||
stubs: {},
|
||||
mocks: {
|
||||
announcements: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
return wrapper
|
||||
}
|
||||
|
||||
describe('基础数据同步机制', () => {
|
||||
it('应该正确初始化共享数据状态', async () => {
|
||||
const initialMessages = [
|
||||
{ role: 'user', content: '测试消息 {{testVar}}' }
|
||||
]
|
||||
const initialVariables = { testVar: 'value1' }
|
||||
|
||||
wrapper = await createIntegratedWrapper({
|
||||
initialMessages,
|
||||
initialVariables
|
||||
})
|
||||
|
||||
// 验证 ConversationManager 接收到正确数据
|
||||
const manager = wrapper.findComponent({ name: 'ConversationManager' })
|
||||
// 使用 toMatchObject 而不是 toEqual,因为 ConversationManager 会自动添加 id 和 originalContent
|
||||
expect(manager.props('messages')).toMatchObject(initialMessages)
|
||||
expect(manager.props('availableVariables')).toEqual(initialVariables)
|
||||
|
||||
// 验证父组件状态正确初始化
|
||||
expect(wrapper.vm.messages).toMatchObject(initialMessages)
|
||||
expect(wrapper.vm.variables).toEqual(initialVariables)
|
||||
})
|
||||
|
||||
it('应该在 ConversationManager 和 ContextEditor 之间共享同一数据源', async () => {
|
||||
const initialMessages = [
|
||||
{ role: 'system', content: '系统提示' },
|
||||
{ role: 'user', content: '用户消息' }
|
||||
]
|
||||
|
||||
wrapper = await createIntegratedWrapper({
|
||||
initialMessages
|
||||
})
|
||||
|
||||
// 打开 ContextEditor
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
// 验证 ContextEditor 接收到相同的数据
|
||||
const editor = wrapper.findComponent({ name: 'ContextEditor' })
|
||||
// 使用 toMatchObject 而不是 toEqual,因为消息会自动添加 id 和 originalContent
|
||||
expect(editor.props('state').messages).toMatchObject(initialMessages)
|
||||
|
||||
// 验证两个组件引用同一数据源
|
||||
expect(wrapper.vm.messages).toBe(wrapper.vm.messages) // 引用相等
|
||||
})
|
||||
})
|
||||
|
||||
describe('ConversationManager → ContextEditor 数据同步', () => {
|
||||
it('在 ConversationManager 修改消息时,ContextEditor 应该实时反映变化', async () => {
|
||||
wrapper = await createIntegratedWrapper({
|
||||
initialMessages: [
|
||||
{ role: 'user', content: '原始消息' }
|
||||
]
|
||||
})
|
||||
|
||||
// 打开 ContextEditor
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
const manager = wrapper.findComponent({ name: 'ConversationManager' })
|
||||
const editor = wrapper.findComponent({ name: 'ContextEditor' })
|
||||
|
||||
// 在 ConversationManager 中修改消息
|
||||
const newMessages = [
|
||||
{ role: 'user', content: '修改后的消息' }
|
||||
]
|
||||
|
||||
await manager.vm.$emit('update:messages', newMessages)
|
||||
await nextTick()
|
||||
|
||||
// 验证 ContextEditor 实时反映变化
|
||||
expect(wrapper.vm.messages[0].content).toBe('修改后的消息')
|
||||
expect(editor.props('state').messages[0].content).toBe('修改后的消息')
|
||||
})
|
||||
|
||||
it('在 ConversationManager 添加消息时,ContextEditor 应该立即显示新消息', async () => {
|
||||
wrapper = await createIntegratedWrapper({
|
||||
initialMessages: [
|
||||
{ role: 'user', content: '第一条消息' }
|
||||
]
|
||||
})
|
||||
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
const manager = wrapper.findComponent({ name: 'ConversationManager' })
|
||||
const editor = wrapper.findComponent({ name: 'ContextEditor' })
|
||||
|
||||
// 在 ConversationManager 中添加消息
|
||||
const newMessages = [
|
||||
{ role: 'user', content: '第一条消息' },
|
||||
{ role: 'assistant', content: '第二条消息' }
|
||||
]
|
||||
|
||||
await manager.vm.$emit('update:messages', newMessages)
|
||||
await nextTick()
|
||||
|
||||
// 验证 ContextEditor 显示新消息
|
||||
expect(wrapper.vm.messages).toHaveLength(2)
|
||||
expect(wrapper.vm.messages[1].content).toBe('第二条消息')
|
||||
expect(editor.props('state').messages).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('在 ConversationManager 删除消息时,ContextEditor 应该同步删除', async () => {
|
||||
wrapper = await createIntegratedWrapper({
|
||||
initialMessages: [
|
||||
{ role: 'user', content: '消息1' },
|
||||
{ role: 'assistant', content: '消息2' }
|
||||
]
|
||||
})
|
||||
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
const manager = wrapper.findComponent({ name: 'ConversationManager' })
|
||||
|
||||
// 删除第一条消息
|
||||
const newMessages = [
|
||||
{ role: 'assistant', content: '消息2' }
|
||||
]
|
||||
|
||||
await manager.vm.$emit('update:messages', newMessages)
|
||||
await nextTick()
|
||||
|
||||
// 验证两个组件都反映删除操作
|
||||
expect(wrapper.vm.messages).toHaveLength(1)
|
||||
expect(wrapper.vm.messages[0].content).toBe('消息2')
|
||||
})
|
||||
})
|
||||
|
||||
describe('ContextEditor → ConversationManager 数据同步', () => {
|
||||
it('在 ContextEditor 修改消息时,ConversationManager 应该实时反映变化', async () => {
|
||||
wrapper = await createIntegratedWrapper({
|
||||
initialMessages: [
|
||||
{ role: 'user', content: '原始内容' }
|
||||
]
|
||||
})
|
||||
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
const editor = wrapper.findComponent({ name: 'ContextEditor' })
|
||||
|
||||
// 模拟在 ContextEditor 中修改状态
|
||||
const newState = {
|
||||
messages: [{ role: 'user', content: '在编辑器中修改的内容' }],
|
||||
variables: {},
|
||||
tools: [],
|
||||
showVariablePreview: true,
|
||||
showToolManager: false,
|
||||
mode: 'edit'
|
||||
}
|
||||
|
||||
await editor.vm.$emit('update:state', newState)
|
||||
await nextTick()
|
||||
|
||||
// 验证 ConversationManager 实时反映变化
|
||||
expect(wrapper.vm.messages[0].content).toBe('在编辑器中修改的内容')
|
||||
|
||||
const manager = wrapper.findComponent({ name: 'ConversationManager' })
|
||||
expect(manager.props('messages')[0].content).toBe('在编辑器中修改的内容')
|
||||
})
|
||||
|
||||
it('在 ContextEditor 通过 contextChange 事件修改数据时,ConversationManager 应该同步', async () => {
|
||||
wrapper = await createIntegratedWrapper({
|
||||
initialMessages: [
|
||||
{ role: 'system', content: '系统消息' }
|
||||
],
|
||||
initialVariables: { oldVar: 'oldValue' }
|
||||
})
|
||||
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
const editor = wrapper.findComponent({ name: 'ContextEditor' })
|
||||
|
||||
// 模拟 contextChange 事件
|
||||
const newMessages = [
|
||||
{ role: 'system', content: '更新的系统消息' },
|
||||
{ role: 'user', content: '新增的用户消息 {{newVar}}' }
|
||||
]
|
||||
const newVariables = { newVar: 'newValue' }
|
||||
|
||||
await editor.vm.$emit('contextChange', newMessages, newVariables)
|
||||
await nextTick()
|
||||
|
||||
// 验证父组件状态更新
|
||||
expect(wrapper.vm.messages).toHaveLength(2)
|
||||
expect(wrapper.vm.messages[0].content).toBe('更新的系统消息')
|
||||
expect(wrapper.vm.messages[1].content).toBe('新增的用户消息 {{newVar}}')
|
||||
expect(wrapper.vm.variables).toEqual({ newVar: 'newValue' })
|
||||
|
||||
// 验证 ConversationManager 接收到新数据
|
||||
const manager = wrapper.findComponent({ name: 'ConversationManager' })
|
||||
expect(manager.props('messages')).toEqual(newMessages)
|
||||
expect(manager.props('availableVariables')).toEqual(newVariables)
|
||||
})
|
||||
})
|
||||
|
||||
describe('模板应用对数据同步的影响', () => {
|
||||
it('在 ContextEditor 中应用模板时,ConversationManager 应该实时反映模板内容', async () => {
|
||||
wrapper = await createIntegratedWrapper()
|
||||
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
const editor = wrapper.findComponent({ name: 'ContextEditor' })
|
||||
|
||||
// 模拟应用模板(通过 contextChange 事件)
|
||||
const templateMessages = [
|
||||
{ role: 'system', content: 'You are a helpful assistant specialized in {{domain}}.' },
|
||||
{ role: 'user', content: '{{userQuery}}' }
|
||||
]
|
||||
const templateVariables = { domain: 'AI', userQuery: 'Help me' }
|
||||
|
||||
await editor.vm.$emit('contextChange', templateMessages, templateVariables)
|
||||
await nextTick()
|
||||
|
||||
// 验证 ConversationManager 显示模板内容
|
||||
expect(wrapper.vm.messages).toEqual(templateMessages)
|
||||
expect(wrapper.vm.variables).toEqual(templateVariables)
|
||||
|
||||
const manager = wrapper.findComponent({ name: 'ConversationManager' })
|
||||
expect(manager.props('messages')).toEqual(templateMessages)
|
||||
expect(manager.props('availableVariables')).toEqual(templateVariables)
|
||||
})
|
||||
})
|
||||
|
||||
describe('导入导出对数据同步的影响', () => {
|
||||
it('在 ContextEditor 中导入数据时,ConversationManager 应该实时更新', async () => {
|
||||
wrapper = await createIntegratedWrapper({
|
||||
initialMessages: [{ role: 'user', content: '旧数据' }]
|
||||
})
|
||||
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
const editor = wrapper.findComponent({ name: 'ContextEditor' })
|
||||
|
||||
// 模拟导入操作(通过 contextChange 事件)
|
||||
const importedMessages = [
|
||||
{ role: 'system', content: '导入的系统消息' },
|
||||
{ role: 'user', content: '导入的用户消息 {{importedVar}}' }
|
||||
]
|
||||
const importedVariables = { importedVar: '导入的变量值' }
|
||||
|
||||
await editor.vm.$emit('contextChange', importedMessages, importedVariables)
|
||||
await nextTick()
|
||||
|
||||
// 验证数据同步
|
||||
expect(wrapper.vm.messages).toEqual(importedMessages)
|
||||
expect(wrapper.vm.variables).toEqual(importedVariables)
|
||||
|
||||
const manager = wrapper.findComponent({ name: 'ConversationManager' })
|
||||
expect(manager.props('messages')).toEqual(importedMessages)
|
||||
expect(manager.props('availableVariables')).toEqual(importedVariables)
|
||||
})
|
||||
|
||||
it('导入的数据应该立即在两个组件中可用', async () => {
|
||||
wrapper = await createIntegratedWrapper()
|
||||
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
// 模拟复杂的导入数据
|
||||
const complexImportedData = {
|
||||
messages: [
|
||||
{ role: 'system', content: '复杂系统提示:处理 {{taskType}} 任务' },
|
||||
{ role: 'user', content: '用户问题:{{question}}' },
|
||||
{ role: 'assistant', content: '助手回复模板' }
|
||||
],
|
||||
variables: {
|
||||
taskType: '数据分析',
|
||||
question: '如何优化性能?'
|
||||
}
|
||||
}
|
||||
|
||||
const editor = wrapper.findComponent({ name: 'ContextEditor' })
|
||||
await editor.vm.$emit('contextChange', complexImportedData.messages, complexImportedData.variables)
|
||||
await nextTick()
|
||||
|
||||
// 验证复杂数据的同步
|
||||
expect(wrapper.vm.messages).toHaveLength(3)
|
||||
expect(wrapper.vm.messages[0].content).toContain('{{taskType}}')
|
||||
expect(wrapper.vm.variables.taskType).toBe('数据分析')
|
||||
|
||||
// 验证 ConversationManager 的变量扫描功能
|
||||
const detectedVars = wrapper.vm.scanVariables(wrapper.vm.messages[0].content)
|
||||
expect(detectedVars).toContain('taskType')
|
||||
})
|
||||
})
|
||||
|
||||
describe('完整的 "编辑即emit → 父级更新 → 实时反映" 链路验证', () => {
|
||||
it('应该验证完整的数据流链路', async () => {
|
||||
const eventsEmitted: any[] = []
|
||||
|
||||
wrapper = await createIntegratedWrapper({
|
||||
initialMessages: [{ role: 'user', content: '测试消息' }]
|
||||
})
|
||||
|
||||
// 用onMounted来监听组件事件(替代Vue 3中移除的$on)
|
||||
const handleMessagesChanged = (messages: any) => {
|
||||
eventsEmitted.push({ type: 'messagesChanged', data: messages })
|
||||
}
|
||||
const handleContextChanged = (context: any) => {
|
||||
eventsEmitted.push({ type: 'contextChanged', data: context })
|
||||
}
|
||||
|
||||
// 模拟事件监听(通过直接验证组件状态变化替代事件监听)
|
||||
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
const manager = wrapper.findComponent({ name: 'ConversationManager' })
|
||||
const editor = wrapper.findComponent({ name: 'ContextEditor' })
|
||||
|
||||
// 第1步:ConversationManager 编辑 → emit
|
||||
const newMessages1 = [{ role: 'user', content: '第1步修改' }]
|
||||
await manager.vm.$emit('update:messages', newMessages1)
|
||||
await nextTick()
|
||||
|
||||
// 验证链路:编辑 → emit → 父级更新 → ContextEditor 反映
|
||||
expect(wrapper.vm.messages[0].content).toBe('第1步修改')
|
||||
expect(editor.props('state').messages[0].content).toBe('第1步修改')
|
||||
|
||||
// 第2步:ContextEditor 编辑 → emit
|
||||
const newMessages2 = [{ role: 'user', content: '第2步修改' }]
|
||||
await editor.vm.$emit('contextChange', newMessages2, {})
|
||||
await nextTick()
|
||||
|
||||
// 验证链路:编辑 → emit → 父级更新 → ConversationManager 反映
|
||||
expect(wrapper.vm.messages[0].content).toBe('第2步修改')
|
||||
expect(manager.props('messages')[0].content).toBe('第2步修改')
|
||||
|
||||
// 验证事件发射(通过emitted()检查)
|
||||
expect(manager.emitted('update:messages')).toBeTruthy()
|
||||
expect(editor.emitted('contextChange')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('应该确保数据一致性不会出现竞态条件', async () => {
|
||||
wrapper = await createIntegratedWrapper({
|
||||
initialMessages: [{ role: 'user', content: '初始消息' }]
|
||||
})
|
||||
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
const manager = wrapper.findComponent({ name: 'ConversationManager' })
|
||||
const editor = wrapper.findComponent({ name: 'ContextEditor' })
|
||||
|
||||
// 快速连续的修改操作
|
||||
const operations = [
|
||||
{ source: 'manager', content: '快速修改1' },
|
||||
{ source: 'editor', content: '快速修改2' },
|
||||
{ source: 'manager', content: '快速修改3' }
|
||||
]
|
||||
|
||||
// 模拟快速连续操作
|
||||
for (const op of operations) {
|
||||
if (op.source === 'manager') {
|
||||
await manager.vm.$emit('update:messages', [{ role: 'user', content: op.content }])
|
||||
} else {
|
||||
await editor.vm.$emit('contextChange', [{ role: 'user', content: op.content }], {})
|
||||
}
|
||||
await nextTick()
|
||||
}
|
||||
|
||||
// 验证最终状态一致
|
||||
expect(wrapper.vm.messages[0].content).toBe('快速修改3')
|
||||
expect(manager.props('messages')[0].content).toBe('快速修改3')
|
||||
expect(editor.props('state').messages[0].content).toBe('快速修改3')
|
||||
})
|
||||
})
|
||||
|
||||
describe('变量同步特性', () => {
|
||||
it('变量更新应该在两个组件中同步反映', async () => {
|
||||
wrapper = await createIntegratedWrapper({
|
||||
initialMessages: [{ role: 'user', content: 'Hello {{name}}!' }],
|
||||
initialVariables: { name: 'World' }
|
||||
})
|
||||
|
||||
wrapper.vm.handleOpenContextEditor()
|
||||
await nextTick()
|
||||
|
||||
const editor = wrapper.findComponent({ name: 'ContextEditor' })
|
||||
|
||||
// 更新变量
|
||||
const newVariables = { name: 'Vue', greeting: 'Hi' }
|
||||
await editor.vm.$emit('contextChange', wrapper.vm.messages, newVariables)
|
||||
await nextTick()
|
||||
|
||||
// 验证变量同步
|
||||
expect(wrapper.vm.variables).toEqual(newVariables)
|
||||
|
||||
const manager = wrapper.findComponent({ name: 'ConversationManager' })
|
||||
expect(manager.props('availableVariables')).toEqual(newVariables)
|
||||
|
||||
// 验证变量替换功能
|
||||
const replacedContent = wrapper.vm.replaceVariables('Hello {{name}}!', wrapper.vm.variables)
|
||||
expect(replacedContent).toBe('Hello Vue!')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user