Files
Catalyst/tests/helpers.test.js
josh 0f2a37cb39
All checks were successful
CI / test (pull_request) Successful in 9m31s
CI / build-dev (pull_request) Has been skipped
fix: centre badge text on instance cards
.badge lacked text-align: center. Inside the card's flex-end right
column, badge text was left-justified within each pill, making state
labels (deployed / testing / degraded) appear skewed to the left.

TDD: CSS regression test added to tests/helpers.test.js — reads
css/app.css directly and asserts the rule is present, so this
cannot regress silently in future.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 12:28:44 -04:00

130 lines
4.2 KiB
JavaScript

// @vitest-environment jsdom
import { describe, it, expect } from 'vitest'
import { readFileSync } from 'fs'
import { join } from 'path'
// ── esc() ─────────────────────────────────────────────────────────────────────
// Mirrors the implementation in ui.js exactly (DOM-based).
// Tests the XSS contract — if the implementation changes, these define
// what it must still guarantee.
function esc(str) {
const d = document.createElement('div')
d.textContent = (str == null) ? '' : String(str)
return d.innerHTML
}
describe('esc', () => {
it('passes through plain strings unchanged', () => {
expect(esc('plex')).toBe('plex')
expect(esc('postgres-primary')).toBe('postgres-primary')
})
it('escapes < and >', () => {
expect(esc('<script>')).toBe('&lt;script&gt;')
expect(esc('</script>')).toBe('&lt;/script&gt;')
})
it('neutralises a script injection payload', () => {
const payload = '<script>alert(1)</script>'
expect(esc(payload)).not.toContain('<script>')
})
it('neutralises an img onerror payload', () => {
const result = esc('<img src=x onerror=alert(1)>')
expect(result).not.toContain('<img')
expect(result).toContain('&lt;img')
expect(result).toContain('&gt;')
})
it('escapes ampersands', () => {
expect(esc('a & b')).toBe('a &amp; b')
})
it('handles null without throwing', () => {
expect(() => esc(null)).not.toThrow()
expect(esc(null)).toBe('')
})
it('handles undefined without throwing', () => {
expect(() => esc(undefined)).not.toThrow()
expect(esc(undefined)).toBe('')
})
it('coerces numbers to string', () => {
expect(esc(137)).toBe('137')
})
})
// ── fmtDate() ─────────────────────────────────────────────────────────────────
function fmtDate(d) {
if (!d) return '—'
try {
return new Date(d).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' })
} catch (e) { return d }
}
describe('fmtDate', () => {
it('formats a valid ISO date string', () => {
const result = fmtDate('2024-03-15T00:00:00')
expect(result).toMatch(/Mar/)
expect(result).toMatch(/15/)
expect(result).toMatch(/2024/)
})
it('returns — for null', () => {
expect(fmtDate(null)).toBe('—')
})
it('returns — for empty string', () => {
expect(fmtDate('')).toBe('—')
})
it('returns — for undefined', () => {
expect(fmtDate(undefined)).toBe('—')
})
})
// ── fmtDateFull() ─────────────────────────────────────────────────────────────
function fmtDateFull(d) {
if (!d) return '—'
try {
return new Date(d).toLocaleString('en-US', {
year: 'numeric', month: 'short', day: 'numeric',
hour: '2-digit', minute: '2-digit',
})
} catch (e) { return d }
}
describe('fmtDateFull', () => {
it('includes date and time components', () => {
const result = fmtDateFull('2024-03-15T14:30:00')
expect(result).toMatch(/Mar/)
expect(result).toMatch(/2024/)
expect(result).toMatch(/\d{1,2}:\d{2}/)
})
it('returns — for null', () => {
expect(fmtDateFull(null)).toBe('—')
})
it('returns — for empty string', () => {
expect(fmtDateFull('')).toBe('—')
})
})
// ── CSS regressions ───────────────────────────────────────────────────────────
const css = readFileSync(join(__dirname, '../css/app.css'), 'utf8')
describe('CSS regressions', () => {
it('.badge has text-align: center so state labels are not left-skewed on cards', () => {
// Regression: badges rendered left-aligned inside the card's flex-end column.
// Without text-align: center, short labels (e.g. "deployed") appear
// left-justified inside their pill rather than centred.
expect(css).toMatch(/\.badge\s*\{[^}]*text-align\s*:\s*center/s)
})
})