From 3a3032da05b442ba3d35840fd33d61945c138716 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Mon, 7 Mar 2022 13:33:42 -0600 Subject: [PATCH 01/14] add anchor prop option; create findElement service --- src/components/focus_trap/focus_trap.tsx | 15 +- src/components/tour/tour_step.tsx | 220 ++++++++++++++--------- src/components/tour/useEuiTour.tsx | 3 +- src/services/findElement.ts | 24 +++ src/services/index.ts | 2 + 5 files changed, 164 insertions(+), 100 deletions(-) create mode 100644 src/services/findElement.ts diff --git a/src/components/focus_trap/focus_trap.tsx b/src/components/focus_trap/focus_trap.tsx index 845e361e580..e635ceccca2 100644 --- a/src/components/focus_trap/focus_trap.tsx +++ b/src/components/focus_trap/focus_trap.tsx @@ -11,13 +11,9 @@ import { FocusOn } from 'react-focus-on'; import { ReactFocusOnProps } from 'react-focus-on/dist/es5/types'; import { CommonProps } from '../common'; +import { findElement, ElementTarget } from '../../services'; -/** - * A DOM node, a selector string (which will be passed to - * `document.querySelector()` to find the DOM node), or a function that - * returns a DOM node. - */ -export type FocusTarget = HTMLElement | string | (() => HTMLElement); +export type FocusTarget = ElementTarget; interface EuiFocusTrapInterface { /** @@ -70,12 +66,7 @@ export class EuiFocusTrap extends Component { // Programmatically sets focus on a nested DOM node; optional setInitialFocus = (initialFocus?: FocusTarget) => { - let node = initialFocus instanceof HTMLElement ? initialFocus : null; - if (typeof initialFocus === 'string') { - node = document.querySelector(initialFocus as string); - } else if (typeof initialFocus === 'function') { - node = (initialFocus as () => HTMLElement)(); - } + const node = findElement(initialFocus); if (!node) return; // `data-autofocus` is part of the 'react-focus-on' API node.setAttribute('data-autofocus', 'true'); diff --git a/src/components/tour/tour_step.tsx b/src/components/tour/tour_step.tsx index 24887e09ed9..8e0bbfbc2fd 100644 --- a/src/components/tour/tour_step.tsx +++ b/src/components/tour/tour_step.tsx @@ -11,10 +11,13 @@ import React, { FunctionComponent, ReactElement, ReactNode, + useEffect, + useRef, + useState, } from 'react'; import classNames from 'classnames'; -import { CommonProps, NoArgCallback } from '../common'; +import { CommonProps, ExclusiveUnion, NoArgCallback } from '../common'; import { EuiBeacon } from '../beacon'; import { EuiButtonEmpty, EuiButtonEmptyProps } from '../button'; @@ -25,88 +28,103 @@ import { EuiPopoverFooter, EuiPopoverProps, EuiPopoverTitle, + EuiWrappingPopover, } from '../popover'; import { EuiTitle } from '../title'; import { EuiTourStepIndicator, EuiTourStepStatus } from './tour_step_indicator'; -import { useGeneratedHtmlId } from '../../services'; +import { useGeneratedHtmlId, findElement, ElementTarget } from '../../services'; type PopoverOverrides = 'button' | 'closePopover'; type EuiPopoverPartials = Partial>; -export interface EuiTourStepProps - extends CommonProps, - Omit, - EuiPopoverPartials { - /** - * Element to which the tour step popover attaches when open - */ - children: ReactElement; - - /** - * Contents of the tour step popover - */ - content: ReactNode; - - /** - * Step will display if set to `true` - */ - isStepOpen?: boolean; - - /** - * Change the default min width of the popover panel - */ - minWidth?: CSSProperties['minWidth']; - - /** - * Change the default max width of the popover panel - */ - maxWidth?: CSSProperties['maxWidth']; - - /** - * Function to call for 'Skip tour' and 'End tour' actions - */ - onFinish: NoArgCallback; - - /** - * The number of the step within the parent tour. 1-based indexing. - */ - step: number; - - /** - * The total number of steps in the tour - */ - stepsTotal: number; - - /** - * Optional, standard DOM `style` attribute. Passed to the EuiPopover panel. - */ - style?: CSSProperties; - - /** - * Smaller title text that appears atop each step in the tour. The subtitle gets wrapped in the appropriate heading level. - */ - subtitle?: ReactNode; - - /** - * Larger title text specific to this step. The title gets wrapped in the appropriate heading level. - */ - title: ReactNode; - - /** - * Extra visual indication of step location - */ - decoration?: 'none' | 'beacon'; - - /** - * Element to replace the 'Skip tour' link in the footer - */ - footerAction?: ReactElement; -} +export type EuiTourStepAnchorProps = ExclusiveUnion< + { + /** + * Element to which the tour step popover attaches when open + */ + children: ReactElement; + /** + * Selector or reference to the element to which the tour step popover attaches when open + */ + anchor?: never; + }, + { + children?: never; + anchor: ElementTarget; + } +>; + +export type EuiTourStepProps = CommonProps & + Omit & + EuiPopoverPartials & + EuiTourStepAnchorProps & { + /** + * Contents of the tour step popover + */ + content: ReactNode; + + /** + * Step will display if set to `true` + */ + isStepOpen?: boolean; + + /** + * Change the default min width of the popover panel + */ + minWidth?: CSSProperties['minWidth']; + + /** + * Change the default max width of the popover panel + */ + maxWidth?: CSSProperties['maxWidth']; + + /** + * Function to call for 'Skip tour' and 'End tour' actions + */ + onFinish: NoArgCallback; + + /** + * The number of the step within the parent tour. 1-based indexing. + */ + step: number; + + /** + * The total number of steps in the tour + */ + stepsTotal: number; + + /** + * Optional, standard DOM `style` attribute. Passed to the EuiPopover panel. + */ + style?: CSSProperties; + + /** + * Smaller title text that appears atop each step in the tour. The subtitle gets wrapped in the appropriate heading level. + */ + subtitle?: ReactNode; + + /** + * Larger title text specific to this step. The title gets wrapped in the appropriate heading level. + */ + title: ReactNode; + + /** + * Extra visual indication of step location + */ + decoration?: 'none' | 'beacon'; + + /** + * Element to replace the 'Skip tour' link in the footer + */ + footerAction?: ReactElement; + }; export const EuiTourStep: FunctionComponent = ({ anchorPosition = 'leftUp', + anchor, + button, children, className, closePopover = () => {}, @@ -131,6 +149,21 @@ export const EuiTourStep: FunctionComponent = ({ ); } + const [willMount, setWillMount] = useState(false); + const node = useRef(null); + + useEffect(() => { + if (anchor) { + console.log(anchor); + window.requestAnimationFrame(() => { + node.current = findElement(anchor); + if (node) { + setWillMount(true); + } + }); + } + }, [anchor]); + const newStyle: CSSProperties = { ...style, maxWidth, minWidth }; const classes = classNames('euiTour', className); @@ -195,20 +228,21 @@ export const EuiTourStep: FunctionComponent = ({ const hasBeacon = decoration === 'beacon'; - return ( - } - {...rest} - > + const popoverProps = { + anchorPosition: anchorPosition, + closePopover: closePopover, + isOpen: isStepOpen, + ownFocus: false, + panelClassName: classes, + panelStyle: newStyle, + offset: hasBeacon ? 10 : 0, + 'aria-labelledby': titleId, + arrowChildren: hasBeacon && , + ...rest, + }; + + const layout = ( + <> {subtitle && ( @@ -221,6 +255,20 @@ export const EuiTourStep: FunctionComponent = ({
{content}
{footer} -
+ ); + + if (!anchor && children) { + return ( + + {layout} + + ); + } + + return willMount && node.current ? ( + + {layout} + + ) : null; }; diff --git a/src/components/tour/useEuiTour.tsx b/src/components/tour/useEuiTour.tsx index e7378dfe824..69a17e780de 100644 --- a/src/components/tour/useEuiTour.tsx +++ b/src/components/tour/useEuiTour.tsx @@ -11,8 +11,7 @@ import { assertNever } from '../common'; import { EuiTourStepProps } from './tour_step'; import { EuiTourAction, EuiTourActions, EuiTourState } from './types'; -export type EuiStatelessTourStep = Omit & - Partial; +export type EuiStatelessTourStep = EuiTourStepProps & Partial; export const useEuiTour = ( stepsArray: EuiStatelessTourStep[], diff --git a/src/services/findElement.ts b/src/services/findElement.ts new file mode 100644 index 00000000000..cac8cb70e18 --- /dev/null +++ b/src/services/findElement.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * A DOM node, a selector string (which will be passed to + * `document.querySelector()` to find the DOM node), or a function that + * returns a DOM node. + */ +export type ElementTarget = HTMLElement | string | (() => HTMLElement); + +export const findElement = (elementTarget?: ElementTarget) => { + let node = elementTarget instanceof HTMLElement ? elementTarget : null; + if (typeof elementTarget === 'string') { + node = document.querySelector(elementTarget as string); + } else if (typeof elementTarget === 'function') { + node = (elementTarget as () => HTMLElement)(); + } + return node; +}; diff --git a/src/services/index.ts b/src/services/index.ts index d37c6a45e77..351641139ba 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -76,6 +76,8 @@ export * from './console'; export { copyToClipboard } from './copy_to_clipboard'; +export * from './findElement'; + export { formatAuto, formatBoolean, From 02dc44ead98850c6711aaed4ad0a4bc773999fa2 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Mon, 7 Mar 2022 13:35:23 -0600 Subject: [PATCH 02/14] docs --- src-docs/src/views/tour/step_dom.js | 43 +++++++++++++++++++++++++ src-docs/src/views/tour/tour_example.js | 22 +++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src-docs/src/views/tour/step_dom.js diff --git a/src-docs/src/views/tour/step_dom.js b/src-docs/src/views/tour/step_dom.js new file mode 100644 index 00000000000..809ca7f8b9a --- /dev/null +++ b/src-docs/src/views/tour/step_dom.js @@ -0,0 +1,43 @@ +import React, { useRef, useState } from 'react'; + +import { + EuiButtonIcon, + EuiText, + EuiSpacer, + EuiTourStep, +} from '../../../../src/components'; + +export default () => { + const [isOpen, setIsOpen] = useState(true); + const anchorRef = useRef(); + return ( +
+ anchorRef.current} + anchor="#anchorTarget" + content={ + +

The tour step content.

+
+ } + isStepOpen={isOpen} + minWidth={300} + onFinish={() => setIsOpen(false)} + step={1} + stepsTotal={1} + title="Title of the current step" + subtitle="Title of the full tour (optional)" + anchorPosition="rightUp" + /> + setIsOpen(!isOpen)} + iconType="globe" + aria-label="Anchor" + buttonRef={anchorRef} + id="anchorTarget" + /> + + +
+ ); +}; diff --git a/src-docs/src/views/tour/tour_example.js b/src-docs/src/views/tour/tour_example.js index 5f75a683fd5..d0a904395a6 100644 --- a/src-docs/src/views/tour/tour_example.js +++ b/src-docs/src/views/tour/tour_example.js @@ -10,6 +10,7 @@ import { } from '../../../../src/components'; import Step from './step'; +import StepDom from './step_dom'; import Tour from './tour'; import Managed from './managed'; import ManagedHook from './managed_hook'; @@ -39,6 +40,7 @@ const stepSnippet = ` `; +const stepDomSource = require('!!raw-loader!./step_dom'); const tourSource = require('!!raw-loader!./tour'); const managedSource = require('!!raw-loader!./managed'); const managedHookSource = require('!!raw-loader!./managed_hook'); @@ -91,6 +93,26 @@ export const TourExample = { demo: , snippet: stepSnippet, }, + { + source: [ + { + type: GuideSectionTypes.JS, + code: stepDomSource, + }, + ], + text: ( + <> +

Using DOM selector as anchor location

+

+ Instead of wrapping the target element, use the{' '} + anchor prop to specify a DOM node. Accepted + values include an HTML element or function returning an HTML + element, or a DOM query selector. +

+ + ), + demo: , + }, { title: 'Standalone steps', source: [ From ed2970535cc10ca9343abeb49338515a9b93c51a Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Mon, 7 Mar 2022 13:44:04 -0600 Subject: [PATCH 03/14] clean up --- src/components/tour/tour_step.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/tour/tour_step.tsx b/src/components/tour/tour_step.tsx index 8e0bbfbc2fd..4a88cdea217 100644 --- a/src/components/tour/tour_step.tsx +++ b/src/components/tour/tour_step.tsx @@ -150,18 +150,23 @@ export const EuiTourStep: FunctionComponent = ({ } const [willMount, setWillMount] = useState(false); + const animationFrameId = useRef(); const node = useRef(null); useEffect(() => { if (anchor) { - console.log(anchor); - window.requestAnimationFrame(() => { + animationFrameId.current = window.requestAnimationFrame(() => { node.current = findElement(anchor); if (node) { setWillMount(true); } }); } + + return () => { + animationFrameId.current && + window.cancelAnimationFrame(animationFrameId.current); + }; }, [anchor]); const newStyle: CSSProperties = { ...style, maxWidth, minWidth }; From 161648da0874b363aa32b50a188128931d9ed0af Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Mon, 14 Mar 2022 10:33:02 -0500 Subject: [PATCH 04/14] CL --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d75c6b1491..a8058efc7fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [`main`](https://github.com/elastic/eui/tree/main) - Updated `testenv` mock for `EuiIcon` to render `aria-label` as text ([#5709](https://github.com/elastic/eui/pull/5709)) +- Added `anchor` prop to `EuiTourStep` to allow for DOM selector attachment ([#5696](https://github.com/elastic/eui/pull/5696)) **Breaking changes** From 44b7416a2a8e988202a99eee0ddb178b460de117 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Mon, 14 Mar 2022 11:22:43 -0500 Subject: [PATCH 05/14] add cypress test --- src/components/tour/tour_step.spec.tsx | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/components/tour/tour_step.spec.tsx diff --git a/src/components/tour/tour_step.spec.tsx b/src/components/tour/tour_step.spec.tsx new file mode 100644 index 00000000000..f635f804a84 --- /dev/null +++ b/src/components/tour/tour_step.spec.tsx @@ -0,0 +1,45 @@ +/* + * 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 { EuiTourStep } from './tour_step'; + +const steps = [ + { + step: 1, + content: 'You are here', + }, +]; + +const config = { + onFinish: () => {}, + stepsTotal: 1, + title: 'A demo', +}; + +describe('EuiTourStep', () => { + describe('with an `anchor` configuration', () => { + it('attaches to the anchor element', () => { + cy.realMount( + <> + Test + + + ); + + expect(cy.get('[data-test-subj="step"]').find('#anchor')).to.exist; + }); + }); +}); From 1c3b5571513fdd92a8fd318560d7aa5262ce629c Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Tue, 15 Mar 2022 16:17:33 -0500 Subject: [PATCH 06/14] naming updates --- src-docs/src/views/tour/step_dom.js | 3 +-- src-docs/src/views/tour/tour_example.js | 2 +- src/components/focus_trap/focus_trap.tsx | 4 ++-- src/components/tour/tour_step.tsx | 20 +++++++++++--------- src/services/findElement.ts | 2 +- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src-docs/src/views/tour/step_dom.js b/src-docs/src/views/tour/step_dom.js index 809ca7f8b9a..ac3257e899b 100644 --- a/src-docs/src/views/tour/step_dom.js +++ b/src-docs/src/views/tour/step_dom.js @@ -13,8 +13,7 @@ export default () => { return (
anchorRef.current} - anchor="#anchorTarget" + anchor="#anchorTarget" // Example of function ref: `anchor={() => anchorRef.current}` content={

The tour step content.

diff --git a/src-docs/src/views/tour/tour_example.js b/src-docs/src/views/tour/tour_example.js index a383467756e..c8f85436ae0 100644 --- a/src-docs/src/views/tour/tour_example.js +++ b/src-docs/src/views/tour/tour_example.js @@ -106,7 +106,7 @@ export const TourExample = {

Instead of wrapping the target element, use the{' '} anchor prop to specify a DOM node. Accepted - values include an HTML element or function returning an HTML + values include an HTML element, a function returning an HTML element, or a DOM query selector.

diff --git a/src/components/focus_trap/focus_trap.tsx b/src/components/focus_trap/focus_trap.tsx index e635ceccca2..fc67ba70f2b 100644 --- a/src/components/focus_trap/focus_trap.tsx +++ b/src/components/focus_trap/focus_trap.tsx @@ -11,7 +11,7 @@ import { FocusOn } from 'react-focus-on'; import { ReactFocusOnProps } from 'react-focus-on/dist/es5/types'; import { CommonProps } from '../common'; -import { findElement, ElementTarget } from '../../services'; +import { findElementBySelectorOrRef, ElementTarget } from '../../services'; export type FocusTarget = ElementTarget; @@ -66,7 +66,7 @@ export class EuiFocusTrap extends Component { // Programmatically sets focus on a nested DOM node; optional setInitialFocus = (initialFocus?: FocusTarget) => { - const node = findElement(initialFocus); + const node = findElementBySelectorOrRef(initialFocus); if (!node) return; // `data-autofocus` is part of the 'react-focus-on' API node.setAttribute('data-autofocus', 'true'); diff --git a/src/components/tour/tour_step.tsx b/src/components/tour/tour_step.tsx index 4a88cdea217..00e89dedd45 100644 --- a/src/components/tour/tour_step.tsx +++ b/src/components/tour/tour_step.tsx @@ -33,7 +33,11 @@ import { import { EuiTitle } from '../title'; import { EuiTourStepIndicator, EuiTourStepStatus } from './tour_step_indicator'; -import { useGeneratedHtmlId, findElement, ElementTarget } from '../../services'; +import { + useGeneratedHtmlId, + findElementBySelectorOrRef, + ElementTarget, +} from '../../services'; type PopoverOverrides = 'button' | 'closePopover'; @@ -149,17 +153,15 @@ export const EuiTourStep: FunctionComponent = ({ ); } - const [willMount, setWillMount] = useState(false); + const [hasValidAnchor, setHasValidAnchor] = useState(false); const animationFrameId = useRef(); - const node = useRef(null); + const anchorNode = useRef(null); useEffect(() => { if (anchor) { animationFrameId.current = window.requestAnimationFrame(() => { - node.current = findElement(anchor); - if (node) { - setWillMount(true); - } + anchorNode.current = findElementBySelectorOrRef(anchor); + setHasValidAnchor(anchorNode.current ? true : false); }); } @@ -271,8 +273,8 @@ export const EuiTourStep: FunctionComponent = ({ ); } - return willMount && node.current ? ( - + return hasValidAnchor && anchorNode.current ? ( + {layout} ) : null; diff --git a/src/services/findElement.ts b/src/services/findElement.ts index cac8cb70e18..202ef1411f9 100644 --- a/src/services/findElement.ts +++ b/src/services/findElement.ts @@ -13,7 +13,7 @@ */ export type ElementTarget = HTMLElement | string | (() => HTMLElement); -export const findElement = (elementTarget?: ElementTarget) => { +export const findElementBySelectorOrRef = (elementTarget?: ElementTarget) => { let node = elementTarget instanceof HTMLElement ? elementTarget : null; if (typeof elementTarget === 'string') { node = document.querySelector(elementTarget as string); From af95aac47a990bd5ec83c465459cee9a0967d1b2 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Tue, 15 Mar 2022 16:22:07 -0500 Subject: [PATCH 07/14] docs content --- src-docs/src/views/tour/step_dom.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src-docs/src/views/tour/step_dom.js b/src-docs/src/views/tour/step_dom.js index ac3257e899b..7a306e8465c 100644 --- a/src-docs/src/views/tour/step_dom.js +++ b/src-docs/src/views/tour/step_dom.js @@ -5,6 +5,7 @@ import { EuiText, EuiSpacer, EuiTourStep, + EuiCode, } from '../../../../src/components'; export default () => { @@ -16,7 +17,9 @@ export default () => { anchor="#anchorTarget" // Example of function ref: `anchor={() => anchorRef.current}` content={ -

The tour step content.

+

+ Popover is attached to the #anchor button +

} isStepOpen={isOpen} @@ -24,8 +27,7 @@ export default () => { onFinish={() => setIsOpen(false)} step={1} stepsTotal={1} - title="Title of the current step" - subtitle="Title of the full tour (optional)" + title="DOM selector as anchor location" anchorPosition="rightUp" /> Date: Tue, 15 Mar 2022 17:05:21 -0500 Subject: [PATCH 08/14] omit button prop --- src/components/tour/tour_step.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/tour/tour_step.tsx b/src/components/tour/tour_step.tsx index 00e89dedd45..6da963feea1 100644 --- a/src/components/tour/tour_step.tsx +++ b/src/components/tour/tour_step.tsx @@ -41,7 +41,7 @@ import { type PopoverOverrides = 'button' | 'closePopover'; -type EuiPopoverPartials = Partial>; +type EuiPopoverPartials = Partial>; export type EuiTourStepAnchorProps = ExclusiveUnion< { @@ -128,7 +128,6 @@ export type EuiTourStepProps = CommonProps & export const EuiTourStep: FunctionComponent = ({ anchorPosition = 'leftUp', anchor, - button, children, className, closePopover = () => {}, From 8647056c1c5b586fcd65c8d0db54557339006e47 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 16 Mar 2022 11:19:59 -0500 Subject: [PATCH 09/14] findElement tests --- src/services/findElement.test.tsx | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/services/findElement.test.tsx diff --git a/src/services/findElement.test.tsx b/src/services/findElement.test.tsx new file mode 100644 index 00000000000..7eb75731665 --- /dev/null +++ b/src/services/findElement.test.tsx @@ -0,0 +1,43 @@ +/* + * 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 { render } from 'enzyme'; +import { findElementBySelectorOrRef } from './findElement'; + +describe('findElementBySelectorOrRef', () => { + describe('when passed `undefined`', () => { + it('should return `null`', () => { + expect(findElementBySelectorOrRef(undefined)).toBe(null); + }); + }); + + describe('when passed an element', () => { + it('should return the element', () => { + render(
); + const element = document.querySelector('#id') as HTMLElement; + expect(findElementBySelectorOrRef(element)).toBe(element); + }); + }); + + describe('when passed a function', () => { + it('should return the result of the function', () => { + render(
); + const element = document.querySelector('#id') as HTMLElement; + expect(findElementBySelectorOrRef(() => element)).toBe(element); + }); + }); + + describe('when passed a DOM selector', () => { + it('should return the result of `querySelector`', () => { + render(
); + const element = document.querySelector('#id') as HTMLElement; + expect(findElementBySelectorOrRef('#element')).toBe(element); + }); + }); +}); From 68dd8e59df0f00d151b4b3b1ea1d64d1bc985d0f Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 16 Mar 2022 11:22:00 -0500 Subject: [PATCH 10/14] use ref --- src-docs/src/views/tour/step_dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-docs/src/views/tour/step_dom.js b/src-docs/src/views/tour/step_dom.js index 7a306e8465c..f564ad84f81 100644 --- a/src-docs/src/views/tour/step_dom.js +++ b/src-docs/src/views/tour/step_dom.js @@ -14,7 +14,7 @@ export default () => { return (
anchorRef.current}` + anchor={() => anchorRef.current} content={

From 6fc9d2d89a7a42578e255de45381815ae8ba3416 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 16 Mar 2022 11:45:42 -0500 Subject: [PATCH 11/14] second example --- src-docs/src/views/tour/step_dom.js | 39 ++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src-docs/src/views/tour/step_dom.js b/src-docs/src/views/tour/step_dom.js index f564ad84f81..7943637b679 100644 --- a/src-docs/src/views/tour/step_dom.js +++ b/src-docs/src/views/tour/step_dom.js @@ -9,7 +9,8 @@ import { } from '../../../../src/components'; export default () => { - const [isOpen, setIsOpen] = useState(true); + const [isOpenRef, setIsOpenRef] = useState(true); + const [isOpenSelector, setIsOpenSelector] = useState(true); const anchorRef = useRef(); return (

@@ -18,23 +19,49 @@ export default () => { content={

- Popover is attached to the #anchor button + Popover is attached to the anchorRef button

} - isStepOpen={isOpen} + isStepOpen={isOpenRef} minWidth={300} - onFinish={() => setIsOpen(false)} + onFinish={() => setIsOpenRef(false)} + step={1} + stepsTotal={1} + title="React ref as anchor location" + anchorPosition="rightDown" + /> + setIsOpenRef(!isOpenRef)} + iconType="globe" + aria-label="Anchor" + buttonRef={anchorRef} + /> + + + + + +

+ Popover is attached to the #anchorTarget button +

+ + } + isStepOpen={isOpenSelector} + minWidth={300} + onFinish={() => setIsOpenSelector(false)} step={1} stepsTotal={1} title="DOM selector as anchor location" anchorPosition="rightUp" /> setIsOpen(!isOpen)} + onClick={() => setIsOpenSelector(!isOpenSelector)} iconType="globe" aria-label="Anchor" - buttonRef={anchorRef} id="anchorTarget" /> From 03a75e19db0fcf73cf68f8893b3c3808396a38d0 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 16 Mar 2022 12:03:59 -0500 Subject: [PATCH 12/14] add jest test --- src/components/tour/tour_step.test.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/components/tour/tour_step.test.tsx b/src/components/tour/tour_step.test.tsx index ad24f2ff247..aa801580bd4 100644 --- a/src/components/tour/tour_step.test.tsx +++ b/src/components/tour/tour_step.test.tsx @@ -11,6 +11,7 @@ import { mount } from 'enzyme'; import { requiredProps } from '../../test/required_props'; import { EuiTourStep } from './tour_step'; +import { EuiWrappingPopover } from '../popover'; jest.mock('../portal', () => ({ EuiPortal: ({ children }: any) => children, @@ -119,4 +120,21 @@ describe('EuiTourStep', () => { expect(component.render()).toMatchSnapshot(); }); + + test('can use the `anchor` prop', () => { + const component = mount( +
+ Test + +
+ ); + + expect(component.find(EuiWrappingPopover)).toBeDefined; + }); }); From c53f34ce98f18b71ddf1f2da21287d80d71a3e4d Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 16 Mar 2022 15:19:27 -0500 Subject: [PATCH 13/14] clean up --- src/components/tour/tour_step.test.tsx | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/components/tour/tour_step.test.tsx b/src/components/tour/tour_step.test.tsx index aa801580bd4..ad24f2ff247 100644 --- a/src/components/tour/tour_step.test.tsx +++ b/src/components/tour/tour_step.test.tsx @@ -11,7 +11,6 @@ import { mount } from 'enzyme'; import { requiredProps } from '../../test/required_props'; import { EuiTourStep } from './tour_step'; -import { EuiWrappingPopover } from '../popover'; jest.mock('../portal', () => ({ EuiPortal: ({ children }: any) => children, @@ -120,21 +119,4 @@ describe('EuiTourStep', () => { expect(component.render()).toMatchSnapshot(); }); - - test('can use the `anchor` prop', () => { - const component = mount( -
- Test - -
- ); - - expect(component.find(EuiWrappingPopover)).toBeDefined; - }); }); From c46ee7f1fb713cd5369d06a679d4523e406b5b6a Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Thu, 17 Mar 2022 13:41:53 -0500 Subject: [PATCH 14/14] use straight jest --- src/services/findElement.test.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/services/findElement.test.tsx b/src/services/findElement.test.tsx index 7eb75731665..7cd844f947f 100644 --- a/src/services/findElement.test.tsx +++ b/src/services/findElement.test.tsx @@ -6,11 +6,13 @@ * Side Public License, v 1. */ -import React from 'react'; -import { render } from 'enzyme'; import { findElementBySelectorOrRef } from './findElement'; describe('findElementBySelectorOrRef', () => { + const element = document.createElement('div'); + element.setAttribute('id', 'element'); + document.body.appendChild(element); + describe('when passed `undefined`', () => { it('should return `null`', () => { expect(findElementBySelectorOrRef(undefined)).toBe(null); @@ -19,25 +21,23 @@ describe('findElementBySelectorOrRef', () => { describe('when passed an element', () => { it('should return the element', () => { - render(
); - const element = document.querySelector('#id') as HTMLElement; expect(findElementBySelectorOrRef(element)).toBe(element); }); }); describe('when passed a function', () => { it('should return the result of the function', () => { - render(
); - const element = document.querySelector('#id') as HTMLElement; expect(findElementBySelectorOrRef(() => element)).toBe(element); }); }); describe('when passed a DOM selector', () => { - it('should return the result of `querySelector`', () => { - render(
); - const element = document.querySelector('#id') as HTMLElement; + it('should return the result of `querySelector` if found', () => { expect(findElementBySelectorOrRef('#element')).toBe(element); }); + + it('should return `null` if not found', () => { + expect(findElementBySelectorOrRef('#doesnotexist')).toBe(null); + }); }); });