Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADD FEEDBACK ADDITIONAL FIELDS #553

Merged
merged 36 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
917288f
add mocked data for new fields
Temiakinsoto Nov 25, 2024
70f5af4
add mock data for number field
Temiakinsoto Nov 25, 2024
d6f5cc3
add mock data for field type
Temiakinsoto Nov 25, 2024
e8d1935
no unused vars
Temiakinsoto Nov 25, 2024
aa4dfb8
add Checkboxes form field
Temiakinsoto Nov 25, 2024
b0cf085
add Url, Dropdown, and Number Field types/component
Temiakinsoto Nov 25, 2024
9630d49
add mock data for phone, url and number
Temiakinsoto Nov 25, 2024
f1de03e
add unit test for Email field
Temiakinsoto Nov 25, 2024
df3e73d
add unit test for Number field
Temiakinsoto Nov 25, 2024
5270cd2
add test for Url field
Temiakinsoto Nov 25, 2024
6c49160
add unit test for Check boxes
Temiakinsoto Nov 25, 2024
53636d8
add unit test for Dropdown field component
Temiakinsoto Nov 25, 2024
2f5167a
add folders for each component+test file
Temiakinsoto Nov 25, 2024
fadc7d4
refactor radio button into separate component and add unit test file
Temiakinsoto Nov 28, 2024
58ed10a
modify default_value and choices value for fieldType checkboxes
Temiakinsoto Nov 28, 2024
94e1f8e
add checkbox field component and unit test
Temiakinsoto Nov 28, 2024
326a38c
add Singlelinefield component and unit test
Temiakinsoto Nov 28, 2024
2036b56
add Multilinefield component and unit test
Temiakinsoto Nov 28, 2024
5bda993
add label for checkbox field
Temiakinsoto Nov 28, 2024
d58bac3
refactor DropdownField component, pass choicesList[] as props
Temiakinsoto Nov 28, 2024
8162d21
refactor CheckboxesField so that it accepts choicesList and defaultVa…
Temiakinsoto Nov 28, 2024
46fc198
remove h2 header for checkbox field
Temiakinsoto Nov 28, 2024
d8f01e3
add new mock-data for checkbox fieldType
Temiakinsoto Nov 28, 2024
8a9f54a
Modify DropdownField test cases, dropdown should be based on choiceLi…
Temiakinsoto Nov 28, 2024
192487f
set unique clean_name for field types checkbox, checkboxes, and dropd…
Temiakinsoto Nov 29, 2024
d8ca154
Merge branch 'main' into feat/CDD-2298-feedback-additional-field-type
rhys-burendo Nov 29, 2024
ba3a716
set type to url for <UrlField /> component
Temiakinsoto Nov 29, 2024
4c8807e
merge test for rendering and checking label
Temiakinsoto Nov 29, 2024
e2dba76
set url attribute type to url
Temiakinsoto Nov 29, 2024
a0431ac
set url attribute type to url
Temiakinsoto Nov 29, 2024
a9b8526
Merge branch 'feat/CDD-2298-feedback-additional-field-type' of https:…
rhys-burendo Dec 2, 2024
eeb8858
Merge branch 'main' into feat/CDD-2298-feedback-additional-field-type
rhys-burendo Dec 3, 2024
b18312d
Merge branch 'feat/CDD-2298-feedback-additional-field-type' of https:…
rhys-burendo Dec 18, 2024
dc3e40c
Merging main, fixing merge issues
rhys-burendo Dec 18, 2024
43b2dc3
Remove required status from feedback
rhys-burendo Dec 19, 2024
20b5444
Radiofield merge issue fix
rhys-burendo Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 86 additions & 113 deletions src/app/components/cms/Feedback/Feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,59 @@ import { z } from 'zod'
import { FormField } from '@/api/models/cms/Page/FormFields'

import { handler } from '../utils/handler'
import CheckboxField from './Fields/Checkbox/CheckboxField'
import CheckboxesField from './Fields/Checkboxes/CheckboxesField'
import DropdownField from './Fields/Dropdown/DropdownField'
import EmailField from './Fields/Email/EmailField'
import MultilineField from './Fields/Multiline/MultilineField'
import NumberField from './Fields/Number/NumberField'
import RadioField from './Fields/Radio/RadioField'
import SinglelineField from './Fields/Singleline/SinglelineField'
import UrlField from './Fields/Url/UrlField'

const initialState = {
message: '',
errors: [],
}

export interface Fieldtype {
label: string
helpText: string
cleanName: string
choicesList?: string[]
defaultValue?: string
defaultValuesList?: string[]
fieldHasError?: boolean
}

interface FieldError {
clean_name: string
label: string
}

export const renderErrorSummary = (errors: FieldError[]) => {
return (
<div className="govuk-error-summary" data-module="govuk-error-summary">
<div role="alert">
<h2 className="govuk-error-summary__title">
<span className="govuk-visually-hidden">Error:</span>The following form fields have errors:{' '}
</h2>
<div className="govuk-error-summary__body">
<ul className="govuk-list govuk-error-summary__list">
{errors.map((item) => {
return (
<li key={item.clean_name}>
<a href={'#' + item.clean_name}>{item.label}</a>
</li>
)
})}
</ul>
</div>
</div>
</div>
)
}

interface FeedbackProps {
formFields: {
id: number
Expand All @@ -28,11 +75,6 @@ interface FeedbackProps {
}[]
}

interface FieldError {
clean_name: string
label: string
}

export default function Feedback({ formFields }: FeedbackProps) {
const [state, formAction] = useFormState(handler.bind(null, formFields), initialState)

Expand Down Expand Up @@ -64,29 +106,6 @@ export default function Feedback({ formFields }: FeedbackProps) {
)
}

export const renderErrorSummary = (errors: FieldError[]) => {
return (
<div className="govuk-error-summary" data-module="govuk-error-summary">
<div role="alert">
<h2 className="govuk-error-summary__title">
<span className="govuk-visually-hidden">Error:</span>The following form fields have errors:{' '}
</h2>
<div className="govuk-error-summary__body">
<ul className="govuk-list govuk-error-summary__list">
{errors.map((item) => {
return (
<li key={item.clean_name}>
<a href={'#' + item.clean_name}>{item.label}</a>
</li>
)
})}
</ul>
</div>
</div>
</div>
)
}

export const renderFormFields = (
errors: FieldError[],
{
Expand All @@ -95,108 +114,62 @@ export const renderFormFields = (
label,
field_type: fieldType,
help_text: helpText,
// TODO: Required validation added in ticket CDD-2300
// required,
choices,
// default_value: defaultValue,
default_value: defaultValue,
}: z.infer<typeof FormField>
) => {
const choicesList = choices.includes('\r\n') ? choices.split('\r\n') : choices.split(',')

// TODO: Implement default values only for checkboxes
// const defaultValuesList = defaultValue.includes('\r\n') ? defaultValue.split('\r\n') : defaultValue.split(',')
const defaultValuesList = defaultValue.includes('\r\n') ? defaultValue.split('\r\n') : defaultValue.split(',')

//does field have errors

const fieldHasError = errors.find(({ clean_name }) => (clean_name === cleanName ? true : false))
// Checks if any errors are present, type conversion to boolean
const fieldHasError = !!errors.find(({ clean_name }) => clean_name === cleanName)

return (
<Fragment key={id}>
{fieldType === 'singleline' && (
<div className="govuk-form-group govuk-!-margin-bottom-9">
<h2 className="govuk-label-wrapper">
<label
className={'govuk-label govuk-label--m' + (fieldHasError ? 'govuk-error-message' : null)}
htmlFor={cleanName}
>
{label}
</label>
</h2>

{helpText.length > 0 ? <div className="govuk-hint">{helpText}</div> : null}

{fieldHasError ? (
<p id="multiline-error" className="govuk-error-message">
<span className="govuk-visually-hidden">Error:</span> Please enter a value as this field is required
</p>
) : null}

<textarea
className="govuk-textarea govuk-textarea--error"
name={cleanName}
id={cleanName}
rows={1}
></textarea>
</div>
<SinglelineField label={label} helpText={helpText} cleanName={cleanName} fieldHasError={fieldHasError} />
)}

{fieldType === 'multiline' && (
<div className={'govuk-form-group' + (fieldHasError ? '--error ' : null) + 'govuk-!-margin-bottom-9'}>
<h2 className="govuk-label-wrapper">
<label className="govuk-label govuk-label--m" htmlFor={cleanName}>
{label}
</label>
</h2>

{helpText.length > 0 ? <div className="govuk-hint">{helpText}</div> : null}

{fieldHasError ? (
<p id="multiline-error" className="govuk-error-message">
<span className="govuk-visually-hidden">Error:</span> Please enter a value as this field is required
</p>
) : null}

<textarea
className={'govuk-textarea ' + (fieldHasError ? 'govuk-textarea--error' : null)}
name={cleanName}
id={cleanName}
rows={5}
/>
</div>
<MultilineField label={label} helpText={helpText} cleanName={cleanName} fieldHasError={fieldHasError} />
)}

{fieldType === 'radio' && (
<div className={'govuk-form-group govuk-form-group' + (fieldHasError ? '--error ' : null)}>
<fieldset className="govuk-fieldset govuk-!-margin-bottom-9">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
<h2 className="govuk-fieldset__heading">{label}</h2>
</legend>

{helpText.length > 0 ? <div className="govuk-hint">{helpText}</div> : null}

{fieldHasError ? (
<p id="multiline-error" className="govuk-error-message">
<span className="govuk-visually-hidden">Error:</span> Please enter a value as this field is required
</p>
) : null}
<div className="govuk-radios" data-module="govuk-radios">
{choicesList.map((choice, key) => {
return (
<div key={key} className="govuk-radios__item">
<input
className="govuk-radios__input"
id={cleanName}
name={cleanName}
type="radio"
value={choice}
/>
<label className="govuk-label govuk-radios__label" htmlFor={cleanName}>
{choice}
</label>
</div>
)
})}
</div>
</fieldset>
</div>
<RadioField
label={label}
helpText={helpText}
cleanName={cleanName}
choicesList={choicesList}
fieldHasError={fieldHasError}
/>
)}

{fieldType === 'email' && <EmailField label={label} helpText={helpText} cleanName={cleanName} />}
Temiakinsoto marked this conversation as resolved.
Show resolved Hide resolved

{fieldType === 'checkbox' && (
<CheckboxField label={label} helpText={helpText} cleanName={cleanName} defaultValue={defaultValue} />
)}

{fieldType === 'checkboxes' && (
Temiakinsoto marked this conversation as resolved.
Show resolved Hide resolved
<CheckboxesField
label={label}
helpText={helpText}
cleanName={cleanName}
choicesList={choicesList}
defaultValuesList={defaultValuesList}
/>
)}

{fieldType === 'number' && <NumberField label={label} helpText={helpText} cleanName={cleanName} />}

{fieldType === 'url' && <UrlField label={label} helpText={helpText} cleanName={cleanName} />}

{fieldType === 'dropdown' && (
<DropdownField label={label} helpText={helpText} cleanName={cleanName} choicesList={choicesList} />
)}
</Fragment>
rhys-burendo marked this conversation as resolved.
Show resolved Hide resolved
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Fieldtype } from '../../Feedback'

export default function CheckboxField({ label, helpText, cleanName, defaultValue }: Fieldtype) {
return (
<div className="govuk-form-group govuk-!-margin-bottom-9">
{helpText.length > 0 ? <div className="govuk-hint">{helpText}</div> : null}
<div className="govuk-checkboxes__item">
<input
className="govuk-checkboxes__input"
name={cleanName}
value={defaultValue}
type="checkbox"
id={cleanName}
/>
<label className="govuk-label govuk-checkboxes__label" htmlFor={cleanName}>
{label}
</label>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { render, screen } from '@/config/test-utils'

import CheckboxField from './CheckboxField'

describe('CheckboxField', () => {
const mockProps = {
label: 'Accept terms and conditions',
helpText: 'Please read the terms carefully.',
cleanName: 'terms_and_conditions',
defaultValue: 'true', // Testing with a string value
}

test('renders checkbox field with label and help text when provided', () => {
render(<CheckboxField {...mockProps} />)

// Check if the label is rendered correctly
const label = screen.getByLabelText(mockProps.label)
expect(label).toBeInTheDocument()

// Check if the help text is rendered correctly
const helpText = screen.getByText(mockProps.helpText)
expect(helpText).toBeInTheDocument()
Temiakinsoto marked this conversation as resolved.
Show resolved Hide resolved
})

test('does not render help text when not provided', () => {
render(<CheckboxField {...{ ...mockProps, helpText: '' }} />)

// Ensure help text is not rendered if it's an empty string
const helpText = screen.queryByText(mockProps.helpText)
expect(helpText).toBeNull()
})

test('checkbox is unchecked by default when defaultValue is an empty string', () => {
render(<CheckboxField {...{ ...mockProps, defaultValue: '' }} />)

// Check if the checkbox is unchecked when defaultValue is an empty string
const checkbox = screen.getByRole('checkbox')
expect(checkbox).not.toBeChecked()
})

test('checkbox is unchecked by default when defaultValue is a falsy string ("false")', () => {
render(<CheckboxField {...{ ...mockProps, defaultValue: 'false' }} />)

// Check if the checkbox is unchecked when defaultValue is "false"
const checkbox = screen.getByRole('checkbox')
expect(checkbox).not.toBeChecked()
})

test('checkbox input has the correct name and id', () => {
render(<CheckboxField {...mockProps} />)

const checkbox = screen.getByRole('checkbox')

// Check if the checkbox has the correct name and id
expect(checkbox).toHaveAttribute('name', mockProps.cleanName)
expect(checkbox).toHaveAttribute('id', mockProps.cleanName)
rhys-burendo marked this conversation as resolved.
Show resolved Hide resolved
})

test('checkbox input value matches the defaultValue prop', () => {
render(<CheckboxField {...mockProps} />)

const checkbox = screen.getByRole('checkbox')

// Ensure the checkbox input value matches defaultValue prop
expect(checkbox).toHaveAttribute('value', mockProps.defaultValue)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Fieldtype } from '../../Feedback'

export default function CheckboxesField({
label,
helpText,
cleanName,
choicesList = [],
defaultValuesList = [],
}: Fieldtype) {
return (
<div className="govuk-form-group govuk-!-margin-bottom-9">
<fieldset className="govuk-fieldset govuk-!-margin-bottom-9">
<legend className="govuk-fieldset__legend govuk-fieldset__legend--m">
<h2 className="govuk-fieldset__heading">
<label className="govuk-label govuk-label--m" htmlFor={cleanName}>
{label}
</label>
</h2>
</legend>

{helpText.length > 0 ? <div className="govuk-hint">{helpText}</div> : null}

<div className="govuk-checkboxes" data-module="govuk-checkboxes">
{choicesList.map((choiceVal, key) => {
const uniqueId = `${cleanName}-${key}` // Generate a unique ID for each checkbox
return (
<div key={key} className="govuk-checkboxes__item">
<input
className="govuk-checkboxes__input"
id={uniqueId}
name={cleanName}
type="checkbox"
value={choiceVal}
defaultChecked={defaultValuesList.includes(choiceVal)}
/>
<label className="govuk-label govuk-checkboxes__label" htmlFor={uniqueId}>
{choiceVal}
</label>
</div>
)
})}
</div>
</fieldset>
</div>
)
}
Loading
Loading