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.
141 lines
6.7 KiB
TypeScript
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()
|
|
}
|
|
})
|
|
})
|