Skip to content

fix(ui5-tokenizer): sync popover list items with token text changes #11854

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 10, 2025
66 changes: 29 additions & 37 deletions packages/main/cypress/specs/MultiComboBox.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,10 @@ describe("MultiComboBox RTL/LTR Arrow Navigation", () => {
.find("[ui5-tokenizer]")
.find("[ui5-token]")
.last()
.should("be.visible")
.should("be.focused");
.as ("lastToken");

cy.get("@lastToken").should("be.visible");
cy.get("@lastToken").should("be.focused");
});

it("should focus last token on arrow left in LTR mode when input is at start", () => {
Expand Down Expand Up @@ -263,8 +265,10 @@ describe("MultiComboBox RTL/LTR Arrow Navigation", () => {
.find("[ui5-tokenizer]")
.find("[ui5-token]")
.last()
.should("be.visible")
.should("be.focused");
.as ("lastToken");

cy.get("@lastToken").should("be.visible");
cy.get("@lastToken").should("be.focused");
});

it("should not focus token when cursor is not at start of input in RTL mode", () => {
Expand All @@ -283,7 +287,6 @@ describe("MultiComboBox RTL/LTR Arrow Navigation", () => {
.realClick();

cy.get("@mcb").should("be.focused");


cy.get("@mcb")
.shadow()
Expand All @@ -297,23 +300,14 @@ describe("MultiComboBox RTL/LTR Arrow Navigation", () => {

cy.get("@mcb").realPress("ArrowRight");

cy.get("@mcb")
.shadow()
.find("input")
.as("input")
.realClick();

cy.get("@input")
.should("be.focused")
.should(($input) => {
expect(($input[0] as HTMLInputElement).selectionStart).to.equal(3);
});

cy.get("@mcb")
.shadow()
.find("[ui5-tokenizer]")
.find("[ui5-token]")
.should("not.be.focused");
.as ("lastToken");

cy.get("@lastToken").should("be.visible");
cy.get("@lastToken").should("not.be.focused");
});

it("should not focus token when text is selected in RTL mode", () => {
Expand Down Expand Up @@ -350,7 +344,10 @@ describe("MultiComboBox RTL/LTR Arrow Navigation", () => {
.shadow()
.find("[ui5-tokenizer]")
.find("[ui5-token]")
.should("not.have.focus");
.as ("lastToken");

cy.get("@lastToken").should("be.visible");
cy.get("@lastToken").should("not.be.focused");
});

it("should navigate from last token back to input with arrow left in RTL mode", () => {
Expand Down Expand Up @@ -378,17 +375,13 @@ describe("MultiComboBox RTL/LTR Arrow Navigation", () => {
.find("[ui5-tokenizer]")
.find("[ui5-token]")
.last()
.as("lastToken")
.should("have.focus");
.as("lastToken");

cy.get("@lastToken")
.should("be.focused")
.realPress("ArrowLeft");
cy.get("@lastToken").should("be.visible");
cy.get("@lastToken").should("be.focused");
cy.get("@lastToken").realPress("ArrowLeft");

cy.get("@mcb")
.shadow()
.find("input")
.should("be.focused");
cy.get("@mcb").should("be.focused");
});

it("should navigate from last token back to input with arrow right in LTR mode", () => {
Expand Down Expand Up @@ -416,15 +409,10 @@ describe("MultiComboBox RTL/LTR Arrow Navigation", () => {
.find("[ui5-tokenizer]")
.find("[ui5-token]")
.last()
.as("lastToken")
.should("be.focused");

cy.get("@lastToken").realPress("ArrowRight");
.realPress("ArrowRight");

cy.get("@mcb")
.shadow()
.find("input")
.should("be.focused");
cy.get("@mcb").should("be.visible");
cy.get("@mcb").should("be.focused");
});

it("should handle empty input case in RTL mode", () => {
Expand Down Expand Up @@ -464,7 +452,11 @@ describe("MultiComboBox RTL/LTR Arrow Navigation", () => {
.find("[ui5-tokenizer]")
.find("[ui5-token]")
.last()
.should("have.focus");
.as("lastToken");

cy.get("@lastToken").should("be.visible");
cy.get("@lastToken").should("be.focused");

});
});

Expand Down
96 changes: 96 additions & 0 deletions packages/main/cypress/specs/Tokenizer.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,99 @@ describe("Tokenizer - multi-line and Clear All", () => {
});
});

describe("Tokenizer - Popover List Item Text Updates", () => {
it("updates list item text in popover when token text changes", () => {
cy.mount(
<Tokenizer id="test-token-text-update" style={{ width: "100px" }}>
<Token text="Original Text" id="token-to-modify"></Token>
<Token text="Bulgaria"></Token>
<Token text="Canada"></Token>
<Token text="Denmark"></Token>
<Token text="Estonia"></Token>
<Token text="Finland"></Token>
<Token text="Germany"></Token>
</Tokenizer>
);

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find(".ui5-tokenizer-more-text")
.realClick();

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find("[ui5-responsive-popover]")
.should("be.visible");

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find("[ui5-responsive-popover] [ui5-list] [ui5-li]")
.eq(0)
.should("have.attr", "text", "Original Text");

cy.get("#token-to-modify").invoke("prop", "text", "Updated Text");

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find("[ui5-responsive-popover] [ui5-list] [ui5-li]")
.eq(0)
.should("have.attr", "text", "Updated Text");

cy.get("#test-token-text-update")
.shadow()
.find("[ui5-responsive-popover] [ui5-list] [ui5-li]")
.eq(0)
.should("not.have.attr", "text", "Original Text");
});

it("updates multiple list items when multiple token texts change", () => {
cy.mount(
<Tokenizer id="test-multiple-token-updates" style={{ width: "100px" }}>
<Token text="Token 1" id="token-1"></Token>
<Token text="Token 2" id="token-2"></Token>
<Token text="Token 3" id="token-3"></Token>
<Token text="Denmark"></Token>
<Token text="Estonia"></Token>
</Tokenizer>
);

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find(".ui5-tokenizer-more-text")
.realClick();

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find("[ui5-responsive-popover] [ui5-list] [ui5-li]")
.eq(0)
.should("have.attr", "text", "Token 1");

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find("[ui5-responsive-popover] [ui5-list] [ui5-li]")
.eq(1)
.should("have.attr", "text", "Token 2");

cy.get<Token>("[ui5-token]").eq(0).invoke("prop", "text", "Modified Token 1");
cy.get<Token>("[ui5-token]").eq(1).invoke("prop", "text", "Modified Token 2");

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find("[ui5-responsive-popover] [ui5-list] [ui5-li]")
.eq(0)
.should("have.attr", "text", "Modified Token 1");

cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find("[ui5-responsive-popover] [ui5-list] [ui5-li]")
.eq(1)
.should("have.attr", "text", "Modified Token 2");

// Verify unchanged token remains the same
cy.get<Tokenizer>("[ui5-tokenizer]")
.shadow()
.find("[ui5-responsive-popover] [ui5-list] [ui5-li]")
.eq(2)
.should("have.attr", "text", "Token 3");
});
});
4 changes: 4 additions & 0 deletions packages/main/src/Tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ class Tokenizer extends UI5Element {
type: HTMLElement,
"default": true,
individualSlots: true,
invalidateOnChildChange: {
properties: ["text"],
slots: false,
},
})
tokens!: Array<Token>;

Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/TokenizerPopoverTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function TokenizerPopoverTemplate(this: Tokenizer) {
onItemDelete={this.itemDelete}
>
{this._tokens
.map(token => <ListItemStandard key={String(token._id)} data-ui5-token-ref-id={token._id} wrappingType="Normal">{token.text}</ListItemStandard>)}
.map(token => <ListItemStandard key={String(token._id)} data-ui5-token-ref-id={token._id} wrappingType="Normal" text={token.text}></ListItemStandard>)}
</List>

{this._isPhone &&
Expand Down
Loading