OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / test / unit / syntax.test.js

231 lines · javascript ref:
/**
 * Unit tests for lib/syntax.js
 */

import test from 'brittle'
import { detectLanguage, getPrismLanguageUrl, getPrismAssets, isBinaryContent, getFileIcon } from '../../lib/syntax.js'
import { sampleFiles } from '../helpers/fixtures.js'

// ========================================
// detectLanguage tests
// ========================================

test('detectLanguage - returns plaintext for empty input', async (t) => {
  t.is(detectLanguage(''), 'plaintext')
  t.is(detectLanguage(null), 'plaintext')
  t.is(detectLanguage(undefined), 'plaintext')
})

test('detectLanguage - detects JavaScript files', async (t) => {
  t.is(detectLanguage('app.js'), 'javascript')
  t.is(detectLanguage('index.mjs'), 'javascript')
  t.is(detectLanguage('utils.cjs'), 'javascript')
  t.is(detectLanguage('/path/to/file.js'), 'javascript')
})

test('detectLanguage - detects TypeScript files', async (t) => {
  t.is(detectLanguage('app.ts'), 'typescript')
  t.is(detectLanguage('component.tsx'), 'tsx')
  t.is(detectLanguage('types.d.ts'), 'typescript')
})

test('detectLanguage - detects JSX/TSX files', async (t) => {
  t.is(detectLanguage('Component.jsx'), 'jsx')
  t.is(detectLanguage('Page.tsx'), 'tsx')
})

test('detectLanguage - detects Python files', async (t) => {
  t.is(detectLanguage('main.py'), 'python')
  t.is(detectLanguage('script.pyw'), 'python')
})

test('detectLanguage - detects Ruby files', async (t) => {
  t.is(detectLanguage('app.rb'), 'ruby')
  t.is(detectLanguage('template.erb'), 'erb')
})

test('detectLanguage - detects Go files', async (t) => {
  t.is(detectLanguage('main.go'), 'go')
  t.is(detectLanguage('go.mod'), 'go-module')
})

test('detectLanguage - detects Rust files', async (t) => {
  t.is(detectLanguage('main.rs'), 'rust')
  t.is(detectLanguage('lib.rs'), 'rust')
})

test('detectLanguage - detects C/C++ files', async (t) => {
  t.is(detectLanguage('main.c'), 'c')
  t.is(detectLanguage('header.h'), 'c')
  t.is(detectLanguage('app.cpp'), 'cpp')
  t.is(detectLanguage('utils.hpp'), 'cpp')
})

test('detectLanguage - detects web files', async (t) => {
  t.is(detectLanguage('index.html'), 'html')
  t.is(detectLanguage('page.htm'), 'html')
  t.is(detectLanguage('styles.css'), 'css')
  t.is(detectLanguage('theme.scss'), 'scss')
  t.is(detectLanguage('variables.less'), 'less')
})

test('detectLanguage - detects data format files', async (t) => {
  t.is(detectLanguage('package.json'), 'json')
  t.is(detectLanguage('config.yaml'), 'yaml')
  t.is(detectLanguage('settings.yml'), 'yaml')
  t.is(detectLanguage('config.toml'), 'toml')
})

test('detectLanguage - detects shell scripts', async (t) => {
  t.is(detectLanguage('script.sh'), 'bash')
  t.is(detectLanguage('deploy.bash'), 'bash')
  t.is(detectLanguage('setup.zsh'), 'bash')
})

test('detectLanguage - detects markdown', async (t) => {
  t.is(detectLanguage('README.md'), 'markdown')
  t.is(detectLanguage('docs.markdown'), 'markdown')
})

test('detectLanguage - detects special filenames', async (t) => {
  t.is(detectLanguage('Dockerfile'), 'docker')
  t.is(detectLanguage('Makefile'), 'makefile')
  t.is(detectLanguage('Gemfile'), 'ruby')
  t.is(detectLanguage('Rakefile'), 'ruby')
})

test('detectLanguage - detects dotfiles', async (t) => {
  t.is(detectLanguage('.gitignore'), 'gitignore')
  t.is(detectLanguage('.editorconfig'), 'ini')
})

test('detectLanguage - case insensitive', async (t) => {
  t.is(detectLanguage('FILE.JS'), 'javascript')
  t.is(detectLanguage('README.MD'), 'markdown')
  t.is(detectLanguage('DOCKERFILE'), 'docker')
})

test('detectLanguage - handles full paths', async (t) => {
  t.is(detectLanguage('/home/user/project/src/index.js'), 'javascript')
  t.is(detectLanguage('src/components/App.tsx'), 'tsx')
  t.is(detectLanguage('./lib/utils.py'), 'python')
})

test('detectLanguage - returns plaintext for unknown extensions', async (t) => {
  t.is(detectLanguage('file.xyz'), 'plaintext')
  t.is(detectLanguage('data.unknown'), 'plaintext')
})

// ========================================
// getPrismLanguageUrl tests
// ========================================

test('getPrismLanguageUrl - returns null for plaintext', async (t) => {
  t.is(getPrismLanguageUrl('plaintext'), null)
})

test('getPrismLanguageUrl - returns CDN URL for languages', async (t) => {
  const url = getPrismLanguageUrl('javascript')
  t.ok(url.includes('prism-javascript'))
  t.ok(url.includes('cdnjs.cloudflare.com'))
})

// ========================================
// getPrismAssets tests
// ========================================

test('getPrismAssets - returns CSS and JS arrays', async (t) => {
  const assets = getPrismAssets('javascript')

  t.ok(Array.isArray(assets.css))
  t.ok(Array.isArray(assets.js))
  t.ok(assets.css.length > 0)
  t.ok(assets.js.length > 0)
})

test('getPrismAssets - includes theme CSS', async (t) => {
  const assets = getPrismAssets('javascript')
  t.ok(assets.css.some(url => url.includes('prism-tomorrow')))
})

test('getPrismAssets - includes core JS', async (t) => {
  const assets = getPrismAssets('javascript')
  t.ok(assets.js.some(url => url.includes('prism.min.js')))
})

test('getPrismAssets - includes language-specific JS', async (t) => {
  const assets = getPrismAssets('python')
  t.ok(assets.js.some(url => url.includes('prism-python')))
})

test('getPrismAssets - excludes language JS for plaintext', async (t) => {
  const assets = getPrismAssets('plaintext')
  t.not(assets.js.some(url => url.includes('prism-plaintext')))
})

// ========================================
// isBinaryContent tests
// ========================================

test('isBinaryContent - returns false for empty input', async (t) => {
  t.is(isBinaryContent(''), false)
  t.is(isBinaryContent(null), false)
})

test('isBinaryContent - returns false for text content', async (t) => {
  t.is(isBinaryContent('Hello, World!'), false)
  t.is(isBinaryContent('const x = 1;\nconst y = 2;'), false)
  t.is(isBinaryContent('Line 1\nLine 2\tTab\rCarriage'), false)
})

test('isBinaryContent - returns true for null bytes', async (t) => {
  t.is(isBinaryContent('hello\x00world'), true)
  t.is(isBinaryContent('\x00\x00\x00'), true)
})

test('isBinaryContent - returns true for high non-printable ratio', async (t) => {
  // Create content with many non-printable characters
  let binary = ''
  for (let i = 0; i < 100; i++) {
    binary += String.fromCharCode(1) // Non-printable
  }
  t.is(isBinaryContent(binary), true)
})

test('isBinaryContent - handles mixed content', async (t) => {
  // Content with some non-printable but below threshold
  const content = 'Normal text content\nwith newlines\tand tabs'
  t.is(isBinaryContent(content), false)
})

// ========================================
// getFileIcon tests
// ========================================

test('getFileIcon - returns folder icon for directories', async (t) => {
  t.is(getFileIcon('javascript', 'directory'), '📁')
  t.is(getFileIcon('', 'directory'), '📁')
})

test('getFileIcon - returns package icon for submodules', async (t) => {
  t.is(getFileIcon('javascript', 'submodule'), '📦')
})

test('getFileIcon - returns language-specific icons', async (t) => {
  t.is(getFileIcon('javascript'), '📜')
  t.is(getFileIcon('typescript'), '📘')
  t.is(getFileIcon('python'), '🐍')
  t.is(getFileIcon('ruby'), '💎')
  t.is(getFileIcon('go'), '🐹')
  t.is(getFileIcon('rust'), '🦀')
  t.is(getFileIcon('java'), '☕')
  t.is(getFileIcon('docker'), '🐳')
  t.is(getFileIcon('markdown'), '📝')
})

test('getFileIcon - returns default icon for unknown languages', async (t) => {
  t.is(getFileIcon('plaintext'), '📄')
  t.is(getFileIcon('unknown'), '📄')
  t.is(getFileIcon(''), '📄')
})