import { describe, it, expect } from "vitest"; import { LanguageRegistry } from "../languages/language-registry.js"; import { StrictLanguageConfigSchema } from "../languages/types.js"; import { typescriptConfig } from "../languages/configs/typescript.js"; import { pythonConfig } from "../languages/configs/python.js"; describe("LanguageRegistry", () => { it("registers and retrieves a language config by id", () => { const registry = new LanguageRegistry(); registry.register(typescriptConfig); expect(registry.getById("typescript")).toEqual(typescriptConfig); }); it("retrieves config by file extension", () => { const registry = new LanguageRegistry(); registry.register(typescriptConfig); expect(registry.getByExtension(".ts")?.id).toBe("typescript"); expect(registry.getByExtension(".tsx")?.id).toBe("typescript"); }); it("retrieves config for a file path", () => { const registry = new LanguageRegistry(); registry.register(typescriptConfig); registry.register(pythonConfig); expect(registry.getForFile("src/index.ts")?.id).toBe("typescript"); expect(registry.getForFile("app/models.py")?.id).toBe("python"); }); it("returns null for unknown extensions", () => { const registry = new LanguageRegistry(); registry.register(typescriptConfig); expect(registry.getByExtension(".xyz")).toBeNull(); expect(registry.getForFile("file.unknown")).toBeNull(); }); it("returns null for files without extensions and no filename match", () => { const registry = new LanguageRegistry(); expect(registry.getForFile("SOMEFILE")).toBeNull(); }); it("lists all registered languages", () => { const registry = new LanguageRegistry(); registry.register(typescriptConfig); registry.register(pythonConfig); const all = registry.getAllLanguages(); expect(all).toHaveLength(2); expect(all.map(c => c.id)).toContain("typescript"); expect(all.map(c => c.id)).toContain("python"); }); describe("createDefault", () => { it("registers all 40 built-in language configs", () => { const registry = LanguageRegistry.createDefault(); const all = registry.getAllLanguages(); expect(all.length).toBe(40); }); it("maps all expected extensions", () => { const registry = LanguageRegistry.createDefault(); expect(registry.getByExtension(".ts")?.id).toBe("typescript"); expect(registry.getByExtension(".py")?.id).toBe("python"); expect(registry.getByExtension(".go")?.id).toBe("go"); expect(registry.getByExtension(".rs")?.id).toBe("rust"); expect(registry.getByExtension(".java")?.id).toBe("java"); expect(registry.getByExtension(".rb")?.id).toBe("ruby"); expect(registry.getByExtension(".php")?.id).toBe("php"); expect(registry.getByExtension(".swift")?.id).toBe("swift"); expect(registry.getByExtension(".kt")?.id).toBe("kotlin"); expect(registry.getByExtension(".cs")?.id).toBe("csharp"); expect(registry.getByExtension(".cpp")?.id).toBe("cpp"); expect(registry.getByExtension(".c")?.id).toBe("c"); expect(registry.getByExtension(".h")?.id).toBe("c"); expect(registry.getByExtension(".lua")?.id).toBe("lua"); expect(registry.getByExtension(".js")?.id).toBe("javascript"); }); it("has no duplicate extension mappings across configs", () => { const registry = LanguageRegistry.createDefault(); const all = registry.getAllLanguages(); const allExtensions: string[] = []; for (const config of all) { allExtensions.push(...config.extensions); } const unique = new Set(allExtensions); expect(unique.size).toBe(allExtensions.length); }); it("every config has at least one concept", () => { const registry = LanguageRegistry.createDefault(); for (const config of registry.getAllLanguages()) { expect(config.concepts.length).toBeGreaterThan(0); } }); }); describe("Non-code language configs", () => { it("detects all non-code file types via extension", () => { const registry = LanguageRegistry.createDefault(); const expectations: [string, string][] = [ ["README.md", "markdown"], ["config.yaml", "yaml"], ["package.json", "json"], ["config.toml", "toml"], [".env", "env"], ["pom.xml", "xml"], ["Dockerfile", "dockerfile"], ["schema.sql", "sql"], ["schema.graphql", "graphql"], ["types.proto", "protobuf"], ["main.tf", "terraform"], ["Makefile", "makefile"], ["deploy.sh", "shell"], ["index.html", "html"], ["styles.css", "css"], ["data.csv", "csv"], ["deploy.ps1", "powershell"], ]; for (const [file, expectedId] of expectations) { const config = registry.getForFile(file); expect(config?.id, `${file} should be detected as ${expectedId}`).toBe(expectedId); } }); it("detects filename-based configs (Dockerfile, Makefile, Jenkinsfile)", () => { const registry = LanguageRegistry.createDefault(); expect(registry.getForFile("Dockerfile")?.id).toBe("dockerfile"); expect(registry.getForFile("Makefile")?.id).toBe("makefile"); expect(registry.getForFile("Jenkinsfile")?.id).toBe("jenkinsfile"); expect(registry.getForFile("src/Dockerfile")?.id).toBe("dockerfile"); expect(registry.getForFile("build/Makefile")?.id).toBe("makefile"); }); it("detects filename-based configs for docker-compose", () => { const registry = LanguageRegistry.createDefault(); expect(registry.getForFile("docker-compose.yml")?.id).toBe("docker-compose"); expect(registry.getForFile("docker-compose.yaml")?.id).toBe("docker-compose"); expect(registry.getForFile("compose.yml")?.id).toBe("docker-compose"); }); it("detects .env file variants", () => { const registry = LanguageRegistry.createDefault(); expect(registry.getForFile(".env")?.id).toBe("env"); expect(registry.getForFile(".env.local")?.id).toBe("env"); expect(registry.getForFile(".env.production")?.id).toBe("env"); }); }); describe("StrictLanguageConfigSchema refinement", () => { it("rejects configs with empty extensions AND no filenames", () => { const result = StrictLanguageConfigSchema.safeParse({ id: "empty-lang", displayName: "Empty", extensions: [], concepts: ["nothing"], filePatterns: { entryPoints: [], barrels: [], tests: [], config: [] }, }); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain("at least one extension or filename"); } }); it("rejects configs with empty extensions AND empty filenames", () => { const result = StrictLanguageConfigSchema.safeParse({ id: "empty-lang", displayName: "Empty", extensions: [], filenames: [], concepts: ["nothing"], filePatterns: { entryPoints: [], barrels: [], tests: [], config: [] }, }); expect(result.success).toBe(false); }); it("accepts configs with extensions but no filenames", () => { const result = StrictLanguageConfigSchema.safeParse({ id: "ext-lang", displayName: "ExtLang", extensions: [".ext"], concepts: ["something"], filePatterns: { entryPoints: [], barrels: [], tests: [], config: [] }, }); expect(result.success).toBe(true); }); it("accepts configs with filenames but empty extensions", () => { const result = StrictLanguageConfigSchema.safeParse({ id: "filename-lang", displayName: "FilenameLang", extensions: [], filenames: ["Specialfile"], concepts: ["something"], filePatterns: { entryPoints: [], barrels: [], tests: [], config: [] }, }); expect(result.success).toBe(true); }); }); });