import { describe, test, expect } from 'bun:test'
import { rewriteHtmlPaths, isHtmlContent } from './html-rewriter'
describe('rewriteHtmlPaths', () => {
const basePath = '/identifier/site/'
describe('absolute paths', () => {
test('rewrites absolute paths with leading slash', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('
')
})
test('rewrites nested absolute paths', () => {
const html = ''
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('')
})
})
describe('relative paths from root document', () => {
test('rewrites relative paths with ./ prefix', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('
')
})
test('rewrites relative paths without prefix', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('
')
})
test('rewrites relative paths with ../ (should stay at root)', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('
')
})
})
describe('relative paths from nested documents', () => {
test('rewrites relative path from nested document', () => {
const html = '
'
const result = rewriteHtmlPaths(
html,
basePath,
'folder1/folder2/index.html'
)
expect(result).toBe(
'
'
)
})
test('rewrites plain filename from nested document', () => {
const html = ''
const result = rewriteHtmlPaths(
html,
basePath,
'folder1/folder2/index.html'
)
expect(result).toBe(
''
)
})
test('rewrites ../ to go up one level', () => {
const html = '
'
const result = rewriteHtmlPaths(
html,
basePath,
'folder1/folder2/folder3/index.html'
)
expect(result).toBe(
'
'
)
})
test('rewrites multiple ../ to go up multiple levels', () => {
const html = ''
const result = rewriteHtmlPaths(
html,
basePath,
'folder1/folder2/folder3/index.html'
)
expect(result).toBe(
''
)
})
test('rewrites ../ with additional path segments', () => {
const html = '
'
const result = rewriteHtmlPaths(
html,
basePath,
'pages/about/index.html'
)
expect(result).toBe(
'
'
)
})
test('handles complex nested relative paths', () => {
const html = ''
const result = rewriteHtmlPaths(
html,
basePath,
'pages/blog/post/index.html'
)
expect(result).toBe(
''
)
})
test('handles ../ going past root (stays at root)', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'folder1/index.html')
expect(result).toBe('
')
})
})
describe('external URLs and special schemes', () => {
test('does not rewrite http URLs', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('
')
})
test('does not rewrite https URLs', () => {
const html = ''
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe(
''
)
})
test('does not rewrite protocol-relative URLs', () => {
const html = ''
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe(
''
)
})
test('does not rewrite data URIs', () => {
const html =
''
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe(
'
'
)
})
test('does not rewrite mailto links', () => {
const html = 'Email'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('Email')
})
test('does not rewrite tel links', () => {
const html = 'Call'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('Call')
})
})
describe('different HTML attributes', () => {
test('rewrites src attribute', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('
')
})
test('rewrites href attribute', () => {
const html = 'Link'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('Link')
})
test('rewrites action attribute', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('
')
})
test('handles single quotes', () => {
const html = "
"
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe("
")
})
test('handles mixed quotes in same document', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe(
'
'
)
})
})
describe('multiple rewrites in same document', () => {
test('rewrites multiple attributes in complex HTML', () => {
const html = `
About
`
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toContain('href="/identifier/site/css/style.css"')
expect(result).toContain('src="/identifier/site/js/app.js"')
expect(result).toContain('src="/identifier/site/images/logo.png"')
expect(result).toContain('href="/identifier/site/about.html"')
expect(result).toContain('action="/identifier/site/submit"')
})
test('handles mix of relative and absolute paths', () => {
const html = `
`
const result = rewriteHtmlPaths(
html,
basePath,
'folder1/folder2/page.html'
)
expect(result).toContain('src="/identifier/site/abs/image.png"')
expect(result).toContain(
'src="/identifier/site/folder1/folder2/rel/image.png"'
)
expect(result).toContain(
'src="/identifier/site/folder1/parent/image.png"'
)
expect(result).toContain('src="https://external.com/image.png"')
})
})
describe('edge cases', () => {
test('handles empty src attribute', () => {
const html = '
'
const result = rewriteHtmlPaths(html, '/identifier/site', 'index.html')
expect(result).toBe('
')
})
test('handles basePath with trailing slash', () => {
const html = '
'
const result = rewriteHtmlPaths(
html,
'/identifier/site/',
'index.html'
)
expect(result).toBe('
')
})
test('handles whitespace around equals sign', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('
')
})
test('preserves query strings in URLs', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe('
')
})
test('preserves hash fragments in URLs', () => {
const html = 'Link'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe(
'Link'
)
})
test('handles paths with special characters', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe(
'
'
)
})
})
describe('real-world scenario', () => {
test('handles the example from the bug report', () => {
// HTML file at: /folder1/folder2/folder3/index.html
// Image at: /folder1/folder2/img.png
// Reference: src="../img.png"
const html = '
'
const result = rewriteHtmlPaths(
html,
basePath,
'folder1/folder2/folder3/index.html'
)
expect(result).toBe(
'
'
)
})
test('handles deeply nested static site structure', () => {
// A typical static site with nested pages and shared assets
const html = `
Back to Blog
Home
`
const result = rewriteHtmlPaths(
html,
basePath,
'blog/posts/my-post.html'
)
// Assets two levels up
expect(result).toContain('href="/identifier/site/css/style.css"')
expect(result).toContain('href="/identifier/site/css/theme.css"')
expect(result).toContain('src="/identifier/site/js/main.js"')
expect(result).toContain('src="/identifier/site/images/logo.png"')
// Same directory
expect(result).toContain(
'src="/identifier/site/blog/posts/post-image.jpg"'
)
// One level up
expect(result).toContain('href="/identifier/site/blog/index.html"')
// Two levels up
expect(result).toContain('href="/identifier/site/index.html"')
})
})
})
describe('isHtmlContent', () => {
test('identifies HTML by content type', () => {
expect(isHtmlContent('file.txt', 'text/html')).toBe(true)
expect(isHtmlContent('file.txt', 'text/html; charset=utf-8')).toBe(
true
)
})
test('identifies HTML by .html extension', () => {
expect(isHtmlContent('index.html')).toBe(true)
expect(isHtmlContent('page.html', undefined)).toBe(true)
expect(isHtmlContent('/path/to/file.html')).toBe(true)
})
test('identifies HTML by .htm extension', () => {
expect(isHtmlContent('index.htm')).toBe(true)
expect(isHtmlContent('page.htm', undefined)).toBe(true)
})
test('handles case-insensitive extensions', () => {
expect(isHtmlContent('INDEX.HTML')).toBe(true)
expect(isHtmlContent('page.HTM')).toBe(true)
expect(isHtmlContent('File.HtMl')).toBe(true)
})
test('returns false for non-HTML files', () => {
expect(isHtmlContent('script.js')).toBe(false)
expect(isHtmlContent('style.css')).toBe(false)
expect(isHtmlContent('image.png')).toBe(false)
expect(isHtmlContent('data.json')).toBe(false)
})
test('returns false for files with no extension', () => {
expect(isHtmlContent('README')).toBe(false)
expect(isHtmlContent('Makefile')).toBe(false)
})
})
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe(
'
'
)
})
test('rewrites srcset with width descriptors', () => {
const html = '
'
const result = rewriteHtmlPaths(html, basePath, 'index.html')
expect(result).toBe(
'
'
)
})
test('rewrites srcset with relative paths from nested document', () => {
const html = '
'
const result = rewriteHtmlPaths(
html,
basePath,
'folder1/folder2/index.html'
)
expect(result).toBe(
'
'
)
})
})
describe('quote handling', () => {
test('handles double quotes', () => {
const html = '