import { describe, it, expect } from 'vitest' import { dom } from '@remix-run/events' import { type Remix, Catch } from './component.ts' import { renderToStream } from './stream.ts' import { hydrated } from './hydrated.ts' import { drain, readChunks, withResolvers } from './test/utils.ts' import { Frame } from './component.ts' import { invariant } from './invariant.ts' let hydrationScriptSelector = 'script[type="application/json"][rmx-hydrated]' describe('stream', () => { describe('basic nodes', () => { it('should render to a stream', () => { const stream = renderToStream(
Hello, world!
) expect(stream).toBeDefined() }) it('streams basic HTML', async () => { let stream = renderToStream(
Hello, world!
) let html = await drain(stream) expect(html).toBe('
Hello, world!
') }) it('renders string nodes', async () => { let stream = renderToStream('Hello, world!') let html = await drain(stream) expect(html).toBe('Hello, world!') }) it('renders number nodes', async () => { let stream = renderToStream(42) let html = await drain(stream) expect(html).toBe('42') }) it('renders 0', async () => { let stream = renderToStream(0) let html = await drain(stream) expect(html).toBe('0') }) it('renders bigint nodes', async () => { let stream = renderToStream(BigInt(9007199254740991)) let html = await drain(stream) expect(html).toBe('9007199254740991') }) it('renders boolean nodes', async () => { let stream = renderToStream(true) let html = await drain(stream) expect(html).toBe('') }) it('renders null nodes', async () => { let stream = renderToStream(null) let html = await drain(stream) expect(html).toBe('') }) it('renders undefined nodes', async () => { let stream = renderToStream(undefined) let html = await drain(stream) expect(html).toBe('') }) it('renders array of nodes', async () => { let stream = renderToStream([
One
, Two]) let html = await drain(stream) expect(html).toBe('
One
Two') }) it('renders mixed array of nodes', async () => { let stream = renderToStream([
One
, 'text', 42, null, undefined]) let html = await drain(stream) expect(html).toBe('
One
text42') }) it('renders fragments', async () => { let stream = renderToStream( <>

Title

Paragraph

Content
, ) let html = await drain(stream) expect(html).toBe('

Title

Paragraph

Content
') }) }) describe('component nodes', () => { it('renders component nodes', async () => { function Greeting({ name }: { name: string }) { return
Hello, {name}!
} let stream = renderToStream() let html = await drain(stream) expect(html).toBe('
Hello, World!
') }) it('renders 0', async () => { function Test(this: Remix.Handle) { let n = 0 return () => {n} } let stream = renderToStream() let html = await drain(stream) expect(html).toBe('0') }) it('renders stateful component nodes', async () => { function Stateful(this: Remix.Handle) { return () =>
Stateful
} let stream = renderToStream() let html = await drain(stream) expect(html).toBe('
Stateful
') }) it('provides and reads context', async () => { type ThemeContext = { color: string; size: number } function ThemeProvider(this: Remix.Handle, { children }: { children: any }) { this.context.set({ color: 'blue', size: 16 }) return children } function ThemedText(this: Remix.Handle) { let theme = this.context.get(ThemeProvider) return

Themed!

} function App() { return (
) } let stream = renderToStream() let html = await drain(stream) expect(html).toBe('

Themed!

') }) it('provides and reads nested context', async () => { type ThemeContext = { color: string } type UserContext = { name: string } function ThemeProvider(this: Remix.Handle, { children }: { children: any }) { this.context.set({ color: 'red' }) return children } function UserProvider(this: Remix.Handle, { children }: { children: any }) { this.context.set({ name: 'John' }) return children } function Display(this: Remix.Handle) { let theme = this.context.get(ThemeProvider) let user = this.context.get(UserProvider) return

Hello, {user.name}!

} function App() { return (
) } let stream = renderToStream() let html = await drain(stream) expect(html).toBe('

Hello, John!

') }) it('provides context to multiple consumers', async () => { type CountContext = { count: number } function CountProvider(this: Remix.Handle, { children }: { children: any }) { this.context.set({ count: 42 }) return children } function CountDisplay(this: Remix.Handle) { let { count } = this.context.get(CountProvider) return Count: {count} } function DoubleDisplay(this: Remix.Handle) { let { count } = this.context.get(CountProvider) return Double: {count * 2} } function App() { return (

) } let stream = renderToStream() let html = await drain(stream) expect(html).toBe('
Count: 42
Double: 84
') }) }) describe('special props', () => { it('renders innerHTML on elements', async () => { let htmlContent = 'Bold text and italic text' let stream = renderToStream(

Title

After innerHTML

, ) let html = await drain(stream) expect(html).toBe( '

Title

Bold text and italic text

After innerHTML

', ) }) it('changes className to class', async () => { let stream = renderToStream(
Content
) let html = await drain(stream) expect(html).toBe('
Content
') }) it('changes htmlFor to for', async () => { let stream = renderToStream( <> , ) let html = await drain(stream) expect(html).toBe('') }) it('changes acceptCharset to accept-charset', async () => { let stream = renderToStream(
, ) let html = await drain(stream) expect(html).toBe('
') }) it('changes httpEquiv to http-equiv', async () => { let stream = renderToStream( , ) let html = await drain(stream) expect(html).toBe( '', ) }) it('handles namespaced xlinkHref to xlink:href', async () => { let stream = renderToStream( , ) let html = await drain(stream) expect(html).toBe('') }) it("lowercases camelCase attributes that don't need special handling", async () => { let stream = renderToStream( , ) let html = await drain(stream) expect(html).toBe( '', ) }) it('handles table attributes colSpan and rowSpan', async () => { let stream = renderToStream(
Cell
, ) let html = await drain(stream) expect(html).toBe('
Cell
') }) it('filters framework-specific props', async () => { let stream = renderToStream(
  • First
  • Second
, ) let html = await drain(stream) // Framework props should not appear in HTML expect(html).not.toContain('key=') expect(html).not.toContain('on=') // But regular HTML attributes should be preserved expect(html).toContain('type="button"') expect(html).toContain('
  • First
  • ') expect(html).toContain('
  • Second
  • ') }) }) describe('svg', () => { it('renders SVG with preserved viewBox and kebab-cased attributes', async () => { let stream = renderToStream( , ) let html = await drain(stream) expect(html).toBe( '', ) }) it('renders foreignObject subtree as HTML (className -> class)', async () => { let stream = renderToStream(
    Hello
    , ) let html = await drain(stream) expect(html).toBe( '
    Hello
    ', ) }) it('renders xmlLang and xmlSpace as xml:lang and xml:space', async () => { let stream = renderToStream( Hi , ) let html = await drain(stream) expect(html).toBe('Hi') }) }) describe('styles', () => { it('handles css prop with style objects', async () => { let stream = renderToStream(
    Styled text
    ) let html = await drain(stream) // Should have a style tag in head expect(html).toContain('') }) it('places styles in head when no html root', async () => { let stream = renderToStream(
    No HTML root
    ) let html = await drain(stream) // Style should be in a head element expect(html).toMatch(/^