Skip to content
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
1 change: 0 additions & 1 deletion src/components/PlayerCasoContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import ExamService from "../services/ExamService";
import { useHistory } from 'react-router-dom';
import { alertError, alertSuccess } from "../services/AlertService";
import CasoContext from "../context/CasoContext";
import { CustomButton } from "./custom";
import EnarmUtil from "../modules/EnarmUtil";
import ContributionTypeSelector from "./ContributionTypeSelector";
import ContributionsSummary from "./ContributionsSummary";
Expand Down
19 changes: 11 additions & 8 deletions src/components/Profile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -302,14 +302,17 @@ const Profile = () => {
{categories.map(cat => {
const isSelected = selectedSpecialties.includes(cat.id);
return (
<div key={cat.id} className="col s12 m6 l4" style={{ marginBottom: '1rem' }}>
<CustomCheckbox
id={`spec-${cat.id}`}
label={cat.name}
checked={isSelected}
onChange={() => toggleSpecialty(cat.id)}
/>
</div>
<CustomCheckbox
key={cat.id}
id={`spec-${cat.id}`}
label={cat.name}
checked={isSelected}
onChange={() => toggleSpecialty(cat.id)}
s={12}
m={6}
l={4}
style={{ marginBottom: '1rem' }}
/>
);
})}
</div>
Expand Down
37 changes: 18 additions & 19 deletions src/components/admin/AnswerForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,24 @@ const AnswerForm = ({

return (
<CustomRow className="answer-wrapper" style={{ marginBottom: '15px', alignItems: 'center' }}>
<CustomCol s={3} m={2}>
<div style={{ marginTop: '20px' }}>
<CustomCheckbox
id={`answer-iscorrect-${keyId}`}
label="¿Correcta?"
checked={answer.is_correct}
onChange={(event) => {
onChangeAnswer(
questionIndex,
answerIndex,
"is_correct",
event
);
}}
name={answerIsCorrectName}
value="true"
/>
</div>
</CustomCol>
<CustomCheckbox
s={3}
m={2}
style={{ marginTop: '20px' }}
id={`answer-iscorrect-${keyId}`}
label="¿Correcta?"
checked={answer.is_correct}
onChange={(event) => {
onChangeAnswer(
questionIndex,
answerIndex,
"is_correct",
event
);
}}
name={answerIsCorrectName}
value="true"
/>
<CustomCol s={8} m={9}>
<CustomTextInput
id={`answer-text-${keyId}`}
Expand Down
98 changes: 81 additions & 17 deletions src/components/custom/CustomCheckbox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ import PropTypes from 'prop-types';
const CustomCheckbox = ({
id,
label,
checked = false, // Renamed from 'value' for clarity with checkbox 'checked' attribute
checked = false,
onChange,
disabled = false,
className = '', // Applied to the input element
labelClassName = '', // Applied to the label element
indeterminate = false,
value, // HTML value attribute, not for checked state
...props
s,
m,
l,
xl,
offset,
required,
helperText,
wrapperClassName = '',
...props // Contains style and other custom props
}) => {
const inputRef = useRef(null);

Expand All @@ -26,22 +34,70 @@ const CustomCheckbox = ({
inputClasses += ' indeterminate-checkbox';
}

// The main wrapper is the label for Materialize checkboxes
return (
<label htmlFor={id} className={labelClassName} {...props}>
<input
ref={inputRef}
type="checkbox"
id={id}
checked={checked}
onChange={onChange}
disabled={disabled}
className={inputClasses.trim()}
value={value} // HTML value attribute
/>
<span>{label}</span>
</label>
// Construct grid and wrapper classes
const hasGrid = s || m || l || xl || offset || wrapperClassName;
let finalWrapperClasses = wrapperClassName;
if (s) finalWrapperClasses += ` col s${s}`;
if (m) finalWrapperClasses += ` col m${m}`;
if (l) finalWrapperClasses += ` col l${l}`;
if (xl) finalWrapperClasses += ` col xl${xl}`;
if (offset) {
offset.split(' ').forEach(off => {
if (off) finalWrapperClasses += ` offset-${off}`;
});
}

const helperTextId = helperText ? `${id}-helper` : undefined;

const checkboxContent = (
<>
<label
htmlFor={id}
className={labelClassName.trim()}
{...(!hasGrid ? props : {})} // Apply style/custom props to label if NOT wrapped in div
>
<input
ref={inputRef}
type="checkbox"
id={id}
checked={checked}
onChange={onChange}
disabled={disabled}
className={inputClasses.trim()}
value={value}
aria-required={required ? 'true' : undefined}
aria-describedby={helperTextId}
/>
<span>
{label}
{required && (
<span
className="red-text"
style={{ marginLeft: '4px', fontWeight: 'bold' }}
aria-hidden="true"
title="Obligatorio"
>
*
</span>
)}
</span>
</label>
{helperText && <span id={helperTextId} className="helper-text">{helperText}</span>}
</>
);

if (hasGrid) {
return (
<div
className={finalWrapperClasses.trim()}
{...props} // Apply style/custom props to wrapper div if present
>
{checkboxContent}
</div>
);
}

return checkboxContent;
};

CustomCheckbox.propTypes = {
Expand All @@ -54,6 +110,14 @@ CustomCheckbox.propTypes = {
labelClassName: PropTypes.string, // For the label element
indeterminate: PropTypes.bool,
value: PropTypes.string, // HTML value attribute for the checkbox
s: PropTypes.number,
m: PropTypes.number,
l: PropTypes.number,
xl: PropTypes.number,
offset: PropTypes.string,
required: PropTypes.bool,
helperText: PropTypes.string,
wrapperClassName: PropTypes.string,
};

export default CustomCheckbox;
85 changes: 85 additions & 0 deletions src/components/custom/CustomCheckbox.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { render, screen } from '@testing-library/react';
import { describe, test, expect } from 'vitest';
import CustomCheckbox from './CustomCheckbox';

describe('CustomCheckbox', () => {
test('renders correctly with label', () => {
render(<CustomCheckbox id="test-check" label="Test Label" />);
expect(screen.getByLabelText(/Test Label/)).toBeInTheDocument();
});

test('applies grid classes and style to wrapper div', () => {
const { container } = render(
<CustomCheckbox
id="grid-check"
label="Grid Label"
s={12}
m={6}
offset="s1"
style={{ marginBottom: '1rem' }}
data-testid="outer-wrapper"
/>
);

const wrapper = container.firstChild;
expect(wrapper.tagName).toBe('DIV');
expect(wrapper).toHaveClass('col');
expect(wrapper).toHaveClass('s12');
expect(wrapper).toHaveClass('m6');
expect(wrapper).toHaveClass('offset-s1');
expect(wrapper).toHaveStyle('margin-bottom: 16px');
expect(wrapper).toHaveAttribute('data-testid', 'outer-wrapper');
});

test('applies style and custom props to label if not wrapped in div', () => {
const { container } = render(
<CustomCheckbox
id="no-wrap-check"
label="No Wrap"
style={{ color: 'red' }}
data-custom="test"
/>
);
const label = container.firstChild;
expect(label.tagName).toBe('LABEL');
expect(label).toHaveStyle('color: red');
expect(label).toHaveAttribute('data-custom', 'test');
});

test('renders required indicator and aria-required', () => {
render(<CustomCheckbox id="req-check" label="Required Label" required />);

const input = screen.getByLabelText(/Required Label/);
expect(input).toHaveAttribute('aria-required', 'true');

const asterisk = screen.getByTitle('Obligatorio');
expect(asterisk).toBeInTheDocument();
expect(asterisk).toHaveClass('red-text');
expect(asterisk.textContent).toBe('*');
});

test('renders helper text and links with aria-describedby', () => {
render(<CustomCheckbox id="help-check" label="Help Label" helperText="Be careful" />);

const helper = screen.getByText('Be careful');
expect(helper).toBeInTheDocument();
expect(helper).toHaveClass('helper-text');
expect(helper).toHaveAttribute('id', 'help-check-helper');

const input = screen.getByLabelText(/Help Label/);
expect(input).toHaveAttribute('aria-describedby', 'help-check-helper');
});

test('does not wrap in div if no grid or wrapperClassName provided', () => {
const { container } = render(<CustomCheckbox id="no-wrap" label="No Wrap" />);
expect(container.firstChild.tagName).toBe('LABEL');
});

test('wraps in div if wrapperClassName is provided', () => {
const { container } = render(
<CustomCheckbox id="wrap-class" label="Wrap Class" wrapperClassName="custom-wrap" />
);
expect(container.firstChild.tagName).toBe('DIV');
expect(container.firstChild).toHaveClass('custom-wrap');
});
});