/** * ───────────────────────────────────────────── * SPB Git — Personal Git Platform * ───────────────────────────────────────────── * Author : Simon-Pierre Boucher * Contact : contact@spboucher.ai * File : test/unit/languages.test.mjs * Purpose : Unit tests — language detection + percentages * License : MIT © Simon-Pierre Boucher * ───────────────────────────────────────────── */ import { describe, it, expect } from 'vitest'; import { detectLanguage, computeLanguages, languageColor } from '../../src/stats/languages.mjs'; describe('detectLanguage', () => { it('maps common extensions', () => { expect(detectLanguage('src/index.mjs')).toBe('JavaScript'); expect(detectLanguage('main.py')).toBe('Python'); expect(detectLanguage('lib.rs')).toBe('Rust'); expect(detectLanguage('Dockerfile')).toBe('Dockerfile'); expect(detectLanguage('CMakeLists.txt')).toBe('CMake'); expect(detectLanguage('notes.md')).toBe('Markdown'); }); it('returns null for unknown files', () => { expect(detectLanguage('data.unknownext')).toBe(null); expect(detectLanguage('LICENSE')).toBe(null); }); }); describe('computeLanguages', () => { it('computes byte percentages, programming+markup only', () => { const { languages, totalBytes } = computeLanguages([ { path: 'a.py', size: 750 }, { path: 'b.js', size: 250 }, { path: 'README.md', size: 5000 }, // prose → excluded { path: 'data.json', size: 9000 }, // data → excluded { path: 'node_modules/x.js', size: 4000 },// vendored → excluded { path: 'app.min.js', size: 12000 }, // minified → excluded ]); expect(totalBytes).toBe(1000); expect(languages[0]).toMatchObject({ name: 'Python', percent: 75 }); expect(languages[1]).toMatchObject({ name: 'JavaScript', percent: 25 }); }); it('handles empty input', () => { expect(computeLanguages([]).languages).toEqual([]); }); }); describe('languageColor', () => { it('returns linguist colors with a grey fallback', () => { expect(languageColor('Python')).toBe('#3572A5'); expect(languageColor('NotALanguage')).toBe('#8b93a3'); }); });