merge #202: editor Vue + 28 legacy language syntax highlight (ShelbyTang)
This commit is contained in:
@@ -122,6 +122,7 @@
|
||||
"@codemirror/lang-python": "^6.2.1",
|
||||
"@codemirror/lang-rust": "^6.0.2",
|
||||
"@codemirror/lang-sql": "^6.10.0",
|
||||
"@codemirror/lang-vue": "^0.1.3",
|
||||
"@codemirror/lang-xml": "^6.1.0",
|
||||
"@codemirror/lang-yaml": "^6.1.3",
|
||||
"@codemirror/language": "^6.12.4",
|
||||
|
||||
Generated
+15
@@ -47,6 +47,9 @@ importers:
|
||||
'@codemirror/lang-sql':
|
||||
specifier: ^6.10.0
|
||||
version: 6.10.0
|
||||
'@codemirror/lang-vue':
|
||||
specifier: ^0.1.3
|
||||
version: 0.1.3
|
||||
'@codemirror/lang-xml':
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0
|
||||
@@ -265,6 +268,9 @@ packages:
|
||||
'@codemirror/lang-sql@6.10.0':
|
||||
resolution: {integrity: sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==}
|
||||
|
||||
'@codemirror/lang-vue@0.1.3':
|
||||
resolution: {integrity: sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==}
|
||||
|
||||
'@codemirror/lang-xml@6.1.0':
|
||||
resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==}
|
||||
|
||||
@@ -3015,6 +3021,15 @@ snapshots:
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.10
|
||||
|
||||
'@codemirror/lang-vue@0.1.3':
|
||||
dependencies:
|
||||
'@codemirror/lang-html': 6.4.12
|
||||
'@codemirror/lang-javascript': 6.2.5
|
||||
'@codemirror/language': 6.12.4
|
||||
'@lezer/common': 1.5.2
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.10
|
||||
|
||||
'@codemirror/lang-xml@6.1.0':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.3
|
||||
|
||||
+126
-4
@@ -4,12 +4,12 @@
|
||||
* the CodeMirror language packages (bundled into the client).
|
||||
*/
|
||||
import { Language, LanguageSupport, StreamLanguage } from '@codemirror/language'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
import { javascript, jsxLanguage, typescriptLanguage, tsxLanguage } from '@codemirror/lang-javascript'
|
||||
import { json } from '@codemirror/lang-json'
|
||||
import { markdown } from '@codemirror/lang-markdown'
|
||||
import { python } from '@codemirror/lang-python'
|
||||
import { html } from '@codemirror/lang-html'
|
||||
import { css } from '@codemirror/lang-css'
|
||||
import { css, cssLanguage } from '@codemirror/lang-css'
|
||||
import { xml } from '@codemirror/lang-xml'
|
||||
import { yaml } from '@codemirror/lang-yaml'
|
||||
import { sql } from '@codemirror/lang-sql'
|
||||
@@ -18,13 +18,36 @@ import { cpp } from '@codemirror/lang-cpp'
|
||||
import { rust } from '@codemirror/lang-rust'
|
||||
import { go } from '@codemirror/lang-go'
|
||||
import { php } from '@codemirror/lang-php'
|
||||
import { vue } from '@codemirror/lang-vue'
|
||||
import { shell } from '@codemirror/legacy-modes/mode/shell'
|
||||
import { toml } from '@codemirror/legacy-modes/mode/toml'
|
||||
import { nginx } from '@codemirror/legacy-modes/mode/nginx'
|
||||
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile'
|
||||
import { properties } from '@codemirror/legacy-modes/mode/properties'
|
||||
import { csharp, kotlin } from '@codemirror/legacy-modes/mode/clike'
|
||||
import { csharp, kotlin, dart, scala, objectiveCpp } from '@codemirror/legacy-modes/mode/clike'
|
||||
import { swift } from '@codemirror/legacy-modes/mode/swift'
|
||||
import { sCSS, less } from '@codemirror/legacy-modes/mode/css'
|
||||
import { sass } from '@codemirror/legacy-modes/mode/sass'
|
||||
import { stylus } from '@codemirror/legacy-modes/mode/stylus'
|
||||
import { ruby } from '@codemirror/legacy-modes/mode/ruby'
|
||||
import { lua } from '@codemirror/legacy-modes/mode/lua'
|
||||
import { perl } from '@codemirror/legacy-modes/mode/perl'
|
||||
import { r } from '@codemirror/legacy-modes/mode/r'
|
||||
import { groovy } from '@codemirror/legacy-modes/mode/groovy'
|
||||
import { powerShell } from '@codemirror/legacy-modes/mode/powershell'
|
||||
import { diff } from '@codemirror/legacy-modes/mode/diff'
|
||||
import { protobuf } from '@codemirror/legacy-modes/mode/protobuf'
|
||||
import { cmake } from '@codemirror/legacy-modes/mode/cmake'
|
||||
import { pug } from '@codemirror/legacy-modes/mode/pug'
|
||||
import { tcl } from '@codemirror/legacy-modes/mode/tcl'
|
||||
import { haskell } from '@codemirror/legacy-modes/mode/haskell'
|
||||
import { clojure } from '@codemirror/legacy-modes/mode/clojure'
|
||||
import { erlang } from '@codemirror/legacy-modes/mode/erlang'
|
||||
import { julia } from '@codemirror/legacy-modes/mode/julia'
|
||||
import { pascal } from '@codemirror/legacy-modes/mode/pascal'
|
||||
import { vb } from '@codemirror/legacy-modes/mode/vb'
|
||||
import { vhdl } from '@codemirror/legacy-modes/mode/vhdl'
|
||||
import { stex } from '@codemirror/legacy-modes/mode/stex'
|
||||
|
||||
/** The lowercased file extension of a path ('' when none). */
|
||||
export function extOf(path: string): string {
|
||||
@@ -63,10 +86,47 @@ export function languageKeyForExt(ext: string): string | null {
|
||||
case 'nginx': case 'conf': return 'nginx'
|
||||
case 'dockerfile': case 'docker': return 'dockerfile'
|
||||
case 'properties': case 'env': return 'properties'
|
||||
case 'vue': return 'vue'
|
||||
case 'scss': return 'scss'
|
||||
case 'sass': return 'sass'
|
||||
case 'less': return 'less'
|
||||
case 'styl': return 'stylus'
|
||||
case 'rb': return 'ruby'
|
||||
case 'lua': return 'lua'
|
||||
case 'pl': case 'pm': return 'perl'
|
||||
case 'r': return 'r'
|
||||
case 'dart': return 'dart'
|
||||
case 'scala': case 'sc': return 'scala'
|
||||
case 'groovy': return 'groovy'
|
||||
case 'ps1': case 'psm1': return 'powershell'
|
||||
case 'diff': case 'patch': return 'diff'
|
||||
case 'proto': return 'protobuf'
|
||||
case 'cmake': return 'cmake'
|
||||
case 'pug': return 'pug'
|
||||
case 'tcl': return 'tcl'
|
||||
case 'hs': return 'haskell'
|
||||
case 'clj': case 'cljs': return 'clojure'
|
||||
case 'erl': return 'erlang'
|
||||
case 'jl': return 'julia'
|
||||
case 'pas': return 'pascal'
|
||||
case 'vb': return 'vb'
|
||||
case 'vhd': return 'vhdl'
|
||||
case 'tex': return 'stex'
|
||||
case 'mm': return 'objectivecpp'
|
||||
// '.v' (Verilog/Coq/V) 与 '.m' (Objective-C/MATLAB) 存在跨语言歧义,
|
||||
// 故意不映射——错配高亮比纯文本更误导。
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
|
||||
/** Per-lang `<style>` parsers for the Vue SFC factory (built once, shared). */
|
||||
const VUE_STYLE_PARSERS = {
|
||||
scss: StreamLanguage.define(sCSS).parser,
|
||||
sass: StreamLanguage.define(sass).parser,
|
||||
less: StreamLanguage.define(less).parser,
|
||||
stylus: StreamLanguage.define(stylus).parser,
|
||||
}
|
||||
|
||||
const FACTORIES: Record<string, () => Language | LanguageSupport> = {
|
||||
js: () => javascript({ jsx: true }),
|
||||
jsx: () => javascript({ jsx: true }),
|
||||
@@ -94,10 +154,72 @@ const FACTORIES: Record<string, () => Language | LanguageSupport> = {
|
||||
nginx: () => StreamLanguage.define(nginx),
|
||||
dockerfile: () => StreamLanguage.define(dockerFile),
|
||||
properties: () => StreamLanguage.define(properties),
|
||||
// Vue SFC: lang-html's default html() base ALREADY parses <script>
|
||||
// (no type → JS, type="module" → JS, lang="ts" → TS, JSON/importmap MIME
|
||||
// types) and <style> → CSS through its built-in defaultNesting. The
|
||||
// explicit entries only ADD what the defaults miss — case-insensitive
|
||||
// lang="ts", lang="tsx"/"jsx" (the defaults only recognize the text/jsx &
|
||||
// text/typescript-jsx MIME types), and per-lang <style> preprocessor
|
||||
// dispatch. Same-tag entries are first-match wins and ours precede the
|
||||
// defaults, so the predicates stay narrow: a constant-true entry would
|
||||
// shadow the default JSON/importmap script handling and force the CSS
|
||||
// parser onto <style lang="stylus">.
|
||||
vue: () => vue({
|
||||
base: html({
|
||||
nestedLanguages: [
|
||||
{ tag: 'script', attrs: a => (a.lang ?? '').toLowerCase() === 'ts', parser: typescriptLanguage.parser },
|
||||
{ tag: 'script', attrs: a => (a.lang ?? '').toLowerCase() === 'tsx', parser: tsxLanguage.parser },
|
||||
{ tag: 'script', attrs: a => (a.lang ?? '').toLowerCase() === 'jsx', parser: jsxLanguage.parser },
|
||||
{ tag: 'style', attrs: a => (a.lang ?? '').toLowerCase() === 'css', parser: cssLanguage.parser },
|
||||
{ tag: 'style', attrs: a => (a.lang ?? '').toLowerCase() === 'scss', parser: VUE_STYLE_PARSERS.scss },
|
||||
{ tag: 'style', attrs: a => (a.lang ?? '').toLowerCase() === 'sass', parser: VUE_STYLE_PARSERS.sass },
|
||||
{ tag: 'style', attrs: a => (a.lang ?? '').toLowerCase() === 'less', parser: VUE_STYLE_PARSERS.less },
|
||||
{ tag: 'style', attrs: a => (a.lang ?? '').toLowerCase() === 'stylus', parser: VUE_STYLE_PARSERS.stylus },
|
||||
],
|
||||
}),
|
||||
}),
|
||||
scss: () => StreamLanguage.define(sCSS),
|
||||
sass: () => StreamLanguage.define(sass),
|
||||
less: () => StreamLanguage.define(less),
|
||||
stylus: () => StreamLanguage.define(stylus),
|
||||
ruby: () => StreamLanguage.define(ruby),
|
||||
lua: () => StreamLanguage.define(lua),
|
||||
perl: () => StreamLanguage.define(perl),
|
||||
r: () => StreamLanguage.define(r),
|
||||
dart: () => StreamLanguage.define(dart),
|
||||
scala: () => StreamLanguage.define(scala),
|
||||
groovy: () => StreamLanguage.define(groovy),
|
||||
powershell: () => StreamLanguage.define(powerShell),
|
||||
diff: () => StreamLanguage.define(diff),
|
||||
protobuf: () => StreamLanguage.define(protobuf),
|
||||
cmake: () => StreamLanguage.define(cmake),
|
||||
pug: () => StreamLanguage.define(pug),
|
||||
tcl: () => StreamLanguage.define(tcl),
|
||||
haskell: () => StreamLanguage.define(haskell),
|
||||
clojure: () => StreamLanguage.define(clojure),
|
||||
erlang: () => StreamLanguage.define(erlang),
|
||||
julia: () => StreamLanguage.define(julia),
|
||||
pascal: () => StreamLanguage.define(pascal),
|
||||
vb: () => StreamLanguage.define(vb),
|
||||
vhdl: () => StreamLanguage.define(vhdl),
|
||||
stex: () => StreamLanguage.define(stex),
|
||||
objectivecpp: () => StreamLanguage.define(objectiveCpp),
|
||||
}
|
||||
|
||||
/** Every language key the extension table can produce (test seam). */
|
||||
export function supportedLanguageKeys(): readonly string[] {
|
||||
return Object.keys(FACTORIES)
|
||||
}
|
||||
|
||||
/** The CodeMirror language support for a path, or null for plain text. */
|
||||
export function languageForPath(path: string): Language | LanguageSupport | null {
|
||||
const key = languageKeyForExt(extOf(path))
|
||||
return key === null ? null : FACTORIES[key]!()
|
||||
if (key === null) return null
|
||||
try {
|
||||
return FACTORIES[key]!()
|
||||
} catch (error) {
|
||||
// A broken factory degrades to plain text, never crashes the editor.
|
||||
console.warn(`[dsh-better-sidebar] language factory "${key}" failed:`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
+131
-1
@@ -1,6 +1,8 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { LanguageSupport } from '@codemirror/language'
|
||||
import { isImageExt } from '../src/client/image-types.ts'
|
||||
import { extOf, languageKeyForExt } from '../src/client/lang.ts'
|
||||
import { extOf, languageForPath, languageKeyForExt, supportedLanguageKeys } from '../src/client/lang.ts'
|
||||
import { isPdfExt } from '../src/client/pdf-types.ts'
|
||||
|
||||
describe('editor language mapping', () => {
|
||||
@@ -25,6 +27,134 @@ describe('editor language mapping', () => {
|
||||
expect(languageKeyForExt('log')).toBeNull()
|
||||
expect(languageKeyForExt('')).toBeNull()
|
||||
})
|
||||
|
||||
it('maps the P0/P1 extension batch (vue + legacy-modes) and keeps ambiguous ones plain', () => {
|
||||
// P0
|
||||
expect(languageKeyForExt('vue')).toBe('vue')
|
||||
// P1
|
||||
expect(languageKeyForExt('scss')).toBe('scss')
|
||||
expect(languageKeyForExt('sass')).toBe('sass')
|
||||
expect(languageKeyForExt('less')).toBe('less')
|
||||
expect(languageKeyForExt('styl')).toBe('stylus')
|
||||
expect(languageKeyForExt('rb')).toBe('ruby')
|
||||
expect(languageKeyForExt('lua')).toBe('lua')
|
||||
expect(languageKeyForExt('pl')).toBe('perl')
|
||||
expect(languageKeyForExt('pm')).toBe('perl')
|
||||
expect(languageKeyForExt('r')).toBe('r')
|
||||
expect(languageKeyForExt('dart')).toBe('dart')
|
||||
expect(languageKeyForExt('scala')).toBe('scala')
|
||||
expect(languageKeyForExt('sc')).toBe('scala')
|
||||
expect(languageKeyForExt('groovy')).toBe('groovy')
|
||||
expect(languageKeyForExt('ps1')).toBe('powershell')
|
||||
expect(languageKeyForExt('psm1')).toBe('powershell')
|
||||
expect(languageKeyForExt('diff')).toBe('diff')
|
||||
expect(languageKeyForExt('patch')).toBe('diff')
|
||||
expect(languageKeyForExt('proto')).toBe('protobuf')
|
||||
expect(languageKeyForExt('cmake')).toBe('cmake')
|
||||
expect(languageKeyForExt('pug')).toBe('pug')
|
||||
expect(languageKeyForExt('tcl')).toBe('tcl')
|
||||
expect(languageKeyForExt('hs')).toBe('haskell')
|
||||
expect(languageKeyForExt('clj')).toBe('clojure')
|
||||
expect(languageKeyForExt('cljs')).toBe('clojure')
|
||||
expect(languageKeyForExt('erl')).toBe('erlang')
|
||||
expect(languageKeyForExt('jl')).toBe('julia')
|
||||
expect(languageKeyForExt('pas')).toBe('pascal')
|
||||
expect(languageKeyForExt('vb')).toBe('vb')
|
||||
expect(languageKeyForExt('vhd')).toBe('vhdl')
|
||||
expect(languageKeyForExt('tex')).toBe('stex')
|
||||
expect(languageKeyForExt('mm')).toBe('objectivecpp')
|
||||
// 跨语言歧义扩展故意不映射(错配高亮比纯文本更误导)
|
||||
expect(languageKeyForExt('v')).toBeNull()
|
||||
expect(languageKeyForExt('m')).toBeNull()
|
||||
})
|
||||
|
||||
// 完备性回归门:switch case 与 FACTORIES 必须双向一一对应。①扩展清单
|
||||
// 从 lang.ts 源码的 `case 'xxx'` 正则派生、与下方手写 pairs 双向差集
|
||||
// (「加了 case 忘更新清单」在此暴露);②每个 ext 的 key 逐条断言
|
||||
// (「case 指向错误 key」在此暴露);③key 集合与 supportedLanguageKeys()
|
||||
// 双向差集(「加了 case 忘加 factory」或「factory 成了死条目」在此暴露);
|
||||
// ④每个已映射扩展都能构造出语言(「factory 构造期抛错」在此暴露)。
|
||||
it('every mapped extension resolves without throwing and no language key is unreachable', () => {
|
||||
const pairs: Array<[string, string]> = [
|
||||
['js', 'js'], ['mjs', 'js'], ['cjs', 'js'], ['jsx', 'jsx'],
|
||||
['ts', 'ts'], ['mts', 'ts'], ['cts', 'ts'], ['tsx', 'tsx'],
|
||||
['json', 'json'], ['jsonc', 'json'], ['md', 'md'], ['markdown', 'md'],
|
||||
['py', 'python'], ['pyw', 'python'], ['html', 'html'], ['htm', 'html'],
|
||||
['css', 'css'], ['xml', 'xml'], ['xsl', 'xml'], ['yaml', 'yaml'],
|
||||
['yml', 'yaml'], ['sql', 'sql'], ['java', 'java'], ['cs', 'csharp'],
|
||||
['kt', 'kotlin'], ['kts', 'kotlin'], ['swift', 'swift'],
|
||||
['c', 'c'], ['h', 'c'], ['cc', 'cpp'], ['cpp', 'cpp'], ['cxx', 'cpp'],
|
||||
['hpp', 'cpp'], ['hh', 'cpp'], ['hxx', 'cpp'], ['rs', 'rust'],
|
||||
['go', 'go'], ['php', 'php'], ['sh', 'shell'], ['bash', 'shell'],
|
||||
['zsh', 'shell'], ['toml', 'toml'], ['nginx', 'nginx'], ['conf', 'nginx'],
|
||||
['dockerfile', 'dockerfile'], ['docker', 'dockerfile'],
|
||||
['properties', 'properties'], ['env', 'properties'],
|
||||
['vue', 'vue'], ['scss', 'scss'], ['sass', 'sass'], ['less', 'less'],
|
||||
['styl', 'stylus'], ['rb', 'ruby'], ['lua', 'lua'], ['pl', 'perl'],
|
||||
['pm', 'perl'], ['r', 'r'], ['dart', 'dart'], ['scala', 'scala'],
|
||||
['sc', 'scala'], ['groovy', 'groovy'], ['ps1', 'powershell'],
|
||||
['psm1', 'powershell'], ['diff', 'diff'], ['patch', 'diff'],
|
||||
['proto', 'protobuf'], ['cmake', 'cmake'], ['pug', 'pug'], ['tcl', 'tcl'],
|
||||
['hs', 'haskell'], ['clj', 'clojure'], ['cljs', 'clojure'],
|
||||
['erl', 'erlang'], ['jl', 'julia'], ['pas', 'pascal'], ['vb', 'vb'],
|
||||
['vhd', 'vhdl'], ['tex', 'stex'], ['mm', 'objectivecpp'],
|
||||
]
|
||||
const mappedKeys = new Set(pairs.map(([, key]) => key))
|
||||
const supported = supportedLanguageKeys()
|
||||
// 双向:switch 每个返回值都有 factory;每个 factory 都可达(无死条目)。
|
||||
expect([...supported].sort()).toEqual([...mappedKeys].sort())
|
||||
// 扩展清单与 switch 源码的双向差集(锚定真源,防手写镜像漂移)。
|
||||
const source = readFileSync(new URL('../src/client/lang.ts', import.meta.url), 'utf8')
|
||||
const switchExts = new Set<string>()
|
||||
for (const match of source.matchAll(/case\s+'([a-z0-9]+)'/g)) switchExts.add(match[1]!)
|
||||
expect([...switchExts].sort()).toEqual([...pairs.map(([ext]) => ext)].sort())
|
||||
// 每个 ext 的 key 逐条断言 + 都能构造出语言。
|
||||
for (const [ext, key] of pairs) {
|
||||
expect(languageKeyForExt(ext), ext).toBe(key)
|
||||
expect(languageForPath(`/work/example.${ext}`), ext).not.toBeNull()
|
||||
}
|
||||
})
|
||||
|
||||
// P0 目标行为断言:vue 语言解析 SFC 时 template/script/style 三段
|
||||
// 均产出语法树节点(插值证明 Vue 层生效而非纯 HTML 底座)。
|
||||
// 强度要点:`interface` 声明只有 TS 解析器能无错吃掉(lezer JS 是
|
||||
// 超集、会报 error 节点),证明 lang="ts" 分派生效而非 JS 兜底;
|
||||
// `type="application/json"` 内容产出 SingleExpression,证明未被
|
||||
// JS 条目遮蔽。
|
||||
it('parses a Vue SFC through the vue language', () => {
|
||||
const lang = languageForPath('/work/App.vue')
|
||||
expect(lang).toBeInstanceOf(LanguageSupport)
|
||||
const parser = (lang as LanguageSupport).language.parser
|
||||
const tree = parser.parse(
|
||||
'<template><p v-if="ok">{{ msg }}</p></template>\n'
|
||||
+ '<script setup lang="ts">interface A { x: number }</script>\n'
|
||||
+ '<script type="application/json">{"a": 1}</script>\n'
|
||||
+ '<style scoped>.a { color: red; }</style>',
|
||||
)
|
||||
const names = new Set<string>()
|
||||
let hasError = false
|
||||
tree.iterate({ enter: (node) => { if (node.type.isError) hasError = true; names.add(node.name) } })
|
||||
expect(hasError).toBe(false)
|
||||
expect(names.has('Script')).toBe(true)
|
||||
expect(names.has('StyleSheet')).toBe(true)
|
||||
expect(names.has('Interpolation')).toBe(true)
|
||||
expect(names.has('SingleExpression')).toBe(true)
|
||||
})
|
||||
|
||||
// P1-2 修复守护:<style lang="scss"> 必须分派到 scss stream parser
|
||||
// (无 StyleSheet 节点、无 error),而非被 CSS parser 错配接管。
|
||||
it('dispatches <style lang="scss"> to the scss parser, not CSS', () => {
|
||||
const lang = languageForPath('/work/App.vue') as LanguageSupport
|
||||
const tree = lang.language.parser.parse('<style lang="scss">$c: red; .a { color: $c }</style>')
|
||||
const names = new Set<string>()
|
||||
let hasError = false
|
||||
tree.iterate({ enter: (node) => { if (node.type.isError) hasError = true; names.add(node.name) } })
|
||||
expect(hasError).toBe(false)
|
||||
// 两条正向鉴别:无 StyleSheet = CSS parser 未接管;无 StyleText =
|
||||
// 内容确有 parser 接管(删掉 scss 条目时内容会退化回纯文本 StyleText)。
|
||||
expect(names.has('StyleSheet')).toBe(false)
|
||||
expect(names.has('StyleText')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('pdf preview kind', () => {
|
||||
|
||||
Reference in New Issue
Block a user