mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
The Settings terminal added a full xterm plus Tauri PTY stack for a job that is better handled by dedicated install and configuration flows. This change removes the Settings tab, frontend terminal wiring, Tauri terminal commands, and the terminal-only dependencies so the desktop settings surface stays narrower and less fragile. Constraint: The worktree already contains unrelated desktop icon and UI changes, so this commit stages only the terminal-removal slice Rejected: Keep a hidden or runtime-only terminal stub | it would still preserve the heavy cross-layer maintenance surface Rejected: Remove only the Settings tab and leave the Tauri PTY backend | that would leave dead code and unused dependencies behind Confidence: high Scope-risk: narrow Reversibility: clean Directive: If future install workflows need more power, prefer Settings-native actions and runtime refresh over reintroducing a general shell tab Tested: bun x vitest run src/__tests__/generalSettings.test.tsx src/__tests__/skillsSettings.test.tsx src/__tests__/pluginsSettings.test.tsx src/__tests__/agentsSettings.test.tsx src/__tests__/mcpSettings.test.tsx src/components/settings/InstallCenter.test.tsx; bun run lint; bun run build; cargo check --manifest-path desktop/src-tauri/Cargo.toml Not-tested: Manual packaged desktop app click-through after removing the Settings terminal tab
260 lines
7.5 KiB
TypeScript
260 lines
7.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { fireEvent, render, screen } from '@testing-library/react'
|
|
import '@testing-library/jest-dom'
|
|
|
|
import { Settings } from '../pages/Settings'
|
|
import { useSkillStore } from '../stores/skillStore'
|
|
import { useSettingsStore } from '../stores/settingsStore'
|
|
import { useSessionStore } from '../stores/sessionStore'
|
|
import { useTabStore, SETTINGS_TAB_ID } from '../stores/tabStore'
|
|
import { useUIStore } from '../stores/uiStore'
|
|
|
|
vi.mock('../api/agents', () => ({
|
|
agentsApi: {
|
|
list: vi.fn().mockResolvedValue({ activeAgents: [], allAgents: [] }),
|
|
},
|
|
}))
|
|
|
|
vi.mock('../stores/providerStore', () => ({
|
|
useProviderStore: () => ({
|
|
providers: [],
|
|
activeId: null,
|
|
presets: [],
|
|
isLoading: false,
|
|
isPresetsLoading: false,
|
|
fetchProviders: vi.fn(),
|
|
fetchPresets: vi.fn(),
|
|
deleteProvider: vi.fn(),
|
|
activateProvider: vi.fn(),
|
|
activateOfficial: vi.fn(),
|
|
testProvider: vi.fn(),
|
|
createProvider: vi.fn(),
|
|
updateProvider: vi.fn(),
|
|
testConfig: vi.fn(),
|
|
}),
|
|
}))
|
|
|
|
vi.mock('../pages/AdapterSettings', () => ({
|
|
AdapterSettings: () => <div>Adapter Settings Mock</div>,
|
|
}))
|
|
|
|
vi.mock('../stores/agentStore', () => ({
|
|
useAgentStore: () => ({
|
|
activeAgents: [],
|
|
allAgents: [],
|
|
isLoading: false,
|
|
error: null,
|
|
selectedAgent: null,
|
|
fetchAgents: vi.fn(),
|
|
selectAgent: vi.fn(),
|
|
}),
|
|
}))
|
|
|
|
vi.mock('../components/chat/CodeViewer', () => ({
|
|
CodeViewer: ({ code }: { code: string }) => <pre data-testid="code-viewer">{code}</pre>,
|
|
}))
|
|
|
|
const MOCK_FETCH_SKILLS = vi.fn()
|
|
const MOCK_FETCH_SKILL_DETAIL = vi.fn()
|
|
const MOCK_CLEAR_SELECTION = vi.fn()
|
|
|
|
function switchToSkillsTab() {
|
|
fireEvent.click(screen.getByText('Skills'))
|
|
}
|
|
|
|
describe('Settings > Skills tab', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
useSettingsStore.setState({ locale: 'en' })
|
|
useSessionStore.setState({
|
|
sessions: [
|
|
{
|
|
id: 'session-1',
|
|
title: 'Active session',
|
|
createdAt: '2026-04-20T00:00:00.000Z',
|
|
modifiedAt: '2026-04-20T00:00:00.000Z',
|
|
messageCount: 1,
|
|
projectPath: '/workspace/project',
|
|
workDir: '/workspace/project',
|
|
workDirExists: true,
|
|
},
|
|
],
|
|
activeSessionId: 'session-1',
|
|
isLoading: false,
|
|
error: null,
|
|
selectedProjects: [],
|
|
availableProjects: ['/workspace/project'],
|
|
})
|
|
useTabStore.setState({ tabs: [], activeTabId: null })
|
|
useUIStore.setState({ pendingSettingsTab: null })
|
|
useSkillStore.setState({
|
|
skills: [],
|
|
selectedSkill: null,
|
|
selectedSkillReturnTab: 'skills',
|
|
isLoading: false,
|
|
isDetailLoading: false,
|
|
error: null,
|
|
fetchSkills: MOCK_FETCH_SKILLS,
|
|
fetchSkillDetail: MOCK_FETCH_SKILL_DETAIL,
|
|
clearSelection: MOCK_CLEAR_SELECTION,
|
|
})
|
|
})
|
|
|
|
it('renders browser summary and grouped skill cards', () => {
|
|
useSkillStore.setState({
|
|
skills: [
|
|
{
|
|
name: 'alpha',
|
|
displayName: 'Alpha Skill',
|
|
description: 'First skill description',
|
|
source: 'user',
|
|
userInvocable: true,
|
|
version: '1.0.0',
|
|
contentLength: 400,
|
|
hasDirectory: true,
|
|
},
|
|
{
|
|
name: 'beta',
|
|
description: 'Second skill description',
|
|
source: 'project',
|
|
userInvocable: false,
|
|
contentLength: 200,
|
|
hasDirectory: true,
|
|
},
|
|
{
|
|
name: 'telegram:access',
|
|
displayName: 'Telegram Access',
|
|
description: 'Plugin-provided access workflow',
|
|
source: 'plugin',
|
|
pluginName: 'telegram',
|
|
userInvocable: true,
|
|
contentLength: 280,
|
|
hasDirectory: true,
|
|
},
|
|
],
|
|
})
|
|
|
|
render(<Settings />)
|
|
switchToSkillsTab()
|
|
|
|
expect(screen.getByText('Browse installed skills')).toBeInTheDocument()
|
|
expect(screen.getByText('Skill Browser')).toBeInTheDocument()
|
|
expect(screen.getByText('Total skills')).toBeInTheDocument()
|
|
expect(screen.getByText('Alpha Skill')).toBeInTheDocument()
|
|
expect(screen.getByText('Second skill description')).toBeInTheDocument()
|
|
expect(screen.getAllByText('Plugin').length).toBeGreaterThan(0)
|
|
expect(screen.getByText('Telegram Access')).toBeInTheDocument()
|
|
})
|
|
|
|
it('uses the active session workDir even when settings tab is focused', () => {
|
|
const fetchSkills = vi.fn()
|
|
useSkillStore.setState({
|
|
skills: [],
|
|
selectedSkill: null,
|
|
isLoading: false,
|
|
isDetailLoading: false,
|
|
error: null,
|
|
fetchSkills,
|
|
fetchSkillDetail: MOCK_FETCH_SKILL_DETAIL,
|
|
clearSelection: MOCK_CLEAR_SELECTION,
|
|
})
|
|
useTabStore.setState({
|
|
activeTabId: SETTINGS_TAB_ID,
|
|
tabs: [{ sessionId: SETTINGS_TAB_ID, title: 'Settings', type: 'settings', status: 'idle' }],
|
|
})
|
|
|
|
render(<Settings />)
|
|
switchToSkillsTab()
|
|
|
|
expect(fetchSkills).toHaveBeenCalledWith('/workspace/project')
|
|
})
|
|
|
|
it('opens skill detail with metadata cards and parsed markdown body', () => {
|
|
useSkillStore.setState({
|
|
selectedSkill: {
|
|
meta: {
|
|
name: 'alpha',
|
|
displayName: 'Alpha Skill',
|
|
description: 'First skill description',
|
|
source: 'user',
|
|
userInvocable: true,
|
|
version: '1.0.0',
|
|
contentLength: 400,
|
|
hasDirectory: true,
|
|
},
|
|
tree: [
|
|
{ name: 'SKILL.md', path: 'SKILL.md', type: 'file' },
|
|
{ name: 'run.ts', path: 'run.ts', type: 'file' },
|
|
],
|
|
files: [
|
|
{
|
|
path: 'SKILL.md',
|
|
content: '# Hello\n\nBody content',
|
|
body: '# Hello\n\nBody content',
|
|
language: 'markdown',
|
|
isEntry: true,
|
|
frontmatter: {
|
|
description: 'Frontmatter description',
|
|
'allowed-tools': ['Read', 'Edit'],
|
|
model: 'sonnet',
|
|
},
|
|
},
|
|
{
|
|
path: 'run.ts',
|
|
content: 'console.log("hello")',
|
|
language: 'typescript',
|
|
isEntry: false,
|
|
},
|
|
],
|
|
skillRoot: '/tmp/alpha',
|
|
},
|
|
selectedSkillReturnTab: 'skills',
|
|
})
|
|
|
|
render(<Settings />)
|
|
switchToSkillsTab()
|
|
|
|
expect(screen.getByText('Skill metadata')).toBeInTheDocument()
|
|
expect(screen.getByText('/slash')).toBeInTheDocument()
|
|
expect(screen.getByText('Frontmatter description')).toBeInTheDocument()
|
|
expect(screen.getByText('Read, Edit')).toBeInTheDocument()
|
|
expect(screen.getByText('Hello')).toBeInTheDocument()
|
|
expect(screen.queryByText(/^---$/)).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('returns to plugins tab when skill detail was opened from plugins', () => {
|
|
useSkillStore.setState({
|
|
selectedSkill: {
|
|
meta: {
|
|
name: 'telegram:access',
|
|
displayName: 'Access',
|
|
description: 'Plugin skill',
|
|
source: 'plugin',
|
|
userInvocable: true,
|
|
contentLength: 200,
|
|
hasDirectory: true,
|
|
},
|
|
tree: [{ name: 'SKILL.md', path: 'SKILL.md', type: 'file' }],
|
|
files: [
|
|
{
|
|
path: 'SKILL.md',
|
|
content: '# Access',
|
|
body: '# Access',
|
|
language: 'markdown',
|
|
isEntry: true,
|
|
},
|
|
],
|
|
skillRoot: '/tmp/telegram-access',
|
|
},
|
|
selectedSkillReturnTab: 'plugins',
|
|
})
|
|
|
|
render(<Settings />)
|
|
switchToSkillsTab()
|
|
|
|
fireEvent.click(screen.getByText('Back to list'))
|
|
|
|
expect(screen.getByText('Installed Plugins')).toBeInTheDocument()
|
|
})
|
|
})
|