Skip to content
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

[EuiContextMenuPanel] Convert flakey Jest focus/keyboard tests to Cypress #5261

Merged
merged 1 commit into from
Oct 14, 2021
Merged
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
131 changes: 131 additions & 0 deletions src/components/context_menu/context_menu_panel.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { mount } from '@cypress/react';

import { EuiContextMenuItem } from './context_menu_item';
import { EuiContextMenuPanel } from './context_menu_panel';

describe('EuiContextMenuPanel', () => {
describe('focus behavior', () => {
it('is set on the first focusable element by default if there are no items and hasFocus is true', () => {
mount(
<EuiContextMenuPanel>
<button data-test-subj="button">Hello world</button>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added Hello world text here to make the button focus more obvious in the Cypress browser real-time preview

</EuiContextMenuPanel>
);

cy.focused().should('have.attr', 'data-test-subj', 'button');
});

it('is not set on anything if hasFocus is false', () => {
mount(
<EuiContextMenuPanel hasFocus={false}>
<button data-test-subj="button">Hello world</button>
</EuiContextMenuPanel>
);

cy.focused().should('not.exist');
});
});

describe('keyboard navigation of items', () => {
const items = [
<EuiContextMenuItem key="A" data-test-subj="itemA">
Option A
</EuiContextMenuItem>,
<EuiContextMenuItem key="B" data-test-subj="itemB">
Option B
</EuiContextMenuItem>,
<EuiContextMenuItem key="C" data-test-subj="itemC">
Option C
</EuiContextMenuItem>,
];

describe('up/down keys', () => {
beforeEach(() => {
mount(<EuiContextMenuPanel items={items} />);
cy.wait(100); // Intermittent flake workaround: without this, the first downarrow key does not always focus into the menu items as expected
});

it('focuses the panel by default', () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FUN FACT: Did you know that if you blindly copy/paste Jest tests into Cypress and don't remove the async from it('focuses the panel by default', async () => {, you will get a bunch of indecipherable warnings and all your tests will pass on group run even when they shouldn't? 🤦‍♀️ ☠️

Posting this so others hopefully don't make the same mistake as me in the future lol

cy.focused().should('have.attr', 'class', 'euiContextMenuPanel');
});

it('down arrow key focuses the first menu item', () => {
cy.get('body').type('{downarrow}');

cy.focused().should('have.attr', 'data-test-subj', 'itemA');
});

it('subsequently, down arrow key focuses the next menu item', () => {
cy.get('body').type('{downarrow}');
cy.get('body').type('{downarrow}');

cy.focused().should('have.attr', 'data-test-subj', 'itemB');
});

it('up arrow key wraps to last menu item', () => {
cy.get('body').type('{uparrow}');

cy.focused().should('have.attr', 'data-test-subj', 'itemC');
});

it('down arrow key wraps to first menu item', () => {
cy.get('body').type('{uparrow}');
cy.get('body').type('{downarrow}');

cy.focused().should('have.attr', 'data-test-subj', 'itemA');
});

it('subsequently, up arrow key focuses the previous menu item', () => {
cy.get('body').type('{uparrow}');
cy.get('body').type('{uparrow}');

cy.focused().should('have.attr', 'data-test-subj', 'itemB');
});
});

describe('left/right arrow keys', () => {
it("right arrow key shows next panel with focused item's index", () => {
const showNextPanelHandler = cy.stub();
mount(
<EuiContextMenuPanel
items={items}
showNextPanel={showNextPanelHandler}
/>
);

cy.get('body')
.type('{downarrow}')
.type('{rightarrow}')
.then(() => {
expect(showNextPanelHandler).to.be.called;
});
});

it('left arrow key shows previous panel', () => {
const showPreviousPanelHandler = cy.stub();
mount(
<EuiContextMenuPanel
items={items}
showPreviousPanel={showPreviousPanelHandler}
/>
);

cy.get('body')
.type('{downarrow}')
.type('{leftarrow}')
.then(() => {
expect(showPreviousPanelHandler).to.be.called;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NB: expect gets called immediately where as cy. commands are async promises, so we have to put the stub expect in a then chain for the test to pass as expected

});
});
});
});
});
126 changes: 2 additions & 124 deletions src/components/context_menu/context_menu_panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import React from 'react';
import { render, mount, ReactWrapper } from 'enzyme';
import { render, mount } from 'enzyme';
import { findTestSubject, requiredProps } from '../../test';

import { EuiContextMenuPanel, SIZES } from './context_menu_panel';
Expand Down Expand Up @@ -279,129 +279,7 @@ describe('EuiContextMenuPanel', () => {
});
});

describe('behavior', () => {
describe('focus', () => {
it('is set on the first focusable element by default if there are no items and hasFocus is true', async () => {
const component = mount(
<EuiContextMenuPanel>
<button data-test-subj="button" />
</EuiContextMenuPanel>
);

await tick(20);

expect(findTestSubject(component, 'button').getDOMNode()).toBe(
document.activeElement
);
});

it('is not set on anything if hasFocus is false', () => {
const component = mount(
<EuiContextMenuPanel hasFocus={false}>
<button data-test-subj="button" />
</EuiContextMenuPanel>
);

expect(findTestSubject(component, 'button').getDOMNode()).not.toBe(
document.activeElement
);
});
});

describe('keyboard navigation of items', () => {
let component: ReactWrapper;
let showNextPanelHandler: jest.Mock;
let showPreviousPanelHandler: jest.Mock;

beforeEach(() => {
showNextPanelHandler = jest.fn();
showPreviousPanelHandler = jest.fn();

component = mount(
<EuiContextMenuPanel
items={items}
showNextPanel={showNextPanelHandler}
showPreviousPanel={showPreviousPanelHandler}
/>
);
});

it('focuses the panel by default', async () => {
await tick(20);

expect(component.getDOMNode()).toBe(document.activeElement);
});

it('down arrow key focuses the first menu item', async () => {
component.simulate('keydown', { key: keys.ARROW_DOWN });

await tick(20);
expect(findTestSubject(component, 'itemA').getDOMNode()).toBe(
document.activeElement
);
});

it('subsequently, down arrow key focuses the next menu item', async () => {
component.simulate('keydown', { key: keys.ARROW_DOWN });
component.simulate('keydown', { key: keys.ARROW_DOWN });

await tick(20);
expect(findTestSubject(component, 'itemB').getDOMNode()).toBe(
document.activeElement
);
});

it('down arrow key wraps to first menu item', async () => {
component.simulate('keydown', { key: keys.ARROW_UP });
component.simulate('keydown', { key: keys.ARROW_DOWN });

await tick(20);
expect(findTestSubject(component, 'itemA').getDOMNode()).toBe(
document.activeElement
);
});

it('up arrow key focuses the last menu item', async () => {
component.simulate('keydown', { key: keys.ARROW_UP });

await tick(20);
expect(findTestSubject(component, 'itemC').getDOMNode()).toBe(
document.activeElement
);
});

it('subsequently, up arrow key focuses the previous menu item', async () => {
component.simulate('keydown', { key: keys.ARROW_UP });
component.simulate('keydown', { key: keys.ARROW_UP });

await tick(20);
expect(findTestSubject(component, 'itemB').getDOMNode()).toBe(
document.activeElement
);
});

it('up arrow key wraps to last menu item', async () => {
component.simulate('keydown', { key: keys.ARROW_DOWN });
component.simulate('keydown', { key: keys.ARROW_UP });

await tick(20);
expect(findTestSubject(component, 'itemC').getDOMNode()).toBe(
document.activeElement
);
});
Comment on lines -383 to -391
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test felt redundant to me with lines 364-371 so I removed it in favor of just one test 🤷‍♀️


it("right arrow key shows next panel with focused item's index", () => {
component.simulate('keydown', { key: keys.ARROW_DOWN });
component.simulate('keydown', { key: keys.ARROW_RIGHT });
expect(showNextPanelHandler).toHaveBeenCalledWith(0);
});

it('left arrow key shows previous panel', () => {
component.simulate('keydown', { key: keys.ARROW_LEFT });
expect(showPreviousPanelHandler).toHaveBeenCalledTimes(1);
});
});
});
// @see Cypress context_menu_panel.spec.tsx for focus & keyboard nav testing

describe('updating items and content', () => {
describe('updates to items', () => {
Expand Down