import { createRequire } from 'node:module' import type nunjucks from 'nunjucks' import { vi } from 'vitest' import type { Mocked } from 'vitest' import { StructureType } from '@ministryofjustice/hmpps-forge/core/authoring' import { BlockDefinition, EvaluatedBlock, ComponentRegistryEntry } from '@ministryofjustice/hmpps-forge/core/components' import type { NunjucksComponentRenderer } from '../utils/nunjucksComponent' /** * Test helper for MOJ Frontend components * * Provides utilities for testing component data transformation and rendering. */ export class MojComponentTestHelper { private static readonly require = createRequire(import.meta.url) private readonly renderFn: NunjucksComponentRenderer private mockNunjucksEnv: Mocked constructor(component: ComponentRegistryEntry) { this.renderFn = (block, nunjucksEnv) => { const rendered = component.render(block, nunjucksEnv) if (typeof rendered !== 'string') { throw new Error('MOJ component test helpers only support synchronous Nunjucks components') } return rendered } this.mockNunjucksEnv = { render: vi.fn().mockReturnValue('
Mocked HTML
'), } as unknown as Mocked } /** * Gets the params object passed to MOJ templates * * MOJ templates expect params wrapped in a { params: ... } object. * This method extracts just the params for easy assertion. */ getParams(props: Partial> = {}): Record { const { context } = this.executeComponent(props) return (context as { params: Record }).params } /** * Executes the component and returns the template and context passed to nunjucks */ executeComponent(props: Partial> = {}) { const block: EvaluatedBlock = { type: StructureType.BLOCK, ...props, } as EvaluatedBlock this.renderFn(block, this.mockNunjucksEnv) const lastCallIndex = this.mockNunjucksEnv.render.mock.calls.length - 1 const [template, context] = this.mockNunjucksEnv.render.mock.calls[lastCallIndex] return { template, context } } /** * Renders the component with real nunjucks for DOM testing */ renderWithNunjucks(props: Partial> = {}) { const nunjucksReal = MojComponentTestHelper.require('nunjucks') as typeof nunjucks const govukPath = MojComponentTestHelper.require .resolve('govuk-frontend/package.json') .replace('/package.json', '/dist/') const mojPath = MojComponentTestHelper.require .resolve('@ministryofjustice/frontend/package.json') .replace('/package.json', '/') const realEnv = nunjucksReal.configure([govukPath, mojPath]) const block: EvaluatedBlock = { type: StructureType.BLOCK, ...props, } as EvaluatedBlock return this.renderFn(block, realEnv) } /** * Reset mock between tests */ resetMock() { this.mockNunjucksEnv.render.mockClear() } }