Files
MyGoNavi/frontend/src/components/ai/aiSlashCommands.test.ts
2026-06-22 15:12:42 +08:00

227 lines
11 KiB
TypeScript

import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
import {
filterAISlashCommands,
getFeaturedAISlashCommands,
groupAISlashCommands,
} from './aiSlashCommands';
const source = readFileSync(new URL('./aiSlashCommands.ts', import.meta.url), 'utf8');
const zhCnCatalog = JSON.parse(readFileSync(new URL('../../../../shared/i18n/zh-CN.json', import.meta.url), 'utf8'));
const zhTwCatalog = JSON.parse(readFileSync(new URL('../../../../shared/i18n/zh-TW.json', import.meta.url), 'utf8'));
const enUsCatalog = JSON.parse(readFileSync(new URL('../../../../shared/i18n/en-US.json', import.meta.url), 'utf8'));
const jaJpCatalog = JSON.parse(readFileSync(new URL('../../../../shared/i18n/ja-JP.json', import.meta.url), 'utf8'));
const deDeCatalog = JSON.parse(readFileSync(new URL('../../../../shared/i18n/de-DE.json', import.meta.url), 'utf8'));
const ruRuCatalog = JSON.parse(readFileSync(new URL('../../../../shared/i18n/ru-RU.json', import.meta.url), 'utf8'));
const zhCnTranslate = (key: string) => zhCnCatalog[key] || key;
const diagnosticSlashCommandIds = [
'health',
'tools',
'budget',
'hotspots',
'mcp',
'mcpfail',
'mcpadd',
'mcpdraft',
'mcptool',
'connfail',
'shortcuts',
'applog',
'airender',
'safety',
'activity',
'tx',
] as const;
describe('aiSlashCommands', () => {
it('uses i18n keys and english fallback instead of legacy Chinese slash metadata literals', () => {
expect(source).toContain("catalogTranslate('en-US', key, params)");
expect(source).toContain('ai_chat.input.slash.category.generate.title');
expect(source).toContain('ai_chat.input.slash.health.label');
expect(source).toContain('ai_chat.input.slash.tx.prompt');
expect(source).not.toContain("title: 'SQL 生成'");
expect(source).not.toContain("description: '直接产出 SQL、测试数据或迁移草稿。'");
expect(source).not.toContain("label: '🩺 AI 配置体检'");
expect(source).not.toContain("prompt: '请先调用 inspect_ai_setup_health");
});
it('keeps slash keywords behind localized catalog keys instead of production Chinese literals', () => {
expect(source).toContain('keywordKey:');
expect(source).not.toContain("keywords: ['查询'");
expect(source).not.toContain("'工具目录'");
expect(source).not.toContain("'自动提交'");
});
it('keeps slash category, empty-state, command, and keyword keys present in all six catalogs', () => {
const slashCommandIds = [
'query',
'sql',
'mock',
'diff',
'explain',
'optimize',
'schema',
'index',
...diagnosticSlashCommandIds,
] as const;
const requiredKeys = [
'ai_chat.input.slash.category.generate.title',
'ai_chat.input.slash.category.generate.description',
'ai_chat.input.slash.category.review.title',
'ai_chat.input.slash.category.review.description',
'ai_chat.input.slash.category.diagnose.title',
'ai_chat.input.slash.category.diagnose.description',
'ai_chat.input.slash.empty.title',
'ai_chat.input.slash.empty.description',
'ai_chat.input.slash.empty.summary',
...slashCommandIds.flatMap((id) => ([
`ai_chat.input.slash.${id}.label`,
`ai_chat.input.slash.${id}.desc`,
`ai_chat.input.slash.${id}.prompt`,
`ai_chat.input.slash.${id}.keywords`,
])),
];
for (const key of requiredKeys) {
expect(zhCnCatalog[key]).toBeTruthy();
expect(zhTwCatalog[key]).toBeTruthy();
expect(enUsCatalog[key]).toBeTruthy();
expect(jaJpCatalog[key]).toBeTruthy();
expect(deDeCatalog[key]).toBeTruthy();
expect(ruRuCatalog[key]).toBeTruthy();
}
});
it('returns all default commands when only slash is present', () => {
const commands = filterAISlashCommands('/');
const sql = commands.find((command) => command.cmd === '/sql');
const health = commands.find((command) => command.cmd === '/health');
const groups = groupAISlashCommands(commands);
expect(commands.length).toBeGreaterThan(8);
expect(sql).toMatchObject({
label: '📝 Generate SQL',
desc: 'Describe requirements and generate statements',
prompt: 'Generate SQL from the following requirements:',
});
expect(health).toMatchObject({
label: '🩺 AI health check',
desc: 'Run health probes for the current AI setup',
prompt: 'Call inspect_ai_setup_health first. Run a full health check of the current GoNavi AI setup, then summarize blockers, warnings, and nextActions.',
});
expect(commands.some((command) => command.cmd === '/health')).toBe(true);
expect(commands.some((command) => command.cmd === '/tools')).toBe(true);
expect(commands.some((command) => command.cmd === '/budget')).toBe(true);
expect(commands.some((command) => command.cmd === '/hotspots')).toBe(true);
expect(commands.some((command) => command.cmd === '/mcp')).toBe(true);
expect(commands.some((command) => command.cmd === '/mcpfail')).toBe(true);
expect(commands.some((command) => command.cmd === '/mcpadd')).toBe(true);
expect(commands.some((command) => command.cmd === '/mcpdraft')).toBe(true);
expect(commands.some((command) => command.cmd === '/mcptool')).toBe(true);
expect(commands.some((command) => command.cmd === '/connfail')).toBe(true);
expect(commands.some((command) => command.cmd === '/shortcuts')).toBe(true);
expect(commands.some((command) => command.cmd === '/applog')).toBe(true);
expect(commands.some((command) => command.cmd === '/airender')).toBe(true);
expect(commands.some((command) => command.cmd === '/tx')).toBe(true);
expect(groups[0]).toMatchObject({ key: 'generate', title: 'SQL generation' });
expect(groups[1]).toMatchObject({ key: 'review', title: 'Structure review' });
expect(groups[2]).toMatchObject({ key: 'diagnose', title: 'Diagnostic probes' });
});
it('supports filtering by chinese keywords in addition to command prefix', () => {
const commands = filterAISlashCommands('体检', zhCnTranslate);
expect(commands.map((command) => command.cmd)).toContain('/health');
expect(commands.map((command) => command.cmd)).not.toContain('/mcpadd');
});
it('supports filtering builtin tool catalog diagnostics by keyword and command prefix', () => {
expect(filterAISlashCommands('工具目录', zhCnTranslate).map((command) => command.cmd)).toContain('/tools');
expect(filterAISlashCommands('参数提示', zhCnTranslate).map((command) => command.cmd)).toContain('/tools');
expect(filterAISlashCommands('/too').map((command) => command.cmd)).toContain('/tools');
});
it('supports filtering context budget diagnostics by keyword and command prefix', () => {
expect(filterAISlashCommands('上下文', zhCnTranslate).map((command) => command.cmd)).toContain('/budget');
expect(filterAISlashCommands('变慢', zhCnTranslate).map((command) => command.cmd)).toContain('/budget');
expect(filterAISlashCommands('/bud').map((command) => command.cmd)).toContain('/budget');
});
it('supports filtering code hotspot diagnostics by keyword and command prefix', () => {
expect(filterAISlashCommands('大文件', zhCnTranslate).map((command) => command.cmd)).toContain('/hotspots');
expect(filterAISlashCommands('拆分', zhCnTranslate).map((command) => command.cmd)).toContain('/hotspots');
expect(filterAISlashCommands('/hot').map((command) => command.cmd)).toContain('/hotspots');
});
it('supports filtering shortcut diagnostics by chinese keyword and command prefix', () => {
expect(filterAISlashCommands('快捷键', zhCnTranslate).map((command) => command.cmd)).toContain('/shortcuts');
expect(filterAISlashCommands('/sho').map((command) => command.cmd)).toContain('/shortcuts');
});
it('supports filtering connection-failure diagnostics by chinese keyword and command prefix', () => {
expect(filterAISlashCommands('连接失败', zhCnTranslate).map((command) => command.cmd)).toContain('/connfail');
expect(filterAISlashCommands('/conn').map((command) => command.cmd)).toContain('/connfail');
});
it('supports filtering app-log diagnostics by chinese keyword and command prefix', () => {
expect(filterAISlashCommands('日志', zhCnTranslate).map((command) => command.cmd)).toContain('/applog');
expect(filterAISlashCommands('/app').map((command) => command.cmd)).toContain('/applog');
});
it('supports filtering ai-render diagnostics by chinese keyword and command prefix', () => {
expect(filterAISlashCommands('气泡空白', zhCnTranslate).map((command) => command.cmd)).toContain('/airender');
expect(filterAISlashCommands('/air').map((command) => command.cmd)).toContain('/airender');
});
it('supports filtering sql editor transaction diagnostics by keyword and command prefix', () => {
expect(filterAISlashCommands('自动提交', zhCnTranslate).map((command) => command.cmd)).toContain('/tx');
expect(filterAISlashCommands('未提交', zhCnTranslate).map((command) => command.cmd)).toContain('/tx');
expect(filterAISlashCommands('/tx').map((command) => command.cmd)).toContain('/tx');
});
it('supports filtering mcp tool schema diagnostics by keyword and command prefix', () => {
expect(filterAISlashCommands('arguments').map((command) => command.cmd)).toContain('/mcptool');
expect(filterAISlashCommands('MCP工具参数', zhCnTranslate).map((command) => command.cmd)).toContain('/mcptool');
expect(filterAISlashCommands('/mcpt').map((command) => command.cmd)).toContain('/mcptool');
});
it('supports filtering mcp runtime failure diagnostics by keyword and command prefix', () => {
expect(filterAISlashCommands('运行期失败', zhCnTranslate).map((command) => command.cmd)).toContain('/mcpfail');
expect(filterAISlashCommands('工具发现0个', zhCnTranslate).map((command) => command.cmd)).toContain('/mcpfail');
expect(filterAISlashCommands('stdio').map((command) => command.cmd)).toContain('/mcpfail');
expect(filterAISlashCommands('/mcpf').map((command) => command.cmd)).toContain('/mcpfail');
});
it('supports filtering mcp draft validation diagnostics by keyword and command prefix', () => {
expect(filterAISlashCommands('MCP草稿', zhCnTranslate).map((command) => command.cmd)).toContain('/mcpdraft');
expect(filterAISlashCommands('启动命令', zhCnTranslate).map((command) => command.cmd)).toContain('/mcpdraft');
expect(filterAISlashCommands('/mcpd').map((command) => command.cmd)).toContain('/mcpdraft');
});
it('groups commands by configured category order', () => {
const groups = groupAISlashCommands(filterAISlashCommands('/'));
expect(groups[0]?.key).toBe('generate');
expect(groups[1]?.key).toBe('review');
expect(groups[2]?.key).toBe('diagnose');
});
it('keeps featured commands available for empty-state quick picks', () => {
const featured = getFeaturedAISlashCommands().map((command) => command.cmd);
expect(featured).toContain('/sql');
expect(featured).toContain('/health');
expect(featured).toContain('/mcp');
expect(featured).toContain('/mcpadd');
expect(featured).toContain('/connfail');
expect(featured).toContain('/tx');
expect(featured).not.toContain('/tools');
expect(featured).not.toContain('/budget');
expect(featured).not.toContain('/mcpfail');
expect(featured).not.toContain('/shortcuts');
});
});