CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
language-stats.test.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 07efa08 | 1 | /** |
| 2 | * Block J30 — Repository language breakdown. Pure helper tests. | |
| 3 | * | |
| 4 | * Exercises: | |
| 5 | * - detectLanguage (extension map, filename map, case-insensitivity, null paths) | |
| 6 | * - isVendoredOrGenerated (prefixes, nested prefixes, lockfile basenames) | |
| 7 | * - computeLanguageStats (vendoring, size caps, min-bytes fold, unclassified) | |
| 8 | * - foldIntoOther (idempotency, threshold, sort stability) | |
| 9 | * - formatBytes / formatPercent (boundaries + NaN / negative handling) | |
| 10 | * - buildLanguageReport (one-shot equivalence) | |
| 11 | * - Route smoke test | |
| 12 | * - __internal parity | |
| 13 | */ | |
| 14 | ||
| 15 | import { describe, it, expect } from "bun:test"; | |
| 16 | import { | |
| 17 | EXTENSION_MAP, | |
| 18 | FILENAME_MAP, | |
| 19 | LANGUAGE_COLORS, | |
| 20 | DEFAULT_LANGUAGE_COLOR, | |
| 21 | VENDORED_PREFIXES, | |
| 22 | GENERATED_SUFFIXES, | |
| 23 | detectLanguage, | |
| 24 | isVendoredOrGenerated, | |
| 25 | computeLanguageStats, | |
| 26 | foldIntoOther, | |
| 27 | formatBytes, | |
| 28 | formatPercent, | |
| 29 | buildLanguageReport, | |
| 30 | __internal, | |
| 31 | type LanguageFileEntry, | |
| 32 | type LanguageReport, | |
| 33 | } from "../lib/language-stats"; | |
| 34 | ||
| 35 | describe("language-stats — detectLanguage", () => { | |
| 36 | it("resolves common extensions", () => { | |
| 37 | expect(detectLanguage("src/app.ts")).toBe("TypeScript"); | |
| 38 | expect(detectLanguage("src/app.tsx")).toBe("TypeScript"); | |
| 39 | expect(detectLanguage("lib/foo.js")).toBe("JavaScript"); | |
| 40 | expect(detectLanguage("lib/foo.mjs")).toBe("JavaScript"); | |
| 41 | expect(detectLanguage("lib/foo.cjs")).toBe("JavaScript"); | |
| 42 | expect(detectLanguage("scripts/build.py")).toBe("Python"); | |
| 43 | expect(detectLanguage("pkg/main.go")).toBe("Go"); | |
| 44 | expect(detectLanguage("src/lib.rs")).toBe("Rust"); | |
| 45 | expect(detectLanguage("App.java")).toBe("Java"); | |
| 46 | expect(detectLanguage("App.kt")).toBe("Kotlin"); | |
| 47 | expect(detectLanguage("README.md")).toBe("Markdown"); | |
| 48 | expect(detectLanguage("docs/index.mdx")).toBe("MDX"); | |
| 49 | expect(detectLanguage("config.yaml")).toBe("YAML"); | |
| 50 | expect(detectLanguage("config.yml")).toBe("YAML"); | |
| 51 | expect(detectLanguage("package.json")).toBe("JSON"); | |
| 52 | expect(detectLanguage("query.gql")).toBe("GraphQL"); | |
| 53 | expect(detectLanguage("schema.graphql")).toBe("GraphQL"); | |
| 54 | expect(detectLanguage("style.scss")).toBe("SCSS"); | |
| 55 | expect(detectLanguage("index.html")).toBe("HTML"); | |
| 56 | expect(detectLanguage("hello.zig")).toBe("Zig"); | |
| 57 | expect(detectLanguage("hello.dart")).toBe("Dart"); | |
| 58 | }); | |
| 59 | ||
| 60 | it("resolves filename-only languages", () => { | |
| 61 | expect(detectLanguage("Dockerfile")).toBe("Dockerfile"); | |
| 62 | expect(detectLanguage("docker/Dockerfile")).toBe("Dockerfile"); | |
| 63 | expect(detectLanguage("Makefile")).toBe("Makefile"); | |
| 64 | expect(detectLanguage("GNUmakefile")).toBe("Makefile"); | |
| 65 | expect(detectLanguage("Rakefile")).toBe("Ruby"); | |
| 66 | expect(detectLanguage("Gemfile")).toBe("Ruby"); | |
| 67 | expect(detectLanguage("Jenkinsfile")).toBe("Groovy"); | |
| 68 | expect(detectLanguage("CMakeLists.txt")).toBe("CMake"); | |
| 69 | expect(detectLanguage("meson.build")).toBe("Meson"); | |
| 70 | }); | |
| 71 | ||
| 72 | it("is case-insensitive for both maps", () => { | |
| 73 | expect(detectLanguage("src/FOO.TS")).toBe("TypeScript"); | |
| 74 | expect(detectLanguage("DOCKERFILE")).toBe("Dockerfile"); | |
| 75 | expect(detectLanguage("makefile")).toBe("Makefile"); | |
| 76 | }); | |
| 77 | ||
| 78 | it("returns null for unrecognised files", () => { | |
| 79 | expect(detectLanguage("unknown.xyz")).toBeNull(); | |
| 80 | expect(detectLanguage("no-extension")).toBeNull(); | |
| 81 | expect(detectLanguage("")).toBeNull(); | |
| 82 | }); | |
| 83 | ||
| 84 | it("guards non-string input", () => { | |
| 85 | // @ts-expect-error — intentionally wrong | |
| 86 | expect(detectLanguage(null)).toBeNull(); | |
| 87 | // @ts-expect-error | |
| 88 | expect(detectLanguage(undefined)).toBeNull(); | |
| 89 | // @ts-expect-error | |
| 90 | expect(detectLanguage(42)).toBeNull(); | |
| 91 | }); | |
| 92 | ||
| 93 | it("treats dotfiles (leading-dot basenames) as extensionless when not in filename map", () => { | |
| 94 | // `.env`, `.gitignore` etc. have no extension by our rule. | |
| 95 | expect(detectLanguage(".gitignore")).toBeNull(); | |
| 96 | expect(detectLanguage(".env")).toBeNull(); | |
| 97 | }); | |
| 98 | ||
| 99 | it("picks the last extension only", () => { | |
| 100 | expect(detectLanguage("archive.tar.gz")).toBeNull(); | |
| 101 | expect(detectLanguage("index.d.ts")).toBe("TypeScript"); | |
| 102 | expect(detectLanguage("component.test.tsx")).toBe("TypeScript"); | |
| 103 | }); | |
| 104 | }); | |
| 105 | ||
| 106 | describe("language-stats — isVendoredOrGenerated", () => { | |
| 107 | it("flags top-level vendored prefixes", () => { | |
| 108 | for (const pref of VENDORED_PREFIXES) { | |
| 109 | expect(isVendoredOrGenerated(pref + "any/file.ts")).toBe(true); | |
| 110 | } | |
| 111 | }); | |
| 112 | it("flags nested vendored prefixes", () => { | |
| 113 | expect(isVendoredOrGenerated("pkg/node_modules/foo/bar.js")).toBe(true); | |
| 114 | expect(isVendoredOrGenerated("apps/web/dist/main.js")).toBe(true); | |
| 115 | }); | |
| 116 | it("flags lockfile basenames anywhere", () => { | |
| 117 | for (const sfx of GENERATED_SUFFIXES) { | |
| 118 | expect(isVendoredOrGenerated(sfx)).toBe(true); | |
| 119 | expect(isVendoredOrGenerated(`some/nested/${sfx}`)).toBe(true); | |
| 120 | } | |
| 121 | }); | |
| 122 | it("does not flag ordinary source files", () => { | |
| 123 | expect(isVendoredOrGenerated("src/app.ts")).toBe(false); | |
| 124 | expect(isVendoredOrGenerated("lib/package.json")).toBe(false); | |
| 125 | expect(isVendoredOrGenerated("README.md")).toBe(false); | |
| 126 | }); | |
| 127 | it("tolerates a leading slash", () => { | |
| 128 | expect(isVendoredOrGenerated("/node_modules/foo.js")).toBe(true); | |
| 129 | }); | |
| 130 | it("guards non-string input", () => { | |
| 131 | // @ts-expect-error | |
| 132 | expect(isVendoredOrGenerated(null)).toBe(false); | |
| 133 | // @ts-expect-error | |
| 134 | expect(isVendoredOrGenerated(undefined)).toBe(false); | |
| 135 | }); | |
| 136 | }); | |
| 137 | ||
| 138 | describe("language-stats — computeLanguageStats (basic rollup)", () => { | |
| 139 | const entries: LanguageFileEntry[] = [ | |
| 140 | { path: "src/a.ts", size: 100 }, | |
| 141 | { path: "src/b.ts", size: 300 }, | |
| 142 | { path: "src/c.py", size: 200 }, | |
| 143 | { path: "README.md", size: 50 }, | |
| 144 | { path: "unknown.xyz", size: 9999 }, // dropped | |
| 145 | ]; | |
| 146 | ||
| 147 | it("aggregates bytes per language and computes percentages", () => { | |
| 148 | const r = computeLanguageStats(entries); | |
| 149 | expect(r.totalFiles).toBe(5); | |
| 150 | expect(r.countedFiles).toBe(4); // unknown dropped | |
| 151 | expect(r.totalBytes).toBe(100 + 300 + 200 + 50); | |
| 152 | ||
| 153 | const ts = r.buckets.find((b) => b.language === "TypeScript")!; | |
| 154 | const py = r.buckets.find((b) => b.language === "Python")!; | |
| 155 | const md = r.buckets.find((b) => b.language === "Markdown")!; | |
| 156 | expect(ts.bytes).toBe(400); | |
| 157 | expect(ts.fileCount).toBe(2); | |
| 158 | expect(py.bytes).toBe(200); | |
| 159 | expect(md.bytes).toBe(50); | |
| 160 | ||
| 161 | const totalPct = r.buckets.reduce((acc, b) => acc + b.percent, 0); | |
| 162 | expect(Math.round(totalPct)).toBe(100); | |
| 163 | ||
| 164 | // Sorted by bytes desc — TS comes first. | |
| 165 | expect(r.buckets[0]!.language).toBe("TypeScript"); | |
| 166 | expect(r.primary?.language).toBe("TypeScript"); | |
| 167 | }); | |
| 168 | ||
| 169 | it("assigns colours from the map", () => { | |
| 170 | const r = computeLanguageStats(entries); | |
| 171 | const ts = r.buckets.find((b) => b.language === "TypeScript")!; | |
| 172 | expect(ts.color).toBe(LANGUAGE_COLORS.get("TypeScript")); | |
| 173 | }); | |
| 174 | ||
| 175 | it("falls back to the default colour for unmapped languages", () => { | |
| 176 | const out = computeLanguageStats([ | |
| 177 | { path: "hi.pl", size: 100 }, // Perl — not in LANGUAGE_COLORS | |
| 178 | ]); | |
| 179 | const perl = out.buckets.find((b) => b.language === "Perl")!; | |
| 180 | expect(perl.color).toBe(DEFAULT_LANGUAGE_COLOR); | |
| 181 | }); | |
| 182 | ||
| 183 | it("ignores vendored paths by default", () => { | |
| 184 | const r = computeLanguageStats([ | |
| 185 | { path: "src/a.ts", size: 100 }, | |
| 186 | { path: "node_modules/foo/bar.js", size: 10_000 }, | |
| 187 | { path: "dist/main.js", size: 20_000 }, | |
| 188 | { path: "package-lock.json", size: 5_000 }, | |
| 189 | ]); | |
| 190 | expect(r.totalBytes).toBe(100); | |
| 191 | expect(r.buckets).toHaveLength(1); | |
| 192 | expect(r.buckets[0]!.language).toBe("TypeScript"); | |
| 193 | }); | |
| 194 | ||
| 195 | it("counts vendored paths when ignoreVendored=false", () => { | |
| 196 | const r = computeLanguageStats( | |
| 197 | [ | |
| 198 | { path: "src/a.ts", size: 100 }, | |
| 199 | { path: "node_modules/foo/bar.js", size: 10_000 }, | |
| 200 | ], | |
| 201 | { ignoreVendored: false } | |
| 202 | ); | |
| 203 | expect(r.totalBytes).toBe(10_100); | |
| 204 | const js = r.buckets.find((b) => b.language === "JavaScript")!; | |
| 205 | expect(js.bytes).toBe(10_000); | |
| 206 | }); | |
| 207 | ||
| 208 | it("caps per-file size via maxFileSize", () => { | |
| 209 | const r = computeLanguageStats( | |
| 210 | [ | |
| 211 | { path: "src/small.ts", size: 100 }, | |
| 212 | { path: "src/huge.ts", size: 50_000 }, | |
| 213 | ], | |
| 214 | { maxFileSize: 1_000 } | |
| 215 | ); | |
| 216 | const ts = r.buckets.find((b) => b.language === "TypeScript")!; | |
| 217 | // small=100 + huge capped at 1_000 = 1_100 | |
| 218 | expect(ts.bytes).toBe(1_100); | |
| 219 | }); | |
| 220 | ||
| 221 | it("skips zero-size files and negative sizes", () => { | |
| 222 | const r = computeLanguageStats([ | |
| 223 | { path: "src/a.ts", size: 0 }, | |
| 224 | { path: "src/b.ts", size: -5 }, | |
| 225 | { path: "src/c.ts", size: 10 }, | |
| 226 | ]); | |
| 227 | expect(r.countedFiles).toBe(1); | |
| 228 | expect(r.totalBytes).toBe(10); | |
| 229 | }); | |
| 230 | ||
| 231 | it("skips entries with non-string path or bogus size", () => { | |
| 232 | const r = computeLanguageStats([ | |
| 233 | // @ts-expect-error | |
| 234 | { path: 42, size: 1 }, | |
| 235 | { path: "src/a.ts", size: Number.NaN }, | |
| 236 | { path: "src/b.ts", size: 10 }, | |
| 237 | ]); | |
| 238 | expect(r.totalBytes).toBe(10); | |
| 239 | }); | |
| 240 | ||
| 241 | it("folds languages below minLanguageBytes into Other", () => { | |
| 242 | const r = computeLanguageStats( | |
| 243 | [ | |
| 244 | { path: "src/big.ts", size: 10_000 }, | |
| 245 | { path: "tiny.py", size: 50 }, | |
| 246 | { path: "small.go", size: 100 }, | |
| 247 | ], | |
| 248 | { minLanguageBytes: 200 } | |
| 249 | ); | |
| 250 | const other = r.buckets.find((b) => b.language === "Other")!; | |
| 251 | expect(other.bytes).toBe(150); | |
| 252 | expect(other.fileCount).toBe(2); | |
| 253 | expect(other.color).toBe(LANGUAGE_COLORS.get("Other")); | |
| 254 | // Other must sort last. | |
| 255 | expect(r.buckets.at(-1)!.language).toBe("Other"); | |
| 256 | // primary must skip Other. | |
| 257 | expect(r.primary?.language).toBe("TypeScript"); | |
| 258 | }); | |
| 259 | ||
| 260 | it("returns empty report when nothing classifies", () => { | |
| 261 | const r = computeLanguageStats([ | |
| 262 | { path: "unknown.xyz", size: 10 }, | |
| 263 | ]); | |
| 264 | expect(r.totalBytes).toBe(0); | |
| 265 | expect(r.buckets).toHaveLength(0); | |
| 266 | expect(r.primary).toBeNull(); | |
| 267 | }); | |
| 268 | ||
| 269 | it("handles an empty input", () => { | |
| 270 | const r = computeLanguageStats([]); | |
| 271 | expect(r.totalFiles).toBe(0); | |
| 272 | expect(r.countedFiles).toBe(0); | |
| 273 | expect(r.totalBytes).toBe(0); | |
| 274 | expect(r.buckets).toEqual([]); | |
| 275 | expect(r.primary).toBeNull(); | |
| 276 | }); | |
| 277 | ||
| 278 | it("breaks bucket ties by language name", () => { | |
| 279 | const r = computeLanguageStats([ | |
| 280 | { path: "a.py", size: 100 }, | |
| 281 | { path: "a.rb", size: 100 }, | |
| 282 | { path: "a.go", size: 100 }, | |
| 283 | ]); | |
| 284 | // All equal — alphabetical. | |
| 285 | expect(r.buckets.map((b) => b.language)).toEqual([ | |
| 286 | "Go", | |
| 287 | "Python", | |
| 288 | "Ruby", | |
| 289 | ]); | |
| 290 | }); | |
| 291 | }); | |
| 292 | ||
| 293 | describe("language-stats — foldIntoOther", () => { | |
| 294 | function mkReport(pairs: Array<[string, number]>): LanguageReport { | |
| 295 | const total = pairs.reduce((acc, [, n]) => acc + n, 0); | |
| 296 | return { | |
| 297 | totalBytes: total, | |
| 298 | totalFiles: pairs.length, | |
| 299 | countedFiles: pairs.length, | |
| 300 | buckets: pairs.map(([language, bytes]) => ({ | |
| 301 | language, | |
| 302 | bytes, | |
| 303 | fileCount: 1, | |
| 304 | percent: total === 0 ? 0 : (bytes / total) * 100, | |
| 305 | color: LANGUAGE_COLORS.get(language) ?? DEFAULT_LANGUAGE_COLOR, | |
| 306 | })), | |
| 307 | primary: null, | |
| 308 | }; | |
| 309 | } | |
| 310 | ||
| 311 | it("folds buckets below the threshold into Other", () => { | |
| 312 | const r = mkReport([ | |
| 313 | ["TypeScript", 900], | |
| 314 | ["Python", 80], | |
| 315 | ["Ruby", 20], | |
| 316 | ]); | |
| 317 | const out = foldIntoOther(r, 10); // 10% | |
| 318 | // Only TS survives (Python=8%, Ruby=2%). | |
| 319 | expect(out.buckets.map((b) => b.language)).toEqual(["TypeScript", "Other"]); | |
| 320 | const other = out.buckets.find((b) => b.language === "Other")!; | |
| 321 | expect(other.bytes).toBe(100); | |
| 322 | expect(other.fileCount).toBe(2); | |
| 323 | expect(out.primary?.language).toBe("TypeScript"); | |
| 324 | }); | |
| 325 | ||
| 326 | it("is idempotent when called twice", () => { | |
| 327 | const r = mkReport([ | |
| 328 | ["TypeScript", 900], | |
| 329 | ["Python", 80], | |
| 330 | ["Ruby", 20], | |
| 331 | ]); | |
| 332 | const once = foldIntoOther(r, 10); | |
| 333 | const twice = foldIntoOther(once, 10); | |
| 334 | expect(twice.buckets.map((b) => `${b.language}:${b.bytes}`)).toEqual( | |
| 335 | once.buckets.map((b) => `${b.language}:${b.bytes}`) | |
| 336 | ); | |
| 337 | }); | |
| 338 | ||
| 339 | it("threshold <=0 is a no-op", () => { | |
| 340 | const r = mkReport([ | |
| 341 | ["TypeScript", 900], | |
| 342 | ["Python", 100], | |
| 343 | ]); | |
| 344 | expect(foldIntoOther(r, 0)).toBe(r); | |
| 345 | expect(foldIntoOther(r, -5)).toBe(r); | |
| 346 | }); | |
| 347 | ||
| 348 | it("Other always sorts last after folding", () => { | |
| 349 | const r = mkReport([ | |
| 350 | ["TypeScript", 500], | |
| 351 | ["Python", 500], | |
| 352 | ["Ruby", 1], | |
| 353 | ]); | |
| 354 | const out = foldIntoOther(r, 1); | |
| 355 | expect(out.buckets.at(-1)!.language).toBe("Other"); | |
| 356 | }); | |
| 357 | }); | |
| 358 | ||
| 359 | describe("language-stats — formatBytes", () => { | |
| 360 | it("formats bytes, KB, MB, GB, TB", () => { | |
| 361 | expect(formatBytes(0)).toBe("0 B"); | |
| 362 | expect(formatBytes(512)).toBe("512 B"); | |
| 363 | expect(formatBytes(1024)).toBe("1.0 KB"); | |
| 364 | expect(formatBytes(1536)).toBe("1.5 KB"); | |
| 365 | expect(formatBytes(1024 * 1024)).toBe("1.0 MB"); | |
| 366 | expect(formatBytes(1024 * 1024 * 1024)).toBe("1.0 GB"); | |
| 367 | expect(formatBytes(1024 ** 4)).toBe("1.0 TB"); | |
| 368 | }); | |
| 369 | it("drops decimals past 10 in a unit", () => { | |
| 370 | expect(formatBytes(10 * 1024)).toBe("10 KB"); | |
| 371 | expect(formatBytes(1024 * 1024 * 15)).toBe("15 MB"); | |
| 372 | }); | |
| 373 | it("handles negative and non-finite input", () => { | |
| 374 | expect(formatBytes(-1)).toBe("0 B"); | |
| 375 | expect(formatBytes(Number.NaN)).toBe("0 B"); | |
| 376 | expect(formatBytes(Number.POSITIVE_INFINITY)).toBe("0 B"); | |
| 377 | }); | |
| 378 | }); | |
| 379 | ||
| 380 | describe("language-stats — formatPercent", () => { | |
| 381 | it("formats to given digits and clamps to [0,100]", () => { | |
| 382 | expect(formatPercent(50)).toBe("50.0%"); | |
| 383 | expect(formatPercent(50, 0)).toBe("50%"); | |
| 384 | expect(formatPercent(50, 2)).toBe("50.00%"); | |
| 385 | expect(formatPercent(-5)).toBe("0.0%"); | |
| 386 | expect(formatPercent(200)).toBe("100.0%"); | |
| 387 | }); | |
| 388 | it("handles NaN", () => { | |
| 389 | expect(formatPercent(Number.NaN)).toBe("0%"); | |
| 390 | }); | |
| 391 | }); | |
| 392 | ||
| 393 | describe("language-stats — buildLanguageReport", () => { | |
| 394 | const entries: LanguageFileEntry[] = [ | |
| 395 | { path: "src/a.ts", size: 1_000 }, | |
| 396 | { path: "main.py", size: 100 }, | |
| 397 | { path: "tiny.rb", size: 5 }, | |
| 398 | ]; | |
| 399 | ||
| 400 | it("matches computeLanguageStats when foldUnderPercent is 0", () => { | |
| 401 | const a = computeLanguageStats(entries); | |
| 402 | const b = buildLanguageReport({ entries }); | |
| 403 | expect(a.buckets.map((x) => x.language)).toEqual( | |
| 404 | b.buckets.map((x) => x.language) | |
| 405 | ); | |
| 406 | expect(a.totalBytes).toBe(b.totalBytes); | |
| 407 | }); | |
| 408 | ||
| 409 | it("applies foldUnderPercent after rollup", () => { | |
| 410 | const r = buildLanguageReport({ entries, foldUnderPercent: 2 }); | |
| 411 | // Ruby=5B out of 1105 ≈ 0.45% < 2% → folds into Other | |
| 412 | expect(r.buckets.some((b) => b.language === "Ruby")).toBe(false); | |
| 413 | expect(r.buckets.some((b) => b.language === "Other")).toBe(true); | |
| 414 | }); | |
| 415 | ||
| 416 | it("respects ignoreVendored=false", () => { | |
| 417 | const r = buildLanguageReport({ | |
| 418 | entries: [ | |
| 419 | { path: "src/a.ts", size: 100 }, | |
| 420 | { path: "node_modules/foo/bar.js", size: 10_000 }, | |
| 421 | ], | |
| 422 | ignoreVendored: false, | |
| 423 | }); | |
| 424 | expect(r.buckets.some((b) => b.language === "JavaScript")).toBe(true); | |
| 425 | }); | |
| 426 | }); | |
| 427 | ||
| 428 | describe("language-stats — routes", () => { | |
| 429 | it("GET /:o/:r/languages returns 200 or 404 (never 500)", async () => { | |
| 430 | const { default: app } = await import("../app"); | |
| 431 | const res = await app.request("/alice/repo/languages"); | |
| 432 | expect([200, 404]).toContain(res.status); | |
| 433 | }); | |
| 434 | it("ignores bogus query params", async () => { | |
| 435 | const { default: app } = await import("../app"); | |
| 436 | const res = await app.request( | |
| 437 | "/alice/repo/languages?include_vendored=yes&fold=abc" | |
| 438 | ); | |
| 439 | expect([200, 404]).toContain(res.status); | |
| 440 | }); | |
| 441 | }); | |
| 442 | ||
| 443 | describe("language-stats — __internal parity", () => { | |
| 444 | it("re-exports every helper", () => { | |
| 445 | expect(__internal.EXTENSION_MAP).toBe(EXTENSION_MAP); | |
| 446 | expect(__internal.FILENAME_MAP).toBe(FILENAME_MAP); | |
| 447 | expect(__internal.LANGUAGE_COLORS).toBe(LANGUAGE_COLORS); | |
| 448 | expect(__internal.DEFAULT_LANGUAGE_COLOR).toBe(DEFAULT_LANGUAGE_COLOR); | |
| 449 | expect(__internal.VENDORED_PREFIXES).toBe(VENDORED_PREFIXES); | |
| 450 | expect(__internal.GENERATED_SUFFIXES).toBe(GENERATED_SUFFIXES); | |
| 451 | expect(__internal.detectLanguage).toBe(detectLanguage); | |
| 452 | expect(__internal.isVendoredOrGenerated).toBe(isVendoredOrGenerated); | |
| 453 | expect(__internal.computeLanguageStats).toBe(computeLanguageStats); | |
| 454 | expect(__internal.foldIntoOther).toBe(foldIntoOther); | |
| 455 | expect(__internal.formatBytes).toBe(formatBytes); | |
| 456 | expect(__internal.formatPercent).toBe(formatPercent); | |
| 457 | expect(__internal.buildLanguageReport).toBe(buildLanguageReport); | |
| 458 | expect(typeof __internal.colorFor).toBe("function"); | |
| 459 | }); | |
| 460 | }); |