Files
deepseek-harness/packages/util/code-language/tests/code-language.spec.ts
T
mektpoy b0ad792e6b fix(web): keep dedicated previews and the read-card lang domain
The shared code-language table must not claim a suffix a dedicated viewer
declares, and the Host read card must keep the `lang` values it already
persisted.

- Drop `csv` from the shared table: Code registers before Spreadsheet, so
  the earlier body would take `.csv` and the browser snapshot gate fails.
- Add `readLangHintForPath`, the read card's legacy projection over the
  shared table: the 43 pre-unification suffixes keep their old short ids
  byte for byte, new suffixes use the canonical id.
- Add an ownership regression test over the full preview registry that
  turns red when `csv` returns, plus byte-level and alias-normalized tests
  for the read projection.
2026-09-21 19:14:08 +08:00

141 lines
6.7 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { CODE_HIGHLIGHT_EXTENSIONS, languageForPath, readLangHintForPath } from '../src/index.ts'
describe('languageForPath', () => {
it.each([
// Windows-facing script and shell extensions.
['build.bat', 'bat'], ['build.cmd', 'bat'], ['deploy.ps1', 'powershell'],
['module.psm1', 'powershell'], ['data.psd1', 'powershell'], ['config.fish', 'fish'],
// Config, data, and text extensions.
['app.properties', 'ini'], ['app.conf', 'ini'], ['app.cfg', 'ini'],
['.env', 'dotenv'], ['server.log', 'log'],
['change.diff', 'diff'], ['fix.patch', 'diff'], ['api.http', 'http'],
['notebook.ipynb', 'json'],
// Documentation and markup extensions.
['guide.rst', 'rst'], ['paper.tex', 'latex'], ['style.sty', 'latex'],
['doc.cls', 'latex'], ['refs.bib', 'bibtex'], ['Info.plist', 'xml'],
['logo.svg', 'xml'], ['manual.adoc', 'asciidoc'],
// Language extensions.
['analysis.r', 'r'], ['model.jl', 'julia'], ['main.dart', 'dart'],
['Main.scala', 'scala'], ['core.clj', 'clojure'], ['ui.cljs', 'clojure'],
['data.edn', 'clojure'], ['app.erl', 'erlang'], ['app.hrl', 'erlang'],
['app.ex', 'elixir'], ['app.exs', 'elixir'], ['Main.hs', 'haskell'],
['Types.fs', 'fsharp'], ['Types.fsi', 'fsharp'], ['script.fsx', 'fsharp'],
['Form.vb', 'vb'], ['script.pl', 'perl'], ['Module.pm', 'perl'],
['top.v', 'verilog'], ['top.sv', 'system-verilog'], ['defs.svh', 'system-verilog'],
['schema.graphql', 'graphql'], ['query.gql', 'graphql'], ['message.proto', 'proto'],
['main.tf', 'hcl'], ['terraform.tfvars', 'hcl'], ['stack.hcl', 'hcl'],
['flake.nix', 'nix'], ['App.vue', 'vue'], ['App.svelte', 'svelte'],
['Makefile', undefined], ['build.mk', 'make'], ['CMakeLists.cmake', 'cmake'],
['build.gradle', 'groovy'],
])('resolves %s to %s', (path, language) => {
expect(languageForPath(path)).toBe(language)
})
it('keeps the extensions the two former tables already agreed on', () => {
expect(languageForPath('src/a.ts')).toBe('typescript')
expect(languageForPath('src/a.TSX')).toBe('typescript')
expect(languageForPath('/abs/module.mjs')).toBe('javascript')
expect(languageForPath('conf.yml')).toBe('yaml')
expect(languageForPath('README.md')).toBe('markdown')
expect(languageForPath('C:\\src\\main.rs')).toBe('rust')
})
it('resolves every extension that only the preview table knew, including the new ones', () => {
for (const extension of ['jsonl', 'ndjson', 'pyw', 'pyi', 'rake', 'gemspec', 'hh', 'hxx', 'kts', 'xhtml', 'xsd', 'xsl', 'xslt']) {
expect(languageForPath(`file.${extension}`), extension).toBeDefined()
}
})
it('is case-insensitive and accepts Windows separators', () => {
expect(languageForPath('C:\\path\\X.PS1')).toBe('powershell')
expect(languageForPath('C:\\Proj\\Build.CMD')).toBe('bat')
expect(languageForPath('dir\\sub\\Main.SCALA')).toBe('scala')
})
it('reads the suffix after the last path segment and last dot', () => {
expect(languageForPath('a.py.bak')).toBeUndefined()
expect(languageForPath('archive.tar.gz')).toBeUndefined()
expect(languageForPath('/dir.py/plain')).toBeUndefined()
expect(languageForPath('dir.ts/README')).toBeUndefined()
})
it('returns undefined for a dotfile, an extensionless name, a trailing dot, and an unknown suffix', () => {
expect(languageForPath('.gitignore')).toBeUndefined()
expect(languageForPath('/etc/hosts')).toBeUndefined()
expect(languageForPath('trailingdot.')).toBeUndefined()
expect(languageForPath('data.unknownext')).toBeUndefined()
})
it('returns undefined for a filename whose extension is an Object.prototype key', () => {
expect(languageForPath('foo.constructor')).toBeUndefined()
expect(languageForPath('foo.__proto__')).toBeUndefined()
expect(languageForPath('foo.toString')).toBeUndefined()
expect(languageForPath('foo.hasOwnProperty')).toBeUndefined()
})
it('leaves certificate and lock extensions unlisted', () => {
for (const extension of ['pem', 'crt', 'key', 'cer', 'lock']) {
expect(languageForPath(`secret.${extension}`), extension).toBeUndefined()
}
})
it('leaves delimited-spreadsheet suffixes to the Spreadsheet preview', () => {
// The Code preview registers before the Spreadsheet preview, so listing csv
// here would make Code the earlier candidate and steal the suffix from the
// viewer that must render it.
for (const extension of ['csv', 'tsv']) {
expect(languageForPath(`table.${extension}`), extension).toBeUndefined()
}
})
})
describe('readLangHintForPath', () => {
it('keeps the read card pre-unification short hints for the old suffixes', () => {
expect(readLangHintForPath('src/a.ts')).toBe('ts')
expect(readLangHintForPath('src/a.tsx')).toBe('tsx')
expect(readLangHintForPath('src/a.mts')).toBe('ts')
expect(readLangHintForPath('src/a.cts')).toBe('ts')
expect(readLangHintForPath('src/a.js')).toBe('js')
expect(readLangHintForPath('src/a.jsx')).toBe('jsx')
expect(readLangHintForPath('src/a.mjs')).toBe('js')
expect(readLangHintForPath('src/a.cjs')).toBe('js')
expect(readLangHintForPath('src/a.jsonc')).toBe('json')
expect(readLangHintForPath('src/a.cc')).toBe('cpp')
expect(readLangHintForPath('src/a.hpp')).toBe('cpp')
expect(readLangHintForPath('src/a.sh')).toBe('sh')
expect(readLangHintForPath('src/a.kt')).toBe('kotlin')
expect(readLangHintForPath('README.md')).toBe('md')
expect(readLangHintForPath('README.markdown')).toBe('md')
expect(readLangHintForPath('page.htm')).toBe('html')
})
it('uses the canonical id for a suffix added after the old read table', () => {
expect(readLangHintForPath('build.ps1')).toBe('powershell')
expect(readLangHintForPath('.env')).toBe('dotenv')
expect(readLangHintForPath('infra.tf')).toBe('hcl')
expect(readLangHintForPath('paper.tex')).toBe('latex')
expect(readLangHintForPath('server.log')).toBe('log')
expect(readLangHintForPath('message.proto')).toBe('proto')
})
it('returns undefined exactly when the shared table does not recognize the suffix', () => {
for (const path of ['.gitignore', '/etc/hosts', 'trailingdot.', 'data.unknownext', 'foo.constructor']) {
expect(readLangHintForPath(path), path).toBeUndefined()
}
})
})
describe('CODE_HIGHLIGHT_EXTENSIONS', () => {
it('lists every recognized suffix exactly once', () => {
expect(new Set(CODE_HIGHLIGHT_EXTENSIONS).size).toBe(CODE_HIGHLIGHT_EXTENSIONS.length)
expect(CODE_HIGHLIGHT_EXTENSIONS.length).toBeGreaterThan(100)
})
it('resolves each listed suffix back to a language', () => {
for (const extension of CODE_HIGHLIGHT_EXTENSIONS) {
expect(languageForPath(`file.${extension}`), extension).toBeDefined()
}
})
})