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
102 changes: 102 additions & 0 deletions packages/main/cypress/specs/Tokenizer.cy.tsx
Comment thread
ivoplashkov marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -1824,6 +1824,108 @@ describe("Clipboard Operations", () => {
// Only the selected token should be copied
cy.get("@clipboardWrite").should("have.been.calledOnceWith", "Selected");
});

it("should copy focused token text when no tokens are selected", () => {
cy.mount(
<Tokenizer>
<Token text="Focused"></Token>
<Token text="Other"></Token>
</Tokenizer>
);

// Tab into the tokenizer to focus the first token without selecting it
cy.realPress("Tab");
cy.get("[ui5-token]").eq(0).should("have.prop", "focused", true);
cy.get("[ui5-token]").eq(0).should("have.prop", "selected", false);

cy.window().then((win) => {
cy.stub(win.navigator.clipboard, "writeText").as("clipboardWrite");
Object.defineProperty(win, "isSecureContext", { value: true, writable: true });
});

cy.realPress(["Control", "c"]);

cy.get("@clipboardWrite").should("have.been.calledOnceWith", "Focused");
});

it("should not cut token in readonly mode", () => {
cy.mount(
<Tokenizer readonly={true}>
<Token text="ReadonlyToken"></Token>
<Token text="Other"></Token>
</Tokenizer>
);

cy.realPress("Tab");
cy.get("[ui5-token]").eq(0).should("have.prop", "focused", true);

cy.window().then((win) => {
cy.stub(win.navigator.clipboard, "writeText").as("clipboardWrite");
Object.defineProperty(win, "isSecureContext", { value: true, writable: true });
});

cy.realPress(["Control", "x"]);

// Should copy but not delete in readonly mode
cy.get("@clipboardWrite").should("have.been.calledOnceWith", "ReadonlyToken");
cy.get("[ui5-token]").should("have.length", 2);
});

it("should cut only selected tokens when there are both selected and focused tokens", () => {
cy.mount(
<Tokenizer onTokenDelete={onTokenDelete}>
<Token text="Selected1" selected></Token>
<Token text="Selected2" selected></Token>
<Token text="NotSelected"></Token>
</Tokenizer>
);

// Focus the third token via keyboard without selecting it
cy.realPress("Tab");
cy.realPress("ArrowRight");
cy.realPress("ArrowRight");
cy.get("[ui5-token]").eq(2).should("have.prop", "focused", true);
cy.get("[ui5-token]").eq(2).should("have.prop", "selected", false);
// First two remain selected
cy.get("[ui5-token]").eq(0).should("have.prop", "selected", true);
cy.get("[ui5-token]").eq(1).should("have.prop", "selected", true);

cy.window().then((win) => {
cy.stub(win.navigator.clipboard, "writeText").as("clipboardWrite");
Object.defineProperty(win, "isSecureContext", { value: true, writable: true });
});

cy.realPress(["Control", "x"]);

// Should cut the selected tokens, not the focused one
cy.get("@clipboardWrite").should("have.been.calledOnceWith", "Selected1\r\nSelected2");
cy.get("[ui5-token]").should("have.length", 1);
cy.get("[ui5-token]").eq(0).should("have.prop", "text", "NotSelected");
});

it("should cut focused token when no tokens are selected", () => {
cy.mount(
<Tokenizer onTokenDelete={onTokenDelete}>
<Token text="Focused"></Token>
<Token text="Other"></Token>
</Tokenizer>
);

cy.realPress("Tab");
cy.get("[ui5-token]").eq(0).should("have.prop", "focused", true);
cy.get("[ui5-token]").eq(0).should("have.prop", "selected", false);

cy.window().then((win) => {
cy.stub(win.navigator.clipboard, "writeText").as("clipboardWrite");
Object.defineProperty(win, "isSecureContext", { value: true, writable: true });
});

cy.realPress(["Control", "x"]);

cy.get("@clipboardWrite").should("have.been.calledOnceWith", "Focused");
cy.get("[ui5-token]").should("have.length", 1);
cy.get("[ui5-token]").eq(0).should("have.prop", "text", "Other");
});
});

describe("Tokenizer - getFocusDomRef Method", () => {
Expand Down
16 changes: 10 additions & 6 deletions packages/main/src/Tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -721,17 +721,21 @@ class Tokenizer extends UI5Element implements IFormInputElement {

const isCut = e.key.toLowerCase() === "x" || isDeleteShift(e);
const selectedTokens = this._tokens.filter(token => token.selected);
const focusedToken = selectedTokens.find(token => token.focused);
const focusedToken = this._tokens.find(token => token.focused);
let tokensToCopy = selectedTokens;
if (!tokensToCopy.length && focusedToken) {
tokensToCopy = [focusedToken];
}

if (isCut) {
const cutResult = this._fillClipboard(ClipboardDataOperation.cut, selectedTokens);
if (isCut && !this.readonly && tokensToCopy.length) {
const cutResult = this._fillClipboard(ClipboardDataOperation.cut, tokensToCopy);

focusedToken && this.deleteToken(focusedToken);
this.deleteToken(tokensToCopy[0]);

return cutResult;
}

return this._fillClipboard(ClipboardDataOperation.copy, selectedTokens);
return this._fillClipboard(ClipboardDataOperation.copy, tokensToCopy);
}

if (isCtrl && e.key.toLowerCase() === "i" && this._tokens.length > 0) {
Expand Down Expand Up @@ -1079,7 +1083,7 @@ class Tokenizer extends UI5Element implements IFormInputElement {
}

_fillClipboard(shortcutName: ClipboardDataOperation, tokens: Array<IToken>) {
const tokensTexts = tokens.filter(token => token.selected).map(token => token.text).join("\r\n");
const tokensTexts = tokens.map(token => token.text).join("\r\n");

// Async clipboard API (works in secure contexts - HTTPS/localhost)
if (navigator.clipboard?.writeText && window.isSecureContext) {
Expand Down
Loading