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

DS-1264: feat(checkbox, checkbox-group): add required state for checkboxes #1035

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,39 @@ describe('Checkbox', () => {

expect(tree).toMatchSnapshot();
});

test('should display warning message if input is invalid and not checked', () => {
const wrapper = mountWithTheme(
<CheckboxGroup
label="Vehicule"
checkboxGroup={checkboxGroup}
required
valid={false}
id="checkbox-group-test"
/>,
);

const warning = wrapper.find('div#checkbox-group-test_validationAlert');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je pense que t'as pas besoin du div dans le sélecteur?

expect(warning).toBeDefined();
});

test('should hide warning message if group is invalid and one input is checked', () => {
const wrapper = mountWithTheme(
<CheckboxGroup
label="Vehicule"
checkboxGroup={checkboxGroup}
required
valid={false}
id="checkbox-group-test"
/>,
);

const warningPreCheck = wrapper.find('div#checkbox-group-test_validationAlert');
expect(warningPreCheck).toBeDefined();
Comment on lines +75 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pas besoin de ce bout là, ton test précédent fait la même chose.


wrapper.find('input').at(0).simulate('change');

const warningPostCheck = wrapper.find('div#checkbox-group-test_validationAlert');
expect(warningPostCheck).toEqual({});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ exports[`Checkbox Matches the snapshot 1`] = `
class="c0"
>
<input
aria-invalid="false"
aria-labelledby=""
class="c1"
data-testid="checkboxGroup-boat"
name="vehicule1"
Expand Down Expand Up @@ -240,6 +242,8 @@ exports[`Checkbox Matches the snapshot 1`] = `
class="c0"
>
<input
aria-invalid="false"
aria-labelledby=""
checked=""
class="c1"
data-testid="checkboxGroup-plane"
Expand Down Expand Up @@ -368,6 +372,8 @@ exports[`Checkbox Matches the snapshot 1`] = `
disabled=""
>
<input
aria-invalid="false"
aria-labelledby=""
class="c1"
data-testid="checkboxGroup-car"
disabled=""
Expand Down Expand Up @@ -496,6 +502,8 @@ exports[`Checkbox Matches the snapshot 1`] = `
class="c0"
>
<input
aria-invalid="false"
aria-labelledby=""
class="c1"
data-testid="checkboxGroup-bike"
name="vehicule4"
Expand Down
60 changes: 57 additions & 3 deletions packages/react/src/components/checkbox-group/checkbox-group.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { ChangeEvent, VoidFunctionComponent } from 'react';
import { ChangeEvent, useState, VoidFunctionComponent } from 'react';
import styled from 'styled-components';
import { useDataAttributes } from '../../hooks/use-data-attributes';
import { Checkbox } from '../checkbox/checkbox';
import { useDeviceContext } from '../device-context-provider/device-context-provider';
import { useTranslation } from '../../i18n/use-translation';
import { v4 as uuid } from '../../utils/uuid';
import { Icon } from '../icon/icon';

const Legend = styled.legend`
font-size: 0.75rem;
Expand All @@ -10,9 +14,27 @@ const Legend = styled.legend`
padding: 0;
`;

const StyledIcon = styled(Icon)`
align-self: center;
display: flex;
margin-right: var(--spacing-base);
`;

const ValidationErrorAlert = styled.div<{ label: string }>`
align-items: flex-start;
color: ${({ theme }) => theme.component['checkbox-error-border-color']};
display: flex;
margin: ${({ label }) => `${label ? 'calc(var(--spacing-1x) * -1) ' : '0'} 0 0 var(--spacing-1x) `};
padding-bottom: var(--spacing-1x);
`;

interface CheckboxProps {
id?: string;
label?: string;
checkedValues?: string[];
required?: boolean;
valid?: boolean;
validationErrorMessage?: string;
checkboxGroup: {
label: string,
name: string,
Expand All @@ -25,35 +47,67 @@ interface CheckboxProps {
}

export const CheckboxGroup: VoidFunctionComponent<CheckboxProps> = ({
id,
label,
checkedValues,
checkboxGroup,
required,
valid = true,
validationErrorMessage,
onChange,
...props
}) => {
const { isMobile } = useDeviceContext();
const { t } = useTranslation('checkbox');
const dataAttributes = useDataAttributes(props);
const dataTestId = dataAttributes['data-testid'] ?? 'checkboxGroup';
const validationAlertId = `${id || uuid()}_validationAlert`;
const [checkedState, setCheckedState] = useState(
new Array(checkboxGroup.length).fill(false),
);
const areAllCheckboxUnchecked = checkedState.every((e) => e === false);

const handleOnChange = (position: number): void => {
const updatedCheckedState = checkedState.map((item, index) => (index === position ? !item : item));

setCheckedState(updatedCheckedState);
};
Comment on lines +65 to +74
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je suis pas certain qu'on doive gérer le state. Ça devrait être synchronisé avec les checkedValues. Ou sinon il faudrait gérer le mode controlled et uncontrolled. Là on est un peu entre les deux.
Vous en pensez quoi @pylafleur @savutsang ? Est-ce qu'on se sert tant que ça du mode uncontrolled?


return (
<>
{label && <Legend>{label}</Legend>}
{
required && !valid && areAllCheckboxUnchecked
&& (
<ValidationErrorAlert id={validationAlertId} label={label || ''}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Même question

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Et le message semble affiché dès le load du component. Je pense qu'on voulait pas ça, on l'avait enlevé sur le TextInput il me semble. @LarryMatte ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le message d'erreur devrait s'afficher si l'utilisateur n'a pas sélectionné de checkbox et qu'il submit le formulaire. La validation va fail et le message d'erreur va s'afficher.

<StyledIcon name="alertOctagon" size={isMobile ? '24' : '16'} />
{validationErrorMessage || t('validationErrorMessage')}
</ValidationErrorAlert>
)
}
{checkboxGroup.map(({
defaultChecked,
disabled,
label: checkboxLabel,
name,
value,
}) => (
}, pos) => (
<Checkbox
key={`${name}-${value}`}
checked={checkedValues?.includes(value)}
data-testid={`${dataTestId}-${value}`}
defaultChecked={defaultChecked}
disabled={disabled}
isInGroup
label={checkboxLabel}
name={name}
required={required}
valid={valid}
value={value}
onChange={onChange}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
handleOnChange(pos);
onChange?.(event);
}}
/>
))}
</>
Expand Down
54 changes: 54 additions & 0 deletions packages/react/src/components/checkbox/checkbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,60 @@ describe('Checkbox', () => {
expect(input.prop('disabled')).toBe(true);
});

test('should be required when required prop is set to true', () => {
const wrapper = mountWithTheme(<Checkbox {...defaultProps} required />);

const input = wrapper.find('input');
expect(input.prop('required')).toBe(true);
});

test('should display warning message if input is invalid and not checked', () => {
const wrapper = mountWithTheme(<Checkbox {...defaultProps} id="checkbox-test" required valid={false} />);

const warning = wrapper.find('div#checkbox-test_validationAlert');
expect(warning).toBeDefined();
});

test('should hide warning message if input is invalid and then checked', () => {
const wrapper = mountWithTheme(<Checkbox {...defaultProps} id="checkbox-test" required valid={false} />);

const warningPreCheck = wrapper.find('div#checkbox-test_validationAlert');
expect(warningPreCheck).toBeDefined();

wrapper.find('input').simulate('change');

const warningPostCheck = wrapper.find('div#checkbox-test_validationAlert');
expect(warningPostCheck).toEqual({});
});

test('should have aria-labelledby prop when warning message is displayed and input is invalid', () => {
const wrapper = mountWithTheme(<Checkbox {...defaultProps} required id="checkbox-test" valid={false} />);

const input = wrapper.find('input');
expect(input.prop('aria-labelledby')).toBe('checkbox-test_validationAlert');
});

test('should have empty aria-labelledby prop when input is valid', () => {
const wrapper = mountWithTheme(<Checkbox {...defaultProps} required id="checkbox-test" />);

const input = wrapper.find('input');
expect(input.prop('aria-labelledby')).toBe('');
});

test('should have aria-invalid prop set to true when warning message is displayed and input is invalid', () => {
const wrapper = mountWithTheme(<Checkbox {...defaultProps} required id="checkbox-test" valid={false} />);

const input = wrapper.find('input');
expect(input.prop('aria-invalid')).toBe(true);
});

test('should have aria-invalid prop set to false when input is valid', () => {
const wrapper = mountWithTheme(<Checkbox {...defaultProps} required id="checkbox-test" />);

const input = wrapper.find('input');
expect(input.prop('aria-invalid')).toBe(false);
});

test('matches snapshot', () => {
const tree = mountWithTheme(<Checkbox {...defaultProps} />);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,26 @@ exports[`Checkbox matches snapshot 1`] = `
className="c0"
>
<styled.input
aria-invalid={false}
aria-labelledby=""
name="vehicule"
onChange={[Function]}
type="checkbox"
value="boat"
>
<input
aria-invalid={false}
aria-labelledby=""
className="c1"
name="vehicule"
onChange={[Function]}
type="checkbox"
value="boat"
/>
</styled.input>
<styled.span>
<styled.span
$valid={true}
>
<span
className="c2 c3"
>
Expand Down
Loading