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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Fixed

- We fixed an issue where checkboxes remained visible in read-only "Content only" mode. Now only selected items are displayed as text, consistent with radio button behavior.

## [1.1.1] - 2026-02-24

### Fixed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { render } from "@testing-library/react";
import { CheckboxRadioSelectionContainerProps } from "../../typings/CheckboxRadioSelectionProps";
import CheckboxRadioSelection from "../CheckboxRadioSelection";
import { CheckboxSelection } from "../components/CheckboxSelection/CheckboxSelection";
import { MultiSelector } from "../helpers/types";

// Mock the selector to avoid implementation dependencies for basic tests
jest.mock("../helpers/getSelector", () => ({
Expand Down Expand Up @@ -89,3 +91,94 @@ describe("CheckboxRadioSelection", () => {
expect(widget?.className).toContain("widget-checkbox-radio-selection");
});
});

function makeMultiSelector(overrides: Partial<MultiSelector> = {}): MultiSelector {
return {
type: "multi",
status: "available",
readOnly: false,
currentId: [],
clearable: false,
customContentType: "no",
validation: undefined,
updateProps: jest.fn(),
setValue: jest.fn(),
getOptions: jest.fn(() => ["option1", "option2", "option3"]),
options: {
status: "available",
searchTerm: "",
isLoading: false,
getAll: jest.fn(() => ["option1", "option2", "option3"]),
setSearchTerm: jest.fn(),
onAfterSearchTermChange: jest.fn(),
_updateProps: jest.fn(),
_optionToValue: jest.fn(),
_valueToOption: jest.fn()
},
caption: {
get: jest.fn((v: string) => `Caption ${v}`),
render: jest.fn((v: string | null | number | null) => `Caption ${v}`),
emptyCaption: "Select an option",
formatter: undefined
},
...overrides
};
}

const baseCheckboxProps = {
inputId: "test-checkbox",
tabIndex: 0,
ariaRequired: { status: "available" as const, value: false } as any,
ariaLabel: undefined,
groupName: undefined,
noOptionsText: "No options"
};

describe("CheckboxSelection – read-only text mode", () => {
it("hides unselected options and their inputs", () => {
const selector = makeMultiSelector({ readOnly: true, currentId: ["option1"] });
const { queryByDisplayValue } = render(
<CheckboxSelection {...baseCheckboxProps} selector={selector} readOnlyStyle="text" />
);

// unselected options should not appear at all
expect(queryByDisplayValue("option2")).toBeNull();
expect(queryByDisplayValue("option3")).toBeNull();
});

it("renders no <input> for the selected option in text mode", () => {
const selector = makeMultiSelector({ readOnly: true, currentId: ["option1"] });
const { queryAllByRole } = render(
<CheckboxSelection {...baseCheckboxProps} selector={selector} readOnlyStyle="text" />
);

expect(queryAllByRole("checkbox")).toHaveLength(0);
});

it("renders selected option caption text", () => {
const selector = makeMultiSelector({ readOnly: true, currentId: ["option1"] });
const { getByText } = render(
<CheckboxSelection {...baseCheckboxProps} selector={selector} readOnlyStyle="text" />
);

expect(getByText("Caption option1")).toBeTruthy();
});

it("renders all inputs when not read-only", () => {
const selector = makeMultiSelector({ readOnly: false, currentId: ["option1"] });
const { getAllByRole } = render(
<CheckboxSelection {...baseCheckboxProps} selector={selector} readOnlyStyle="text" />
);

expect(getAllByRole("checkbox")).toHaveLength(3);
});

it("renders inputs in bordered read-only mode", () => {
const selector = makeMultiSelector({ readOnly: true, currentId: ["option1"] });
const { getAllByRole } = render(
<CheckboxSelection {...baseCheckboxProps} selector={selector} readOnlyStyle="bordered" />
);

expect(getAllByRole("checkbox")).toHaveLength(3);
});
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { If } from "@mendix/widget-plugin-component-kit/If";
import classNames from "classnames";
import { MouseEvent, ReactElement } from "react";
import { MultiSelector, SelectionBaseProps } from "../../helpers/types";
Expand Down Expand Up @@ -48,6 +49,9 @@ export function CheckboxSelection({
{options.map((optionId, index) => {
const isSelected = currentIds.includes(optionId);
const checkboxId = `${inputId}-checkbox-${index}`;
if (isReadOnly && !isSelected && readOnlyStyle === "text") {
return null;
}

return (
<div
Expand All @@ -56,18 +60,20 @@ export function CheckboxSelection({
"widget-checkbox-radio-selection-item-selected": isSelected
})}
>
<input
type="checkbox"
id={checkboxId}
name={name}
value={optionId}
checked={isSelected}
disabled={isReadOnly}
tabIndex={tabIndex}
onChange={e => handleChange(optionId, e.target.checked)}
aria-describedby={isSingleCheckbox && selector.validation ? errorId : undefined}
aria-invalid={isSingleCheckbox && selector.validation ? true : undefined}
/>
<If condition={!isReadOnly || readOnlyStyle !== "text"}>
<input
type="checkbox"
id={checkboxId}
name={name}
value={optionId}
checked={isSelected}
disabled={isReadOnly}
tabIndex={tabIndex}
onChange={e => handleChange(optionId, e.target.checked)}
aria-describedby={isSingleCheckbox && selector.validation ? errorId : undefined}
aria-invalid={isSingleCheckbox && selector.validation ? true : undefined}
/>
</If>
<CaptionContent
onClick={(e: MouseEvent<HTMLDivElement>) => {
e.preventDefault();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

&-readonly {
&.widget-checkbox-radio-selection-readonly-text {
.radio-item {
.radio-item,
.checkbox-item {
display: none;

&.widget-checkbox-radio-selection-item-selected {
Expand Down
Loading