From a8359978a2ad9599635e7788905c0d0064311077 Mon Sep 17 00:00:00 2001 From: 1Copenut Date: Tue, 10 Jan 2023 16:59:38 -0600 Subject: [PATCH 1/4] Added a11y specs for EuiNotificationEvent, EuiPageHeader, EuiPortal * Added a11y and keyboard specs for EuiNotificationEvent. * Added page header a11y tests. * Added a11y and keyboard specs for EuiPortal. --- .../notification/notification_event.a11y.tsx | 101 +++++++++ .../page/page_header/page_header.a11y.tsx | 37 ++++ src/components/portal/portal.a11y.tsx | 191 ++++++++++++++++++ 3 files changed, 329 insertions(+) create mode 100644 src/components/notification/notification_event.a11y.tsx create mode 100644 src/components/page/page_header/page_header.a11y.tsx create mode 100644 src/components/portal/portal.a11y.tsx diff --git a/src/components/notification/notification_event.a11y.tsx b/src/components/notification/notification_event.a11y.tsx new file mode 100644 index 000000000000..6d8ebe9d55dc --- /dev/null +++ b/src/components/notification/notification_event.a11y.tsx @@ -0,0 +1,101 @@ +/* + * 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, { useState } from 'react'; +import { EuiContextMenuItem } from '../context_menu'; +import { EuiNotificationEvent } from './notification_event'; +import { EuiPanel } from '../panel'; + +const NotificationEvent = () => { + const [isRead, setIsRead] = useState(false); + + const onRead = (id, isRead) => { + setIsRead(!isRead); + }; + + const onOpenContextMenu = (id) => { + return [ + onRead(id, isRead)} + > + {isRead ? 'Mark as unread' : 'Mark as read'} + , + + {}}> + View messages like this + , + + {}}> + Don’t notify me about this + , + ]; + }; + + return ( + + {}} + onClickTitle={() => {}} + /> + + ); +}; + +describe('EuiNotificationEvent', () => { + beforeEach(() => { + cy.viewport(1024, 768); // medium breakpoint + cy.realMount(); + cy.get('article.euiNotificationEvent').should('exist'); + }); + + describe('Automated accessibility check', () => { + it('has zero violations on first render', () => { + cy.checkAxe(); + }); + + it('has zero violations when popover is open', () => { + cy.get( + 'button[data-test-subj="cy-eui-notification-1-notificationEventMetaButton"]' + ).realClick(); + cy.get('div.euiPopover__panel').should('exist'); + cy.checkAxe(); + }); + }); + + describe('Keyboard accessibility', () => { + it('has zero violations when the popover is opened by keyboard', () => { + cy.repeatRealPress('Tab'); + cy.get( + 'button[data-test-subj="cy-eui-notification-1-notificationEventMetaButton"]' + ).should('have.focus'); + cy.realPress('Enter'); + cy.get('div.euiPopover__panel').should('exist'); + cy.checkAxe(); + cy.realPress('Escape'); + cy.get('div.euiPopover__panel').should('not.exist'); + cy.checkAxe(); + }); + }); +}); diff --git a/src/components/page/page_header/page_header.a11y.tsx b/src/components/page/page_header/page_header.a11y.tsx new file mode 100644 index 000000000000..320d9bcf4a09 --- /dev/null +++ b/src/components/page/page_header/page_header.a11y.tsx @@ -0,0 +1,37 @@ +/* + * 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 { EuiButton } from '../../button'; +import { EuiPageHeader } from './page_header'; + +describe('EuiPageHeader', () => { + beforeEach(() => { + cy.viewport(1024, 768); // medium breakpoint + cy.realMount( + Add something, + Do something, + ]} + /> + ); + cy.get('h1.euiTitle').should('exist'); + }); + + describe('Automated accessibility check', () => { + it('has zero violations on first render', () => { + cy.checkAxe(); + }); + }); +}); diff --git a/src/components/portal/portal.a11y.tsx b/src/components/portal/portal.a11y.tsx new file mode 100644 index 000000000000..2ebf0a9b050c --- /dev/null +++ b/src/components/portal/portal.a11y.tsx @@ -0,0 +1,191 @@ +/* + * 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, { useState } from 'react'; +import { EuiButton, EuiButtonIcon } from '../button'; +import { EuiEmptyPrompt } from '../empty_prompt'; +import { EuiFocusTrap } from '../focus_trap'; +import { EuiHorizontalRule } from '../horizontal_rule'; +import { EuiOverlayMask } from '../overlay_mask'; +import { EuiPanel } from '../panel'; +import { EuiPortal } from './portal'; +import { EuiSpacer } from '../spacer'; +import { EuiText, EuiTextColor } from '../text'; +import { EuiTitle } from '../title'; +import { euiCanAnimate } from '../../global_styling'; +import { euiFlyoutSlideInRight } from '../flyout'; +import { useEuiTheme } from '../../services'; +import { css } from '@emotion/react'; + +const Portal = () => { + const [isCustomFlyoutVisible, setIsCustomFlyoutVisible] = useState(false); + const euiThemeContext = useEuiTheme(); + const euiTheme = euiThemeContext.euiTheme; + + const toggleCustomFlyout = () => { + setIsCustomFlyoutVisible(!isCustomFlyoutVisible); + }; + + const closeCustomFlyout = () => { + setIsCustomFlyoutVisible(false); + }; + + let customFlyout; + + if (isCustomFlyoutVisible) { + customFlyout = ( + + + + +
+ {/* Flyout Header */} +
+ + +

Let's get started!

+
+ + + + +
+ + {/* Flyout Body */} +
+
+ +

+ Elastic Observability provides a unified view into the + health and performance of your entire digital ecosystem. + With easy ingest of multiple kinds of data via pre-built + collectors for hundreds of data sources. +

+ + + +
    +
  1. +

    Step 1

    +

    Select an ingestion method

    + + +
  2. +
  3. + +

    Step 2

    +

    Select an ingestion method

    +
    + + +
  4. +
  5. + +

    Step 3

    +

    Select an ingestion method

    +
    + + +
  6. +
+
+
+
+
+
+
+
+
+ ); + } + + return ( +
+ Observe my data} + titleSize="xs" + body={ +

+ Choose one of our many integrations to bring your data in, and start + visualizing it. +

+ } + actions={View guide} + /> + {customFlyout} +
+ ); +}; + +describe('EuiPortal', () => { + beforeEach(() => { + cy.viewport(1024, 768); // medium breakpoint + cy.realMount(); + cy.get('div[data-relative-to-header="above"]').should('not.exist'); + }); + + describe('Automated accessibility check', () => { + it('has zero violations on first render', () => { + cy.checkAxe(); + }); + + it('has zero violations after the portal is activated', () => { + cy.get('button[type="button"]').contains('View guide').realClick(); + cy.get('div[data-relative-to-header="above"]').should('exist'); + cy.checkAxe(); + }); + }); + + describe('Keyboard accessibility', () => { + it('has zero violations when the portal is opened by keyboard', () => { + cy.realPress('Tab'); + cy.get('button[type="button"]').should('have.focus'); + cy.realPress('Enter'); + cy.get('div[data-relative-to-header="above"]').should('exist'); + cy.get('button[aria-label="Close modal"]').should('have.focus'); + cy.checkAxe(); + cy.realPress('Enter'); + cy.get('div[data-relative-to-header="above"]').should('not.exist'); + cy.get('button[type="button"]').should('have.focus'); + cy.checkAxe(); + }); + }); +}); From 89212ffe446b44bb4b4bf9b680f0224a3e2e2c6c Mon Sep 17 00:00:00 2001 From: 1Copenut Date: Thu, 12 Jan 2023 16:17:23 -0600 Subject: [PATCH 2/4] Simplifying Portal test, adding breadcrumb and tabs to Page Header. --- .../page/page_header/page_header.a11y.tsx | 26 +++++++++++ src/components/portal/portal.a11y.tsx | 45 ------------------- 2 files changed, 26 insertions(+), 45 deletions(-) diff --git a/src/components/page/page_header/page_header.a11y.tsx b/src/components/page/page_header/page_header.a11y.tsx index 320d9bcf4a09..732334164cb5 100644 --- a/src/components/page/page_header/page_header.a11y.tsx +++ b/src/components/page/page_header/page_header.a11y.tsx @@ -20,6 +20,32 @@ describe('EuiPageHeader', () => { pageTitle="Page title" iconType="logoKibana" description="This description should be describing the current page as depicted by the page title. It will never extend beneath the right side content." + breadcrumbs={[ + { + text: 'Breadcrumb 1', + href: '#', + onClick: (e) => e.preventDefault(), + }, + { + text: 'Breadcrumb 2', + href: '#', + onClick: (e) => e.preventDefault(), + }, + { + text: 'Current', + href: '#', + onClick: (e) => e.preventDefault(), + }, + ]} + tabs={[ + { + label: 'Tab 1', + isSelected: true, + }, + { + label: 'Tab 2', + }, + ]} rightSideItems={[ Add something, Do something, diff --git a/src/components/portal/portal.a11y.tsx b/src/components/portal/portal.a11y.tsx index 2ebf0a9b050c..b166cb56f6c7 100644 --- a/src/components/portal/portal.a11y.tsx +++ b/src/components/portal/portal.a11y.tsx @@ -12,12 +12,10 @@ import React, { useState } from 'react'; import { EuiButton, EuiButtonIcon } from '../button'; import { EuiEmptyPrompt } from '../empty_prompt'; import { EuiFocusTrap } from '../focus_trap'; -import { EuiHorizontalRule } from '../horizontal_rule'; import { EuiOverlayMask } from '../overlay_mask'; import { EuiPanel } from '../panel'; import { EuiPortal } from './portal'; import { EuiSpacer } from '../spacer'; -import { EuiText, EuiTextColor } from '../text'; import { EuiTitle } from '../title'; import { euiCanAnimate } from '../../global_styling'; import { euiFlyoutSlideInRight } from '../flyout'; @@ -82,49 +80,6 @@ const Portal = () => { inset-inline-end: ${euiTheme.size.base}; `} /> - - - - - {/* Flyout Body */} -
-
- -

- Elastic Observability provides a unified view into the - health and performance of your entire digital ecosystem. - With easy ingest of multiple kinds of data via pre-built - collectors for hundreds of data sources. -

- - - -
    -
  1. -

    Step 1

    -

    Select an ingestion method

    - - -
  2. -
  3. - -

    Step 2

    -

    Select an ingestion method

    -
    - - -
  4. -
  5. - -

    Step 3

    -

    Select an ingestion method

    -
    - - -
  6. -
-
-
From 9abb1b7fe7bfd1b4ef2913679d2aaba2f5c3382b Mon Sep 17 00:00:00 2001 From: 1Copenut Date: Thu, 12 Jan 2023 17:23:02 -0600 Subject: [PATCH 3/4] Simplified two components, added a state check to EuiNotification. --- .../notification/notification_event.a11y.tsx | 9 ++ .../page/page_header/page_header.a11y.tsx | 26 ------ src/components/portal/portal.a11y.tsx | 85 ++----------------- 3 files changed, 17 insertions(+), 103 deletions(-) diff --git a/src/components/notification/notification_event.a11y.tsx b/src/components/notification/notification_event.a11y.tsx index 6d8ebe9d55dc..b39c18d9b11b 100644 --- a/src/components/notification/notification_event.a11y.tsx +++ b/src/components/notification/notification_event.a11y.tsx @@ -82,6 +82,15 @@ describe('EuiNotificationEvent', () => { cy.get('div.euiPopover__panel').should('exist'); cy.checkAxe(); }); + + it('has zero violations after the Mark as read button is clicked', () => { + cy.get( + 'button[data-test-subj="cy-eui-notification-1-notificationEventMetaButton"]' + ).realClick(); + cy.get('div.euiPopover__panel').should('exist'); + cy.get('div.euiPopover__panel button').first().realClick(); + cy.checkAxe(); + }); }); describe('Keyboard accessibility', () => { diff --git a/src/components/page/page_header/page_header.a11y.tsx b/src/components/page/page_header/page_header.a11y.tsx index 732334164cb5..320d9bcf4a09 100644 --- a/src/components/page/page_header/page_header.a11y.tsx +++ b/src/components/page/page_header/page_header.a11y.tsx @@ -20,32 +20,6 @@ describe('EuiPageHeader', () => { pageTitle="Page title" iconType="logoKibana" description="This description should be describing the current page as depicted by the page title. It will never extend beneath the right side content." - breadcrumbs={[ - { - text: 'Breadcrumb 1', - href: '#', - onClick: (e) => e.preventDefault(), - }, - { - text: 'Breadcrumb 2', - href: '#', - onClick: (e) => e.preventDefault(), - }, - { - text: 'Current', - href: '#', - onClick: (e) => e.preventDefault(), - }, - ]} - tabs={[ - { - label: 'Tab 1', - isSelected: true, - }, - { - label: 'Tab 2', - }, - ]} rightSideItems={[ Add something, Do something, diff --git a/src/components/portal/portal.a11y.tsx b/src/components/portal/portal.a11y.tsx index b166cb56f6c7..73e12e6ed890 100644 --- a/src/components/portal/portal.a11y.tsx +++ b/src/components/portal/portal.a11y.tsx @@ -9,23 +9,11 @@ /// import React, { useState } from 'react'; -import { EuiButton, EuiButtonIcon } from '../button'; -import { EuiEmptyPrompt } from '../empty_prompt'; -import { EuiFocusTrap } from '../focus_trap'; -import { EuiOverlayMask } from '../overlay_mask'; -import { EuiPanel } from '../panel'; +import { EuiButton } from '../button'; import { EuiPortal } from './portal'; -import { EuiSpacer } from '../spacer'; -import { EuiTitle } from '../title'; -import { euiCanAnimate } from '../../global_styling'; -import { euiFlyoutSlideInRight } from '../flyout'; -import { useEuiTheme } from '../../services'; -import { css } from '@emotion/react'; const Portal = () => { const [isCustomFlyoutVisible, setIsCustomFlyoutVisible] = useState(false); - const euiThemeContext = useEuiTheme(); - const euiTheme = euiThemeContext.euiTheme; const toggleCustomFlyout = () => { setIsCustomFlyoutVisible(!isCustomFlyoutVisible); @@ -40,71 +28,15 @@ const Portal = () => { if (isCustomFlyoutVisible) { customFlyout = ( - - - -
- {/* Flyout Header */} -
- - -

Let's get started!

-
- - -
-
-
-
-
+
This is the portal. Click anywhere to close.
+ Close portal
); } return (
- Observe my data} - titleSize="xs" - body={ -

- Choose one of our many integrations to bring your data in, and start - visualizing it. -

- } - actions={View guide} - /> + View guide {customFlyout}
); @@ -124,7 +56,7 @@ describe('EuiPortal', () => { it('has zero violations after the portal is activated', () => { cy.get('button[type="button"]').contains('View guide').realClick(); - cy.get('div[data-relative-to-header="above"]').should('exist'); + cy.get('div[data-euiportal="true"]').should('exist'); cy.checkAxe(); }); }); @@ -134,12 +66,11 @@ describe('EuiPortal', () => { cy.realPress('Tab'); cy.get('button[type="button"]').should('have.focus'); cy.realPress('Enter'); - cy.get('div[data-relative-to-header="above"]').should('exist'); - cy.get('button[aria-label="Close modal"]').should('have.focus'); + cy.get('div[data-euiportal="true"]').should('exist'); cy.checkAxe(); + cy.realPress('Tab'); cy.realPress('Enter'); - cy.get('div[data-relative-to-header="above"]').should('not.exist'); - cy.get('button[type="button"]').should('have.focus'); + cy.get('div[data-euiportal="true"]').should('not.exist'); cy.checkAxe(); }); }); From 41282491e2be6f6ad6853f7996f3595005889555 Mon Sep 17 00:00:00 2001 From: 1Copenut Date: Fri, 13 Jan 2023 16:28:04 -0600 Subject: [PATCH 4/4] Further reducing the EuiPortal specs to test just that one component. --- src/components/portal/portal.a11y.tsx | 36 ++++++++------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/src/components/portal/portal.a11y.tsx b/src/components/portal/portal.a11y.tsx index 73e12e6ed890..99c22cc77b2a 100644 --- a/src/components/portal/portal.a11y.tsx +++ b/src/components/portal/portal.a11y.tsx @@ -13,31 +13,31 @@ import { EuiButton } from '../button'; import { EuiPortal } from './portal'; const Portal = () => { - const [isCustomFlyoutVisible, setIsCustomFlyoutVisible] = useState(false); + const [isPortalVisible, setIsPortalVisible] = useState(false); - const toggleCustomFlyout = () => { - setIsCustomFlyoutVisible(!isCustomFlyoutVisible); + const togglePortal = () => { + setIsPortalVisible(!isPortalVisible); }; - const closeCustomFlyout = () => { - setIsCustomFlyoutVisible(false); + const closePortal = () => { + setIsPortalVisible(false); }; - let customFlyout; + let customPortal; - if (isCustomFlyoutVisible) { - customFlyout = ( + if (isPortalVisible) { + customPortal = (
This is the portal. Click anywhere to close.
- Close portal + Close portal
); } return (
- View guide - {customFlyout} + View guide + {customPortal}
); }; @@ -60,18 +60,4 @@ describe('EuiPortal', () => { cy.checkAxe(); }); }); - - describe('Keyboard accessibility', () => { - it('has zero violations when the portal is opened by keyboard', () => { - cy.realPress('Tab'); - cy.get('button[type="button"]').should('have.focus'); - cy.realPress('Enter'); - cy.get('div[data-euiportal="true"]').should('exist'); - cy.checkAxe(); - cy.realPress('Tab'); - cy.realPress('Enter'); - cy.get('div[data-euiportal="true"]').should('not.exist'); - cy.checkAxe(); - }); - }); });