Skip to content

Commit

Permalink
fix bug with Tab editing, enable Tabstrip tests
Browse files Browse the repository at this point in the history
  • Loading branch information
heswell committed Jun 21, 2024
1 parent 51b8ab9 commit 3f888ce
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 444 deletions.
1 change: 0 additions & 1 deletion vuu-ui/packages/vuu-popups/src/popup-menu/usePopupMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export const usePopupMenu = ({

const handleMenuClose = useCallback<PopupCloseCallback>(
(reason) => {
console.log("onClose");
setMenuOpen(false);
// If user has clicked the MenuButton whilst menu is open, we want to close it.
// The PopupService will close it for us as a 'click-away' event. We don't want
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
// TODO try and get TS path alias working to avoid relative paths like this
import { DefaultTabstrip } from "../../../../../../showcase/src/examples/UiControls/Tabstrip.examples";
import {
DefaultTabstrip,
TabstripEditableLabels,
TabstripRemoveTab,
} from "../../../../../../showcase/src/examples/UiControls/Tabstrip.examples";

const OVERFLOW_ITEMS = ".vuuOverflowContainer-wrapContainer > *";
const OVERFLOWED_ITEMS = ".vuuOverflowContainer-wrapContainer > .wrapped";
Expand All @@ -13,7 +17,7 @@ describe("WHEN initial size is sufficient to display all contents", () => {
cy.mount(<DefaultTabstrip width={500} />);
const tabstrip = cy.findByRole("tablist");
tabstrip.should("have.class", "vuuTabstrip");
// The overflow Inidcator will be present, but have zero width
// The overflow Indicator will be present, but have zero width
cy.get(".vuuOverflowContainer-wrapContainer > *")
.should("have.length", 6)
.filter(":visible")
Expand Down Expand Up @@ -241,147 +245,117 @@ describe("WHEN initial size is sufficient to display all contents", () => {
// });
// });

// describe("Editable Tabs", () => {
// describe("WHEN enableRenameTab is set", () => {
// it("THEN all tabs are editable", () => {
// cy.mount(<SimpleTabstrip enableRenameTab width={400} />);
// cy.get(".saltTabstrip-inner .saltEditableLabel").should("have.length", 5);
// });
// });

// describe("WHEN ENTER is pressed on tab selected via keyboard", () => {
// it("THEN tab enters edit state", () => {
// cy.mount(<SimpleTabstrip enableRenameTab width={400} />);
// cy.get(".saltTabstrip-inner > *:first-child").realClick();
// cy.wait(100); // ArrowRight need some time to move focus after click
// // Navigate to second tab ...
// cy.realPress("ArrowRight");
// // First press of ENTER selects ...
// cy.realPress("Enter");
// // Second press enters edit mode ...
// cy.realPress("Enter");
// cy.get(".saltTabstrip-inner > *:nth-child(2) .saltEditableLabel").should(
// "have.class",
// "saltEditableLabel-editing"
// );
// cy.get(
// ".saltTabstrip-inner > *:nth-child(2) .saltEditableLabel-input"
// ).should("be.focused");
// });
// });

// describe("WHEN ENTER is pressed on tab selected via click", () => {
// it("THEN tab label enters edit state", () => {
// cy.mount(<SimpleTabstrip enableRenameTab width={400} />);
// cy.get(".saltTabstrip-inner > *:first-child").realClick();
// cy.realPress("Enter");
// cy.get(".saltTabstrip-inner > *:first-child .saltEditableLabel").should(
// "have.class",
// "saltEditableLabel-editing"
// );
// cy.get(
// ".saltTabstrip-inner > *:first-child .saltEditableLabel-input"
// ).should("be.focused");
// });
// });
describe("Editable Tabs", () => {
describe("WHEN enableRenameTab is set", () => {
it("THEN all tabs are editable", () => {
cy.mount(<TabstripEditableLabels />);
cy.get(".vuuEditableLabel").should("have.length", 5);
});
});

// describe("WHEN editable tab is double clicked", () => {
// it("THEN tab label enters edit state", () => {
// cy.mount(<SimpleTabstrip enableRenameTab width={400} />);
// cy.get(".saltTabstrip-inner > *:first-child").dblclick();
// cy.get(".saltTabstrip-inner > *:first-child .saltEditableLabel").should(
// "have.class",
// "saltEditableLabel-editing"
// );
// cy.get(
// ".saltTabstrip-inner > *:first-child .saltEditableLabel-input"
// ).should("be.focused");
// });
// });
describe("WHEN ENTER is pressed on tab selected via keyboard", () => {
it("THEN tab enters edit state", () => {
cy.mount(<TabstripEditableLabels />);
cy.findByRole("tab", { name: "Home" }).realClick();
cy.realPress("ArrowRight");
// First press of ENTER selects ...
cy.realPress("Enter");
// // Second press enters edit mode ...
cy.realPress("Enter");
cy.findByRole("tab", { name: "Transactions" }).should(
"have.class",
"vuuTab-editing"
);
cy.findByRole("textbox").should("be.focused");
});
});

// describe("WHEN characters are typed during edit state", () => {
// it("THEN editable input value is updated", () => {
// cy.mount(<SimpleTabstrip enableRenameTab width={400} />);
// cy.get(".saltTabstrip-inner > *:first-child").realClick();
// cy.realPress("Enter");
// cy.realType("test");
// cy.get(
// ".saltTabstrip-inner > *:first-child .saltEditableLabel-input"
// ).should("have.attr", "value", "test");
// });
// });
// describe("WHEN ENTER is pressed after edit", () => {
// it("THEN edited value is applied to tab label", () => {
// cy.mount(<SimpleTabstrip enableRenameTab width={400} />);
// cy.get(".saltTabstrip-inner > *:first-child").realClick();
// cy.realPress("Enter");
// cy.realType("test");
// cy.realPress("Enter");
// cy.get(
// ".saltTabstrip-inner > *:first-child .saltEditableLabel-input"
// ).should("have.length", 0);
// cy.get(".saltTabstrip-inner > *:first-child .saltEditableLabel").should(
// "have.text",
// "test"
// );
// cy.get(".saltTabstrip-inner > *:first-child").should("be.focused");
// });
// });
// describe("WHEN ESC is pressed after edit", () => {
// it("THEN edited value is not applied to tab label", () => {
// cy.mount(<SimpleTabstrip enableRenameTab width={400} />);
// cy.get(".saltTabstrip-inner > *:first-child").realClick();
// cy.realPress("Enter");
// cy.realType("test");
// cy.realPress("Escape");
// cy.get(
// ".saltTabstrip-inner > *:first-child .saltEditableLabel-input"
// ).should("have.length", 0);
// cy.get(".saltTabstrip-inner > *:first-child .saltEditableLabel").should(
// "have.text",
// "Home"
// );
// cy.get(".saltTabstrip-inner > *:first-child").should("be.focused");
// });
// });
// });
describe("WHEN ENTER is pressed on tab selected via click", () => {
it("THEN tab label enters edit state", () => {
cy.mount(<TabstripEditableLabels />);
cy.findByRole("tab", { name: "Home" }).realClick();
cy.realPress("Enter");
cy.findByRole("tab", { name: "Home" }).should(
"have.class",
"vuuTab-editing"
);
cy.findByRole("textbox").should("be.focused");
});
});

// describe("Removing Tabs.", () => {
// describe("GIVEN a controlled Tabstrip.", () => {
// describe("WHEN enableCloseTab is set", () => {
// it("THEN all tabs are closeable", () => {
// cy.mount(<SimpleTabstripAddRemoveTab enableCloseTab width={600} />);
// cy.get(".saltTabstrip-inner .saltTab-closeButton").should(
// "have.length",
// 5
// );
// });
describe("WHEN characters are typed during edit state", () => {
it("THEN editable input value is updated", () => {
cy.mount(<TabstripEditableLabels />);
cy.findByRole("tab", { name: "Home" }).realClick();
cy.realPress("Enter");
cy.realType("test");
cy.findByRole("textbox").should("have.attr", "value", "test");
});
});
describe("WHEN ENTER is pressed after edit", () => {
it("THEN edited value is applied to tab label", () => {
cy.mount(<TabstripEditableLabels />);
cy.findByRole("tab", { name: "Home" }).realClick();
cy.realPress("Enter");
cy.realType("test");
cy.realPress("Enter");
cy.findByRole("tab", { name: "test" }).should(
"not.have.class",
"vuuTab-editing"
);
cy.findByRole("tab", { name: "test" }).should("be.focused");
});
});

// describe("WHEN close button is clicked", () => {
// it("THEN tab is closed", () => {
// cy.mount(<SimpleTabstripAddRemoveTab enableCloseTab width={600} />);
// cy.get(
// ".saltTabstrip-inner > *:first-child .saltTab-closeButton"
// ).realClick();
// cy.get(".saltTabstrip-inner > .saltTab").should("have.length", 4);
// });
// });
describe("WHEN ESC is pressed after edit", () => {
it("THEN edited value is not applied to tab label", () => {
cy.mount(<TabstripEditableLabels />);
cy.findByRole("tab", { name: "Home" }).realClick();
cy.realPress("Enter");
cy.realType("test");
cy.realPress("Escape");
cy.findByRole("tab", { name: "Home" }).should(
"not.have.class",
"vuuTab-editing"
);
cy.findByRole("tab", { name: "Home" }).should("be.focused");
});
});
});

// describe("WHEN backspace button is pressed and closeable tab has focus", () => {
// it("THEN tab is closed", () => {
// cy.mount(<SimpleTabstripAddRemoveTab enableCloseTab width={600} />);
// // Select the first tab ...
// cy.get(".saltTab").eq(0).realClick();
// // Close the selected tab
// cy.realPress("Backspace");
// cy.get(".saltTab").should("have.length", 4);
// // The (new) first tab should now be selected and focused
// cy.get(".saltTab").eq(0).should("have.ariaSelected");
// cy.get(".saltTab").eq(0).should("be.focused");
// });
// });
// });
// });
describe("Removing Tabs.", () => {
describe("GIVEN a controlled Tabstrip.", () => {
describe("WHEN enableCloseTab is set", () => {
it("THEN all tabs will have context menu", () => {
cy.mount(<TabstripRemoveTab />);
cy.get(".vuuTabMenu").should("have.length", 5);
});

describe("WHEN close menu item is clicked", () => {
it("THEN tab is closed", () => {
cy.mount(<TabstripRemoveTab />);
cy.findByRole("tab", { name: "Home" }).realClick();
cy.realPress("ArrowDown");
cy.realPress("Enter");
cy.findAllByRole("tab").should("have.length", 4);
});
});

// describe("WHEN backspace button is pressed and closeable tab has focus", () => {
// it("THEN tab is closed", () => {
// cy.mount(<TabstripRemoveTab />);
// // Select the first tab ...
// cy.get(".saltTab").eq(0).realClick();
// // Close the selected tab
// cy.realPress("Backspace");
// cy.get(".saltTab").should("have.length", 4);
// // The (new) first tab should now be selected and focused
// cy.get(".saltTab").eq(0).should("have.ariaSelected");
// cy.get(".saltTab").eq(0).should("be.focused");
// });
});
});
});

// describe("GIVEN a controlled Tabs component", () => {
// describe("WHEN enableCloseTab is set", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,20 @@ export const EditableLabel = forwardRef(function EditableLabel(
({ cancelEdit = false, allowDeactivation = false } = {}) => {
setEditing(false);
const originalValue = initialValue.current;
if (originalValue !== value) {
if (cancelEdit) {
if (cancelEdit || originalValue === value) {
if (originalValue !== value) {
setValue(originalValue);
} else {
initialValue.current = value;
}
onExitEditMode?.(
originalValue,
originalValue,
allowDeactivation,
cancelEdit
);
} else {
initialValue.current = value;
onExitEditMode?.(originalValue, value, allowDeactivation, cancelEdit);
}
onExitEditMode &&
onExitEditMode(originalValue, value, allowDeactivation, cancelEdit);
},
[onExitEditMode, setEditing, setValue, value]
);
Expand Down
1 change: 1 addition & 0 deletions vuu-ui/packages/vuu-ui-controls/src/tabstrip/Tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export const Tab = forwardRef(function Tab(
<div
{...props}
aria-controls={ariaControls}
aria-label={label}
aria-selected={selected}
className={cx(classBase, className, {
[`${classBase}-closeable`]: closeable,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useControlled } from "@salt-ds/core";
import {
dispatchMouseEvent,
getElementByDataIndex,
getFocusableElement,
orientationType,
} from "@finos/vuu-utils";
Expand Down Expand Up @@ -71,13 +72,11 @@ function nextItemIdx(count: number, direction: directionType, idx: number) {
}
}

const isNonWrappedElement = (element: HTMLElement | null) =>
element !== null && !element.classList.contains("wrapped");
const isEditing = (element: HTMLElement | null | undefined) =>
element != null && element.classList.contains("vuuTab-editing");

const getElementByPosition = (container: HTMLElement | null, index: number) =>
container
? (container.querySelector(`[data-index="${index}"]`) as HTMLElement)
: null;
const isNonWrappedElement = (element: HTMLElement | null | undefined) =>
element != null && !element.classList.contains("wrapped");

export interface ContainerNavigationProps {
onBlur: FocusEventHandler;
Expand Down Expand Up @@ -162,10 +161,14 @@ export const useKeyboardNavigation = ({
}

const setFocus = () => {
const element = getElementByPosition(containerRef.current, tabIndex);
if (element) {
const focussableElement = getFocusableElement(element);
focussableElement?.focus();
if (tabIndex !== -1) {
const element = getElementByDataIndex(containerRef.current, tabIndex);
if (element) {
const focusableElement = getFocusableElement(element);
if (!isEditing(focusableElement)) {
focusableElement?.focus();
}
}
}
};
if (immediateFocus) {
Expand Down Expand Up @@ -222,7 +225,7 @@ export const useKeyboardNavigation = ({
((nextDirection === "fwd" && nextIdx < indexCount) ||
(nextDirection === "bwd" && nextIdx > 0)) &&
!isNonWrappedElement(
getElementByPosition(containerRef.current, nextIdx)
getElementByDataIndex(containerRef.current, nextIdx)
)
) {
const newIdx = nextItemIdx(indexCount, nextDirection, nextIdx);
Expand Down Expand Up @@ -265,15 +268,15 @@ export const useKeyboardNavigation = ({
);

const highlightedTabHasMenu = useCallback(() => {
const el = getElementByPosition(containerRef.current, highlightedIdx);
const el = getElementByDataIndex(containerRef.current, highlightedIdx);
if (el) {
return el.querySelector(".vuuPopupMenu") != null;
}
return false;
}, [containerRef, highlightedIdx]);

const activateTabMenu = useCallback(() => {
const el = getElementByPosition(containerRef.current, highlightedIdx);
const el = getElementByDataIndex(containerRef.current, highlightedIdx);
const menuEl = el?.querySelector(".vuuPopupMenu") as HTMLElement;
if (menuEl) {
dispatchMouseEvent(menuEl, "click");
Expand Down
Loading

0 comments on commit 3f888ce

Please sign in to comment.