import { describe, it, expect } from 'vitest'
import {
generateOgImage,
generateOgSvg,
wrapText,
escapeXml,
} from '../../../src/services/og-image.js'
// ===========================================================================
// escapeXml
// ===========================================================================
describe('escapeXml', () => {
it('escapes ampersands', () => {
expect(escapeXml('A & B')).toBe('A & B')
})
it('escapes angle brackets', () => {
expect(escapeXml("")).toBe(
'<script>alert('xss')</script>'
)
})
it('escapes quotes', () => {
expect(escapeXml('He said "hello"')).toBe('He said "hello"')
})
it('returns empty string unchanged', () => {
expect(escapeXml('')).toBe('')
})
it('returns plain text unchanged', () => {
expect(escapeXml('Hello World')).toBe('Hello World')
})
})
// ===========================================================================
// wrapText
// ===========================================================================
describe('wrapText', () => {
it('returns single line when text fits', () => {
const result = wrapText('Short title', 40, 3)
expect(result).toEqual(['Short title'])
})
it('wraps text at word boundaries', () => {
const result = wrapText('This is a longer title that should wrap', 20, 3)
expect(result.length).toBeGreaterThan(1)
// Each line should be at most 20 characters (approximately)
for (const line of result) {
// Allow some overflow for long words
expect(line.length).toBeLessThanOrEqual(25)
}
})
it('limits to maxLines', () => {
const longText = 'word '.repeat(50).trim()
const result = wrapText(longText, 20, 3)
expect(result.length).toBeLessThanOrEqual(3)
})
it('adds ellipsis when text is truncated', () => {
const longText = 'word '.repeat(50).trim()
const result = wrapText(longText, 20, 2)
const lastLine = result[result.length - 1]
expect(lastLine).toContain('\u2026')
})
it('handles empty string', () => {
const result = wrapText('', 40, 3)
expect(result).toEqual([])
})
it('handles single long word', () => {
const result = wrapText('Supercalifragilisticexpialidocious', 10, 3)
expect(result.length).toBeGreaterThanOrEqual(1)
})
})
// ===========================================================================
// generateOgSvg
// ===========================================================================
describe('generateOgSvg', () => {
it('returns valid SVG string with correct dimensions', () => {
const svg = generateOgSvg({
title: 'My Topic',
category: 'general',
communityName: 'Test Community',
})
expect(svg).toContain('width="1200"')
expect(svg).toContain('height="630"')
expect(svg).toContain('xmlns=')
})
it('includes the topic title', () => {
const svg = generateOgSvg({
title: 'My Amazing Topic',
category: 'general',
communityName: 'Test Community',
})
expect(svg).toContain('My Amazing Topic')
})
it('includes the category name', () => {
const svg = generateOgSvg({
title: 'Topic',
category: 'announcements',
communityName: 'Test Community',
})
expect(svg).toContain('ANNOUNCEMENTS')
})
it('includes the community name', () => {
const svg = generateOgSvg({
title: 'Topic',
category: 'general',
communityName: 'My Forum',
})
expect(svg).toContain('My Forum')
})
it('includes Barazo branding', () => {
const svg = generateOgSvg({
title: 'Topic',
category: 'general',
communityName: 'Test Community',
})
expect(svg).toContain('barazo.forum')
})
it('escapes special characters in title', () => {
const svg = generateOgSvg({
title: 'Using