Skip to content

Commit bbe792b

Browse files
feat(ui5-multi-combobox): introduce selectedValues property (#12987)
1 parent 1b685b2 commit bbe792b

31 files changed

Lines changed: 562 additions & 39 deletions

packages/base/cypress/specs/UI5ElementPropsAndAttrs.cy.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ describe("Properties and attributes convert to each other", () => {
7878
.should("not.have.attr", "object-prop");
7979
});
8080

81-
it("Tests that array properties have no attributes", () => {
81+
it("Tests that array properties have attributes", () => {
8282
cy.mount(<Generic></Generic>);
8383

8484
cy.get("[ui5-test-generic]")
@@ -88,7 +88,7 @@ describe("Properties and attributes convert to each other", () => {
8888
.invoke("prop", "multiProp", ["a", "b"]);
8989

9090
cy.get("@testGeneric")
91-
.should("not.have.attr", "multi-prop");
91+
.should("have.attr", "multi-prop", '["a","b"]');
9292
});
9393

9494
it("Tests that noAttribute properties have no attributes", () => {

packages/base/src/UI5Element.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ const defaultConverter = {
8080
if (type === Number) {
8181
return value === null ? undefined : parseFloat(value);
8282
}
83+
84+
if (type === Object || type === Array) {
85+
try {
86+
return JSON.parse(value as string) as object | Array<unknown>;
87+
} catch {
88+
return value;
89+
}
90+
}
91+
8392
return value;
8493
},
8594
toAttribute(value: unknown, type: unknown) {
@@ -89,7 +98,7 @@ const defaultConverter = {
8998

9099
// don't set attributes for arrays and objects
91100
if (type === Object || type === Array) {
92-
return null;
101+
return JSON.stringify(value);
93102
}
94103

95104
// object, array, other

packages/base/src/UI5ElementMetadata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ class UI5ElementMetadata {
133133
*/
134134
hasAttribute(propName: string): boolean {
135135
const propData = this.getProperties()[propName];
136-
return propData.type !== Object && propData.type !== Array && !propData.noAttribute;
136+
return propData.type !== Object && !propData.noAttribute;
137137
}
138138

139139
/**

packages/compat/src/Table.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ class Table extends UI5Element {
359359
@property()
360360
accessibleNameRef?: string;
361361

362-
@property({ type: Array })
362+
@property({ type: Array, noAttribute: true })
363363
_hiddenColumns?: Array<TableColumnInfo>;
364364

365365
@property({ type: Boolean })

packages/compat/src/TableGroupRow.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class TableGroupRow extends UI5Element implements ITableRow {
5656
@property()
5757
mode: `${TableMode}` = "None";
5858

59-
@property({ type: Array })
59+
@property({ type: Array, noAttribute: true })
6060
_columnsInfo?: Array<TableColumnInfo>;
6161

6262
@property()

packages/compat/src/TableRow.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ class TableRow extends UI5Element implements ITableRow {
171171
@property({ type: Boolean })
172172
active = false;
173173

174-
@property({ type: Array })
174+
@property({ type: Array, noAttribute: true })
175175
_columnsInfo?: Array<TableColumnInfo>;
176176

177177
@property()

packages/fiori/src/FlexibleColumnLayout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ class FlexibleColumnLayout extends UI5Element {
308308
* @default undefined
309309
* @private
310310
*/
311-
@property({ type: Array })
311+
@property({ type: Array, noAttribute: true })
312312
_columnLayout?: FlexibleColumnLayoutColumnLayout;
313313

314314
/**

packages/fiori/src/Wizard.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ class Wizard extends UI5Element {
245245
* Stores references to the grouped steps.
246246
* @private
247247
*/
248-
@property({ type: Array })
248+
@property({ type: Array, noAttribute: true })
249249
_groupedTabs: Array<WizardTab> = [];
250250

251251
/**

packages/main/cypress/specs/MultiComboBox.cy.tsx

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,242 @@ describe("General", () => {
815815
.should("have.text", resourceBundle.getText(MULTIINPUT_SHOW_MORE_TOKENS.defaultText, 1));
816816
})
817817
});
818+
819+
it("preselects items based on selectedValues property", () => {
820+
cy.mount(
821+
<MultiComboBox style="width: 300px" selectedValues={["al", "en"]}>
822+
<MultiComboBoxItem text="Albania" value="al"></MultiComboBoxItem>
823+
<MultiComboBoxItem text="Denmark" value="dk"></MultiComboBoxItem>
824+
<MultiComboBoxItem text="England" value="en"></MultiComboBoxItem>
825+
</MultiComboBox>
826+
);
827+
828+
cy.get("ui5-multi-combobox")
829+
.should("have.attr", "selected-values",'["al","en"]');
830+
831+
cy.get("[ui5-mcb-item]")
832+
.eq(0)
833+
.should("be.selected");
834+
835+
cy.get("[ui5-mcb-item]")
836+
.eq(2)
837+
.should("be.selected");
838+
839+
cy.get("[ui5-multi-combobox]")
840+
.as("mcb")
841+
.shadow()
842+
.find("[ui5-tokenizer]")
843+
.as("tokenizer");
844+
845+
cy.get("@tokenizer")
846+
.find("[ui5-token]")
847+
.should("have.length", "2");
848+
});
849+
850+
it("updates selectedValues when a token is deleted", () => {
851+
cy.mount(
852+
<MultiComboBox style="width: 300px" selectedValues={["dk", "en"]}>
853+
<MultiComboBoxItem text="Albania" value="al"></MultiComboBoxItem>
854+
<MultiComboBoxItem text="Denmark" value="dk"></MultiComboBoxItem>
855+
<MultiComboBoxItem text="England" value="en"></MultiComboBoxItem>
856+
</MultiComboBox>
857+
);
858+
859+
cy.get("[ui5-mcb-item]")
860+
.eq(1)
861+
.should("be.selected");
862+
863+
cy.get("[ui5-mcb-item]")
864+
.eq(2)
865+
.should("be.selected");
866+
867+
cy.get("[ui5-multi-combobox]")
868+
.as("mcb")
869+
.shadow()
870+
.find("[ui5-tokenizer]")
871+
.as("tokenizer");
872+
873+
cy.get("@tokenizer")
874+
.find("[ui5-token]")
875+
.eq(1)
876+
.realClick();
877+
878+
cy.realPress("Backspace");
879+
880+
cy.get("@tokenizer")
881+
.find("[ui5-token]")
882+
.should("have.length", "1");
883+
884+
cy.get("[ui5-multi-combobox]")
885+
.should("have.attr", "selected-values", '["dk"]');
886+
});
887+
888+
it("updates selectedValues when selecting items via checkbox", () => {
889+
cy.mount(
890+
<MultiComboBox style="width: 300px">
891+
<MultiComboBoxItem text="Germany" value="DE"></MultiComboBoxItem>
892+
<MultiComboBoxItem text="France" value="FR"></MultiComboBoxItem>
893+
<MultiComboBoxItem text="Italy" value="IT"></MultiComboBoxItem>
894+
<MultiComboBoxItem text="United States" value="US"></MultiComboBoxItem>
895+
</MultiComboBox>
896+
);
897+
898+
cy.get("[ui5-multi-combobox]")
899+
.as("mcb")
900+
.should("have.attr", "selected-values", '[]');
901+
902+
// Open the dropdown
903+
cy.get("@mcb")
904+
.shadow()
905+
.find("[ui5-icon][name='slim-arrow-down']")
906+
.realClick();
907+
908+
// Select first item via checkbox
909+
cy.get("[ui5-mcb-item]")
910+
.eq(0)
911+
.shadow()
912+
.find("[ui5-checkbox]")
913+
.realClick();
914+
915+
cy.get("@mcb")
916+
.should("have.attr", "selected-values", '["DE"]');
917+
918+
// Select second item via checkbox
919+
cy.get("[ui5-mcb-item]")
920+
.eq(1)
921+
.shadow()
922+
.find("[ui5-checkbox]")
923+
.realClick();
924+
925+
cy.get("@mcb")
926+
.should("have.attr", "selected-values", '["DE","FR"]');
927+
928+
// Select third and fourth items
929+
cy.get("[ui5-mcb-item]")
930+
.eq(2)
931+
.shadow()
932+
.find("[ui5-checkbox]")
933+
.realClick();
934+
935+
cy.get("[ui5-mcb-item]")
936+
.eq(3)
937+
.shadow()
938+
.find("[ui5-checkbox]")
939+
.realClick();
940+
941+
cy.get("@mcb")
942+
.should("have.attr", "selected-values", '["DE","FR","IT","US"]');
943+
});
944+
945+
it("selects correct items when selectedValues is set before items are added", () => {
946+
// First mount with selectedValues but no items
947+
cy.mount(
948+
<MultiComboBox id="mcb-late-items" style="width: 300px" selectedValues={["FR", "US"]} />
949+
);
950+
951+
cy.get("[ui5-multi-combobox]")
952+
.as("mcb")
953+
.should("have.attr", "selected-values", '["FR","US"]');
954+
955+
// No tokens yet since no items
956+
cy.get("@mcb")
957+
.shadow()
958+
.find("[ui5-tokenizer]")
959+
.find("[ui5-token]")
960+
.should("have.length", 0);
961+
962+
// Now add items dynamically
963+
cy.get("@mcb").then($mcb => {
964+
const mcb = $mcb[0];
965+
966+
const items = [
967+
{ text: "Germany", value: "DE" },
968+
{ text: "France", value: "FR" },
969+
{ text: "Italy", value: "IT" },
970+
{ text: "United States", value: "US" },
971+
];
972+
973+
items.forEach(item => {
974+
const mcbItem = document.createElement("ui5-mcb-item");
975+
mcbItem.setAttribute("text", item.text);
976+
mcbItem.setAttribute("value", item.value);
977+
mcb.appendChild(mcbItem);
978+
});
979+
});
980+
981+
// Verify items with matching values are now selected
982+
cy.get("[ui5-mcb-item]")
983+
.eq(1) // France
984+
.should("have.attr", "selected");
985+
986+
cy.get("[ui5-mcb-item]")
987+
.eq(3) // United States
988+
.should("have.attr", "selected");
989+
990+
// Verify non-matching items are not selected
991+
cy.get("[ui5-mcb-item]")
992+
.eq(0) // Germany
993+
.should("not.have.attr", "selected");
994+
995+
cy.get("[ui5-mcb-item]")
996+
.eq(2) // Italy
997+
.should("not.have.attr", "selected");
998+
999+
// Verify tokens are created
1000+
cy.get("@mcb")
1001+
.shadow()
1002+
.find("[ui5-tokenizer]")
1003+
.find("[ui5-token]")
1004+
.should("have.length", 2);
1005+
});
1006+
1007+
it("updates selectedValues when selecting item via Enter key (typeahead)", () => {
1008+
cy.mount(
1009+
<MultiComboBox style="width: 300px">
1010+
<MultiComboBoxItem text="Germany" value="DE"></MultiComboBoxItem>
1011+
<MultiComboBoxItem text="France" value="FR"></MultiComboBoxItem>
1012+
<MultiComboBoxItem text="Canada" value="CA"></MultiComboBoxItem>
1013+
<MultiComboBoxItem text="Japan" value="JP"></MultiComboBoxItem>
1014+
</MultiComboBox>
1015+
);
1016+
1017+
cy.get("[ui5-multi-combobox]")
1018+
.as("mcb")
1019+
.should("have.attr", "selected-values", "[]");
1020+
1021+
// Type "Ca" to trigger typeahead for Canada
1022+
cy.get("@mcb")
1023+
.shadow()
1024+
.find("input")
1025+
.realClick()
1026+
.realType("Ca");
1027+
1028+
// Press Enter to select the autocompleted item
1029+
cy.realPress("Enter");
1030+
1031+
// Verify selectedValues is updated
1032+
cy.get("@mcb")
1033+
.should("have.attr", "selected-values", '["CA"]');
1034+
1035+
// Verify token is created
1036+
cy.get("@mcb")
1037+
.shadow()
1038+
.find("[ui5-tokenizer]")
1039+
.find("[ui5-token]")
1040+
.should("have.length", 1);
1041+
1042+
// Type "Ja" to select Japan
1043+
cy.get("@mcb")
1044+
.shadow()
1045+
.find("input")
1046+
.realType("Ja");
1047+
1048+
cy.realPress("Enter");
1049+
1050+
// Verify selectedValues now has both values
1051+
cy.get("@mcb")
1052+
.should("have.attr", "selected-values", '["CA","JP"]');
1053+
});
8181054
});
8191055

8201056
describe("MultiComboBox Truncated Tokens", () => {
@@ -2225,6 +2461,48 @@ describe("Event firing", () => {
22252461
cy.get("@valueStateChangeEvent")
22262462
.should("have.been.calledTwice");
22272463
});
2464+
2465+
it("fires selection-change and updates selectedValues on token deletion", () => {
2466+
const selectionChangeSpy = cy.stub().as("selectionChangeSpy");
2467+
cy.mount(
2468+
<MultiComboBox style="width: 300px" selectedValues={["1", "3"]} onSelectionChange={selectionChangeSpy}>
2469+
<MultiComboBoxItem text="Item 1" value="1"></MultiComboBoxItem>
2470+
<MultiComboBoxItem text="Item 1" value="2"></MultiComboBoxItem>
2471+
<MultiComboBoxItem text="Item 1" value="3"></MultiComboBoxItem>
2472+
</MultiComboBox>
2473+
);
2474+
2475+
cy.get("[ui5-multi-combobox]")
2476+
.as("mcb")
2477+
.shadow()
2478+
.find("[ui5-tokenizer]")
2479+
.as("tokenizer");
2480+
2481+
cy.get("@tokenizer")
2482+
.find("[ui5-token]")
2483+
.eq(0)
2484+
.realClick();
2485+
2486+
cy.realPress("ArrowRight");
2487+
cy.get("@tokenizer")
2488+
.find("[ui5-token]")
2489+
.eq(1)
2490+
.should("be.focused");
2491+
2492+
cy.realPress("Space");
2493+
cy.realPress("Backspace");
2494+
2495+
cy.get("@tokenizer")
2496+
.should("be.empty");
2497+
2498+
cy.get("@selectionChangeSpy")
2499+
.should("have.been.calledOnce");
2500+
cy.get("@selectionChangeSpy").should('have.been.calledWithMatch', Cypress.sinon.match(event => {
2501+
return event.detail.item === undefined;
2502+
}));
2503+
cy.get("[ui5-multi-combobox]")
2504+
.should("have.attr", "selected-values", '[]');
2505+
});
22282506
});
22292507

22302508
describe("MultiComboBox RTL/LTR Arrow Navigation", () => {

0 commit comments

Comments
 (0)