Files
oh-my-pi/packages/coding-agent/test/utils/lang-from-path.test.ts
T
oldschoola 5e1bff6b2b fix(utils): CMakeLists.txt returns cmake instead of text in getLanguageFromPath
The .txt extension match was winning over the basename check in
getLanguageFromPath, returning 'text' instead of 'cmake' for
CMakeLists.txt. Reordered the function to check basename special-cases
first (mirroring detectLanguageId's structure), so CMakeLists.txt,
Dockerfile, .env., .emacs, and justfile all fire before the extension
lookup.

Rewrote tests to defend observable contracts (special filenames, case
handling, lookup ordering) instead of re-stating the EXTENSION_LANG
table. Added contract test pinning that CMakeLists.txt resolves to
'cmake' not 'text'.

Updated CHANGELOG: moved from ### Added (test-only) to ### Fixed
(bug fix).
2026-06-23 10:25:12 -07:00

114 lines
4.0 KiB
TypeScript

/**
* Contract tests for language detection from file paths.
*
* `getLanguageFromPath` returns the highlight language id for a given file
* path, or undefined if unknown. `detectLanguageId` returns the LSP language
* identifier, falling back to "plaintext".
*
* These tests defend observable contracts (special filenames, case handling,
* unknown fallbacks, lookup ordering) — not individual entries from the
* EXTENSION_LANG table, which would just re-state the lookup map.
*/
import { describe, expect, it } from "bun:test";
import { detectLanguageId, getLanguageFromPath } from "../../src/utils/lang-from-path";
describe("getLanguageFromPath", () => {
it("detects Dockerfile by basename (case-insensitive)", () => {
expect(getLanguageFromPath("Dockerfile")).toBe("dockerfile");
expect(getLanguageFromPath("dockerfile")).toBe("dockerfile");
expect(getLanguageFromPath("Dockerfile.dev")).toBe("dockerfile");
expect(getLanguageFromPath("DOCKERFILE")).toBe("dockerfile");
});
it("detects Containerfile", () => {
expect(getLanguageFromPath("Containerfile")).toBe("dockerfile");
});
it("detects .env files by prefix", () => {
expect(getLanguageFromPath(".env.local")).toBe("env");
expect(getLanguageFromPath(".env.production")).toBe("env");
// .env itself — themeExtensionKey returns "env" which matches the table
expect(getLanguageFromPath(".env")).toBe("env");
});
it("detects .emacs", () => {
expect(getLanguageFromPath(".emacs")).toBe("emacs-lisp");
});
it("detects justfile", () => {
expect(getLanguageFromPath("justfile")).toBe("just");
});
it("detects CMakeLists.txt as cmake (basename wins over .txt extension)", () => {
// Without the basename-first check, .txt would match the extension table
// and return "text" instead of "cmake". This test pins that the basename
// check fires before the extension lookup.
expect(getLanguageFromPath("CMakeLists.txt")).toBe("cmake");
expect(getLanguageFromPath("cmakelists.txt")).toBe("cmake");
});
it("is case-insensitive on extensions", () => {
expect(getLanguageFromPath("Main.TS")).toBe("typescript");
expect(getLanguageFromPath("App.TSX")).toBe("tsx");
});
it("returns undefined for unknown extensions", () => {
expect(getLanguageFromPath("file.unknownext")).toBeUndefined();
expect(getLanguageFromPath("file.xyz123")).toBeUndefined();
});
it("returns undefined for files with no extension", () => {
expect(getLanguageFromPath("README")).toBeUndefined();
});
it("returns the last extension when multiple dots are present", () => {
expect(getLanguageFromPath("config.test.ts")).toBe("typescript");
});
it("handles full paths with directories", () => {
expect(getLanguageFromPath("/home/user/project/src/index.ts")).toBe("typescript");
expect(getLanguageFromPath("C:\\Users\\dev\\app\\main.rs")).toBe("rust");
});
});
describe("detectLanguageId", () => {
it("detects Dockerfile as dockerfile", () => {
expect(detectLanguageId("Dockerfile")).toBe("dockerfile");
expect(detectLanguageId("dockerfile.dev")).toBe("dockerfile");
});
it("detects Containerfile as dockerfile", () => {
expect(detectLanguageId("Containerfile")).toBe("dockerfile");
});
it("detects .emacs as emacs-lisp", () => {
expect(detectLanguageId(".emacs")).toBe("emacs-lisp");
});
it("detects Makefile as makefile", () => {
expect(detectLanguageId("Makefile")).toBe("makefile");
expect(detectLanguageId("makefile")).toBe("makefile");
expect(detectLanguageId("gnumakefile")).toBe("makefile");
});
it("detects justfile as just", () => {
expect(detectLanguageId("justfile")).toBe("just");
});
it("detects CMakeLists.txt as cmake", () => {
expect(detectLanguageId("CMakeLists.txt")).toBe("cmake");
});
it("detects .cmake extension as cmake", () => {
expect(detectLanguageId("FindPackage.cmake")).toBe("cmake");
});
it("falls back to plaintext for unknown extensions", () => {
expect(detectLanguageId("file.unknownext")).toBe("plaintext");
});
it("falls back to plaintext for files with no extension", () => {
expect(detectLanguageId("README")).toBe("plaintext");
});
});