forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import { describe, expect, it } from 'vitest'
2import { escapeRawGt, highlightCodeBlock } from '../../../../server/utils/shiki'
3
4describe('escapeRawGt', () => {
5 it('should encode > in arrow functions', () => {
6 const input = '<span style="color:#F97583">=></span>'
7 const output = escapeRawGt(input)
8 expect(output).toBe('<span style="color:#F97583">=></span>')
9 })
10
11 it('should encode > in comparison operators', () => {
12 const input = '<span>x > 5</span>'
13 const output = escapeRawGt(input)
14 expect(output).toBe('<span>x > 5</span>')
15 })
16
17 it('should encode multiple > in text content', () => {
18 const input = '<span>a > b > c</span>'
19 const output = escapeRawGt(input)
20 expect(output).toBe('<span>a > b > c</span>')
21 })
22
23 it('should not affect HTML tag structure', () => {
24 const input = '<span class="test"><code>text</code></span>'
25 const output = escapeRawGt(input)
26 expect(output).toBe('<span class="test"><code>text</code></span>')
27 })
28
29 it('should not affect attributes containing >', () => {
30 // Attributes with > are already encoded by Shiki, but test anyway
31 const input = '<span title="a > b">text</span>'
32 const output = escapeRawGt(input)
33 expect(output).toBe('<span title="a > b">text</span>')
34 })
35
36 it('should handle empty text content', () => {
37 const input = '<span></span><code></code>'
38 const output = escapeRawGt(input)
39 expect(output).toBe('<span></span><code></code>')
40 })
41
42 it('should handle text without special characters', () => {
43 const input = '<span>hello world</span>'
44 const output = escapeRawGt(input)
45 expect(output).toBe('<span>hello world</span>')
46 })
47
48 it('should handle nested spans (Shiki output structure)', () => {
49 const input =
50 '<span class="line"><span style="color:#F97583">const</span><span> x = () =></span><span> 5</span></span>'
51 const output = escapeRawGt(input)
52 expect(output).toBe(
53 '<span class="line"><span style="color:#F97583">const</span><span> x = () =></span><span> 5</span></span>',
54 )
55 })
56
57 it('should handle >= operator', () => {
58 const input = '<span>x >= 5</span>'
59 const output = escapeRawGt(input)
60 expect(output).toBe('<span>x >= 5</span>')
61 })
62
63 it('should handle generic type syntax', () => {
64 const input = '<span>Array<T></span>'
65 const output = escapeRawGt(input)
66 // The < is already encoded, the > should be encoded
67 expect(output).toBe('<span>Array<T></span>')
68 })
69})
70
71describe('highlightCodeBlock', () => {
72 it('should highlight TypeScript code', async () => {
73 const code = 'const x = 1'
74 const html = await highlightCodeBlock(code, 'typescript')
75
76 expect(html).toContain('<pre')
77 expect(html).toContain('const')
78 expect(html).toContain('shiki')
79 })
80
81 it('should encode > in arrow functions', async () => {
82 const code = 'const fn = () => 5'
83 const html = await highlightCodeBlock(code, 'typescript')
84
85 // The > in => should be encoded
86 expect(html).toContain('=>')
87 expect(html).not.toMatch(/=>(?!&)/) // no raw => (except in >)
88 })
89
90 it('should encode > in generic types', async () => {
91 const code = 'const x: Array<string> = []'
92 const html = await highlightCodeBlock(code, 'typescript')
93
94 // Should have encoded >
95 expect(html).toContain('>')
96 })
97
98 it('should fall back to plain code for unknown languages', async () => {
99 const code = 'some random code > with special < chars'
100 const html = await highlightCodeBlock(code, 'unknownlang123')
101
102 expect(html).toContain('>')
103 expect(html).toContain('<')
104 expect(html).toContain('language-unknownlang123')
105 })
106
107 it('should escape special characters in fallback', async () => {
108 const code = '<script>alert("xss")</script>'
109 const html = await highlightCodeBlock(code, 'unknownlang123')
110
111 expect(html).toContain('<script>')
112 expect(html).not.toContain('<script>')
113 })
114})