Skip to main content
Glama
docs-parser.test.ts9.32 kB
import { describe, it, expect } from "vitest"; // Test the internal functions by importing the module and testing via parseDocumentation // Since internal functions aren't exported, we test behavior through the public API // For unit testing, we'll create mock implementations that test the parsing logic describe("Docs Parser", () => { describe("frontmatter extraction", () => { it("extracts frontmatter with title", () => { const content = `--- title: My Page category: guides --- # Content here`; const result = extractFrontmatter(content); expect(result.frontmatter.title).toBe("My Page"); expect(result.frontmatter.category).toBe("guides"); expect(result.body).toContain("# Content here"); }); it("handles content without frontmatter", () => { const content = `# Just a heading Some content without frontmatter`; const result = extractFrontmatter(content); expect(result.frontmatter).toEqual({}); expect(result.body).toBe(content); }); it("handles quoted values in frontmatter", () => { const content = `--- title: "Quoted Title" description: 'Single quoted' --- Body`; const result = extractFrontmatter(content); expect(result.frontmatter.title).toBe("Quoted Title"); expect(result.frontmatter.description).toBe("Single quoted"); }); }); describe("title extraction", () => { it("extracts h1 from markdown", () => { const content = `# My Title Some content`; expect(extractTitle(content)).toBe("My Title"); }); it("extracts h1 from HTML", () => { const content = `<h1>HTML Title</h1> Some content`; expect(extractTitle(content)).toBe("HTML Title"); }); it("returns Untitled when no heading found", () => { const content = `Some content without heading`; expect(extractTitle(content)).toBe("Untitled"); }); }); describe("keyword extraction", () => { it("extracts component names (MButton, MCard)", () => { const content = `Use MButton for actions and MCard for containers`; const keywords = extractKeywords(content, "Components"); expect(keywords).toContain("mbutton"); expect(keywords).toContain("mcard"); }); it("extracts CSS class names (mc-*)", () => { const content = `Apply mc-button and mc-card-primary classes`; const keywords = extractKeywords(content, "Styling"); expect(keywords).toContain("mc-button"); expect(keywords).toContain("mc-card-primary"); }); it("extracts code keywords", () => { const content = `Define props and emit events with slots`; const keywords = extractKeywords(content, "API"); expect(keywords).toContain("props"); expect(keywords).toContain("emit"); expect(keywords).toContain("events"); expect(keywords).toContain("slots"); }); it("adds title words as keywords", () => { const keywords = extractKeywords("content", "Button Component Guide"); expect(keywords).toContain("button"); expect(keywords).toContain("component"); expect(keywords).toContain("guide"); }); it("skips short words from title", () => { const keywords = extractKeywords("content", "A is on"); // Words with 2 or fewer characters should be skipped expect(keywords).not.toContain("a"); expect(keywords).not.toContain("is"); expect(keywords).not.toContain("on"); }); }); describe("content cleaning", () => { it("removes import statements", () => { const content = `import { Button } from '@mozaic/vue' import React from 'react' # Content`; const cleaned = cleanContent(content); expect(cleaned).not.toContain("import"); expect(cleaned).toContain("# Content"); }); it("removes JSX components but keeps text content", () => { const content = `<MyComponent prop="value">Inner text</MyComponent>`; const cleaned = cleanContent(content); expect(cleaned).toContain("Inner text"); expect(cleaned).not.toContain("<MyComponent"); }); it("removes empty code blocks", () => { const content = "Before\n```\n```\nAfter"; const cleaned = cleanContent(content); expect(cleaned).not.toContain("```"); }); it("normalizes excessive whitespace", () => { const content = "Line 1\n\n\n\n\nLine 2"; const cleaned = cleanContent(content); expect(cleaned).toBe("Line 1\n\nLine 2"); }); }); describe("category inference", () => { it("infers components category", () => { expect(inferCategory("/docs/components/button/index.mdx")).toBe("components"); }); it("infers foundations category from foundation path", () => { expect(inferCategory("/docs/foundation/colors.mdx")).toBe("foundations"); }); it("infers foundations category from tokens path", () => { expect(inferCategory("/docs/tokens/spacing.mdx")).toBe("foundations"); }); it("infers patterns category", () => { expect(inferCategory("/docs/patterns/forms.mdx")).toBe("patterns"); }); it("infers guides category from getting-started", () => { expect(inferCategory("/docs/getting-started/install.mdx")).toBe("guides"); }); it("infers guides category from guide path", () => { expect(inferCategory("/docs/guide/usage.mdx")).toBe("guides"); }); it("returns other for unknown paths", () => { expect(inferCategory("/docs/random/page.mdx")).toBe("other"); }); }); describe("URL path generation", () => { it("generates URL path from file path", () => { const result = generateUrlPath("/docs/components/button/index.mdx", "/docs"); expect(result).toBe("/components/button"); }); it("removes .mdx extension", () => { const result = generateUrlPath("/docs/guide.mdx", "/docs"); expect(result).toBe("/guide"); }); it("removes .md extension", () => { const result = generateUrlPath("/docs/readme.md", "/docs"); expect(result).toBe("/readme"); }); it("converts to lowercase", () => { const result = generateUrlPath("/docs/Components/Button.mdx", "/docs"); expect(result).toBe("/components/button"); }); }); }); // Helper functions to test - reimplemented for unit testing function extractFrontmatter(content: string): { frontmatter: Record<string, string>; body: string; } { const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/; const match = content.match(frontmatterRegex); if (match) { const frontmatterStr = match[1]; const body = match[2]; const frontmatter: Record<string, string> = {}; const lines = frontmatterStr.split("\n"); for (const line of lines) { const colonIndex = line.indexOf(":"); if (colonIndex !== -1) { const key = line.substring(0, colonIndex).trim(); const value = line .substring(colonIndex + 1) .trim() .replace(/^['"]|['"]$/g, ""); frontmatter[key] = value; } } return { frontmatter, body }; } return { frontmatter: {}, body: content }; } function extractTitle(content: string): string { const h1Match = content.match(/^#\s+(.+)$/m); if (h1Match) { return h1Match[1]; } const mdxMatch = content.match(/<h1[^>]*>([^<]+)<\/h1>/i); if (mdxMatch) { return mdxMatch[1]; } return "Untitled"; } function extractKeywords(content: string, title: string): string[] { const keywords = new Set<string>(); title.split(/\s+/).forEach((word) => { if (word.length > 2) { keywords.add(word.toLowerCase()); } }); const componentRegex = /\bM[A-Z][a-zA-Z]+/g; const componentMatches = content.match(componentRegex); if (componentMatches) { componentMatches.forEach((match) => keywords.add(match.toLowerCase())); } const cssClassRegex = /\bmc-[a-z0-9-]+/g; const cssMatches = content.match(cssClassRegex); if (cssMatches) { cssMatches.forEach((match) => keywords.add(match)); } const codeKeywords = ["props", "slots", "events", "emit", "component", "style"]; codeKeywords.forEach((keyword) => { if (content.toLowerCase().includes(keyword)) { keywords.add(keyword); } }); return Array.from(keywords); } function cleanContent(content: string): string { let cleaned = content.replace(/^import\s+.*$/gm, ""); cleaned = cleaned.replace(/<[A-Z][a-zA-Z]*[^>]*>([\s\S]*?)<\/[A-Z][a-zA-Z]*>/g, "$1"); cleaned = cleaned.replace(/```\s*```/g, ""); cleaned = cleaned.replace(/\n{3,}/g, "\n\n"); return cleaned.trim(); } function inferCategory(filePath: string): string { const pathLower = filePath.toLowerCase(); if (pathLower.includes("component")) { return "components"; } if (pathLower.includes("foundation") || pathLower.includes("token")) { return "foundations"; } if (pathLower.includes("pattern")) { return "patterns"; } if (pathLower.includes("getting-started") || pathLower.includes("guide")) { return "guides"; } return "other"; } function generateUrlPath(filePath: string, basePath: string): string { let urlPath = filePath.replace(basePath, ""); urlPath = urlPath.replace(/\.(mdx?|md)$/, ""); urlPath = urlPath.replace(/\\/g, "/").toLowerCase(); urlPath = urlPath.replace(/\/index$/, ""); return urlPath; }

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