Skip to content
Merged
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 1 addition & 6 deletions packages/eui/.storybook/loki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ export const LOKI_SELECTORS = {
/**
* Portal element content selector
*/
portal: '[data-euiportal="true"]',
/**
* Body selector
* TODO: remove when LOKI_SELECTORS.portal selector works as expected again
*/
body: 'body',
portal: '#storybook-root',
} as const;

/**
Expand Down
52 changes: 35 additions & 17 deletions packages/eui/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import React from 'react';
import type { Preview } from '@storybook/react';
import { useState, useMemo } from '@storybook/preview-api';
import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';

/*
Expand Down Expand Up @@ -57,24 +58,41 @@ import { hideStorybookControls } from './utils';
const preview: Preview = {
decorators: [
customJsxDecorator,
(Story, context) => (
<EuiProvider
colorMode={context.globals.colorMode}
{...(context.componentId === 'theming-euiprovider' && context.args)}
>
<div
/* #story-wrapper should always be the element that wraps <Story /> */
id="story-wrapper"
css={[
writingModeStyles.writingMode,
// @ts-ignore - we're manually ensuring `writingMode` globals match our Emotion style keys
writingModeStyles[context.globals.writingMode],
]}
(Story, context) => {
const [portalSibling, setPortalSibling] = useState<HTMLDivElement | null>(
null
);
const portalInsert = useMemo(() => {
if (portalSibling) {
return {
EuiPortal: {
insert: { sibling: portalSibling, position: 'after' as const },
},
};
}
}, [portalSibling]);

return (
<EuiProvider
colorMode={context.globals.colorMode}
componentDefaults={portalInsert}
{...(context.componentId === 'theming-euiprovider' && context.args)}
>
<Story />
</div>
</EuiProvider>
),
<div
ref={setPortalSibling}
/* #story-wrapper should always be the element that wraps <Story /> */
id="story-wrapper"
css={[
writingModeStyles.writingMode,
// @ts-ignore - we're manually ensuring `writingMode` globals match our Emotion style keys
writingModeStyles[context.globals.writingMode],
]}
>
{portalInsert && <Story />}
</div>
</EuiProvider>
);
},
],
globalTypes: {
colorMode: {
Expand Down
46 changes: 46 additions & 0 deletions packages/eui/.storybook/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 { queries, within, waitFor, fireEvent, expect } from '@storybook/test';
import * as dataTestSubjQueries from '../src/test/rtl/data_test_subj_queries';

/**
* Custom Storybook within util with EUI query helpers
* + additional chained async/waitFor component utils
*
* @see https://storybook.js.org/docs/writing-stories/play-function#writing-stories-with-the-play-function
* @see https://testing-library.com/docs/dom-testing-library/api-within/
*/
const customWithin = (canvasElement: HTMLElement) => {
const canvas = within<typeof queries & typeof dataTestSubjQueries>(
canvasElement,
{ ...queries, ...dataTestSubjQueries }
);

return {
...canvas,

/**
* 1. Loki doesn't like userEvent, only fireEvent
* 2. Storybook fires fireEvents too early (esp. on page load), so we add a waitFor
*/
waitForAndClick: async (testSubject: string) => {
Copy link
Contributor Author

@cee-chen cee-chen Sep 5, 2024

Choose a reason for hiding this comment

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

For the life of me I couldn't get the following components to work correctly with play()+Loki (they worked great in actual browsers/Storybook however):

  • EuiToolTip
  • EuiIconTip
  • EuiSelectableTemplateSitewide

I tried a variety of events (focus, mouseOver) as well as userEvent with no success. Posting this because I'm hoping someone else has some ideas that I don't 🫠

Copy link
Contributor

Choose a reason for hiding this comment

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

We had noticed that too last time we touched this Loki setup. 🙈
If I remember correctly, somehow for the visual regression it was not actually focussing the browser at the right time.

cc @tkajtoch

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sadly still couldn't get this working, so going to go ahead and merge this PR in as-is 🥲

await waitFor(() =>
expect(canvas.getByTestSubject(testSubject)).toBeInTheDocument()
);
await fireEvent.click(canvas.getByTestSubject(testSubject));
},

waitForEuiPopoverVisible: async () =>
await waitFor(() =>
expect(canvasElement.querySelector('[data-popover-open]')).toBeVisible()
),
};
};

export { customWithin as within };
3 changes: 3 additions & 0 deletions packages/eui/changelogs/upcoming/8003.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**Bug fixes**

- Fixed `EuiPopover` to correctly inherit from `EuiProvider`'s `componentDefaults.EuiPortal.insert`
4 changes: 2 additions & 2 deletions packages/eui/src/components/combo_box/combo_box.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export const Groups: Story = {
include: ['options'],
},
loki: {
chromeSelector: LOKI_SELECTORS.body,
chromeSelector: LOKI_SELECTORS.portal,
},
},
args: {
Expand Down Expand Up @@ -167,7 +167,7 @@ export const NestedOptionsGroups: Story = {
include: ['options'],
},
loki: {
chromeSelector: LOKI_SELECTORS.body,
chromeSelector: LOKI_SELECTORS.portal,
},
},
args: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@
*/

import type { Meta, StoryObj } from '@storybook/react';

import { fireEvent, waitFor } from '@storybook/test';
import { within } from '../../../../.storybook/test';
import { LOKI_SELECTORS } from '../../../../.storybook/loki';
import { enableFunctionToggleControls } from '../../../../.storybook/utils';
import { REFRESH_UNIT_OPTIONS } from '../types';

import { REFRESH_UNIT_OPTIONS } from '../types';
import { EuiAutoRefresh, EuiAutoRefreshProps } from './auto_refresh';

const meta: Meta<EuiAutoRefreshProps> = {
title: 'Forms/EuiAutoRefresh/EuiAutoRefresh',
component: EuiAutoRefresh,
parameters: {
loki: {
// TODO: uncomment once loki CLI is fixed for portal component stories
// chromeSelector: LOKI_SELECTORS.portal,
chromeSelector: LOKI_SELECTORS.portal,
},
},
argTypes: {
Expand All @@ -42,15 +43,13 @@ export default meta;
type Story = StoryObj<EuiAutoRefreshProps>;

export const Playground: Story = {
// TODO: uncomment once loki CLI is fixed for portal component stories
// play: lokiPlayDecorator(async (context) => {
// const { bodyElement, step } = context;
// const canvas = within(bodyElement);
// await step('show popover on click of the input', async () => {
// await userEvent.click(canvas.getByLabelText('Auto refresh'));
// await waitFor(() => {
// expect(canvas.getByRole('dialog')).toBeVisible();
// });
// });
// }),
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
await step('show popover on click', async () => {
await waitFor(async () => {
await fireEvent.click(canvas.getByLabelText('Auto refresh'));
});
await canvas.waitForEuiPopoverVisible();
});
},
};
21 changes: 14 additions & 7 deletions packages/eui/src/components/date_picker/date_picker.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import moment from 'moment';

import { fireEvent, waitFor } from '@storybook/test';
import { within } from '../../../.storybook/test';
import { LOKI_SELECTORS } from '../../../.storybook/loki';
import {
disableStorybookControls,
enableFunctionToggleControls,
} from '../../../.storybook/utils';
import { LOKI_SELECTORS } from '../../../.storybook/loki';

import {
EuiDatePicker,
EuiDatePickerProps,
Expand Down Expand Up @@ -119,6 +121,16 @@ const meta: Meta<EuiDatePickerProps> = {
selected: null,
utcOffset: undefined,
},
// Open the datepicker automatically for Loki VRT
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
await step('show popover on click', async () => {
await waitFor(async () => {
await fireEvent.click(canvas.getByRole('textbox'));
});
await canvas.waitForEuiPopoverVisible();
});
},
};
disableStorybookControls(meta, ['inputRef']);
enableFunctionToggleControls(meta, ['onClear', 'onChange']);
Expand All @@ -135,9 +147,6 @@ export const Playground: Story = {
},
},
args: {
// NOTE: loki play interactions won't work in CLI somehow
// TODO: exchange with loki play() interactions once fixed
autoFocus: true,
// setting a selected date to ensure VRT does not
// automatically updated based on the current date
selected: moment('Tue Mar 19 2024 18:54:51 GMT+0100'),
Expand All @@ -162,7 +171,6 @@ export const TimeSelect: Story = {
},
},
args: {
autoFocus: true, // Open the datepicker automatically for Loki VRT
showTimeSelect: true,
showTimeSelectOnly: false,
selected: moment('01/01/1970').hours(23).minutes(0),
Expand Down Expand Up @@ -191,7 +199,6 @@ export const RestrictedDaySelect: Story = {
},
},
args: {
autoFocus: true, // Open the datepicker automatically for Lok VRT,
selected: moment('01/02/1970'),
maxDate: moment('01/01/1970'),
minDate: moment('12/31/1969'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@

import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';

import { expect } from '@storybook/test';
import { within } from '../../../../.storybook/test';
import { LOKI_SELECTORS } from '../../../../.storybook/loki';
import { enableFunctionToggleControls } from '../../../../.storybook/utils';

import { EuiLink } from '../../link';
import { ApplyTime, REFRESH_UNIT_OPTIONS } from '../types';

Expand All @@ -21,12 +24,6 @@ import {
const meta: Meta<EuiSuperDatePickerProps> = {
title: 'Forms/EuiSuperDatePicker/EuiSuperDatePicker',
component: EuiSuperDatePicker,
parameters: {
loki: {
// TODO: uncomment once loki CLI is fixed for portal component stories
// chromeSelector: LOKI_SELECTORS.portal,
},
},
argTypes: {
refreshIntervalUnits: {
control: 'radio',
Expand Down Expand Up @@ -70,6 +67,9 @@ export const CustomQuickSelectPanel: Story = {
controls: {
include: ['customQuickSelectPanels', 'onTimeChange'],
},
loki: {
chromeSelector: LOKI_SELECTORS.portal,
},
},
args: {
customQuickSelectPanels: [
Expand All @@ -79,18 +79,14 @@ export const CustomQuickSelectPanel: Story = {
},
],
},
// TODO: uncomment once loki CLI is fixed for portal component stories
// play: lokiPlayDecorator(async (context) => {
// const { bodyElement, step } = context;
// const canvas = within(bodyElement);
// await step('show popover on click of the quick select button', async () => {
// await userEvent.click(canvas.getByLabelText('Date quick select'));
// await waitFor(() => {
// expect(canvas.getByRole('dialog')).toBeVisible();
// expect(canvas.getByText('Custom quick select panel')).toBeVisible();
// });
// });
// }),
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
await step('show popover on click of the quick select button', async () => {
canvas.waitForAndClick('superDatePickerToggleQuickMenuButton');
await canvas.waitForEuiPopoverVisible();
expect(canvas.getByText('Custom quick select panel')).toBeVisible();
});
},
};

function CustomPanel({ applyTime }: { applyTime?: ApplyTime }) {
Expand Down
6 changes: 3 additions & 3 deletions packages/eui/src/components/header/header.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const DarkThemeWithSitewideSearch: Story = {
parameters: {
layout: 'fullscreen',
controls: { include: ['theme'] },
loki: { chromeSelector: LOKI_SELECTORS.body }, // Required to capture the open popover
loki: { chromeSelector: LOKI_SELECTORS.portal }, // Required to capture the open popover
},
args: {
theme: 'dark',
Expand Down Expand Up @@ -155,8 +155,8 @@ export const DarkThemeWithSitewideSearch: Story = {
};

const MultipleFixedHeadersExample = () => {
const [fixedHeadersCount, setFixedHeadersCount] = useState(3); // eslint-disable-line react-hooks/rules-of-hooks
const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); // eslint-disable-line react-hooks/rules-of-hooks
const [fixedHeadersCount, setFixedHeadersCount] = useState(3);
const [isFlyoutOpen, setIsFlyoutOpen] = useState(false);

const sections = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { fireEvent, waitFor } from '@storybook/test';
import { within } from '../../../../.storybook/test';
import { LOKI_SELECTORS } from '../../../../.storybook/loki';

import { EuiHeader, EuiHeaderSection, EuiHeaderSectionItem } from '../';
Expand All @@ -22,11 +24,9 @@ const meta: Meta<EuiHeaderLinksProps> = {
// Component defaults
gutterSize: 's',
popoverBreakpoints: ['xs', 's'],
// VRT
popoverProps: { isOpen: true },
},
// Required to capture mobile popover
parameters: { loki: { chromeSelector: LOKI_SELECTORS.body } },
parameters: { loki: { chromeSelector: LOKI_SELECTORS.portal } },
};

export default meta;
Expand All @@ -46,4 +46,18 @@ export const Playground: Story = {
</EuiHeaderSection>
</EuiHeader>
),
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
await waitFor(() => canvas.getByLabelText('App menu')); // Wait for rendering to finish

step('Open mobile popover', async () => {
await waitFor(async () => {
const mobilePopover = canvas.queryByLabelText('Open menu');
if (mobilePopover) {
await fireEvent.click(mobilePopover);
await canvas.waitForEuiPopoverVisible();
}
});
});
},
};
Loading