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 13 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
29 changes: 28 additions & 1 deletion src/app/components/cms/Feedback/Feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@ import { z } from 'zod'
import { FormField } from '@/api/models/cms/Page/FormFields'

import { handler } from '../utils/handler'
import CheckboxesField from './Fields/Checkboxes/CheckboxesField'
import DropdownField from './Fields/Dropdown/DropdownField'
import EmailField from './Fields/Email/EmailField'
import NumberField from './Fields/Number/NumberField'
import UrlField from './Fields/Url/UrlField'

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

export interface Fieldtype {
label: string
helpText: string
cleanName: string
choices?: string
defaultValue?: string
}

interface FeedbackProps {
formFields: {
id: number
Expand Down Expand Up @@ -67,7 +80,7 @@ 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(',')

Expand Down Expand Up @@ -123,6 +136,20 @@ export const renderFormFields = ({
</div>
</fieldset>
)}

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

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

{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} choices={choices} />
Temiakinsoto marked this conversation as resolved.
Show resolved Hide resolved
)}
</Fragment>
rhys-burendo marked this conversation as resolved.
Show resolved Hide resolved
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Fieldtype } from '../../Feedback'

export default function CheckboxesField({ label, helpText, cleanName, defaultValue = '' }: Fieldtype) {
const defaultValuesList = defaultValue.includes('\r\n') ? defaultValue.split('\r\n') : defaultValue.split(',')
const trimmedValuesList = defaultValuesList.map((value) => value.trim())

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">
{trimmedValuesList.map((defaultVal, key) => {
Temiakinsoto marked this conversation as resolved.
Show resolved Hide resolved
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={defaultVal}
/>
<label className="govuk-label govuk-checkboxes__label" htmlFor={uniqueId}>
{defaultVal}
</label>
</div>
)
})}
</div>
</fieldset>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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 option from the list.',
cleanName: 'what_would_you_like_to_see_on_the_dashboard_in_the_future',
defaultValue: 'Option 1, Option 2, Option 3',
}

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

const labelElement = screen.getByText(mockProps.label)
expect(labelElement).toBeInTheDocument()
})

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

const helpTextElement = screen.getByText(mockProps.helpText)
expect(helpTextElement).toBeInTheDocument()
})

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

const helpTextElement = screen.queryByText(mockProps.helpText)
expect(helpTextElement).toBeNull()
})

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

const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes).toHaveLength(3) // There are 3 choices in the defaultValue
})

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

// Check that the checkboxes are present
const checkboxOption1 = screen.getByLabelText('Option 1')
const checkboxOption2 = screen.getByLabelText('Option 2')
const checkboxOption3 = screen.getByLabelText('Option 3')

expect(checkboxOption1).toBeInTheDocument()
expect(checkboxOption2).toBeInTheDocument()
expect(checkboxOption3).toBeInTheDocument()

// Check that the checkboxes have the correct value
expect(checkboxOption1).toHaveAttribute('value', 'Option 1')
expect(checkboxOption2).toHaveAttribute('value', 'Option 2')
expect(checkboxOption3).toHaveAttribute('value', 'Option 3')
})

it('should correctly handle defaultValue with newline characters', () => {
const propsWithNewline = {
...mockProps,
defaultValue: 'Option 1\r\nOption 2\r\nOption 3',
}

render(<CheckboxesField {...propsWithNewline} />)

const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes).toHaveLength(3) // There are 3 options in the defaultValue with newline characters
})

it('should correctly render the cleanName in the checkbox inputs', () => {
render(<CheckboxesField {...mockProps} />)

const checkboxes = screen.getAllByRole('checkbox')
checkboxes.forEach((checkbox, index) => {
const uniqueId = `${mockProps.cleanName}-${index}` // Match the generated id
expect(checkbox).toHaveAttribute('name', mockProps.cleanName)
expect(checkbox).toHaveAttribute('id', uniqueId) // Check that the id is unique
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Fieldtype } from '../../Feedback'

export default function DropdownField({ label, helpText, cleanName, choices = '' }: Fieldtype) {
const choicesList = choices.includes('\r\n') ? choices.split('\r\n') : choices.split(',')
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>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { render, screen } from '@/config/test-utils'

import DropdownField from './DropdownField'

describe('Dropdownfield Component', () => {
const mockProps = {
label: 'What would you like to see on the dashboard in the future?',
helpText: 'Pick one option from the list.',
cleanName: 'what_would_you_like_to_see_on_the_dashboard_in_the_future',
choices: 'Option 1, Option 2, Option 3',
}

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

const labelElement = screen.getByText('What would you like to see on the dashboard in the future?')
expect(labelElement).toBeInTheDocument()
})

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

const helpTextElement = screen.getByText('Pick one option from the list.')
expect(helpTextElement).toBeInTheDocument()
})

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

const helpTextElement = screen.queryByText('Pick one option from the list.')
expect(helpTextElement).toBeNull()
})

it('should render the default "Choose an option" option', () => {
render(<DropdownField {...mockProps} />)

const defaultOption = screen.getByText('Choose an option')
expect(defaultOption).toBeInTheDocument()
})

it('should render options based on choices string with commas', () => {
render(<DropdownField {...mockProps} />)

const options = screen.getAllByRole('option')
expect(options).toHaveLength(4) // 3 options + 1 default option
expect(screen.getByText('Option 1')).toBeInTheDocument()
expect(screen.getByText('Option 2')).toBeInTheDocument()
expect(screen.getByText('Option 3')).toBeInTheDocument()
})

it('should render options based on choices string with newline', () => {
const propsWithNewlineChoices = {
...mockProps,
choices: 'Option 1\r\nOption 2\r\nOption 3',
}
render(<DropdownField {...propsWithNewlineChoices} />)

const options = screen.getAllByRole('option')
expect(options).toHaveLength(4) // 3 options + 1 default option
expect(screen.getByText('Choose an option')).toBeInTheDocument()
expect(screen.getByText('Option 1')).toBeInTheDocument()
expect(screen.getByText('Option 2')).toBeInTheDocument()
expect(screen.getByText('Option 3')).toBeInTheDocument()
})

it('should correctly render the cleanName in the select input', () => {
render(<DropdownField {...mockProps} />)

const selectElement = screen.getByRole('combobox')
expect(selectElement).toHaveAttribute('name', 'what_would_you_like_to_see_on_the_dashboard_in_the_future')
expect(selectElement).toHaveAttribute('id', 'what_would_you_like_to_see_on_the_dashboard_in_the_future')
})
})
18 changes: 18 additions & 0 deletions src/app/components/cms/Feedback/Fields/Email/EmailField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client'

import { Fieldtype } from '../../Feedback'

export default function EmailField({ label, helpText, cleanName }: 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}

<input className="govuk-input" name={cleanName} id={cleanName} type="email" />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { render, screen } from '@/config/test-utils'

import EmailField from './EmailField'

describe('EmailField Component', () => {
it('should render the component with a label, helpText, and an email input', () => {
const label = 'Email address'
const helpText = 'Please enter a valid email address.'
const cleanName = 'email'

render(<EmailField label={label} helpText={helpText} cleanName={cleanName} />)

const labelElement = screen.getByLabelText(label)
expect(labelElement).toBeInTheDocument()

const helpTextElement = screen.getByText(helpText)
expect(helpTextElement).toBeInTheDocument()

const inputElement = screen.getByRole('textbox')
expect(inputElement).toHaveAttribute('type', 'email')
expect(inputElement).toHaveAttribute('name', cleanName)
expect(inputElement).toHaveAttribute('id', cleanName)
})
})
18 changes: 18 additions & 0 deletions src/app/components/cms/Feedback/Fields/Number/NumberField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client'

import { Fieldtype } from '../../Feedback'

export default function NumberField({ label, helpText, cleanName }: 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}

<input className="govuk-input" name={cleanName} id={cleanName} inputMode="numeric" />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { render, screen } from '@/config/test-utils'

import NumberField from './NumberField'

describe('NumberField', () => {
test('should render the component with a label, helpText, and numeric input field', () => {
const label = 'Phone Number'
const helpText = 'Please enter your phone number.'
const cleanName = 'phone-number'

render(<NumberField label={label} helpText={helpText} cleanName={cleanName} />)

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

// help text rendered correctly
const helpTextElement = screen.getByText(helpText)
expect(helpTextElement).toBeInTheDocument()

// input rendered with correct attributes
const inputElement = screen.getByRole('textbox')
expect(inputElement).toHaveAttribute('name', cleanName)
expect(inputElement).toHaveAttribute('id', cleanName)
expect(inputElement).toHaveAttribute('inputMode', 'numeric')
})

test('should not render help text when helpText is an empty string', () => {
const label = 'Phone Number'
const helpText = ''
const cleanName = 'phone-number'

render(<NumberField label={label} helpText={helpText} cleanName={cleanName} />)

// Check that help text is not rendered
const helpTextElement = screen.queryByText('Please enter your phone number.')
expect(helpTextElement).not.toBeInTheDocument()
})
})
Loading
Loading