Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions docs-site/e2e/copy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ test.describe('Copy to Clipboard', () => {
});

test('copy button is visible on skill cards', async ({ page }) => {
const skillCard = page.locator('.skills-grid a').first();
const skillCard = page.locator('.skills-grid > div').first();
await skillCard.waitFor({ state: 'visible', timeout: 10000 });

await skillCard.hover();
Expand All @@ -18,7 +18,7 @@ test.describe('Copy to Clipboard', () => {
});

test('clicking copy button shows success feedback', async ({ page }) => {
const skillCard = page.locator('.skills-grid a').first();
const skillCard = page.locator('.skills-grid > div').first();
await skillCard.waitFor({ state: 'visible', timeout: 10000 });
await skillCard.hover();

Expand All @@ -30,17 +30,22 @@ test.describe('Copy to Clipboard', () => {
await expect(copiedButton).toBeVisible({ timeout: 3000 });
});

test('copy button copies correct install command', async ({ page }) => {
const skillCard = page.locator('.skills-grid a').first();
await skillCard.waitFor({ state: 'visible', timeout: 10000 });
await skillCard.hover();

const copyButton = skillCard.locator('button:has-text("Copy")');
await expect(copyButton).toBeVisible();
await copyButton.click();

const clipboardText = await page.evaluate(() => navigator.clipboard.readText());

expect(clipboardText).toMatch(/npx skills add microsoft\/skills --skill/);
});
const commandCases = [
{ source: 'direct', name: 'mcp-builder', command: 'npx skills add microsoft/skills --skill mcp-builder' },
{ source: 'azure-skills plugin', name: 'microsoft-foundry', command: 'npx skills add microsoft/azure-skills --skill microsoft-foundry' },
{ source: 'generic plugin', name: 'azure-ai-projects-py', command: 'npx skills add https://github.com/microsoft/skills/tree/main/.github/plugins/azure-sdk-python/skills/azure-ai-projects-py' },
];

for (const { source, name, command } of commandCases) {
test(`copy button has exact install command for ${source} skill (${name})`, async ({ page }) => {
await page.getByTestId('search-input').fill(name);
const skillCard = page.locator('.skills-grid > div').filter({
has: page.getByRole('heading', { level: 3, name, exact: true }),
});
await skillCard.waitFor({ state: 'visible', timeout: 10000 });

const copyButton = skillCard.getByRole('button', { name: 'Copy' });
await expect(copyButton).toHaveAttribute('title', `Copy: ${command}`);
});
}
});
13 changes: 12 additions & 1 deletion docs-site/src/components/SkillCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,24 @@ export const LANG_LABELS: Record<string, string> = {
core: 'Core',
};

// Skills come from different sources/plugins.
// First matching prefix wins, and default to this repo's root.
export function getInstallCommand(skill: Pick<Skill, 'name' | 'path'>): string {
const sources: [prefix: string, command: () => string][] = [
['.github/plugins/azure-skills/', () => `npx skills add microsoft/azure-skills --skill ${skill.name}`],
['.github/plugins/', () => `npx skills add https://github.com/microsoft/skills/tree/main/${skill.path}`],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this prefix cover a skill that cannot be installed via either of the other two commands?

];
const match = sources.find(([prefix]) => skill.path.startsWith(prefix));
return match ? match[1]() : `npx skills add microsoft/skills --skill ${skill.name}`;
}

export function SkillCard({ skill, onClick }: SkillCardProps) {
const [copied, setCopied] = useState(false);

const langStyle = LANG_STYLES[skill.language] || LANG_STYLES.core;
const langLabel = LANG_LABELS[skill.language] || skill.language;

const installCommand = `npx skills add microsoft/skills --skill ${skill.name}`;
const installCommand = getInstallCommand(skill);

const handleCopy = useCallback(async (e: React.MouseEvent) => {
e.preventDefault();
Expand Down
4 changes: 2 additions & 2 deletions docs-site/src/components/SkillDetailModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState, useCallback } from 'react';
import type { Skill } from './SkillCard';
import { LANG_STYLES, LANG_LABELS } from './SkillCard';
import { LANG_STYLES, LANG_LABELS, getInstallCommand } from './SkillCard';

interface SkillDetailModalProps {
skill: Skill | null;
Expand Down Expand Up @@ -30,7 +30,7 @@ export function SkillDetailModal({ skill, onClose }: SkillDetailModalProps) {
}
}, [onClose]);

const installCommand = skill ? `npx skills add microsoft/skills --skill ${skill.name}` : '';
const installCommand = skill ? getInstallCommand(skill) : '';

const handleCopy = useCallback(async () => {
try {
Expand Down
Loading