From 6d2332e37b66f18d07ffad9c25e3fdc3d1d76f08 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 1 May 2026 19:33:31 +0200 Subject: [PATCH 1/6] add context and utility --- src/context.ts | 7 +++++++ src/lib/transformDescription.tsx | 12 ++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/lib/transformDescription.tsx diff --git a/src/context.ts b/src/context.ts index 9bbd9c533..9d6e22954 100644 --- a/src/context.ts +++ b/src/context.ts @@ -4,6 +4,7 @@ import { Components } from './types/remoteFlows'; export const FormFieldsContext = createContext<{ components: Components; + transformHtmlToComponents?: (htmlContent: string) => React.ReactNode; } | null>(null); export const useFormFields = () => { @@ -17,6 +18,12 @@ export const useFormFields = () => { }; }; +// Internal hook for accessing transformer (used during field processing and in FormDescription/FieldSetField) +export const useTransformer = () => { + const context = useContext(FormFieldsContext); + return context?.transformHtmlToComponents; +}; + export const RemoteFlowContext = createContext<{ client: Client | null }>({ client: null, }); diff --git a/src/lib/transformDescription.tsx b/src/lib/transformDescription.tsx new file mode 100644 index 000000000..c931be01d --- /dev/null +++ b/src/lib/transformDescription.tsx @@ -0,0 +1,12 @@ +import { ReactNode } from 'react'; + +export function transformDescriptionHtml( + description: string, + transformer?: (html: string) => ReactNode, +): ReactNode { + if (transformer) { + return transformer(description); + } + + return description; +} From 0b5db10e5346c74f86506e23ffb683f4777989f3 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 1 May 2026 20:07:08 +0200 Subject: [PATCH 2/6] feat(form) - add transformer to the description --- src/components/ui/form.tsx | 34 +++++-- src/components/ui/tests/form.test.tsx | 128 +++++++++++++++++++++++++- 2 files changed, 155 insertions(+), 7 deletions(-) diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx index 377291f0f..7f6edf69b 100644 --- a/src/components/ui/form.tsx +++ b/src/components/ui/form.tsx @@ -11,6 +11,7 @@ import { import { cn, sanitizeHtml } from '@/src/lib/utils'; import { Label } from '@/src/components/ui/label'; +import { useTransformer } from '@/src/context'; const Form = FormProvider; @@ -145,28 +146,49 @@ export function BaseFormDescription({ id?: string; } & Omit, 'children' | 'className' | 'id'>) { const Component = as || 'p'; + const transformHtmlToComponents = useTransformer(); + // we check if children is a string, happens in 95% of the cases I believe if (typeof children === 'string') { - return ( - <> + // if we have a transformer, we use it to transform the children + if (transformHtmlToComponents) { + const transformed = transformHtmlToComponents(children); + return ( - {' '} - {helpCenter && helpCenter} + {transformed} {helpCenter && helpCenter} - + ); + } + // if we don't have a transformer, we sanitize the children and render it as a string + return ( + + {' '} + {helpCenter && helpCenter} + ); } + + // this happens when in theory we pass a ReactNode, I don't really know if this case works in the real world + // I believe we added when we started but scared to remove it return ( {typeof children === 'function' ? children() : children} diff --git a/src/components/ui/tests/form.test.tsx b/src/components/ui/tests/form.test.tsx index b23b8b5a1..5464ce027 100644 --- a/src/components/ui/tests/form.test.tsx +++ b/src/components/ui/tests/form.test.tsx @@ -1,9 +1,11 @@ import { FormDescription } from '@/src/components/ui/form'; import { screen, render } from '@testing-library/react'; import { TestProviders } from '@/src/tests/testHelpers'; -import { PropsWithChildren } from 'react'; +import { PropsWithChildren, ReactNode } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { $TSFixMe } from '@/src/types/remoteFlows'; +import { FormFieldsContext } from '@/src/context'; +import { lazyDefaultComponents } from '@/src/lazy-default-components'; const wrapper = ({ children }: PropsWithChildren) => { const TestComponent = () => { @@ -17,6 +19,26 @@ const wrapper = ({ children }: PropsWithChildren) => { ); }; +const createWrapperWithTransformer = ( + transformHtml?: (html: string) => ReactNode, +) => { + return ({ children }: PropsWithChildren) => { + const methods = useForm(); + return ( + + + {children} + + + ); + }; +}; + describe('Form', () => { describe('FormDescription', () => { it('should render the description text when passing a normal string', () => { @@ -100,4 +122,108 @@ describe('Form', () => { expect(linkWithRel.getAttribute('rel')).toBe('noreferrer noopener'); }); }); + + describe('FormDescription with HTML transformer', () => { + it('should use the custom transformer when provided', () => { + const customTransformer = (html: string) => { + if (html.includes('')) { + return Important text; + } + return {html}; + }; + + render( + {'Important text'}, + { + wrapper: createWrapperWithTransformer(customTransformer), + }, + ); + + expect(screen.getByTestId('custom-bold')).toBeInTheDocument(); + expect(screen.getByTestId('custom-bold').textContent).toBe( + 'Important text', + ); + }); + + it('should transform complex HTML with details element (Accordion pattern)', () => { + const accordionTransformer = (html: string) => { + if (html.includes('data-component="Accordion"')) { + return ( +
+
Accordion Title
+
Accordion Content
+
+ ); + } + return ; + }; + + render( + + { + '
Title

Content

' + } +
, + { + wrapper: createWrapperWithTransformer(accordionTransformer), + }, + ); + + expect(screen.getByTestId('custom-accordion')).toBeInTheDocument(); + expect(screen.getByTestId('accordion-summary')).toBeInTheDocument(); + expect(screen.getByTestId('accordion-content')).toBeInTheDocument(); + }); + + it('should not invoke transformer for non-string children', () => { + const transformerSpy = vi.fn((html: string) => html); + + const CustomComponent = () => ( + Custom React Component + ); + + render( + + + , + { + wrapper: createWrapperWithTransformer(transformerSpy), + }, + ); + + expect(transformerSpy).not.toHaveBeenCalled(); + expect(screen.getByTestId('custom-component')).toBeInTheDocument(); + }); + + it('should pass raw unsanitized HTML to transformer', () => { + let receivedHtml = ''; + const capturingTransformer = (html: string) => { + receivedHtml = html; + return
{html}
; + }; + + const rawHtml = '

Content

'; + render({rawHtml}, { + wrapper: createWrapperWithTransformer(capturingTransformer), + }); + + expect(receivedHtml).toBe(rawHtml); + expect(screen.getByTestId('captured')).toBeInTheDocument(); + }); + + it('should sanitize dangerous HTML when no transformer is provided', () => { + render( + + { + '

Safe content

' + } +
, + { + wrapper: createWrapperWithTransformer(undefined), + }, + ); + + expect(screen.queryByText('alert("xss")')).not.toBeInTheDocument(); + expect(screen.getByTestId('safe')).toBeInTheDocument(); + }); + }); }); From 86c9cfb848982843b480e5a7fcc7e56a121a07a5 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 1 May 2026 20:11:50 +0200 Subject: [PATCH 3/6] changes --- src/components/ui/form.tsx | 2 +- src/lib/transformDescription.tsx | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 src/lib/transformDescription.tsx diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx index 7f6edf69b..8803eff43 100644 --- a/src/components/ui/form.tsx +++ b/src/components/ui/form.tsx @@ -188,7 +188,7 @@ export function BaseFormDescription({ id={id} className={cn('text-base-color text-xs', className)} data-sanitized='false' - data-children-type='not-string' + data-children-type='other' {...props} > {typeof children === 'function' ? children() : children} diff --git a/src/lib/transformDescription.tsx b/src/lib/transformDescription.tsx deleted file mode 100644 index c931be01d..000000000 --- a/src/lib/transformDescription.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { ReactNode } from 'react'; - -export function transformDescriptionHtml( - description: string, - transformer?: (html: string) => ReactNode, -): ReactNode { - if (transformer) { - return transformer(description); - } - - return description; -} From b213c80ea9d922095f05e01628484859e03e3bfc Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 1 May 2026 20:19:48 +0200 Subject: [PATCH 4/6] fix mock tests --- .../form/fields/tests/CheckBoxField.test.tsx | 10 +++++--- .../form/fields/tests/CountryField.test.tsx | 8 ++++++- .../fields/tests/DatePickerField.test.tsx | 10 +++++--- .../form/fields/tests/FieldSetField.test.tsx | 10 +++++--- .../fields/tests/FileUploadField.test.tsx | 10 +++++--- .../fields/tests/MultiSelectField.test.tsx | 10 +++++--- .../form/fields/tests/NumberField.test.tsx | 10 +++++--- .../fields/tests/RadioGroupField.test.tsx | 10 +++++--- .../form/fields/tests/SelectField.test.tsx | 10 +++++--- .../form/fields/tests/TelField.test.tsx | 10 +++++--- .../form/fields/tests/TextAreaField.test.tsx | 10 +++++--- .../form/fields/tests/TextField.test.tsx | 10 +++++--- .../fields/tests/WorkScheduleField.test.tsx | 10 +++++--- ...SONSchemaFormConditionalInputType.test.tsx | 24 +++++++++++-------- .../JSONSchemaFormCustomComponent.test.tsx | 18 ++++++++------ .../shared/table/tests/Table.test.tsx | 18 ++++++++------ .../tests/ContractAmendmentBack.test.tsx | 8 ++++++- .../tests/ContractAmendmentSubmit.test.tsx | 8 ++++++- .../tests/CostCalculatorResetButton.test.tsx | 8 ++++++- .../tests/CostCalculatorSubmitButton.test.tsx | 8 ++++++- .../Onboarding/tests/OnboardingBack.test.tsx | 10 +++++--- .../tests/OnboardingSubmit.test.tsx | 10 +++++--- .../tests/TerminationBack.test.tsx | 8 ++++++- .../tests/TerminationSubmit.test.tsx | 8 ++++++- 24 files changed, 183 insertions(+), 73 deletions(-) diff --git a/src/components/form/fields/tests/CheckBoxField.test.tsx b/src/components/form/fields/tests/CheckBoxField.test.tsx index 2a38515fe..7e7ff0c28 100644 --- a/src/components/form/fields/tests/CheckBoxField.test.tsx +++ b/src/components/form/fields/tests/CheckBoxField.test.tsx @@ -8,9 +8,13 @@ import { CheckboxFieldDefault } from '@/src/components/form/fields/default/Check import { $TSFixMe } from '@/src/types/remoteFlows'; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); describe('CheckBoxField Component', () => { const mockOnChange = vi.fn(); diff --git a/src/components/form/fields/tests/CountryField.test.tsx b/src/components/form/fields/tests/CountryField.test.tsx index de832d4a2..7dbb44aa7 100644 --- a/src/components/form/fields/tests/CountryField.test.tsx +++ b/src/components/form/fields/tests/CountryField.test.tsx @@ -13,7 +13,13 @@ import { JSFField, $TSFixMe } from '@/src/types/remoteFlows'; import { CountryFieldDefault } from '@/src/components/form/fields/default/CountryFieldDefault'; // Mock dependencies -vi.mock('@/src/context'); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); type CountryFieldProps = JSFField & { placeholder?: string; diff --git a/src/components/form/fields/tests/DatePickerField.test.tsx b/src/components/form/fields/tests/DatePickerField.test.tsx index 513de8bfb..4294d9773 100644 --- a/src/components/form/fields/tests/DatePickerField.test.tsx +++ b/src/components/form/fields/tests/DatePickerField.test.tsx @@ -8,9 +8,13 @@ import { DatePickerFieldDefault } from '@/src/components/form/fields/default/Dat import { $TSFixMe } from '@/src/types/remoteFlows'; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); vi.mock('date-fns', async () => { const actual = await vi.importActual('date-fns'); diff --git a/src/components/form/fields/tests/FieldSetField.test.tsx b/src/components/form/fields/tests/FieldSetField.test.tsx index 619c3c50e..7c0870302 100644 --- a/src/components/form/fields/tests/FieldSetField.test.tsx +++ b/src/components/form/fields/tests/FieldSetField.test.tsx @@ -6,9 +6,13 @@ import { $TSFixMe } from '@/src/types/remoteFlows'; import { FieldsetToggleButtonDefault } from '@/src/components/form/fields/default/FieldsetToggleButtonDefault'; import { TextFieldDefault } from '@/src/components/form/fields/default/TextFieldDefault'; -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); vi.mock('@/src/components/shared/zendesk-drawer/ZendeskTriggerButton', () => ({ ZendeskTriggerButton: ({ zendeskId, children, className }: $TSFixMe) => ( diff --git a/src/components/form/fields/tests/FileUploadField.test.tsx b/src/components/form/fields/tests/FileUploadField.test.tsx index 2748cc08a..90b18faab 100644 --- a/src/components/form/fields/tests/FileUploadField.test.tsx +++ b/src/components/form/fields/tests/FileUploadField.test.tsx @@ -8,9 +8,13 @@ import { FileUploadFieldDefault } from '@/src/components/form/fields/default/Fil import { $TSFixMe } from '@/src/types/remoteFlows'; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); describe('FileUploadField Component', () => { const mockOnChange = vi.fn(); diff --git a/src/components/form/fields/tests/MultiSelectField.test.tsx b/src/components/form/fields/tests/MultiSelectField.test.tsx index e9f52cc99..c36797c5e 100644 --- a/src/components/form/fields/tests/MultiSelectField.test.tsx +++ b/src/components/form/fields/tests/MultiSelectField.test.tsx @@ -10,9 +10,13 @@ import { $TSFixMe } from '@/src/types/remoteFlows'; type MultiSelectFieldProps = React.ComponentProps; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); describe('MultiSelectField Component', () => { const mockOnChange = vi.fn(); diff --git a/src/components/form/fields/tests/NumberField.test.tsx b/src/components/form/fields/tests/NumberField.test.tsx index 31717ddbf..f0d1ffea2 100644 --- a/src/components/form/fields/tests/NumberField.test.tsx +++ b/src/components/form/fields/tests/NumberField.test.tsx @@ -10,9 +10,13 @@ import { NumberFieldDefault } from '@/src/components/form/fields/default/NumberF import { TextFieldDefault } from '@/src/components/form/fields/default/TextFieldDefault'; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); describe('NumberField Component', () => { const mockOnChange = vi.fn(); diff --git a/src/components/form/fields/tests/RadioGroupField.test.tsx b/src/components/form/fields/tests/RadioGroupField.test.tsx index 08d4868e6..f29de03e9 100644 --- a/src/components/form/fields/tests/RadioGroupField.test.tsx +++ b/src/components/form/fields/tests/RadioGroupField.test.tsx @@ -19,9 +19,13 @@ type RadioGroupFieldProps = JSFField & { }; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); describe('RadioGroupField Component', () => { const mockOnChange = vi.fn(); diff --git a/src/components/form/fields/tests/SelectField.test.tsx b/src/components/form/fields/tests/SelectField.test.tsx index 029e3f81e..f1f0fe23f 100644 --- a/src/components/form/fields/tests/SelectField.test.tsx +++ b/src/components/form/fields/tests/SelectField.test.tsx @@ -10,9 +10,13 @@ import { $TSFixMe } from '@/src/types/remoteFlows'; type SelectFieldProps = React.ComponentProps; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); describe('SelectField Component', () => { const mockOnChange = vi.fn(); diff --git a/src/components/form/fields/tests/TelField.test.tsx b/src/components/form/fields/tests/TelField.test.tsx index e01b3a786..2418d4cba 100644 --- a/src/components/form/fields/tests/TelField.test.tsx +++ b/src/components/form/fields/tests/TelField.test.tsx @@ -8,9 +8,13 @@ import { TelFieldDefault } from '../default/TelFieldDefault'; import { $TSFixMe } from '@/src/types/remoteFlows'; import { yupResolver } from '@hookform/resolvers/yup'; -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); // Helper function to interact with Radix UI Select async function fillRadixSelect(labelText: string, countryName: string) { diff --git a/src/components/form/fields/tests/TextAreaField.test.tsx b/src/components/form/fields/tests/TextAreaField.test.tsx index 570cad7a8..90e717683 100644 --- a/src/components/form/fields/tests/TextAreaField.test.tsx +++ b/src/components/form/fields/tests/TextAreaField.test.tsx @@ -8,9 +8,13 @@ import { TextAreaFieldDefault } from '@/src/components/form/fields/default/TextA import { $TSFixMe } from '@/src/types/remoteFlows'; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); describe('TextAreaField Component', () => { const mockOnChange = vi.fn(); diff --git a/src/components/form/fields/tests/TextField.test.tsx b/src/components/form/fields/tests/TextField.test.tsx index 4f2f47266..39c4dfba4 100644 --- a/src/components/form/fields/tests/TextField.test.tsx +++ b/src/components/form/fields/tests/TextField.test.tsx @@ -8,9 +8,13 @@ import { TextFieldDefault } from '@/src/components/form/fields/default/TextField import { $TSFixMe } from '@/src/types/remoteFlows'; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); describe('TextField Component', () => { const mockOnChange = vi.fn(); diff --git a/src/components/form/fields/tests/WorkScheduleField.test.tsx b/src/components/form/fields/tests/WorkScheduleField.test.tsx index 90f735da2..6e686caa2 100644 --- a/src/components/form/fields/tests/WorkScheduleField.test.tsx +++ b/src/components/form/fields/tests/WorkScheduleField.test.tsx @@ -12,9 +12,13 @@ import { CheckboxFieldDefault } from '@/src/components/form/fields/default/Check import { TextFieldDefault } from '@/src/components/form/fields/default/TextFieldDefault'; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); type WorkScheduleFieldProps = JSFField & { name: string; diff --git a/src/components/form/tests/JSONSchemaFormConditionalInputType.test.tsx b/src/components/form/tests/JSONSchemaFormConditionalInputType.test.tsx index d5f500000..7d3a0371e 100644 --- a/src/components/form/tests/JSONSchemaFormConditionalInputType.test.tsx +++ b/src/components/form/tests/JSONSchemaFormConditionalInputType.test.tsx @@ -7,16 +7,20 @@ import { NumberFieldDefault } from '@/src/components/form/fields/default/NumberF import { TextFieldDefault } from '@/src/components/form/fields/default/TextFieldDefault'; import { SelectFieldDefault } from '@/src/components/form/fields/default/SelectFieldDefault'; -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(() => ({ - components: { - radio: RadioGroupFieldDefault, - number: NumberFieldDefault, - text: TextFieldDefault, - select: SelectFieldDefault, - }, - })), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(() => ({ + components: { + radio: RadioGroupFieldDefault, + number: NumberFieldDefault, + text: TextFieldDefault, + select: SelectFieldDefault, + }, + })), + }; +}); describe('JSONSchemaForm - Conditional inputType Changes', () => { it('should render NumberField when field.type changes from hidden to number', async () => { diff --git a/src/components/form/tests/JSONSchemaFormCustomComponent.test.tsx b/src/components/form/tests/JSONSchemaFormCustomComponent.test.tsx index 426f9ef81..1330a91c4 100644 --- a/src/components/form/tests/JSONSchemaFormCustomComponent.test.tsx +++ b/src/components/form/tests/JSONSchemaFormCustomComponent.test.tsx @@ -12,13 +12,17 @@ const MockStatement = ({ data }: StatementComponentProps) => ( ); -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(() => ({ - components: { - statement: MockStatement, - }, - })), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(() => ({ + components: { + statement: MockStatement, + }, + })), + }; +}); const CustomToggle = ({ setValue, diff --git a/src/components/shared/table/tests/Table.test.tsx b/src/components/shared/table/tests/Table.test.tsx index a2c594d38..b93a6d211 100644 --- a/src/components/shared/table/tests/Table.test.tsx +++ b/src/components/shared/table/tests/Table.test.tsx @@ -5,13 +5,17 @@ import { $TSFixMe } from '@/src/types/remoteFlows'; import { TableFieldDefault } from '@/src/components/shared/table/TableFieldDefault'; // Mock the context -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(() => ({ - components: { - table: TableFieldDefault, - }, - })), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(() => ({ + components: { + table: TableFieldDefault, + }, + })), + }; +}); describe('Table Component', () => { it('should render table with headers and data', () => { diff --git a/src/flows/ContractAmendment/tests/ContractAmendmentBack.test.tsx b/src/flows/ContractAmendment/tests/ContractAmendmentBack.test.tsx index 649c12951..6020c1c69 100644 --- a/src/flows/ContractAmendment/tests/ContractAmendmentBack.test.tsx +++ b/src/flows/ContractAmendment/tests/ContractAmendmentBack.test.tsx @@ -6,7 +6,13 @@ import { ButtonDefault } from '@/src/components/form/fields/default/ButtonDefaul // Mock the hooks vi.mock('../context'); -vi.mock('@/src/context'); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); const mockUseContractAmendmentContext = vi.mocked(useContractAmendmentContext); const mockUseFormFields = vi.mocked(useFormFields); diff --git a/src/flows/ContractAmendment/tests/ContractAmendmentSubmit.test.tsx b/src/flows/ContractAmendment/tests/ContractAmendmentSubmit.test.tsx index 7c9c1c254..624625479 100644 --- a/src/flows/ContractAmendment/tests/ContractAmendmentSubmit.test.tsx +++ b/src/flows/ContractAmendment/tests/ContractAmendmentSubmit.test.tsx @@ -6,7 +6,13 @@ import { ButtonDefault } from '@/src/components/form/fields/default/ButtonDefaul // Mock the hooks vi.mock('../context'); -vi.mock('@/src/context'); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); const mockUseContractAmendmentContext = vi.mocked(useContractAmendmentContext); const mockUseFormFields = vi.mocked(useFormFields); diff --git a/src/flows/CostCalculator/tests/CostCalculatorResetButton.test.tsx b/src/flows/CostCalculator/tests/CostCalculatorResetButton.test.tsx index 355fe53ad..46418f566 100644 --- a/src/flows/CostCalculator/tests/CostCalculatorResetButton.test.tsx +++ b/src/flows/CostCalculator/tests/CostCalculatorResetButton.test.tsx @@ -6,7 +6,13 @@ import { ButtonDefault } from '@/src/components/form/fields/default/ButtonDefaul // Mock the hooks vi.mock('../context'); -vi.mock('@/src/context'); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); const mockUseCostCalculatorContext = vi.mocked(useCostCalculatorContext); const mockUseFormFields = vi.mocked(useFormFields); diff --git a/src/flows/CostCalculator/tests/CostCalculatorSubmitButton.test.tsx b/src/flows/CostCalculator/tests/CostCalculatorSubmitButton.test.tsx index 255fea96c..9db650171 100644 --- a/src/flows/CostCalculator/tests/CostCalculatorSubmitButton.test.tsx +++ b/src/flows/CostCalculator/tests/CostCalculatorSubmitButton.test.tsx @@ -6,7 +6,13 @@ import { ButtonDefault } from '@/src/components/form/fields/default/ButtonDefaul // Mock the hooks vi.mock('../context'); -vi.mock('@/src/context'); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); const mockUseCostCalculatorContext = vi.mocked(useCostCalculatorContext); const mockUseFormFields = vi.mocked(useFormFields); diff --git a/src/flows/Onboarding/tests/OnboardingBack.test.tsx b/src/flows/Onboarding/tests/OnboardingBack.test.tsx index a3d309d2d..ffa52048b 100644 --- a/src/flows/Onboarding/tests/OnboardingBack.test.tsx +++ b/src/flows/Onboarding/tests/OnboardingBack.test.tsx @@ -6,9 +6,13 @@ import { ButtonDefault } from '@/src/components/form/fields/default/ButtonDefaul import { $TSFixMe } from '@/src/types/remoteFlows'; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); vi.mock('@/src/flows/Onboarding/context', () => ({ useOnboardingContext: vi.fn(), diff --git a/src/flows/Onboarding/tests/OnboardingSubmit.test.tsx b/src/flows/Onboarding/tests/OnboardingSubmit.test.tsx index 2af773346..533c69304 100644 --- a/src/flows/Onboarding/tests/OnboardingSubmit.test.tsx +++ b/src/flows/Onboarding/tests/OnboardingSubmit.test.tsx @@ -6,9 +6,13 @@ import { ButtonDefault } from '@/src/components/form/fields/default/ButtonDefaul import { $TSFixMe } from '@/src/types/remoteFlows'; // Mock dependencies -vi.mock('@/src/context', () => ({ - useFormFields: vi.fn(), -})); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); vi.mock('@/src/flows/Onboarding/context', () => ({ useOnboardingContext: vi.fn(), diff --git a/src/flows/Termination/tests/TerminationBack.test.tsx b/src/flows/Termination/tests/TerminationBack.test.tsx index c80661078..657d527f9 100644 --- a/src/flows/Termination/tests/TerminationBack.test.tsx +++ b/src/flows/Termination/tests/TerminationBack.test.tsx @@ -6,7 +6,13 @@ import { ButtonDefault } from '@/src/components/form/fields/default/ButtonDefaul // Mock the hooks vi.mock('../context'); -vi.mock('@/src/context'); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); const mockUseTerminationContext = vi.mocked(useTerminationContext); const mockUseFormFields = vi.mocked(useFormFields); diff --git a/src/flows/Termination/tests/TerminationSubmit.test.tsx b/src/flows/Termination/tests/TerminationSubmit.test.tsx index 2b13fe561..d70051b2c 100644 --- a/src/flows/Termination/tests/TerminationSubmit.test.tsx +++ b/src/flows/Termination/tests/TerminationSubmit.test.tsx @@ -6,7 +6,13 @@ import { ButtonDefault } from '@/src/components/form/fields/default/ButtonDefaul // Mock the hooks vi.mock('../context'); -vi.mock('@/src/context'); +vi.mock('@/src/context', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useFormFields: vi.fn(), + }; +}); const mockUseTerminationContext = vi.mocked(useTerminationContext); const mockUseFormFields = vi.mocked(useFormFields); From 9b65fdce985f1d45c85015e9cfda1ade6a015bc9 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 1 May 2026 20:41:25 +0200 Subject: [PATCH 5/6] sanitize --- src/components/ui/form.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx index 8803eff43..6013f978f 100644 --- a/src/components/ui/form.tsx +++ b/src/components/ui/form.tsx @@ -149,15 +149,17 @@ export function BaseFormDescription({ const transformHtmlToComponents = useTransformer(); // we check if children is a string, happens in 95% of the cases I believe if (typeof children === 'string') { + const sanitized = sanitizeHtml(children); // if we have a transformer, we use it to transform the children if (transformHtmlToComponents) { - const transformed = transformHtmlToComponents(children); + const transformed = transformHtmlToComponents(sanitized); return ( {transformed} {helpCenter && helpCenter} @@ -174,7 +176,7 @@ export function BaseFormDescription({ data-children-type='string' {...props} > - {' '} + {' '} {helpCenter && helpCenter} ); From 55b05baa31d3cc24b4ffaaca0d15cdbba21e9ef1 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Fri, 1 May 2026 20:47:02 +0200 Subject: [PATCH 6/6] fix tests --- src/components/ui/tests/form.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ui/tests/form.test.tsx b/src/components/ui/tests/form.test.tsx index 5464ce027..aaf8f10ef 100644 --- a/src/components/ui/tests/form.test.tsx +++ b/src/components/ui/tests/form.test.tsx @@ -194,7 +194,7 @@ describe('Form', () => { expect(screen.getByTestId('custom-component')).toBeInTheDocument(); }); - it('should pass raw unsanitized HTML to transformer', () => { + it('should pass sanitized HTML to transformer', () => { let receivedHtml = ''; const capturingTransformer = (html: string) => { receivedHtml = html; @@ -206,7 +206,7 @@ describe('Form', () => { wrapper: createWrapperWithTransformer(capturingTransformer), }); - expect(receivedHtml).toBe(rawHtml); + expect(receivedHtml).toBe('

Content

'); expect(screen.getByTestId('captured')).toBeInTheDocument(); });