test: add browser-based unit tests for helpers, i18n, router

This commit is contained in:
2026-02-05 15:30:58 +01:00
parent bd7a259d72
commit 0b0185deb1
7 changed files with 658 additions and 1 deletions

157
tests/helpers.test.js Normal file
View File

@@ -0,0 +1,157 @@
/**
* Tests for js/utils/helpers.js
*/
import { test, describe, assertEquals, assertTrue } from './test-runner.js'
import { escapeHTML, formatPrice, formatRelativeTime, debounce, truncate } from '../js/utils/helpers.js'
describe('escapeHTML', () => {
test('escapes < and >', () => {
assertEquals(escapeHTML('<script>'), '&lt;script&gt;')
})
test('escapes quotes', () => {
assertEquals(escapeHTML('"hello"'), '&quot;hello&quot;')
})
test('escapes ampersand', () => {
assertEquals(escapeHTML('a & b'), 'a &amp; b')
})
test('escapes single quotes', () => {
assertEquals(escapeHTML("it's"), "it&#039;s")
})
test('handles null', () => {
assertEquals(escapeHTML(null), '')
})
test('handles undefined', () => {
assertEquals(escapeHTML(undefined), '')
})
test('handles numbers by converting to string', () => {
assertEquals(escapeHTML(123), '123')
})
test('handles complex XSS attempt', () => {
const xss = '<img src="x" onerror="alert(1)">'
assertTrue(!escapeHTML(xss).includes('<'))
})
})
describe('formatPrice', () => {
test('formats EUR correctly', () => {
assertEquals(formatPrice(99.5, 'EUR'), '€ 99.50')
})
test('formats USD correctly', () => {
assertEquals(formatPrice(99.5, 'USD'), '$ 99.50')
})
test('formats CHF correctly', () => {
assertEquals(formatPrice(99.5, 'CHF'), 'CHF 99.50')
})
test('formats XMR with 4 decimals', () => {
assertEquals(formatPrice(0.5, 'XMR'), '0.5000 ɱ')
})
test('handles null price', () => {
assertEquals(formatPrice(null, 'EUR'), '')
})
test('handles undefined price', () => {
assertEquals(formatPrice(undefined, 'EUR'), '')
})
test('defaults to EUR', () => {
assertEquals(formatPrice(10), '€ 10.00')
})
test('handles unknown currency', () => {
assertEquals(formatPrice(10, 'GBP'), 'GBP 10.00')
})
})
describe('formatRelativeTime', () => {
test('formats seconds ago', () => {
const date = new Date(Date.now() - 30000) // 30 seconds ago
const result = formatRelativeTime(date, 'en')
assertTrue(result.includes('second'))
})
test('formats minutes ago', () => {
const date = new Date(Date.now() - 5 * 60000) // 5 minutes ago
const result = formatRelativeTime(date, 'en')
assertTrue(result.includes('minute'))
})
test('formats hours ago', () => {
const date = new Date(Date.now() - 2 * 3600000) // 2 hours ago
const result = formatRelativeTime(date, 'en')
assertTrue(result.includes('hour'))
})
test('formats days ago', () => {
const date = new Date(Date.now() - 3 * 86400000) // 3 days ago
const result = formatRelativeTime(date, 'en')
assertTrue(result.includes('day'))
})
test('accepts string date', () => {
const date = new Date(Date.now() - 60000).toISOString()
const result = formatRelativeTime(date, 'en')
assertTrue(result.includes('minute'))
})
test('respects locale de', () => {
const date = new Date(Date.now() - 60000)
const result = formatRelativeTime(date, 'de')
assertTrue(result.includes('Minute'))
})
})
describe('truncate', () => {
test('truncates long strings', () => {
assertEquals(truncate('Hello World', 6), 'Hello…')
})
test('does not truncate short strings', () => {
assertEquals(truncate('Hi', 10), 'Hi')
})
test('handles exact length', () => {
assertEquals(truncate('Hello', 5), 'Hello')
})
test('handles null', () => {
assertEquals(truncate(null, 10), null)
})
test('handles empty string', () => {
assertEquals(truncate('', 10), '')
})
test('uses default maxLength of 100', () => {
const long = 'a'.repeat(150)
const result = truncate(long)
assertEquals(result.length, 100)
assertTrue(result.endsWith('…'))
})
})
describe('debounce', () => {
test('returns a function', () => {
const fn = debounce(() => {})
assertEquals(typeof fn, 'function')
})
test('delays execution', (done) => {
let called = false
const fn = debounce(() => { called = true }, 50)
fn()
assertEquals(called, false)
})
})