import { z } from 'zod' import { BlockType, StructureType } from '@ministryofjustice/hmpps-forge/core/authoring' import type { BlockDefinition, EvaluatedBlock, FieldBlockDefinition, ResolvableString, } from '@ministryofjustice/hmpps-forge/core/components' import { jsxComponent } from './jsxComponent' import { raw } from '../runtime/jsx-runtime' interface TestBadge extends BlockDefinition { /** Text shown inside the badge */ text: ResolvableString } const TestBadge = jsxComponent('testBadge', { render: props => {props.text}, }) interface TestTextInput extends FieldBlockDefinition { /** Label shown above the input */ label: ResolvableString } const TestTextInput = jsxComponent('testTextInput', { field: true, render: props => (
), inputSchema: z.string(), }) const evaluatedBadge = { type: StructureType.BLOCK, variant: 'testBadge', blockType: BlockType.BASIC, text: 'New', } as EvaluatedBlock describe('jsxComponent()', () => { describe('block building', () => { it('should stamp the block envelope onto the props when called as a builder', () => { // Arrange & Act const built = TestBadge({ text: 'New' }) // Assert expect(built).toEqual({ type: StructureType.BLOCK, blockType: BlockType.BASIC, variant: 'testBadge', text: 'New', }) }) it('should stamp a field block when the options declare a field component', () => { // Arrange & Act const built = TestTextInput({ code: 'first_name', label: 'First name' }) // Assert expect(built.blockType).toBe(BlockType.FIELD) expect(built.variant).toBe('testTextInput') }) }) describe('registry entry', () => { it('should render to a plain string when the registry render is invoked', () => { // Arrange & Act const output = TestBadge.render(evaluatedBadge) // Assert expect(typeof output).toBe('string') expect(output).toBe('New') }) it('should escape evaluated prop values when they contain HTML', () => { // Arrange const evaluatedWithHtml = { ...evaluatedBadge, text: '' } as EvaluatedBlock // Act const output = TestBadge.render(evaluatedWithHtml) // Assert expect(output).toBe('<script>alert(1)</script>') }) it('should render field props into the markup when rendering a field component', () => { // Arrange const evaluatedInput = { type: StructureType.BLOCK, variant: 'testTextInput', blockType: BlockType.FIELD, code: 'first_name', label: 'First name', value: 'Ada', } as unknown as EvaluatedBlock // Act const output = TestTextInput.render(evaluatedInput) // Assert expect(output).toBe( '
' + '
', ) }) it('should expose the variant and field options when registered', () => { // Arrange & Act & Assert expect(TestTextInput.variant).toBe('testTextInput') expect(TestTextInput.inputSchema?.safeParse('text').success).toBe(true) expect(TestBadge.inputSchema).toBeUndefined() }) it('should embed pre-rendered child HTML verbatim when the render uses raw()', () => { // Arrange interface TestCard extends BlockDefinition { childHtml: string } const TestCard = jsxComponent('testCard', { render: props =>
{raw(props.childHtml)}
, }) const evaluatedCard = { type: StructureType.BLOCK, variant: 'testCard', blockType: BlockType.BASIC, childHtml: '

Rendered elsewhere

', } as EvaluatedBlock // Act const output = TestCard.render(evaluatedCard) // Assert expect(output).toBe('

Rendered elsewhere

') }) }) })