import { BlockType, StructureType } from '@ministryofjustice/hmpps-forge/core/authoring' import type { RenderedBlock } from '@ministryofjustice/hmpps-forge/core/components' import { GovukComponentTestHelper } from '../../test-utils/GovukComponentTestHelper' import { setupComponentTest } from '../../test-utils/setupComponentTest' import { GovUKTable } from './govukTable' vi.mock('nunjucks') describe('GOV.UK Table Component', () => { setupComponentTest() const helper = new GovukComponentTestHelper(GovUKTable) const renderedBlock = (html: string): RenderedBlock => ({ block: { type: StructureType.BLOCK, blockType: BlockType.BASIC, variant: 'html', }, html, }) describe('Row data transformation', () => { it('sets basic row with single cell', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }]], }) // Assert expect(params.rows).toHaveLength(1) expect(params.rows[0]).toHaveLength(1) expect(params.rows[0][0]).toEqual({ text: 'January' }) }) it('sets basic row with multiple cells', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }, { text: '£95' }]], }) // Assert expect(params.rows).toHaveLength(1) expect(params.rows[0]).toHaveLength(3) expect(params.rows[0][0]).toEqual({ text: 'January' }) expect(params.rows[0][1]).toEqual({ text: '£85' }) expect(params.rows[0][2]).toEqual({ text: '£95' }) }) it('sets multiple rows with cells', async () => { // Arrange & Act const params = await helper.getParams({ rows: [ [{ text: 'January' }, { text: '£85' }], [{ text: 'February' }, { text: '£165' }], [{ text: 'March' }, { text: '£230' }], ], }) // Assert expect(params.rows).toHaveLength(3) expect(params.rows[0]).toEqual([{ text: 'January' }, { text: '£85' }]) expect(params.rows[1]).toEqual([{ text: 'February' }, { text: '£165' }]) expect(params.rows[2]).toEqual([{ text: 'March' }, { text: '£230' }]) }) it('sets cell with html content', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ html: 'January' }, { text: '£85' }]], }) // Assert expect(params.rows[0][0]).toEqual({ html: 'January' }) }) it('sets cells with both text and html', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'This is ignored', html: 'February' }, { text: '£165' }]], }) // Assert expect(params.rows[0][0]).toEqual({ text: 'This is ignored', html: 'February' }) }) it('uses cell blocks over text and html', async () => { // Arrange const blocks = [renderedBlock('Completed'), renderedBlock('Updated')] // Act const params = await helper.getParams({ rows: [[{ text: 'Ignored', html: 'Ignored', blocks }]], }) // Assert expect(params.rows[0][0]).toEqual({ text: undefined, html: 'CompletedUpdated', }) }) }) describe('Header row', () => { it('sets basic head cells', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], head: [{ text: 'Month' }, { text: 'Amount' }], }) // Assert expect(params.head).toHaveLength(2) expect(params.head[0]).toEqual({ text: 'Month' }) expect(params.head[1]).toEqual({ text: 'Amount' }) }) it('sets head with numeric format', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], head: [{ text: 'Month' }, { text: 'Amount', format: 'numeric' }], }) // Assert expect(params.head[1]).toEqual({ text: 'Amount', format: 'numeric' }) }) it('sets head with classes', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], head: [{ text: 'Month', classes: 'app-table__header--custom' }, { text: 'Amount' }], }) // Assert expect(params.head[0]).toEqual({ text: 'Month', classes: 'app-table__header--custom' }) }) it('sets head with html content', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], head: [{ html: 'Month' }, { text: 'Amount' }], }) // Assert expect(params.head[0]).toEqual({ html: 'Month' }) }) it('sets head with colspan', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], head: [{ text: 'Month', colspan: 2 }], }) // Assert expect(params.head[0]).toEqual({ text: 'Month', colspan: 2 }) }) it('sets head with rowspan', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], head: [{ text: 'Month', rowspan: 2 }, { text: 'Amount' }], }) // Assert expect(params.head[0]).toEqual({ text: 'Month', rowspan: 2 }) }) it('sets head with attributes', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], head: [ { text: 'Month', attributes: { 'data-sort': 'month', }, }, { text: 'Amount' }, ], }) // Assert expect(params.head[0]).toEqual({ text: 'Month', attributes: { 'data-sort': 'month', }, }) }) }) describe('Cell options', () => { it('sets cell with numeric format', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85', format: 'numeric' }]], }) // Assert expect(params.rows[0][1]).toEqual({ text: '£85', format: 'numeric' }) }) it('sets cell with colspan', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'Total', colspan: 2 }, { text: '£250' }]], }) // Assert expect(params.rows[0][0]).toEqual({ text: 'Total', colspan: 2 }) }) it('sets cell with rowspan', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'Q1', rowspan: 3 }, { text: 'January' }, { text: '£85' }]], }) // Assert expect(params.rows[0][0]).toEqual({ text: 'Q1', rowspan: 3 }) }) it('sets cell with classes', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85', classes: 'app-table__cell--highlighted' }]], }) // Assert expect(params.rows[0][1]).toEqual({ text: '£85', classes: 'app-table__cell--highlighted' }) }) it('sets cell with attributes', async () => { // Arrange & Act const params = await helper.getParams({ rows: [ [ { text: 'January' }, { text: '£85', attributes: { 'data-value': '85', }, }, ], ], }) // Assert expect(params.rows[0][1]).toEqual({ text: '£85', attributes: { 'data-value': '85', }, }) }) it('sets cell with multiple options', async () => { // Arrange & Act const params = await helper.getParams({ rows: [ [ { text: 'January' }, { text: '£85', format: 'numeric', classes: 'app-table__cell--total', attributes: { 'data-value': '85', }, }, ], ], }) // Assert expect(params.rows[0][1]).toEqual({ text: '£85', format: 'numeric', classes: 'app-table__cell--total', attributes: { 'data-value': '85', }, }) }) }) describe('Caption', () => { it('sets caption text', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], caption: 'Monthly savings', }) // Assert expect(params.caption).toBe('Monthly savings') }) it('sets captionClasses', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], caption: 'Monthly savings', captionClasses: 'govuk-table__caption--m', }) // Assert expect(params.caption).toBe('Monthly savings') expect(params.captionClasses).toBe('govuk-table__caption--m') }) it('sets captionClasses without caption', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], captionClasses: 'govuk-table__caption--l', }) // Assert expect(params.caption).toBeUndefined() expect(params.captionClasses).toBe('govuk-table__caption--l') }) }) describe('First cell is header', () => { it('sets firstCellIsHeader to true', async () => { // Arrange & Act const params = await helper.getParams({ rows: [ [{ text: 'January' }, { text: '£85' }], [{ text: 'February' }, { text: '£165' }], ], firstCellIsHeader: true, }) // Assert expect(params.firstCellIsHeader).toBe(true) }) it('sets firstCellIsHeader to false', async () => { // Arrange & Act const params = await helper.getParams({ rows: [ [{ text: 'January' }, { text: '£85' }], [{ text: 'February' }, { text: '£165' }], ], firstCellIsHeader: false, }) // Assert expect(params.firstCellIsHeader).toBe(false) }) it('leaves firstCellIsHeader undefined when not provided', async () => { // Arrange & Act const params = await helper.getParams({ rows: [ [{ text: 'January' }, { text: '£85' }], [{ text: 'February' }, { text: '£165' }], ], }) // Assert expect(params.firstCellIsHeader).toBeUndefined() }) }) describe('Optional attributes', () => { it('passes through classes', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], classes: 'app-table--custom', }) // Assert expect(params.classes).toBe('app-table--custom') }) it('passes through attributes', async () => { // Arrange & Act const params = await helper.getParams({ rows: [[{ text: 'January' }, { text: '£85' }]], attributes: { 'data-module': 'sortable-table', 'data-track': 'table-interaction', }, }) // Assert expect(params.attributes).toEqual({ 'data-module': 'sortable-table', 'data-track': 'table-interaction', }) }) }) describe('Template and context', () => { it('calls nunjucks with correct template path', async () => { // Arrange & Act const { template } = await helper.executeComponent({ rows: [[{ text: 'January' }, { text: '£85' }]], }) // Assert expect(template).toBe('govuk/components/table/template.njk') }) it('wraps params in correct context structure', async () => { // Arrange & Act const { context } = (await helper.executeComponent({ caption: 'Monthly savings', head: [{ text: 'Month' }, { text: 'Amount' }], rows: [ [{ text: 'January' }, { text: '£85' }], [{ text: 'February' }, { text: '£165' }], ], })) as { context: { params: any } } // Assert expect(context).toHaveProperty('params') expect(context.params.caption).toBe('Monthly savings') expect(context.params.head).toHaveLength(2) expect(context.params.rows).toHaveLength(2) }) }) describe('DOM rendering smoke test', () => { it('renders table with basic rows and head', async () => { // Arrange & Act const html = await helper.renderWithNunjucks({ head: [{ text: 'Month' }, { text: 'Amount' }], rows: [ [{ text: 'January' }, { text: '£85' }], [{ text: 'February' }, { text: '£165' }], ], }) // Assert expect(html).toContain('govuk-table') expect(html).toContain('Month') expect(html).toContain('Amount') expect(html).toContain('January') expect(html).toContain('£85') expect(html).toContain('February') expect(html).toContain('£165') }) it('renders table with caption', async () => { // Arrange & Act const html = await helper.renderWithNunjucks({ caption: 'Monthly savings', captionClasses: 'govuk-table__caption--m', head: [{ text: 'Month' }, { text: 'Amount' }], rows: [[{ text: 'January' }, { text: '£85' }]], }) // Assert expect(html).toContain('govuk-table') expect(html).toContain('Monthly savings') expect(html).toContain('govuk-table__caption--m') }) it('renders table with numeric format', async () => { // Arrange & Act const html = await helper.renderWithNunjucks({ head: [{ text: 'Month' }, { text: 'Amount', format: 'numeric' }], rows: [[{ text: 'January' }, { text: '£85', format: 'numeric' }]], }) // Assert expect(html).toContain('govuk-table') expect(html).toContain('£85') }) it('renders table with firstCellIsHeader', async () => { // Arrange & Act const html = await helper.renderWithNunjucks({ rows: [ [{ text: 'January' }, { text: '£85' }], [{ text: 'February' }, { text: '£165' }], ], firstCellIsHeader: true, }) // Assert expect(html).toContain('govuk-table') expect(html).toContain('January') expect(html).toContain('February') }) it('renders table with custom classes', async () => { // Arrange & Act const html = await helper.renderWithNunjucks({ rows: [[{ text: 'January' }, { text: '£85' }]], classes: 'app-table--custom', }) // Assert expect(html).toContain('govuk-table') expect(html).toContain('app-table--custom') }) it('renders table with colspan and rowspan', async () => { // Arrange & Act const html = await helper.renderWithNunjucks({ head: [{ text: 'Details', colspan: 2 }], rows: [[{ text: 'Q1', rowspan: 3 }, { text: 'January' }]], }) // Assert expect(html).toContain('govuk-table') expect(html).toContain('Details') expect(html).toContain('Q1') expect(html).toContain('January') }) it('renders table with HTML content in cells', async () => { // Arrange & Act const html = await helper.renderWithNunjucks({ head: [{ html: 'Month' }, { text: 'Amount' }], rows: [[{ html: 'January' }, { text: '£85' }]], }) // Assert expect(html).toContain('govuk-table') expect(html).toContain('Month') expect(html).toContain('January') }) it('renders table with block content in cells', async () => { // Arrange const blocks = [renderedBlock('Completed')] // Act const html = await helper.renderWithNunjucks({ rows: [[{ text: 'Status' }, { blocks }]], }) // Assert expect(html).toContain('govuk-table') expect(html).toContain('Status') expect(html).toContain('Completed') }) }) })