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 30 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
85 changes: 43 additions & 42 deletions src/app/components/cms/Feedback/Feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,30 @@ 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[]
}

interface FeedbackProps {
formFields: {
id: number
Expand Down Expand Up @@ -67,61 +85,44 @@ export const renderFormFields = ({
// 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(',')
// Implement default values only for checkboxes
const defaultValuesList = defaultValue.includes('\r\n') ? defaultValue.split('\r\n') : defaultValue.split(',')

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" htmlFor={cleanName}>
{label}
</label>
</h2>
{fieldType === 'singleline' && <SinglelineField label={label} helpText={helpText} cleanName={cleanName} />}

{helpText.length > 0 ? <div className="govuk-hint">{helpText}</div> : null}
{fieldType === 'multiline' && <MultilineField label={label} helpText={helpText} cleanName={cleanName} />}

<textarea className="govuk-textarea" name={cleanName} id={cleanName} rows={1}></textarea>
</div>
{fieldType === 'radio' && (
<RadioField label={label} helpText={helpText} cleanName={cleanName} choicesList={choicesList} />
)}

{fieldType === 'multiline' && (
<div className="govuk-form-group govuk-!-margin-bottom-9">
<h2 className="govuk-label-wrapper">
<label className="govuk-label govuk-label--m" htmlFor={cleanName}>
{label}
</label>
</h2>
{fieldType === 'email' && <EmailField label={label} helpText={helpText} cleanName={cleanName} />}
Temiakinsoto marked this conversation as resolved.
Show resolved Hide resolved

{helpText.length > 0 ? <div className="govuk-hint">{helpText}</div> : null}
{fieldType === 'checkbox' && (
<CheckboxField label={label} helpText={helpText} cleanName={cleanName} defaultValue={defaultValue} />
)}

<textarea className="govuk-textarea" name={cleanName} id={cleanName} rows={5} />
</div>
{fieldType === 'checkboxes' && (
Temiakinsoto marked this conversation as resolved.
Show resolved Hide resolved
<CheckboxesField
label={label}
helpText={helpText}
cleanName={cleanName}
choicesList={choicesList}
defaultValuesList={defaultValuesList}
/>
)}

{fieldType === 'radio' && (
<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>
<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>
{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>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { render, screen } from '@/config/test-utils'

import CheckboxesField from './CheckboxesField'

describe('CheckboxesField Component', () => {
const mockProps = {
label: 'What would you like to see on the dashboard in the future?',
helpText: 'Pick one or more options from the list.',
cleanName: 'dashboard_features',
choicesList: ['Choice 1', 'Choice 2', 'Choice 3'],
defaultValuesList: ['Choice 1', 'Choice 3'], // The items in the defaultValuesList Choices should be checked by default
}

it('should render the label correctly', () => {
render(<CheckboxesField {...mockProps} />)

// Check if the label is rendered correctly
expect(screen.getByText(mockProps.label)).toBeInTheDocument()
})

it('should render the help text if provided', () => {
render(<CheckboxesField {...mockProps} />)

// Check if the help text is rendered
expect(screen.getByText(mockProps.helpText)).toBeInTheDocument()
})

it('should not render help text if not provided', () => {
render(<CheckboxesField {...{ ...mockProps, helpText: '' }} />)

// Check if help text is not rendered
expect(screen.queryByText(mockProps.helpText)).not.toBeInTheDocument()
})

it('should render the correct number of checkboxes from choicesList', () => {
render(<CheckboxesField {...mockProps} />)

// Check if the correct number of checkboxes are rendered
const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes).toHaveLength(mockProps.choicesList.length)
})

it('should render checkboxes with the correct values from choicesList', () => {
render(<CheckboxesField {...mockProps} />)

// Check if the checkboxes have the correct value attribute
const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes[0]).toHaveAttribute('value', mockProps.choicesList[0])
expect(checkboxes[1]).toHaveAttribute('value', mockProps.choicesList[1])
expect(checkboxes[2]).toHaveAttribute('value', mockProps.choicesList[2])
})

it('should render checkboxes with the correct label associated', () => {
render(<CheckboxesField {...mockProps} />)

// Check if the checkboxes have the correct labels
expect(screen.getByLabelText(mockProps.choicesList[0])).toBeInTheDocument()
expect(screen.getByLabelText(mockProps.choicesList[1])).toBeInTheDocument()
expect(screen.getByLabelText(mockProps.choicesList[2])).toBeInTheDocument()
})

it('should render the checkboxes with the correct default checked state', () => {
render(<CheckboxesField {...mockProps} />)

// Check if the checkboxes have the correct default checked state
const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes[0]).toBeChecked() // Choice 1 is in defaultValuesList, hence it should be checked
expect(checkboxes[1]).not.toBeChecked() // Choice 2 is not in defaultValuesList, so it should not be checked
expect(checkboxes[2]).toBeChecked()
})

it('should render unique ids for each checkbox', () => {
render(<CheckboxesField {...mockProps} />)

// Check if each checkbox has a unique id
const checkboxes = screen.getAllByRole('checkbox')
checkboxes.forEach((checkbox, index) => {
expect(checkbox).toHaveAttribute('id', `${mockProps.cleanName}-${index}`)
})
})

it('should not render any checkboxes if choicesList is empty', () => {
render(<CheckboxesField {...{ ...mockProps, choicesList: [] }} />)

// Check if no checkboxes are rendered
const checkboxes = screen.queryAllByRole('checkbox')
expect(checkboxes).toHaveLength(0)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Fieldtype } from '../../Feedback'

export default function DropdownField({ label, helpText, cleanName, choicesList = [] }: Fieldtype) {
return (
<div className="govuk-form-group 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}

<select className="govuk-select" id={cleanName} name={cleanName} aria-describedby={helpText}>
<option value="choose an option" data-testid="submit-button">
Choose an option
</option>
{choicesList.map((choice, key) => {
return (
<option key={key} value={choice}>
{choice}
</option>
)
})}
</select>
</div>
)
}
Loading
Loading