Files
Bendik Aagaard LynghaugandClaude Fable 5 ce1ec9e4a9 rebrand: buuh — a friendly public fork under the uhhm org
Packages renamed to the @uhhm scope (@uhhm/buuh, @uhhm/buuh-html,
@uhhm/buuh-component, @uhhm/buuh-devtools, @uhhm/buuh-migrate,
@uhhm/bankai). Scoping is load-bearing twice over: npm routes registries
per scope so @uhhm/* resolves against project.uhhm.no while everything
else stays on npmjs, and it means this fork never squats upstream's
names anywhere. The codemod now migrates choo v7 apps to the @uhhm
names. README rewritten with the fork framing and full upstream credit;
the choojs RFC moves to docs/upstream-rfc-draft.md, in the drawer for if
this work ever goes home. API unchanged — choo() is still choo().

Also: Gitea Actions CI + release workflows (npm publish to the uhhm
registry on tag push, CDN bundle uploaded as a generic package),
npm run bundle producing dist-cdn/buuh.js (the whole framework as one
minified ES module for import-map use), docs/publishing.md explaining
what Gitea Packages is (a real npm registry) and is not (a CDN — serve
the bundle from a static host with module-safe MIME instead), and
onload.js constructing window.MutationObserver to match its own guard
(surfaced by smoke-testing the bundle outside a full browser).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
2026-09-08 19:55:20 +02:00

62 lines
2.1 KiB
JavaScript

// Server renderer behavior, matching nanohtml 1.x server semantics.
import { test } from 'node:test'
import assert from 'node:assert'
import html from '@uhhm/buuh-html'
import raw from '@uhhm/buuh-html/raw'
test('renders a template to a string', () => {
const res = html`<p>hello</p>`
assert.strictEqual(res.toString(), '<p>hello</p>')
})
test('escapes interpolated text', () => {
const res = html`<p>${'<script>alert(1)</script>'}</p>`
assert.strictEqual(res.toString(), '<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>')
})
test('raw() bypasses escaping', () => {
const res = html`<p>${raw('<em>hi</em>')}</p>`
assert.strictEqual(res.toString(), '<p><em>hi</em></p>')
})
test('nested templates are not double-encoded', () => {
const inner = html`<em>&</em>`
const res = html`<p>${inner}</p>`
assert.strictEqual(res.toString(), '<p><em>&</em></p>')
})
test('arrays of children are joined', () => {
const items = ['a', 'b'].map((x) => html`<li>${x}</li>`)
const res = html`<ul>${items}</ul>`
assert.strictEqual(res.toString(), '<ul><li>a</li><li>b</li></ul>')
})
test('unquoted attribute interpolation is quoted', () => {
const res = html`<div class=${'a b'}></div>`
assert.strictEqual(res.toString(), '<div class="a b"></div>')
})
test('boolean attributes render when truthy and drop when falsy', () => {
const on = html`<input disabled=${true} />`
const off = html`<input disabled=${false} />`
assert.strictEqual(on.toString(), '<input disabled="disabled" />')
assert.strictEqual(off.toString(), '<input />'.replace(' ', ' '))
})
test('event handler functions leave no trace in markup', () => {
const res = html`<button onclick=${() => {}}>go</button>`
assert.strictEqual(res.toString(), '<button >go</button>')
})
test('handler-shaped attributes with non-function values still render', () => {
const res = html`<a onclick=${'confirm()'}>x</a>`
assert.strictEqual(res.toString(), '<a onclick="confirm()">x</a>')
})
test('spread-style object interpolation renders attributes', () => {
const res = html`<div ${{ class: 'x', hidden: true }}></div>`
assert.strictEqual(res.toString(), '<div class="x" hidden="hidden"></div>')
})