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
4 changes: 2 additions & 2 deletions packages/base/cypress/specs/UI5ElementPropsAndAttrs.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe("Properties and attributes convert to each other", () => {
.should("not.have.attr", "object-prop");
});

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

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

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

it("Tests that noAttribute properties have no attributes", () => {
Expand Down
11 changes: 10 additions & 1 deletion packages/base/src/UI5Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ const defaultConverter = {
if (type === Number) {
return value === null ? undefined : parseFloat(value);
}

if (type === Object || type === Array) {
try {
return JSON.parse(value as string) as object | Array<unknown>;
} catch {
return value;
}
}

return value;
},
toAttribute(value: unknown, type: unknown) {
Expand All @@ -89,7 +98,7 @@ const defaultConverter = {

// don't set attributes for arrays and objects
if (type === Object || type === Array) {
return null;
return JSON.stringify(value);
}

// object, array, other
Expand Down
2 changes: 1 addition & 1 deletion packages/base/src/UI5ElementMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class UI5ElementMetadata {
*/
hasAttribute(propName: string): boolean {
const propData = this.getProperties()[propName];
return propData.type !== Object && propData.type !== Array && !propData.noAttribute;
return propData.type !== Object && !propData.noAttribute;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/compat/src/Table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ class Table extends UI5Element {
@property()
accessibleNameRef?: string;

@property({ type: Array })
@property({ type: Array, noAttribute: true })
_hiddenColumns?: Array<TableColumnInfo>;

@property({ type: Boolean })
Expand Down
2 changes: 1 addition & 1 deletion packages/compat/src/TableGroupRow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class TableGroupRow extends UI5Element implements ITableRow {
@property()
mode: `${TableMode}` = "None";

@property({ type: Array })
@property({ type: Array, noAttribute: true })
_columnsInfo?: Array<TableColumnInfo>;

@property()
Expand Down
2 changes: 1 addition & 1 deletion packages/compat/src/TableRow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class TableRow extends UI5Element implements ITableRow {
@property({ type: Boolean })
active = false;

@property({ type: Array })
@property({ type: Array, noAttribute: true })
_columnsInfo?: Array<TableColumnInfo>;

@property()
Expand Down
2 changes: 1 addition & 1 deletion packages/fiori/src/FlexibleColumnLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ class FlexibleColumnLayout extends UI5Element {
* @default undefined
* @private
*/
@property({ type: Array })
@property({ type: Array, noAttribute: true })
_columnLayout?: FlexibleColumnLayoutColumnLayout;

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/fiori/src/Wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ class Wizard extends UI5Element {
* Stores references to the grouped steps.
* @private
*/
@property({ type: Array })
@property({ type: Array, noAttribute: true })
_groupedTabs: Array<WizardTab> = [];

/**
Expand Down
278 changes: 278 additions & 0 deletions packages/main/cypress/specs/MultiComboBox.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,242 @@ describe("General", () => {
.should("have.text", resourceBundle.getText(MULTIINPUT_SHOW_MORE_TOKENS.defaultText, 1));
})
});

it("preselects items based on selectedValues property", () => {
cy.mount(
<MultiComboBox style="width: 300px" selectedValues={["al", "en"]}>
<MultiComboBoxItem text="Albania" value="al"></MultiComboBoxItem>
<MultiComboBoxItem text="Denmark" value="dk"></MultiComboBoxItem>
<MultiComboBoxItem text="England" value="en"></MultiComboBoxItem>
</MultiComboBox>
);

cy.get("ui5-multi-combobox")
.should("have.attr", "selected-values",'["al","en"]');

cy.get("[ui5-mcb-item]")
.eq(0)
.should("be.selected");

cy.get("[ui5-mcb-item]")
.eq(2)
.should("be.selected");

cy.get("[ui5-multi-combobox]")
.as("mcb")
.shadow()
.find("[ui5-tokenizer]")
.as("tokenizer");

cy.get("@tokenizer")
.find("[ui5-token]")
.should("have.length", "2");
});

it("updates selectedValues when a token is deleted", () => {
cy.mount(
<MultiComboBox style="width: 300px" selectedValues={["dk", "en"]}>
<MultiComboBoxItem text="Albania" value="al"></MultiComboBoxItem>
<MultiComboBoxItem text="Denmark" value="dk"></MultiComboBoxItem>
<MultiComboBoxItem text="England" value="en"></MultiComboBoxItem>
</MultiComboBox>
);

cy.get("[ui5-mcb-item]")
.eq(1)
.should("be.selected");

cy.get("[ui5-mcb-item]")
.eq(2)
.should("be.selected");

cy.get("[ui5-multi-combobox]")
.as("mcb")
.shadow()
.find("[ui5-tokenizer]")
.as("tokenizer");

cy.get("@tokenizer")
.find("[ui5-token]")
.eq(1)
.realClick();

cy.realPress("Backspace");

cy.get("@tokenizer")
.find("[ui5-token]")
.should("have.length", "1");

cy.get("[ui5-multi-combobox]")
.should("have.attr", "selected-values", '["dk"]');
});

it("updates selectedValues when selecting items via checkbox", () => {
cy.mount(
<MultiComboBox style="width: 300px">
<MultiComboBoxItem text="Germany" value="DE"></MultiComboBoxItem>
<MultiComboBoxItem text="France" value="FR"></MultiComboBoxItem>
<MultiComboBoxItem text="Italy" value="IT"></MultiComboBoxItem>
<MultiComboBoxItem text="United States" value="US"></MultiComboBoxItem>
</MultiComboBox>
);

cy.get("[ui5-multi-combobox]")
.as("mcb")
.should("have.attr", "selected-values", '[]');

// Open the dropdown
cy.get("@mcb")
.shadow()
.find("[ui5-icon][name='slim-arrow-down']")
.realClick();

// Select first item via checkbox
cy.get("[ui5-mcb-item]")
.eq(0)
.shadow()
.find("[ui5-checkbox]")
.realClick();

cy.get("@mcb")
.should("have.attr", "selected-values", '["DE"]');

// Select second item via checkbox
cy.get("[ui5-mcb-item]")
.eq(1)
.shadow()
.find("[ui5-checkbox]")
.realClick();

cy.get("@mcb")
.should("have.attr", "selected-values", '["DE","FR"]');

// Select third and fourth items
cy.get("[ui5-mcb-item]")
.eq(2)
.shadow()
.find("[ui5-checkbox]")
.realClick();

cy.get("[ui5-mcb-item]")
.eq(3)
.shadow()
.find("[ui5-checkbox]")
.realClick();

cy.get("@mcb")
.should("have.attr", "selected-values", '["DE","FR","IT","US"]');
});

it("selects correct items when selectedValues is set before items are added", () => {
// First mount with selectedValues but no items
cy.mount(
<MultiComboBox id="mcb-late-items" style="width: 300px" selectedValues={["FR", "US"]} />
);

cy.get("[ui5-multi-combobox]")
.as("mcb")
.should("have.attr", "selected-values", '["FR","US"]');

// No tokens yet since no items
cy.get("@mcb")
.shadow()
.find("[ui5-tokenizer]")
.find("[ui5-token]")
.should("have.length", 0);

// Now add items dynamically
cy.get("@mcb").then($mcb => {
const mcb = $mcb[0];

const items = [
{ text: "Germany", value: "DE" },
{ text: "France", value: "FR" },
{ text: "Italy", value: "IT" },
{ text: "United States", value: "US" },
];

items.forEach(item => {
const mcbItem = document.createElement("ui5-mcb-item");
mcbItem.setAttribute("text", item.text);
mcbItem.setAttribute("value", item.value);
mcb.appendChild(mcbItem);
});
});

// Verify items with matching values are now selected
cy.get("[ui5-mcb-item]")
.eq(1) // France
.should("have.attr", "selected");

cy.get("[ui5-mcb-item]")
.eq(3) // United States
.should("have.attr", "selected");

// Verify non-matching items are not selected
cy.get("[ui5-mcb-item]")
.eq(0) // Germany
.should("not.have.attr", "selected");

cy.get("[ui5-mcb-item]")
.eq(2) // Italy
.should("not.have.attr", "selected");

// Verify tokens are created
cy.get("@mcb")
.shadow()
.find("[ui5-tokenizer]")
.find("[ui5-token]")
.should("have.length", 2);
});

it("updates selectedValues when selecting item via Enter key (typeahead)", () => {
cy.mount(
<MultiComboBox style="width: 300px">
<MultiComboBoxItem text="Germany" value="DE"></MultiComboBoxItem>
<MultiComboBoxItem text="France" value="FR"></MultiComboBoxItem>
<MultiComboBoxItem text="Canada" value="CA"></MultiComboBoxItem>
<MultiComboBoxItem text="Japan" value="JP"></MultiComboBoxItem>
</MultiComboBox>
);

cy.get("[ui5-multi-combobox]")
.as("mcb")
.should("have.attr", "selected-values", "[]");

// Type "Ca" to trigger typeahead for Canada
cy.get("@mcb")
.shadow()
.find("input")
.realClick()
.realType("Ca");

// Press Enter to select the autocompleted item
cy.realPress("Enter");

// Verify selectedValues is updated
cy.get("@mcb")
.should("have.attr", "selected-values", '["CA"]');

// Verify token is created
cy.get("@mcb")
.shadow()
.find("[ui5-tokenizer]")
.find("[ui5-token]")
.should("have.length", 1);

// Type "Ja" to select Japan
cy.get("@mcb")
.shadow()
.find("input")
.realType("Ja");

cy.realPress("Enter");

// Verify selectedValues now has both values
cy.get("@mcb")
.should("have.attr", "selected-values", '["CA","JP"]');
});
});

describe("MultiComboBox Truncated Tokens", () => {
Expand Down Expand Up @@ -2225,6 +2461,48 @@ describe("Event firing", () => {
cy.get("@valueStateChangeEvent")
.should("have.been.calledTwice");
});

it("fires selection-change and updates selectedValues on token deletion", () => {
const selectionChangeSpy = cy.stub().as("selectionChangeSpy");
cy.mount(
<MultiComboBox style="width: 300px" selectedValues={["1", "3"]} onSelectionChange={selectionChangeSpy}>
<MultiComboBoxItem text="Item 1" value="1"></MultiComboBoxItem>
<MultiComboBoxItem text="Item 1" value="2"></MultiComboBoxItem>
<MultiComboBoxItem text="Item 1" value="3"></MultiComboBoxItem>
</MultiComboBox>
);

cy.get("[ui5-multi-combobox]")
.as("mcb")
.shadow()
.find("[ui5-tokenizer]")
.as("tokenizer");

cy.get("@tokenizer")
.find("[ui5-token]")
.eq(0)
.realClick();

cy.realPress("ArrowRight");
cy.get("@tokenizer")
.find("[ui5-token]")
.eq(1)
.should("be.focused");

cy.realPress("Space");
cy.realPress("Backspace");

cy.get("@tokenizer")
.should("be.empty");

cy.get("@selectionChangeSpy")
.should("have.been.calledOnce");
cy.get("@selectionChangeSpy").should('have.been.calledWithMatch', Cypress.sinon.match(event => {
return event.detail.item === undefined;
}));
cy.get("[ui5-multi-combobox]")
.should("have.attr", "selected-values", '[]');
});
});

describe("MultiComboBox RTL/LTR Arrow Navigation", () => {
Expand Down
Loading
Loading