Files
dify/web/app/components/base/markdown/__tests__/markdown-utils.spec.ts
Saumya Talwani f50e44b24a test: improve coverage for some test files (#32916)
Signed-off-by: edvatar <88481784+toroleapinc@users.noreply.github.com>
Signed-off-by: -LAN- <laipz8200@outlook.com>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: majiayu000 <1835304752@qq.com>
Co-authored-by: Poojan <poojan@infocusp.com>
Co-authored-by: sahil-infocusp <73810410+sahil-infocusp@users.noreply.github.com>
Co-authored-by: 非法操作 <hjlarry@163.com>
Co-authored-by: Pandaaaa906 <ye.pandaaaa906@gmail.com>
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
Co-authored-by: heyszt <270985384@qq.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Ijas <ijas.ahmd.ap@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: 木之本澪 <kinomotomiovo@gmail.com>
Co-authored-by: KinomotoMio <200703522+KinomotoMio@users.noreply.github.com>
Co-authored-by: 不做了睡大觉 <64798754+stakeswky@users.noreply.github.com>
Co-authored-by: User <user@example.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: edvatar <88481784+toroleapinc@users.noreply.github.com>
Co-authored-by: -LAN- <laipz8200@outlook.com>
Co-authored-by: Leilei <138381132+Inlei@users.noreply.github.com>
Co-authored-by: HaKu <104669497+haku-ink@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: wangxiaolei <fatelei@gmail.com>
Co-authored-by: Varun Chawla <34209028+veeceey@users.noreply.github.com>
Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
Co-authored-by: tda <95275462+tda1017@users.noreply.github.com>
Co-authored-by: root <root@DESKTOP-KQLO90N>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: Niels Kaspers <153818647+nielskaspers@users.noreply.github.com>
Co-authored-by: hj24 <mambahj24@gmail.com>
Co-authored-by: Tyson Cung <45380903+tysoncung@users.noreply.github.com>
Co-authored-by: Stephen Zhou <hi@hyoban.cc>
Co-authored-by: FFXN <31929997+FFXN@users.noreply.github.com>
Co-authored-by: slegarraga <64795732+slegarraga@users.noreply.github.com>
Co-authored-by: 99 <wh2099@pm.me>
Co-authored-by: Br1an <932039080@qq.com>
Co-authored-by: L1nSn0w <l1nsn0w@qq.com>
Co-authored-by: Yunlu Wen <yunlu.wen@dify.ai>
Co-authored-by: akkoaya <151345394+akkoaya@users.noreply.github.com>
Co-authored-by: 盐粒 Yanli <yanli@dify.ai>
Co-authored-by: lif <1835304752@qq.com>
Co-authored-by: weiguang li <codingpunk@gmail.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: HanWenbo <124024253+hwb96@users.noreply.github.com>
Co-authored-by: Coding On Star <447357187@qq.com>
Co-authored-by: CodingOnStar <hanxujiang@dify.com>
Co-authored-by: Stable Genius <stablegenius043@gmail.com>
Co-authored-by: Stable Genius <259448942+stablegenius49@users.noreply.github.com>
Co-authored-by: ふるい <46769295+Echo0ff@users.noreply.github.com>
Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com>
2026-03-06 18:59:16 +08:00

155 lines
5.3 KiB
TypeScript

/**
* Helper to (re)load the module with a mocked config value.
* We need to reset modules because the tested module imports
* ALLOW_UNSAFE_DATA_SCHEME at top-level.
*/
const loadModuleWithConfig = async (allowDataScheme: boolean) => {
vi.resetModules()
vi.doMock('@/config', () => ({ ALLOW_UNSAFE_DATA_SCHEME: allowDataScheme }))
return await import('../markdown-utils')
}
describe('preprocessLaTeX', () => {
let mod: typeof import('../markdown-utils')
beforeEach(async () => {
// config value doesn't matter for LaTeX preprocessing, mock it false
mod = await loadModuleWithConfig(false)
})
it('returns non-string input unchanged', () => {
// call with a non-string (bypass TS type system)
// @ts-expect-error test
const out = mod.preprocessLaTeX(123)
expect(out).toBe(123)
})
it('converts \\[ ... \\] into $$ ... $$', () => {
const input = 'This is math: \\[x^2 + 1\\]'
const out = mod.preprocessLaTeX(input)
expect(out).toContain('$$x^2 + 1$$')
})
it('converts \\( ... \\) into $$ ... $$', () => {
const input = 'Inline: \\(a+b\\)'
const out = mod.preprocessLaTeX(input)
expect(out).toContain('$$a+b$$')
})
it('preserves code blocks (does not transform $ inside them)', () => {
const input = [
'Some text before',
'```js',
'const s = \'$insideCode$\'',
'```',
'And outside $math$',
].join('\n')
const out = mod.preprocessLaTeX(input)
// code block should be preserved exactly (including $ inside)
expect(out).toContain('```js\nconst s = \'$insideCode$\'\n```')
// outside inline $math$ should remain intact (function keeps inline $...$)
expect(out).toContain('$math$')
})
it('does not treat escaped dollar \\$ as math delimiter', () => {
const input = 'Price: \\$5 and math $x$'
const out = mod.preprocessLaTeX(input)
// escaped dollar should remain escaped
expect(out).toContain('\\$5')
// math should still be present
expect(out).toContain('$x$')
})
})
describe('preprocessThinkTag', () => {
let mod: typeof import('../markdown-utils')
beforeEach(async () => {
mod = await loadModuleWithConfig(false)
})
it('transforms single <think>...</think> into details with data-think and ENDTHINKFLAG', () => {
const input = '<think>this is a thought</think>'
const out = mod.preprocessThinkTag(input)
expect(out).toContain('<details data-think=true>')
expect(out).toContain('this is a thought')
expect(out).toContain('[ENDTHINKFLAG]</details>')
})
it('handles multiple <think> tags and inserts newline after closing </details>', () => {
const input = '<think>one</think>\n<think>two</think>'
const out = mod.preprocessThinkTag(input)
// both thoughts become details blocks
const occurrences = (out.match(/<details data-think=true>/g) || []).length
expect(occurrences).toBe(2)
// ensure ENDTHINKFLAG is present twice
const endCount = (out.match(/\[ENDTHINKFLAG\]<\/details>/g) || []).length
expect(endCount).toBe(2)
})
})
describe('customUrlTransform', () => {
afterEach(() => {
vi.resetAllMocks()
vi.resetModules()
})
it('allows fragments (#foo) and protocol-relative (//host) and relative paths', async () => {
const mod = await loadModuleWithConfig(false)
const t = mod.customUrlTransform
expect(t('#some-id')).toBe('#some-id')
expect(t('//example.com/path')).toBe('//example.com/path')
expect(t('relative/path/to/file')).toBe('relative/path/to/file')
expect(t('/absolute/path')).toBe('/absolute/path')
})
it('allows permitted schemes (http, https, mailto, xmpp, irc/ircs, abbr) case-insensitively', async () => {
const mod = await loadModuleWithConfig(false)
const t = mod.customUrlTransform
expect(t('http://example.com')).toBe('http://example.com')
expect(t('HTTPS://example.com')).toBe('HTTPS://example.com')
expect(t('mailto:user@example.com')).toBe('mailto:user@example.com')
expect(t('xmpp:user@example.com')).toBe('xmpp:user@example.com')
expect(t('irc:somewhere')).toBe('irc:somewhere')
expect(t('ircs:secure')).toBe('ircs:secure')
expect(t('abbr:some-ref')).toBe('abbr:some-ref')
})
it('rejects unknown/unsafe schemes (javascript:, ftp:) and returns undefined', async () => {
const mod = await loadModuleWithConfig(false)
const t = mod.customUrlTransform
expect(t('javascript:alert(1)')).toBeUndefined()
expect(t('ftp://example.com/file')).toBeUndefined()
})
it('treats colons inside path/query/fragment as NOT a scheme and returns the original URI', async () => {
const mod = await loadModuleWithConfig(false)
const t = mod.customUrlTransform
// colon after a slash -> part of path
expect(t('folder/name:withcolon')).toBe('folder/name:withcolon')
// colon after question mark -> part of query
expect(t('page?param:http')).toBe('page?param:http')
// colon after hash -> part of fragment
expect(t('page#frag:with:colon')).toBe('page#frag:with:colon')
})
it('respects ALLOW_UNSAFE_DATA_SCHEME: false blocks data:, true allows data:', async () => {
const modFalse = await loadModuleWithConfig(false)
expect(modFalse.customUrlTransform('data:text/plain;base64,SGVsbG8=')).toBeUndefined()
const modTrue = await loadModuleWithConfig(true)
expect(modTrue.customUrlTransform('data:text/plain;base64,SGVsbG8=')).toBe('data:text/plain;base64,SGVsbG8=')
})
})