Skip to content

Commit c752bbb

Browse files
committed
fix(combobox-web): fix inverted editability logic in getReadonly
The `conditionally` branch returned `expression.value` directly, so a truthy value (meaning "editable") made the widget read-only, and vice versa. Fixed by returning `true` only when the expression is not `true`, and added unit tests for all `getReadonly` branches.
1 parent d7e46fa commit c752bbb

3 files changed

Lines changed: 144 additions & 4 deletions

File tree

packages/pluggableWidgets/combobox-web/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1010

1111
- We fixed an issue where the combobox collapsed to 1px width when placed inside a flex row container with `align-items: center`.
1212

13+
- We fixed an issue where the editability setting was inverted: setting editability to `false` allowed editing, and setting it to `true` prevented editing.
14+
1315
## [2.8.1] - 2026-04-30
1416

1517
### Fixed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import Big from "big.js";
2+
import { EditableValue } from "mendix";
3+
import { dynamic, EditableValueBuilder } from "@mendix/widget-plugin-test-utils";
4+
import { getReadonly } from "../utils";
5+
6+
describe("getReadonly", () => {
7+
describe("when targetAttribute is readOnly", () => {
8+
it("is read-only regardless of customEditability 'default'", () => {
9+
const targetAttribute = new EditableValueBuilder<string>().withValue("value").isReadOnly().build();
10+
11+
const result = getReadonly(
12+
targetAttribute as unknown as EditableValue<string | Big>,
13+
"default",
14+
dynamic.available(true)
15+
);
16+
17+
expect(result).toBe(true);
18+
});
19+
20+
it("is read-only regardless of customEditability 'never'", () => {
21+
const targetAttribute = new EditableValueBuilder<string>().withValue("value").isReadOnly().build();
22+
23+
const result = getReadonly(
24+
targetAttribute as unknown as EditableValue<string | Big>,
25+
"never",
26+
dynamic.available(true)
27+
);
28+
29+
expect(result).toBe(true);
30+
});
31+
32+
it("is read-only regardless of customEditability 'conditionally' with expression true", () => {
33+
const targetAttribute = new EditableValueBuilder<string>().withValue("value").isReadOnly().build();
34+
35+
const result = getReadonly(
36+
targetAttribute as unknown as EditableValue<string | Big>,
37+
"conditionally",
38+
dynamic.available(true)
39+
);
40+
41+
expect(result).toBe(true);
42+
});
43+
});
44+
45+
describe("when targetAttribute is editable", () => {
46+
it("is editable when customEditability is 'default'", () => {
47+
const targetAttribute = new EditableValueBuilder<string>().withValue("value").build();
48+
49+
const result = getReadonly(
50+
targetAttribute as unknown as EditableValue<string | Big>,
51+
"default",
52+
dynamic.available(true)
53+
);
54+
55+
expect(result).toBe(false);
56+
});
57+
58+
it("is read-only when customEditability is 'never'", () => {
59+
const targetAttribute = new EditableValueBuilder<string>().withValue("value").build();
60+
61+
const result = getReadonly(
62+
targetAttribute as unknown as EditableValue<string | Big>,
63+
"never",
64+
dynamic.available(true)
65+
);
66+
67+
expect(result).toBe(true);
68+
});
69+
70+
it("is read-only when customEditability is 'conditionally' and expression value is false", () => {
71+
const targetAttribute = new EditableValueBuilder<string>().withValue("value").build();
72+
73+
const result = getReadonly(
74+
targetAttribute as unknown as EditableValue<string | Big>,
75+
"conditionally",
76+
dynamic.available(false)
77+
);
78+
79+
expect(result).toBe(true);
80+
});
81+
82+
it("is editable when customEditability is 'conditionally' and expression value is true", () => {
83+
const targetAttribute = new EditableValueBuilder<string>().withValue("value").build();
84+
85+
const result = getReadonly(
86+
targetAttribute as unknown as EditableValue<string | Big>,
87+
"conditionally",
88+
dynamic.available(true)
89+
);
90+
91+
expect(result).toBe(false);
92+
});
93+
94+
it("is read-only when customEditability is 'conditionally' and expression value is undefined (loading or unavailable)", () => {
95+
const targetAttribute = new EditableValueBuilder<string>().withValue("value").build();
96+
97+
const result = getReadonly(
98+
targetAttribute as unknown as EditableValue<string | Big>,
99+
"conditionally",
100+
dynamic.loading()
101+
);
102+
103+
expect(result).toBe(true);
104+
});
105+
});
106+
107+
describe("when targetAttribute is undefined", () => {
108+
it("is read-only when customEditability is 'never'", () => {
109+
const result = getReadonly(undefined, "never", dynamic.available(true));
110+
111+
expect(result).toBe(true);
112+
});
113+
114+
it("is editable when customEditability is 'default'", () => {
115+
const result = getReadonly(undefined, "default", dynamic.available(false));
116+
117+
expect(result).toBe(false);
118+
});
119+
120+
it("is read-only when customEditability is 'conditionally' and expression value is false", () => {
121+
const result = getReadonly(undefined, "conditionally", dynamic.available(false));
122+
123+
expect(result).toBe(true);
124+
});
125+
126+
it("is editable when customEditability is 'conditionally' and expression value is true", () => {
127+
const result = getReadonly(undefined, "conditionally", dynamic.available(true));
128+
129+
expect(result).toBe(false);
130+
});
131+
132+
it("is read-only when customEditability is 'conditionally' and expression value is undefined (loading or unavailable)", () => {
133+
const result = getReadonly(undefined, "conditionally", dynamic.loading());
134+
135+
expect(result).toBe(true);
136+
});
137+
});
138+
});

packages/pluggableWidgets/combobox-web/src/helpers/Database/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,14 @@ export function getReadonly(
9999
customEditability: ComboboxContainerProps["customEditability"],
100100
customEditabilityExpression: ComboboxContainerProps["customEditabilityExpression"]
101101
): boolean {
102-
if (targetAttribute) {
103-
return targetAttribute.readOnly;
102+
if (targetAttribute?.readOnly) {
103+
return true;
104104
}
105105
if (customEditability === "never") {
106106
return true;
107107
}
108-
if (customEditability === "conditionally") {
109-
return customEditabilityExpression.value ?? true;
108+
if (customEditability === "conditionally" && customEditabilityExpression.value !== true) {
109+
return true;
110110
}
111111
return false;
112112
}

0 commit comments

Comments
 (0)