···188188 const { _statics: statics, _dynamics: dynamics } = is_html(value) ? value : single_part_template(value)
189189 const template = compile_template(statics)
190190191191+ assert(
192192+ template.parts.length === dynamics.length,
193193+ 'expected the same number of dynamics as parts. do you have a ${...} in an unsupported place?',
194194+ )
195195+191196 for (let i = 0; i < template.statics.length - 1; i++) {
192197 yield template.statics[i]
193198 yield* template.parts[i](dynamics)
+65
src/server/tests/basic.test.ts
···11+import { html } from 'dhtml'
22+import { renderToString } from 'dhtml/server'
33+import test, { type TestContext } from 'node:test'
44+55+globalThis.__DEV__ = process.env.NODE_ENV !== 'production'
66+77+test('basic html renders correctly', (t: TestContext) => {
88+ t.assert.strictEqual(renderToString(html`<h1>Hello, world!</h1>`), '<h1>Hello, world!</h1>')
99+})
1010+1111+test('inner content renders correctly', (t: TestContext) => {
1212+ t.assert.strictEqual(renderToString(html`<h1>${html`Inner content!`}</h1>`), '<h1>Inner content!</h1>')
1313+})
1414+1515+test('template with number renders correctly', (t: TestContext) => {
1616+ const template = (n: number) => html`<h1>Hello, ${n}!</h1>`
1717+ t.assert.strictEqual(renderToString(template(1)), '<h1>Hello, 1!</h1>')
1818+ t.assert.strictEqual(renderToString(template(2)), '<h1>Hello, 2!</h1>')
1919+})
2020+2121+test('basic children render correctly', (t: TestContext) => {
2222+ t.assert.strictEqual(
2323+ renderToString(html`<span>${'This is a'}</span> ${html`test`} ${html`test`} ${html`test`}`),
2424+ '<span>This is a</span> test test test',
2525+ )
2626+})
2727+2828+test('errors are thrown cleanly', (t: TestContext) => {
2929+ const oops = new Error('oops')
3030+ let thrown
3131+ try {
3232+ renderToString(
3333+ html`${{
3434+ render() {
3535+ throw oops
3636+ },
3737+ }}`,
3838+ )
3939+ } catch (error) {
4040+ thrown = error
4141+ }
4242+ t.assert.strictEqual(thrown, oops)
4343+})
4444+4545+test('invalid part placement raises error', { skip: process.env.NODE_ENV === 'production' }, (t: TestContext) => {
4646+ t.assert.throws(() => renderToString(html`<${'div'}>${'text'}</${'div'}>`))
4747+})
4848+4949+test('parts in comments do not throw', (t: TestContext) => {
5050+ renderToString(html`<!-- ${'text'} -->`)
5151+})
5252+5353+test(
5454+ 'manually specifying internal template syntax throws',
5555+ { skip: process.env.NODE_ENV === 'production' },
5656+ (t: TestContext) => {
5757+ t.assert.throws(() => {
5858+ renderToString(html`${1} dyn-$0$`)
5959+ })
6060+ },
6161+)
6262+6363+test('syntax close but not exact does not throw', (t: TestContext) => {
6464+ t.assert.strictEqual(renderToString(html`dyn-$${0}1$`), 'dyn-$01$')
6565+})