Skip to content

Commit d031eaf

Browse files
committed
test(combobox-web): add unit tests
1 parent 1623390 commit d031eaf

1 file changed

Lines changed: 140 additions & 0 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import {
2+
dynamic,
3+
EditableValueBuilder,
4+
list,
5+
listAttribute,
6+
obj,
7+
SelectionSingleValueBuilder
8+
} from "@mendix/widget-plugin-test-utils";
9+
import { ListAttributeValue, ObjectItem } from "mendix";
10+
import { ComboboxContainerProps } from "../../../../typings/ComboboxProps";
11+
import { DatabaseSingleSelectionSelector } from "../DatabaseSingleSelectionSelector";
12+
13+
type PropsOverrides = {
14+
items: ObjectItem[];
15+
values: Map<string, string>;
16+
targetValue?: string;
17+
selection?: ReturnType<SelectionSingleValueBuilder["build"]>;
18+
};
19+
20+
function buildProps({ items, values, targetValue, selection }: PropsOverrides): ComboboxContainerProps {
21+
const valueAttr = listAttribute<string>(item => values.get(item.id) ?? "") as ListAttributeValue<string | Big>;
22+
(valueAttr as unknown as { id: string }).id = "valueAttrId" as any;
23+
24+
const captionAttr = listAttribute<string>(item => `caption_${item.id}`);
25+
(captionAttr as unknown as { id: string }).id = "captionAttrId" as any;
26+
27+
const targetAttr =
28+
targetValue === undefined
29+
? new EditableValueBuilder<string>().build()
30+
: new EditableValueBuilder<string>().withValue(targetValue).build();
31+
32+
return {
33+
name: "comboBox",
34+
id: "comboBox1",
35+
source: "database",
36+
optionsSourceType: "association",
37+
optionsSourceDatabaseDataSource: list(items),
38+
optionsSourceDatabaseItemSelection: selection ?? new SelectionSingleValueBuilder().build(),
39+
optionsSourceDatabaseCaptionType: "attribute",
40+
optionsSourceDatabaseCaptionAttribute: captionAttr,
41+
optionsSourceDatabaseValueAttribute: valueAttr,
42+
databaseAttributeString: targetAttr as any,
43+
emptyOptionText: dynamic("Select..."),
44+
optionsSourceDatabaseCustomContentType: "no",
45+
optionsSourceAssociationCustomContentType: "no",
46+
staticDataSourceCustomContentType: "no",
47+
optionsSourceAssociationCaptionType: "attribute",
48+
clearable: true,
49+
filterType: "contains",
50+
lazyLoading: false,
51+
loadingType: "spinner",
52+
customEditability: "default",
53+
customEditabilityExpression: dynamic(false),
54+
filterInputDebounceInterval: 200,
55+
selectedItemsStyle: "text",
56+
readOnlyStyle: "bordered",
57+
selectionMethod: "checkbox",
58+
selectAllButton: false,
59+
selectAllButtonCaption: dynamic("Select All"),
60+
ariaRequired: dynamic(true),
61+
showFooter: false,
62+
selectedItemsSorting: "none",
63+
attributeEnumeration: new EditableValueBuilder<string>().build(),
64+
attributeBoolean: new EditableValueBuilder<boolean>().build(),
65+
attributeAssociation: undefined as any,
66+
staticAttribute: new EditableValueBuilder<string>().build(),
67+
optionsSourceStaticDataSource: []
68+
} as ComboboxContainerProps;
69+
}
70+
71+
describe("DatabaseSingleSelectionSelector.updateProps — external target-attribute changes", () => {
72+
const optionA = obj("A");
73+
const optionB = obj("B");
74+
const items = [optionA, optionB];
75+
const values = new Map<string, string>([
76+
[optionA.id, "v1"],
77+
[optionB.id, "v2"]
78+
]);
79+
80+
it("resolves currentId from targetAttribute.value on initial updateProps", () => {
81+
const selector = new DatabaseSingleSelectionSelector({ filterInputDebounceInterval: 200 });
82+
selector.updateProps(buildProps({ items, values, targetValue: "v1" }));
83+
84+
expect(selector.currentId).toBe(optionA.id);
85+
});
86+
87+
it("refreshes currentId when targetAttribute.value changes externally after a selection exists", () => {
88+
// WC-3355: without this behavior, an external value change (e.g. from a microflow)
89+
// leaves currentId pointing at the stale option.
90+
const selector = new DatabaseSingleSelectionSelector({ filterInputDebounceInterval: 200 });
91+
const selection = new SelectionSingleValueBuilder().build();
92+
93+
selector.updateProps(buildProps({ items, values, targetValue: "v1", selection }));
94+
expect(selector.currentId).toBe(optionA.id);
95+
96+
selector.updateProps(buildProps({ items, values, targetValue: "v2", selection }));
97+
expect(selector.currentId).toBe(optionB.id);
98+
});
99+
100+
it("falls back to loadSelectedValue when new value is not in loaded options", () => {
101+
const selector = new DatabaseSingleSelectionSelector({ filterInputDebounceInterval: 200 });
102+
const selection = new SelectionSingleValueBuilder().build();
103+
const soleItems = [optionA];
104+
const soleValues = new Map<string, string>([[optionA.id, "v1"]]);
105+
106+
selector.updateProps(buildProps({ items: soleItems, values: soleValues, targetValue: "v1", selection }));
107+
const loadSpy = jest.spyOn(selector.options, "loadSelectedValue");
108+
109+
selector.updateProps(buildProps({ items: soleItems, values: soleValues, targetValue: "v-unknown", selection }));
110+
111+
expect(loadSpy).toHaveBeenCalledWith("v-unknown", expect.anything());
112+
});
113+
114+
it("clears currentId and selection when targetAttribute.value is cleared externally", () => {
115+
const selector = new DatabaseSingleSelectionSelector({ filterInputDebounceInterval: 200 });
116+
const selection = new SelectionSingleValueBuilder().build();
117+
const setSelectionSpy = jest.spyOn(selection, "setSelection");
118+
119+
selector.updateProps(buildProps({ items, values, targetValue: "v1", selection }));
120+
expect(selector.currentId).toBe(optionA.id);
121+
122+
selector.updateProps(buildProps({ items, values, targetValue: undefined, selection }));
123+
124+
expect(selector.currentId).toBeNull();
125+
expect(setSelectionSpy).toHaveBeenCalledWith(undefined);
126+
});
127+
128+
it("does not re-resolve currentId when targetAttribute.value is unchanged", () => {
129+
const selector = new DatabaseSingleSelectionSelector({ filterInputDebounceInterval: 200 });
130+
const selection = new SelectionSingleValueBuilder().build();
131+
132+
selector.updateProps(buildProps({ items, values, targetValue: "v1", selection }));
133+
const getAllSpy = jest.spyOn(selector.options, "getAll");
134+
135+
selector.updateProps(buildProps({ items, values, targetValue: "v1", selection }));
136+
137+
expect(getAllSpy).not.toHaveBeenCalled();
138+
expect(selector.currentId).toBe(optionA.id);
139+
});
140+
});

0 commit comments

Comments
 (0)