Skip to main content
Glama
vue-parser.test.ts7.32 kB
import { describe, it, expect } from "vitest"; // Test the Vue prop extraction patterns directly describe("Vue Parser - extractProps", () => { // Helper to extract props using the same logic as vue-parser.ts function extractProps(content: string) { const props: Array<{ name: string; type?: string; required?: boolean; defaultValue?: string; options?: string[]; description?: string; }> = []; // Vue 3 Composition API: defineProps<{...}>() with TypeScript generics const definePropsMatch = content.match(/defineProps<\{([\s\S]*?)\}>\s*\(\)/); if (definePropsMatch) { const propsContent = definePropsMatch[1]; // Extract defaults from withDefaults if present const defaultsMatch = content.match( /withDefaults\s*\(\s*defineProps<[\s\S]*?>\s*\(\)\s*,\s*\{([\s\S]*?)\}\s*\)/ ); const defaultsContent = defaultsMatch ? defaultsMatch[1] : ""; // Parse each prop with its JSDoc comment const propRegex = /(?:\/\*\*[\s\S]*?\*\/\s*)?(\w+)(\?)?:\s*([^;]+);/g; let match; while ((match = propRegex.exec(propsContent)) !== null) { const propName = match[1]; const isOptional = !!match[2]; const propType = match[3].trim(); // Extract description from JSDoc comment before this prop const beforeProp = propsContent.substring(0, match.index); // Match JSDoc: /** ... */ allowing for multi-line content with * prefixes const jsdocMatch = beforeProp.match(/\/\*\*([\s\S]*?)\*\/\s*$/); let description: string | undefined; if (jsdocMatch) { // Remove leading whitespace, asterisks, and collapse to single line description = jsdocMatch[1] .split("\n") .map((line) => line.replace(/^\s*\*?\s*/, "").trim()) .filter((line) => line.length > 0) .join(" ") .trim(); } // Extract default value const defaultMatch = defaultsContent.match( new RegExp(`${propName}\\s*:\\s*['"\`]?([^'"\`,}]+)['"\`]?`) ); const defaultValue = defaultMatch ? defaultMatch[1].trim() : undefined; // Extract options from union types let options: string[] | undefined; if (propType.includes("'") && propType.includes("|")) { options = propType .split("|") .map((s) => s.trim().replace(/['"]/g, "")) .filter((s) => s.length > 0); } props.push({ name: propName, type: propType, required: !isOptional, defaultValue, options, description, }); } } return props; } it("should extract props from defineProps with TypeScript generics", () => { const content = ` const props = defineProps<{ size?: 's' | 'm' | 'l'; disabled?: boolean; }>() `; const props = extractProps(content); expect(props).toHaveLength(2); expect(props[0].name).toBe("size"); expect(props[0].required).toBe(false); expect(props[0].options).toEqual(["s", "m", "l"]); expect(props[1].name).toBe("disabled"); expect(props[1].type).toBe("boolean"); }); it("should extract props with withDefaults", () => { const content = ` const props = withDefaults( defineProps<{ appearance?: 'standard' | 'accent' | 'danger'; size?: 's' | 'm' | 'l'; }>(), { appearance: 'standard', size: 'm', } ) `; const props = extractProps(content); expect(props).toHaveLength(2); expect(props[0].name).toBe("appearance"); expect(props[0].defaultValue).toBe("standard"); expect(props[0].options).toEqual(["standard", "accent", "danger"]); expect(props[1].name).toBe("size"); expect(props[1].defaultValue).toBe("m"); }); it("should handle boolean and string types", () => { const content = ` const props = defineProps<{ isLoading?: boolean; label: string; count?: number; }>() `; const props = extractProps(content); expect(props).toHaveLength(3); expect(props[0].name).toBe("isLoading"); expect(props[0].type).toBe("boolean"); expect(props[0].required).toBe(false); expect(props[1].name).toBe("label"); expect(props[1].type).toBe("string"); expect(props[1].required).toBe(true); expect(props[2].name).toBe("count"); expect(props[2].type).toBe("number"); }); }); describe("Vue Parser - extractSlots", () => { function extractSlots(content: string) { const slots: Array<{ name: string; description?: string }> = []; const seenSlots = new Set<string>(); const slotRegex = /<slot\s*(?:name=["']([^"']+)["'])?[^>]*>/g; let match; while ((match = slotRegex.exec(content)) !== null) { const slotName = match[1] || "default"; if (!seenSlots.has(slotName)) { seenSlots.add(slotName); slots.push({ name: slotName }); } } return slots; } it("should extract default slot", () => { const content = `<template><slot /></template>`; const slots = extractSlots(content); expect(slots).toHaveLength(1); expect(slots[0].name).toBe("default"); }); it("should extract named slots", () => { const content = ` <template> <slot name="header" /> <slot /> <slot name="footer" /> </template> `; const slots = extractSlots(content); expect(slots).toHaveLength(3); expect(slots.map((s) => s.name)).toContain("header"); expect(slots.map((s) => s.name)).toContain("default"); expect(slots.map((s) => s.name)).toContain("footer"); }); it("should not duplicate slots", () => { const content = ` <template> <slot name="icon" /> <slot name="icon" /> </template> `; const slots = extractSlots(content); expect(slots).toHaveLength(1); expect(slots[0].name).toBe("icon"); }); }); describe("Vue Parser - extractEvents", () => { function extractEvents(content: string) { const events: Array<{ name: string; payload?: string }> = []; const seenEvents = new Set<string>(); // Match emit calls: emit('eventName') or $emit('eventName') const emitPatterns = [/emit\s*\(\s*['"]([^'"]+)['"]/g, /\$emit\s*\(\s*['"]([^'"]+)['"]/g]; for (const pattern of emitPatterns) { let match; while ((match = pattern.exec(content)) !== null) { const eventName = match[1]; if (!seenEvents.has(eventName)) { seenEvents.add(eventName); events.push({ name: eventName }); } } } return events; } it("should extract emit events", () => { const content = ` const emit = defineEmits(['update', 'change']) emit('update', value) emit('change', newValue) `; const events = extractEvents(content); expect(events).toHaveLength(2); expect(events.map((e) => e.name)).toContain("update"); expect(events.map((e) => e.name)).toContain("change"); }); it("should extract $emit events from Options API", () => { const content = ` methods: { handleClick() { this.$emit('click', this.value) }, handleChange() { this.$emit('input', newValue) } } `; const events = extractEvents(content); expect(events).toHaveLength(2); expect(events.map((e) => e.name)).toContain("click"); expect(events.map((e) => e.name)).toContain("input"); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MerzoukeMansouri/adeo-mozaic-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server