Files
emdash-cms--emdash/packages/admin/tests/editor/codeBlockLanguages.test.ts
T
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

140 lines
5.1 KiB
TypeScript

/**
* Tests for the code block language helpers.
*
* Covers id/alias lookup, normalization of free-form input, and human-readable
* label fallback. These are the helpers backing the editor's language picker
* and the round-trip between user input and the stored `language` attribute.
*/
import { i18n } from "@lingui/core";
import { describe, it, expect } from "vitest";
import {
CODE_BLOCK_LANGUAGES,
findLanguage,
languageLabelDescriptor,
normalizeLanguage,
} from "../../src/components/editor/codeBlockLanguages";
function resolveLabel(
label: ReturnType<typeof languageLabelDescriptor> | undefined,
): string | undefined {
if (!label) return undefined;
return typeof label === "string" ? label : i18n._(label);
}
describe("findLanguage", () => {
it("returns the canonical entry for a known id", () => {
const ts = findLanguage("typescript");
expect(ts?.id).toBe("typescript");
expect(resolveLabel(ts?.label)).toBe("TypeScript");
});
it("resolves aliases to the canonical entry", () => {
expect(findLanguage("ts")?.id).toBe("typescript");
expect(findLanguage("js")?.id).toBe("javascript");
expect(findLanguage("sh")?.id).toBe("bash");
expect(findLanguage("c++")?.id).toBe("cpp");
expect(findLanguage("c#")?.id).toBe("csharp");
expect(findLanguage("md")?.id).toBe("markdown");
expect(findLanguage("yml")?.id).toBe("yaml");
});
it("is case-insensitive", () => {
expect(findLanguage("TypeScript")?.id).toBe("typescript");
expect(findLanguage("HTML")?.id).toBe("html");
expect(findLanguage(" Python ")?.id).toBe("python");
});
it("returns null for unknown or empty input", () => {
expect(findLanguage("brainfuck")).toBeNull();
expect(findLanguage("")).toBeNull();
expect(findLanguage(null)).toBeNull();
expect(findLanguage(undefined)).toBeNull();
expect(findLanguage(" ")).toBeNull();
});
it("has no duplicate ids or aliases in the curated list", () => {
const seen = new Set<string>();
for (const lang of CODE_BLOCK_LANGUAGES) {
expect(seen.has(lang.id), `duplicate id: ${lang.id}`).toBe(false);
seen.add(lang.id);
for (const alias of lang.aliases ?? []) {
expect(seen.has(alias), `duplicate alias: ${alias}`).toBe(false);
seen.add(alias);
}
}
});
});
describe("normalizeLanguage", () => {
it("returns the canonical id for a known language or alias", () => {
expect(normalizeLanguage("ts")).toBe("typescript");
expect(normalizeLanguage("TypeScript")).toBe("typescript");
expect(normalizeLanguage("c++")).toBe("cpp");
});
it("lowercases and trims unknown input so class names stay stable", () => {
expect(normalizeLanguage(" Brainfuck ")).toBe("brainfuck");
expect(normalizeLanguage("Erlang")).toBe("erlang");
});
it("collapses internal whitespace so the value remains a single class token", () => {
// Frontend renders `<pre class="language-${id}">`; whitespace would
// split that into two classes (`language-objective c`), so unknown
// inputs with spaces are joined with a single hyphen.
expect(normalizeLanguage("Objective C")).toBe("objective-c");
expect(normalizeLanguage("My Lang")).toBe("my-lang");
expect(normalizeLanguage("Pure\tScript")).toBe("pure-script");
expect(normalizeLanguage("a\nb")).toBe("a-b");
});
it("strips other characters that would break a CSS class token", () => {
// Dots, slashes, and other punctuation get collapsed to `-`.
expect(normalizeLanguage("foo.bar")).toBe("foo-bar");
expect(normalizeLanguage("a/b")).toBe("a-b");
expect(normalizeLanguage("plain!text")).toBe("plain-text");
});
it("preserves hyphens and underscores in unknown input", () => {
expect(normalizeLanguage("my-lang")).toBe("my-lang");
expect(normalizeLanguage("my_lang")).toBe("my_lang");
});
it("trims leading and trailing hyphens introduced by sanitization", () => {
expect(normalizeLanguage("@swift")).toBe("swift");
expect(normalizeLanguage("rust!")).toBe("rust");
expect(normalizeLanguage("---x---")).toBe("x");
});
it("returns undefined for input that sanitizes to an empty string", () => {
expect(normalizeLanguage("!!!")).toBeUndefined();
expect(normalizeLanguage("@@@")).toBeUndefined();
});
it("returns undefined for empty input", () => {
expect(normalizeLanguage("")).toBeUndefined();
expect(normalizeLanguage(null)).toBeUndefined();
expect(normalizeLanguage(undefined)).toBeUndefined();
expect(normalizeLanguage(" ")).toBeUndefined();
});
});
describe("languageLabelDescriptor", () => {
it("returns the curated label for known languages", () => {
expect(resolveLabel(languageLabelDescriptor("typescript"))).toBe("TypeScript");
expect(resolveLabel(languageLabelDescriptor("ts"))).toBe("TypeScript");
expect(resolveLabel(languageLabelDescriptor("cpp"))).toBe("C++");
});
it("falls back to the raw id for unknown languages", () => {
expect(resolveLabel(languageLabelDescriptor("brainfuck"))).toBe("brainfuck");
});
it("returns a friendly default for empty input", () => {
expect(resolveLabel(languageLabelDescriptor(null))).toBe("Plain text");
expect(resolveLabel(languageLabelDescriptor(undefined))).toBe("Plain text");
expect(resolveLabel(languageLabelDescriptor(""))).toBe("Plain text");
});
});