Skip to content

Commit

Permalink
DHFPROD-5799: added keyboard navigation to overview page, header, footer
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaker Yang authored and MarkLogic Builder committed Dec 17, 2020
1 parent e48f940 commit b4865e1
Show file tree
Hide file tree
Showing 17 changed files with 517 additions and 822 deletions.
3 changes: 3 additions & 0 deletions marklogic-data-hub-central/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ describe("login", () => {
toolbar.getModelToolbarIcon().trigger("mouseover").click();
cy.url().should("include", "/tiles/model");
tiles.getModelTile().should("exist");
cy.get(".userDropdown").trigger("mouseover");
cy.get("[aria-label=\"user-dropdown\"]").trigger("mousedown");
cy.waitUntil(() => cy.get("#logOut").should("be.visible")).click();

cy.loginAsTestUserWithRoles("hub-central-entity-model-reader")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ describe("Entity Modeling: Writer Role", () => {
propertyTable.getFacetIcon("nicknames").should("exist");
propertyTable.getSortIcon("nicknames").should("exist");

cy.get(".userDropdown").trigger("mouseover");
cy.get("[aria-label=\"user-dropdown\"]").trigger("mousedown");
cy.waitUntil(() => cy.get("#logOut").should("be.visible")).click();
confirmationModal.getNavigationWarnText().should("be.visible");
confirmationModal.getYesButton(ConfirmationType.NavigationWarn).click();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class ProjectInfo {

getAboutProject() {
return cy.get("#info-details");
return cy.get("#service-name");
}

getDownloadButton() {
Expand Down
11 changes: 2 additions & 9 deletions marklogic-data-hub-central/ui/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,9 @@ describe("App component", () => {
fireEvent.click(getByLabelText("tool-" + firstTool));
await expect(getByLabelText("icon-" + firstTool)).toBeInTheDocument();
expect(queryByText("overview")).not.toBeInTheDocument();
fireEvent.click(getByLabelText("header-logo"));
expect(getByLabelText("logo-link").href).toBe("https://www.marklogic.com/");
fireEvent.mouseDown(getByLabelText("title-link"));
expect(getByLabelText("overview")).toBeInTheDocument();

// After switching to non-default, click application name to return to overview
fireEvent.click(getByLabelText("tool-" + firstTool));
await expect(getByLabelText("icon-" + firstTool)).toBeInTheDocument();
expect(queryByText("overview")).not.toBeInTheDocument();
fireEvent.click(getByLabelText("header-title"));
expect(getByLabelText("overview")).toBeInTheDocument();

});

test("Session token stored in local storage", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ div.content {
.link {
cursor: pointer;
}
.link:focus {
outline: 2px solid #7F86B5;
outline-offset: -1px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from "react";
import {mount} from "enzyme";
import Footer from "./footer";

import userEvent from "@testing-library/user-event";

describe("Footer component", () => {
let wrapper;

Expand All @@ -17,4 +19,22 @@ describe("Footer component", () => {
const currentYear = (new Date()).getFullYear();
expect(wrapper.text()).toBe("© " + currentYear + " MarkLogic Corporation|Privacy");
});

it("can focus and goto on privacy link", () => {

global.window = {location: {href: null}};

// get the actual link wrapper by traversing DOM tree
let privacyLink = wrapper.childAt(0).childAt(0).childAt(0).childAt(0).childAt(2);
expect(privacyLink.text()).toBe("Privacy");

// can set link wrapper to focus
privacyLink.getDOMNode().focus();
expect(privacyLink.is(":focus")).toBe(true);

// can tab to link wrapper from another element
wrapper.getDOMNode().focus();
userEvent.tab();
expect(privacyLink.is(":focus")).toBe(true);
});
});
12 changes: 10 additions & 2 deletions marklogic-data-hub-central/ui/src/components/footer/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@ const Footer = (props) => {
const linkStyle = (props.pageTheme && props.pageTheme["footerLink"]) ? props.pageTheme["footerLink"] : null;
const currentYear = (new Date()).getFullYear();

let linkRef = React.createRef<HTMLAnchorElement>();
const linkWrapperKeyDown = (event) => {
if (event.key === "Enter") {
linkRef.current!.click();
}
};

return (
<Layout.Footer>
<div className={styles.content} style={footerStyle}>
<span>© {currentYear} MarkLogic Corporation</span>
|
<span className={styles.link}><a href="https://www.marklogic.com/privacy/" style={linkStyle}>Privacy</a></span>
<span tabIndex={0} onKeyDown={linkWrapperKeyDown} className={styles.link}>
<a tabIndex={-1} ref={linkRef} href="https://www.marklogic.com/privacy/" style={linkStyle}>Privacy</a>
</span>
</div>
</Layout.Footer>

);
};

Expand Down
118 changes: 83 additions & 35 deletions marklogic-data-hub-central/ui/src/components/header/header.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,57 @@ header.container {
cursor: pointer;
padding: 0px 20px 0px 20px;
span.logo {
width: 160px;
overflow: visible;
width: 160px;
overflow: visible;
}
a.logo:focus {
outline: 2px solid #7f86b5;
outline-offset: 1.5px;
}
.vertical{
display: inline-block;
position: relative;
top: 3px;
border-left: dotted 1px rgba(255, 255, 255, 0.65);
height: 24px;
margin: -6px 8px;
cursor: default;
}
.vertical{
display: inline-block;
position: relative;
top: 3px;
border-left: dotted 1px rgba(255, 255, 255, 0.65);
height: 24px;
margin: -6px 8px;
}
}
div.title {
div.titleContainer {
font-family: 'HelveticaNeue-Light', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 28px;
font-weight: 400;
float: left;
margin: 0 60px 0 150px;
text-align: right;
color: #fff;
cursor: pointer;
line-height: 64px;
height: 64px;
.title {
line-height: normal;
font-size: 28px;
font-weight: 400;
display: inline-block;
vertical-align: middle;
}
.title:focus {
outline: 2px solid #7f86b5;
outline-offset: 1.5px;
}
}
}
.serviceName {
font: 400 18px/51px 'HelveticaNeue-Light', 'Helvetica Neue', Helvetica, Arial, sans-serif;
text-decoration: none;
color: white;
}

.infoIcon {
font-size: 24px !important;
color: #b5b8bb;
padding-bottom: 15px;
line-height: 0px;
margin-right: 10px;
.infoIcon {
font-size: 24px !important;
color: #b5b8bb;
margin-right: 0px !important;
}
}

.userIcon {
padding-bottom: 15px;
.serviceName:focus {
outline: 2px solid #7f86b5;
outline-offset: 2px;
}

.infoTooltip {
Expand All @@ -61,7 +75,7 @@ li.menuLink {
}

.ant-menu-dark {
background-color: #2B333C;
background-color: #2B333C;
}
div.iconsContainerAuth {
position: absolute;
Expand All @@ -79,21 +93,55 @@ div.iconsContainerAuth {
height: 24px;
margin: 0 26px;
}
.helpIconContainer {
margin-right: 10px;
margin-left: 10px;
.helpIconLink {
.helpIcon {
color: rgba(255, 255, 255, 0.65);
margin: 0px;
}
.helpIcon:hover {
cursor: pointer;
color: #fff;
}
}
.helpIconLink:focus {
outline: 2px solid #7f86b5;
outline-offset: 2px;
}
}
.userDropDown {
margin: 0 30px 0 15px;
padding: 0 !important;
}
.userDropDown:focus {
outline: 2px solid #7f86b5;
outline-offset: 2px;
}
}

div.iconsContainer {
position: absolute;
top: 12px;
right: 0;
i:hover {
cursor: pointer;
color: #fff;
}
}

span.userDropdown {
i {
font-size: 24px;
.helpIconContainer {
margin-right: 10px;
.helpIconLink {
line-height: 0px;
.helpIcon {
color: rgba(255, 255, 255, 0.65);
margin: 0px;
}
.helpIcon:hover {
cursor: pointer;
color: #fff;
}
}
.helpIconLink:focus {
outline: 2px solid #7f86b5;
outline-offset: 2px;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import {render, cleanup, waitForElement} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {BrowserRouter as Router} from "react-router-dom";
import {UserContext} from "../../util/user-context";
import Header from "./header";
Expand Down Expand Up @@ -80,4 +81,58 @@ describe("Header component", () => {

});

test("verify tabbing and arrow key controls", async () => {
let i: number;

const {getByLabelText} = render(
<Router>
<UserContext.Provider value={userAuthenticated}>
<Header environment = {{...data.environment, dataHubVersion: "5.3-SNAPSHOT"}}/>
</UserContext.Provider>
</Router>
);

const element_logo = getByLabelText("logo-link");
const element_title = getByLabelText("title-link");
const element_service = getByLabelText("service-details");
const element_help = getByLabelText("help-link");
const element_user = getByLabelText("user-dropdown");

const header = [element_logo, element_title, element_service, element_help, element_user];

// verify element exists and can be focused
header.forEach((element, i) => {
expect(element).toBeInTheDocument();
element.focus();
expect(element).toHaveFocus();
});

element_logo.focus();

// verify elements tab in given order
for (i = 1; i < 5; ++i) {
userEvent.tab();
expect(header[i]).toHaveFocus();
}

// verify elements tab backwards in same order
for (i = 3; i >= 0; --i) {
userEvent.tab({shift: true});
expect(header[i]).toHaveFocus();
}

// verify right arrow key progresses focus in given order
element_logo.focus();
for (i = 0; i < 4; ++i) {
fireEvent.keyDown(header[i], {key: "ArrowRight", code: "ArrowRight"});
expect(header[i+1]).toHaveFocus();
}

// verify left arrow key reverses progression of elements in same order
for (i = 4; i > 0; --i) {
fireEvent.keyDown(header[i], {key: "ArrowLeft", code: "ArrowLeft"});
expect(header[i-1]).toHaveFocus();
}
});

});
Loading

0 comments on commit b4865e1

Please sign in to comment.