新增 单元测试框架和测试用例

This commit is contained in:
2026-04-18 03:03:19 +08:00
parent 2d2804ef22
commit aa375bfff0
13 changed files with 1962 additions and 2 deletions

View File

@@ -0,0 +1,80 @@
import { describe, it, expect, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import Footer from './Footer.vue';
describe('Footer.vue', () => {
it('renders correctly with default props', () => {
const wrapper = mount(Footer, {
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.exists()).toBe(true);
expect(wrapper.find('.footer').exists()).toBe(true);
expect(wrapper.find('.footer-status').exists()).toBe(true);
});
it('displays current profile correctly', () => {
const wrapper = mount(Footer, {
props: {
currentProfile: 'dev',
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const statusText = wrapper.find('.footer-status').text();
expect(statusText).toContain('dev');
});
it('displays default profile when no prop provided', () => {
const wrapper = mount(Footer, {
global: {
mocks: {
$t: (key) => key,
},
},
});
const statusText = wrapper.find('.footer-status').text();
expect(statusText).toContain('default');
});
it('displays status dot', () => {
const wrapper = mount(Footer, {
props: {
currentProfile: 'production',
},
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.find('.footer-status-dot').exists()).toBe(true);
});
it('applies translation correctly', () => {
const wrapper = mount(Footer, {
props: {
currentProfile: 'test-profile',
},
global: {
mocks: {
$t: (key) => `translated-${key}`,
},
},
});
const statusText = wrapper.find('.footer-status').text();
expect(statusText).toContain('translated-api.currentConfig');
expect(statusText).toContain('test-profile');
});
});

View File

@@ -0,0 +1,164 @@
import { describe, it, expect, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import SideBar from './SideBar.vue';
describe('SideBar.vue', () => {
it('renders correctly with default props', () => {
const wrapper = mount(SideBar, {
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.exists()).toBe(true);
expect(wrapper.find('.sidebar').exists()).toBe(true);
});
it('has three nav items', () => {
const wrapper = mount(SideBar, {
global: {
mocks: {
$t: (key) => key,
},
},
});
const navItems = wrapper.findAll('.nav-item');
expect(navItems.length).toBe(3);
});
it('has two sections', () => {
const wrapper = mount(SideBar, {
global: {
mocks: {
$t: (key) => key,
},
},
});
const sections = wrapper.findAll('.sidebar-section');
expect(sections.length).toBe(2);
});
it('highlights active section correctly', () => {
const wrapper = mount(SideBar, {
props: {
currentSection: 'api',
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const navItems = wrapper.findAll('.nav-item');
expect(navItems[0].classes('active')).toBe(false);
expect(navItems[1].classes('active')).toBe(true);
expect(navItems[2].classes('active')).toBe(false);
});
it('emits navigate event when nav item is clicked', async () => {
const wrapper = mount(SideBar, {
props: {
currentSection: 'general',
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const navItems = wrapper.findAll('.nav-item');
await navItems[1].trigger('click');
expect(wrapper.emitted('navigate')).toBeTruthy();
expect(wrapper.emitted('navigate')[0][0]).toBe('api');
});
it('displays server count badge correctly', () => {
const wrapper = mount(SideBar, {
props: {
currentSection: 'general',
serverCount: 5
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const badges = wrapper.findAll('.nav-item-badge');
expect(badges.length).toBe(1);
expect(badges[0].text()).toBe('5');
});
it('displays zero server count', () => {
const wrapper = mount(SideBar, {
props: {
currentSection: 'general',
serverCount: 0
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const badges = wrapper.findAll('.nav-item-badge');
expect(badges.length).toBe(1);
expect(badges[0].text()).toBe('0');
});
it('applies translation to section titles', () => {
const wrapper = mount(SideBar, {
global: {
mocks: {
$t: (key) => `translated-${key}`,
},
},
});
const sectionTitles = wrapper.findAll('.sidebar-title');
expect(sectionTitles[0].text()).toBe('translated-sidebar.general');
expect(sectionTitles[1].text()).toBe('translated-sidebar.advanced');
});
it('applies translation to nav item texts', () => {
const wrapper = mount(SideBar, {
global: {
mocks: {
$t: (key) => `translated-${key}`,
},
},
});
const navItems = wrapper.findAll('.nav-item-text');
expect(navItems[0].text()).toBe('translated-sidebar.basicSettings');
expect(navItems[1].text()).toBe('translated-sidebar.apiConfig');
expect(navItems[2].text()).toBe('translated-sidebar.mcpServers');
});
it('handles null currentSection', () => {
const wrapper = mount(SideBar, {
props: {
currentSection: null,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const navItems = wrapper.findAll('.nav-item');
expect(navItems[0].classes('active')).toBe(false);
expect(navItems[1].classes('active')).toBe(false);
expect(navItems[2].classes('active')).toBe(false);
});
});

View File

@@ -0,0 +1,131 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { mount } from '@vue/test-utils';
import TitleBar from './TitleBar.vue';
describe('TitleBar.vue', () => {
beforeEach(() => {
// Mock window.electronAPI
global.window.electronAPI = {
minimize: vi.fn(),
maximize: vi.fn(),
close: vi.fn()
};
});
afterEach(() => {
vi.clearAllMocks();
});
it('renders correctly', () => {
const wrapper = mount(TitleBar, {
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.exists()).toBe(true);
expect(wrapper.find('.titlebar').exists()).toBe(true);
expect(wrapper.find('.titlebar-title').exists()).toBe(true);
expect(wrapper.find('.titlebar-controls').exists()).toBe(true);
});
it('displays app title', () => {
const wrapper = mount(TitleBar, {
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.find('.titlebar-title').text()).toBe('app.title');
});
it('has three window control buttons', () => {
const wrapper = mount(TitleBar, {
global: {
mocks: {
$t: (key) => key,
},
},
});
const buttons = wrapper.findAll('.titlebar-btn');
expect(buttons.length).toBe(3);
});
it('calls minimize when minimize button is clicked', async () => {
const wrapper = mount(TitleBar, {
global: {
mocks: {
$t: (key) => key,
},
},
});
const minimizeButton = wrapper.findAll('.titlebar-btn')[0];
await minimizeButton.trigger('click');
expect(window.electronAPI.minimize).toHaveBeenCalledOnce();
});
it('calls maximize when maximize button is clicked', async () => {
const wrapper = mount(TitleBar, {
global: {
mocks: {
$t: (key) => key,
},
},
});
const maximizeButton = wrapper.findAll('.titlebar-btn')[1];
await maximizeButton.trigger('click');
expect(window.electronAPI.maximize).toHaveBeenCalledOnce();
});
it('calls close when close button is clicked', async () => {
const wrapper = mount(TitleBar, {
global: {
mocks: {
$t: (key) => key,
},
},
});
const closeButton = wrapper.findAll('.titlebar-btn')[2];
await closeButton.trigger('click');
expect(window.electronAPI.close).toHaveBeenCalledOnce();
});
it('has close button with close class', () => {
const wrapper = mount(TitleBar, {
global: {
mocks: {
$t: (key) => key,
},
},
});
const closeButton = wrapper.findAll('.titlebar-btn')[2];
expect(closeButton.classes()).toContain('close');
});
it('applies translation to button tooltips', () => {
const wrapper = mount(TitleBar, {
global: {
mocks: {
$t: (key) => `translated-${key}`,
},
},
});
const buttons = wrapper.findAll('.titlebar-btn');
expect(buttons[0].attributes('title')).toBe('translated-window.minimize');
expect(buttons[1].attributes('title')).toBe('translated-window.maximize');
expect(buttons[2].attributes('title')).toBe('translated-window.close');
});
});

View File

@@ -280,6 +280,11 @@ body {
letter-spacing: -0.01em;
}
.form-required {
color: var(--danger);
margin-left: 3px;
}
.form-input {
width: 100%;
padding: 10px 14px;
@@ -434,6 +439,12 @@ body {
transform: translateY(0) scale(0.98);
}
.btn-secondary {
background: var(--bg-secondary);
color: var(--text-secondary);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
@@ -447,7 +458,7 @@ body {
.btn-danger {
background: var(--danger);
color: white;
box-shadow: 0 2px 4px rgba(239, 68, 68, 0.3);
border: 1px solid var(--danger);
}
.btn-danger:hover {
@@ -470,6 +481,32 @@ body {
font-size: 12px;
}
// Side panel close button (used by ServerPanel and ApiProfileDialog)
.side-panel-close {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
color: var(--text-tertiary);
cursor: pointer;
border-radius: var(--radius);
transition: all 0.2s ease;
}
.side-panel-close:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.side-panel-close svg {
width: 14px;
height: 14px;
stroke: currentColor;
stroke-width: 1.5;
fill: none;
}
// Empty state
.empty-state {
display: flex;
@@ -518,6 +555,10 @@ body {
animation: fadeIn 0.15s ease;
}
.dialog-overlay-top {
z-index: 1400;
}
.dialog {
background: var(--bg-secondary);
border-radius: var(--radius-lg);
@@ -542,6 +583,12 @@ body {
line-height: 1.5;
}
.dialog-body {
padding: 20px 24px;
max-height: 60vh;
overflow-y: auto;
}
.dialog-actions {
display: flex;
justify-content: flex-end;

344
src/views/ApiConfig.test.js Normal file
View File

@@ -0,0 +1,344 @@
import { describe, it, expect, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import ApiConfig from './ApiConfig.vue';
describe('ApiConfig.vue', () => {
const mockSettings = {
apiProfiles: {
'default': {
baseUrl: 'https://api.default.com',
selectedAuthType: 'openai-compatible',
apiKey: '',
modelName: '',
searchApiKey: '',
cna: ''
},
'dev': {
baseUrl: 'https://api.dev.com',
selectedAuthType: 'openai-compatible',
apiKey: 'dev-key',
modelName: 'gpt-4',
searchApiKey: '',
cna: ''
},
'prod': {
baseUrl: 'https://api.prod.com',
selectedAuthType: 'openai-compatible',
apiKey: 'prod-key',
modelName: 'gpt-4',
searchApiKey: '',
cna: ''
}
},
currentApiProfile: 'default'
};
const mockProfiles = [
{ name: 'default' },
{ name: 'dev' },
{ name: 'prod' }
];
it('renders correctly with props', () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.exists()).toBe(true);
expect(wrapper.find('.content-title').exists()).toBe(true);
expect(wrapper.find('.card').exists()).toBe(true);
expect(wrapper.find('.profile-list').exists()).toBe(true);
});
it('displays all profiles', () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const profileItems = wrapper.findAll('.profile-item');
expect(profileItems.length).toBe(3);
});
it('highlights current profile', () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'dev',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const profileItems = wrapper.findAll('.profile-item');
expect(profileItems[0].classes('active')).toBe(false);
expect(profileItems[1].classes('active')).toBe(true);
expect(profileItems[2].classes('active')).toBe(false);
});
it('shows status badge only for current profile', () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const statusBadges = wrapper.findAll('.status-badge');
expect(statusBadges.length).toBe(1);
expect(wrapper.findAll('.profile-item')[0].find('.status-badge').exists()).toBe(true);
expect(wrapper.findAll('.profile-item')[1].find('.status-badge').exists()).toBe(false);
});
it('emits create-profile event when create button is clicked', async () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
await wrapper.find('.btn-primary').trigger('click');
expect(wrapper.emitted('create-profile')).toBeTruthy();
});
it('emits select-profile event when profile is clicked', async () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const profileItems = wrapper.findAll('.profile-item');
await profileItems[1].trigger('click');
expect(wrapper.emitted('select-profile')).toBeTruthy();
expect(wrapper.emitted('select-profile')[0][0]).toBe('dev');
});
it('emits edit-profile event when edit button is clicked', async () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const editButtons = wrapper.findAll('.action-btn');
await editButtons[0].trigger('click');
expect(wrapper.emitted('edit-profile')).toBeTruthy();
expect(wrapper.emitted('edit-profile')[0][0]).toBe('default');
});
it('emits duplicate-profile event when duplicate button is clicked', async () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const duplicateButtons = wrapper.findAll('.action-btn');
await duplicateButtons[1].trigger('click');
expect(wrapper.emitted('duplicate-profile')).toBeTruthy();
expect(wrapper.emitted('duplicate-profile')[0][0]).toBe('default');
});
it('shows delete button only for non-default profiles', () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const profileItems = wrapper.findAll('.profile-item');
const deleteButtons = wrapper.findAll('.action-btn-danger');
expect(deleteButtons.length).toBe(2);
expect(profileItems[0].find('.action-btn-danger').exists()).toBe(false);
expect(profileItems[1].find('.action-btn-danger').exists()).toBe(true);
expect(profileItems[2].find('.action-btn-danger').exists()).toBe(true);
});
it('emits delete-profile event when delete button is clicked', async () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const deleteButtons = wrapper.findAll('.action-btn-danger');
await deleteButtons[0].trigger('click');
expect(wrapper.emitted('delete-profile')).toBeTruthy();
expect(wrapper.emitted('delete-profile')[0][0]).toBe('dev');
});
it('displays correct profile names', () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const profileNames = wrapper.findAll('.profile-name');
expect(profileNames[0].text()).toBe('default');
expect(profileNames[1].text()).toBe('dev');
expect(profileNames[2].text()).toBe('prod');
});
it('displays correct profile URLs', () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const profileUrls = wrapper.findAll('.profile-url');
expect(profileUrls[0].text()).toBe('https://api.default.com');
expect(profileUrls[1].text()).toBe('https://api.dev.com');
expect(profileUrls[2].text()).toBe('https://api.prod.com');
});
it('displays correct profile initials', () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const iconTexts = wrapper.findAll('.profile-icon-text');
expect(iconTexts[0].text()).toBe('D');
expect(iconTexts[1].text()).toBe('D');
expect(iconTexts[2].text()).toBe('P');
});
it('handles empty profiles array', () => {
const wrapper = mount(ApiConfig, {
props: {
profiles: [],
currentProfile: 'default',
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const profileItems = wrapper.findAll('.profile-item');
expect(profileItems.length).toBe(0);
});
it('handles missing apiProfiles in settings', () => {
const settingsWithoutProfiles = { currentApiProfile: 'default' };
const wrapper = mount(ApiConfig, {
props: {
profiles: mockProfiles,
currentProfile: 'default',
settings: settingsWithoutProfiles,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const profileUrls = wrapper.findAll('.profile-url');
expect(profileUrls[0].text()).toBe('');
expect(profileUrls[1].text()).toBe('');
expect(profileUrls[2].text()).toBe('');
});
});

View File

@@ -0,0 +1,157 @@
import { describe, it, expect, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import GeneralSettings from './GeneralSettings.vue';
describe('GeneralSettings.vue', () => {
const mockSettings = {
language: 'zh-CN',
theme: 'Xcode',
bootAnimationShown: true,
checkpointing: { enabled: true },
};
it('renders correctly with props', () => {
const wrapper = mount(GeneralSettings, {
props: {
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.exists()).toBe(true);
expect(wrapper.find('.content-title').exists()).toBe(true);
expect(wrapper.findAll('.card').length).toBe(2);
});
it('displays language options correctly', () => {
const wrapper = mount(GeneralSettings, {
props: {
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const languageOptions = wrapper.findAll('.form-select')[0].findAll('option');
expect(languageOptions.length).toBe(3);
expect(languageOptions[0].attributes('value')).toBe('zh-CN');
expect(languageOptions[1].attributes('value')).toBe('en-US');
expect(languageOptions[2].attributes('value')).toBe('ja-JP');
});
it('displays theme options correctly', () => {
const wrapper = mount(GeneralSettings, {
props: {
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const themeOptions = wrapper.findAll('.form-select')[1].findAll('option');
expect(themeOptions.length).toBe(4);
expect(themeOptions[0].attributes('value')).toBe('Xcode');
expect(themeOptions[1].attributes('value')).toBe('Dark');
expect(themeOptions[2].attributes('value')).toBe('Light');
expect(themeOptions[3].attributes('value')).toBe('Solarized Dark');
});
it('reflects current settings in form controls', async () => {
const wrapper = mount(GeneralSettings, {
props: {
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
await nextTick();
const selectElements = wrapper.findAll('.form-select');
expect(selectElements[0].element.value).toBe('zh-CN');
expect(selectElements[1].element.value).toBe('Xcode');
expect(selectElements[2].element.value).toBe('true');
expect(selectElements[3].element.value).toBe('true');
});
it('applies translation correctly', () => {
const wrapper = mount(GeneralSettings, {
props: {
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => `translated-${key}`,
},
},
});
expect(wrapper.find('.content-title').text()).toBe('translated-general.title');
expect(wrapper.find('.content-desc').text()).toBe('translated-general.description');
});
it('has two cards for settings sections', () => {
const wrapper = mount(GeneralSettings, {
props: {
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const cards = wrapper.findAll('.card');
expect(cards.length).toBe(2);
});
it('displays card titles with icons', () => {
const wrapper = mount(GeneralSettings, {
props: {
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const cardTitles = wrapper.findAll('.card-title');
expect(cardTitles.length).toBe(2);
expect(cardTitles[0].text()).toContain('general.languageInterface');
expect(cardTitles[1].text()).toContain('general.otherSettings');
});
it('shows all form controls with proper structure', () => {
const wrapper = mount(GeneralSettings, {
props: {
settings: mockSettings,
},
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.findAll('.form-row').length).toBe(2);
expect(wrapper.findAll('.form-group').length).toBe(4);
expect(wrapper.findAll('.form-label').length).toBe(4);
expect(wrapper.findAll('.form-select').length).toBe(4);
});
});

View File

@@ -0,0 +1,256 @@
import { describe, it, expect, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import McpServers from './McpServers.vue';
describe('McpServers.vue', () => {
const mockServers = {
'server1': {
description: '第一个服务器',
command: 'node server.js',
args: ['--port', '3000'],
env: {}
},
'server2': {
description: '第二个服务器',
command: 'python server.py',
args: [],
env: { 'PYTHONPATH': '/path/to/python' }
},
'server3': {
command: 'java -jar server.jar',
args: [],
env: {}
}
};
it('renders correctly with props', () => {
const wrapper = mount(McpServers, {
props: {
servers: mockServers,
selectedServer: 'server1',
serverCount: 3
},
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.exists()).toBe(true);
expect(wrapper.find('.content-title').exists()).toBe(true);
expect(wrapper.find('.server-list').exists()).toBe(true);
});
it('displays all servers', () => {
const wrapper = mount(McpServers, {
props: {
servers: mockServers,
selectedServer: 'server1',
serverCount: 3
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const serverItems = wrapper.findAll('.server-item');
expect(serverItems.length).toBe(3);
});
it('highlights selected server', () => {
const wrapper = mount(McpServers, {
props: {
servers: mockServers,
selectedServer: 'server2',
serverCount: 3
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const serverItems = wrapper.findAll('.server-item');
expect(serverItems[0].classes('selected')).toBe(false);
expect(serverItems[1].classes('selected')).toBe(true);
expect(serverItems[2].classes('selected')).toBe(false);
});
it('shows empty state when no servers', () => {
const wrapper = mount(McpServers, {
props: {
servers: {},
selectedServer: null,
serverCount: 0
},
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.find('.empty-state').exists()).toBe(true);
expect(wrapper.find('.empty-state-title').exists()).toBe(true);
expect(wrapper.find('.empty-state-desc').exists()).toBe(true);
expect(wrapper.findAll('.server-item').length).toBe(0);
});
it('emits add-server event when add button is clicked', async () => {
const wrapper = mount(McpServers, {
props: {
servers: mockServers,
selectedServer: 'server1',
serverCount: 3
},
global: {
mocks: {
$t: (key) => key,
},
},
});
await wrapper.find('.btn-primary').trigger('click');
expect(wrapper.emitted('add-server')).toBeTruthy();
});
it('emits select-server event when server is clicked', async () => {
const wrapper = mount(McpServers, {
props: {
servers: mockServers,
selectedServer: 'server1',
serverCount: 3
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const serverItems = wrapper.findAll('.server-item');
await serverItems[1].trigger('click');
expect(wrapper.emitted('select-server')).toBeTruthy();
expect(wrapper.emitted('select-server')[0][0]).toBe('server2');
});
it('displays correct server names', () => {
const wrapper = mount(McpServers, {
props: {
servers: mockServers,
selectedServer: 'server1',
serverCount: 3
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const serverNames = wrapper.findAll('.server-name');
expect(serverNames[0].text()).toBe('server1');
expect(serverNames[1].text()).toBe('server2');
expect(serverNames[2].text()).toBe('server3');
});
it('displays correct server descriptions', () => {
const wrapper = mount(McpServers, {
props: {
servers: mockServers,
selectedServer: 'server1',
serverCount: 3
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const serverDescs = wrapper.findAll('.server-desc');
expect(serverDescs[0].text()).toBe('第一个服务器');
expect(serverDescs[1].text()).toBe('第二个服务器');
expect(serverDescs[2].text()).toBe('mcp.noDescription');
});
it('displays status indicators for all servers', () => {
const wrapper = mount(McpServers, {
props: {
servers: mockServers,
selectedServer: 'server1',
serverCount: 3
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const statusIndicators = wrapper.findAll('.server-status');
expect(statusIndicators.length).toBe(3);
});
it('handles null selectedServer prop', () => {
const wrapper = mount(McpServers, {
props: {
servers: mockServers,
selectedServer: null,
serverCount: 3
},
global: {
mocks: {
$t: (key) => key,
},
},
});
const serverItems = wrapper.findAll('.server-item');
expect(serverItems.length).toBe(3);
expect(serverItems[0].classes('selected')).toBe(false);
expect(serverItems[1].classes('selected')).toBe(false);
expect(serverItems[2].classes('selected')).toBe(false);
});
it('handles zero serverCount with empty servers object', () => {
const wrapper = mount(McpServers, {
props: {
servers: {},
selectedServer: null,
serverCount: 0
},
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.find('.empty-state').exists()).toBe(true);
expect(wrapper.findAll('.server-item').length).toBe(0);
});
it('displays empty state title correctly', () => {
const wrapper = mount(McpServers, {
props: {
servers: {},
selectedServer: null,
serverCount: 0
},
global: {
mocks: {
$t: (key) => key,
},
},
});
expect(wrapper.find('.empty-state-title').text()).toBe('mcp.noServers');
expect(wrapper.find('.empty-state-desc').text()).toBe('mcp.addFirstServer');
});
});