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
8 changes: 8 additions & 0 deletions packages/pluggableWidgets/combobox-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- We fixed an issue where the widget crashed when using the "On filter input change" action with the new string behavior enabled.

- We fixed an issue where the custom editability condition (`never` or `conditionally`) was ignored when the combobox attribute was present and editable.

- We fixed an issue where the editability setting was inverted: setting editability to `false` allowed editing, and setting it to `true` prevented editing. The editability condition expression now behaves correctly: `true` means editable, `false` means read-only.

### Breaking changes

- The editability fix above is a potential breaking change. Apps that worked around the bug by inverting their editability expression will now have the opposite (incorrect) behavior after upgrading. Review any combobox widgets using a custom editability expression and remove the inversion workaround, otherwise fields may unexpectedly become editable or read-only.

## [2.8.1] - 2026-04-30

### Fixed
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import Big from "big.js";
import { EditableValue } from "mendix";
import { dynamic, EditableValueBuilder } from "@mendix/widget-plugin-test-utils";
import { getReadonly } from "../utils";

describe("getReadonly", () => {
describe("when targetAttribute is readOnly", () => {
it("is read-only regardless of customEditability 'default'", () => {
const targetAttribute = new EditableValueBuilder<string>().withValue("value").isReadOnly().build();

const result = getReadonly(
targetAttribute as unknown as EditableValue<string | Big>,
"default",
dynamic.available(true)
);

expect(result).toBe(true);
});

it("is read-only regardless of customEditability 'never'", () => {
const targetAttribute = new EditableValueBuilder<string>().withValue("value").isReadOnly().build();

const result = getReadonly(
targetAttribute as unknown as EditableValue<string | Big>,
"never",
dynamic.available(true)
);

expect(result).toBe(true);
});

it("is read-only regardless of customEditability 'conditionally' with expression true", () => {
const targetAttribute = new EditableValueBuilder<string>().withValue("value").isReadOnly().build();

const result = getReadonly(
targetAttribute as unknown as EditableValue<string | Big>,
"conditionally",
dynamic.available(true)
);

expect(result).toBe(true);
});
});

describe("when targetAttribute is editable", () => {
it("is editable when customEditability is 'default'", () => {
const targetAttribute = new EditableValueBuilder<string>().withValue("value").build();

const result = getReadonly(
targetAttribute as unknown as EditableValue<string | Big>,
"default",
dynamic.available(true)
);

expect(result).toBe(false);
});

it("is read-only when customEditability is 'never'", () => {
const targetAttribute = new EditableValueBuilder<string>().withValue("value").build();

const result = getReadonly(
targetAttribute as unknown as EditableValue<string | Big>,
"never",
dynamic.available(true)
);

expect(result).toBe(true);
});

it("is read-only when customEditability is 'conditionally' and expression value is false", () => {
const targetAttribute = new EditableValueBuilder<string>().withValue("value").build();

const result = getReadonly(
targetAttribute as unknown as EditableValue<string | Big>,
"conditionally",
dynamic.available(false)
);

expect(result).toBe(true);
});

it("is editable when customEditability is 'conditionally' and expression value is true", () => {
const targetAttribute = new EditableValueBuilder<string>().withValue("value").build();

const result = getReadonly(
targetAttribute as unknown as EditableValue<string | Big>,
"conditionally",
dynamic.available(true)
);

expect(result).toBe(false);
});

it("is read-only when customEditability is 'conditionally' and expression is loading", () => {
const targetAttribute = new EditableValueBuilder<string>().withValue("value").build();

const result = getReadonly(
targetAttribute as unknown as EditableValue<string | Big>,
"conditionally",
dynamic.loading()
);

expect(result).toBe(true);
});

it("is read-only when customEditability is 'conditionally' and expression is unavailable", () => {
const targetAttribute = new EditableValueBuilder<string>().withValue("value").build();

const result = getReadonly(
targetAttribute as unknown as EditableValue<string | Big>,
"conditionally",
dynamic.unavailable()
);

expect(result).toBe(true);
});
});

describe("when targetAttribute is undefined", () => {
it("is read-only when customEditability is 'never'", () => {
const result = getReadonly(undefined, "never", dynamic.available(true));

expect(result).toBe(true);
});

it("is editable when customEditability is 'default'", () => {
const result = getReadonly(undefined, "default", dynamic.available(false));

expect(result).toBe(false);
});

it("is read-only when customEditability is 'conditionally' and expression value is false", () => {
const result = getReadonly(undefined, "conditionally", dynamic.available(false));

expect(result).toBe(true);
});

it("is editable when customEditability is 'conditionally' and expression value is true", () => {
const result = getReadonly(undefined, "conditionally", dynamic.available(true));

expect(result).toBe(false);
});

it("is read-only when customEditability is 'conditionally' and expression is loading", () => {
const result = getReadonly(undefined, "conditionally", dynamic.loading());

expect(result).toBe(true);
});

it("is read-only when customEditability is 'conditionally' and expression is unavailable", () => {
const result = getReadonly(undefined, "conditionally", dynamic.unavailable());

expect(result).toBe(true);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,14 @@ export function getReadonly(
customEditability: ComboboxContainerProps["customEditability"],
customEditabilityExpression: ComboboxContainerProps["customEditabilityExpression"]
): boolean {
if (targetAttribute) {
return targetAttribute.readOnly;
if (targetAttribute?.readOnly) {
return true;
}
if (customEditability === "never") {
return true;
}
if (customEditability === "conditionally") {
return customEditabilityExpression.value ?? true;
if (customEditability === "conditionally" && customEditabilityExpression.value !== true) {
return true;
}
return false;
}
Loading