File size: 5,073 Bytes
84aa3bf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | /**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { SkillCommandLoader } from './SkillCommandLoader.js';
import { CommandKind } from '../ui/commands/types.js';
import { ACTIVATE_SKILL_TOOL_NAME } from '@google/gemini-cli-core';
describe('SkillCommandLoader', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let mockConfig: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let mockSkillManager: any;
beforeEach(() => {
mockSkillManager = {
getDisplayableSkills: vi.fn(),
isAdminEnabled: vi.fn().mockReturnValue(true),
};
mockConfig = {
isSkillsSupportEnabled: vi.fn().mockReturnValue(true),
getSkillManager: vi.fn().mockReturnValue(mockSkillManager),
};
});
it('should return an empty array if skills support is disabled', async () => {
mockConfig.isSkillsSupportEnabled.mockReturnValue(false);
const loader = new SkillCommandLoader(mockConfig);
const commands = await loader.loadCommands(new AbortController().signal);
expect(commands).toEqual([]);
});
it('should return an empty array if SkillManager is missing', async () => {
mockConfig.getSkillManager.mockReturnValue(null);
const loader = new SkillCommandLoader(mockConfig);
const commands = await loader.loadCommands(new AbortController().signal);
expect(commands).toEqual([]);
});
it('should return an empty array if skills are admin-disabled', async () => {
mockSkillManager.isAdminEnabled.mockReturnValue(false);
const loader = new SkillCommandLoader(mockConfig);
const commands = await loader.loadCommands(new AbortController().signal);
expect(commands).toEqual([]);
});
it('should load skills as slash commands', async () => {
const mockSkills = [
{ name: 'skill1', description: 'Description 1' },
{ name: 'skill2', description: '' },
];
mockSkillManager.getDisplayableSkills.mockReturnValue(mockSkills);
const loader = new SkillCommandLoader(mockConfig);
const commands = await loader.loadCommands(new AbortController().signal);
expect(commands).toHaveLength(2);
expect(commands[0]).toMatchObject({
name: 'skill1',
description: 'Description 1',
kind: CommandKind.SKILL,
autoExecute: true,
});
expect(commands[1]).toMatchObject({
name: 'skill2',
description: 'Activate the skill2 skill',
kind: CommandKind.SKILL,
autoExecute: true,
});
});
it('should return a tool action when a skill command is executed', async () => {
const mockSkills = [{ name: 'test-skill', description: 'Test skill' }];
mockSkillManager.getDisplayableSkills.mockReturnValue(mockSkills);
const loader = new SkillCommandLoader(mockConfig);
const commands = await loader.loadCommands(new AbortController().signal);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const actionResult = await commands[0].action!({} as any, '');
expect(actionResult).toEqual({
type: 'tool',
toolName: ACTIVATE_SKILL_TOOL_NAME,
toolArgs: { name: 'test-skill' },
postSubmitPrompt: 'Use the skill test-skill',
});
});
it('should return a tool action with postSubmitPrompt when args are provided', async () => {
const mockSkills = [{ name: 'test-skill', description: 'Test skill' }];
mockSkillManager.getDisplayableSkills.mockReturnValue(mockSkills);
const loader = new SkillCommandLoader(mockConfig);
const commands = await loader.loadCommands(new AbortController().signal);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const actionResult = await commands[0].action!({} as any, 'hello world');
expect(actionResult).toEqual({
type: 'tool',
toolName: ACTIVATE_SKILL_TOOL_NAME,
toolArgs: { name: 'test-skill' },
postSubmitPrompt: 'hello world',
});
});
it('should sanitize skill names with spaces', async () => {
const mockSkills = [{ name: 'my awesome skill', description: 'Desc' }];
mockSkillManager.getDisplayableSkills.mockReturnValue(mockSkills);
const loader = new SkillCommandLoader(mockConfig);
const commands = await loader.loadCommands(new AbortController().signal);
expect(commands[0].name).toBe('my-awesome-skill');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const actionResult = (await commands[0].action!({} as any, '')) as any;
expect(actionResult.toolArgs).toEqual({ name: 'my awesome skill' });
});
it('should propagate extensionName to the generated slash command', async () => {
const mockSkills = [
{ name: 'skill1', description: 'desc', extensionName: 'ext1' },
];
mockSkillManager.getDisplayableSkills.mockReturnValue(mockSkills);
const loader = new SkillCommandLoader(mockConfig);
const commands = await loader.loadCommands(new AbortController().signal);
expect(commands[0].extensionName).toBe('ext1');
});
});
|