Skip to content

Commit

Permalink
[EuiImage] Fix fullscreen wrapper css + add targetSelector API to `…
Browse files Browse the repository at this point in the history
…shouldRenderCustomStyles`

- EuiImage renders multiple copies of an image wrapper on the page, so we need a specific way of targeting which DOM node to get the right Emotion classes from

+ convert remaining tests to RTL as cleanup/tech debt

+ remove skipped test entirely
  • Loading branch information
cee-chen committed Jun 30, 2023
1 parent e8c1018 commit 35224a8
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 98 deletions.
5 changes: 3 additions & 2 deletions src/components/image/__snapshots__/image.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ exports[`EuiImage props allowFullScreen 1`] = `

exports[`EuiImage props caption 1`] = `
<figure
aria-label="caption"
class="euiImageWrapper emotion-euiImageWrapper"
>
<img
Expand Down Expand Up @@ -172,7 +173,7 @@ exports[`EuiImage props size 1`] = `
class="euiImage testClass1 testClass2 emotion-euiImage-customSize-euiTestCss"
data-test-subj="test subject string"
src="/cat.jpg"
style="max-width:50px;max-height:50px"
style="max-width: 50px; max-height: 50px;"
/>
</figure>
`;
Expand Down Expand Up @@ -208,7 +209,7 @@ exports[`EuiImage props wrapperProps 1`] = `
class="euiImageWrapper testClass1 testClass2 emotion-euiImageWrapper-euiTestCss"
data-test-subj="test subject string"
src="/cat.jpg"
style="border:2px solid red"
style="border: 2px solid red;"
>
<img
alt="alt"
Expand Down
148 changes: 57 additions & 91 deletions src/components/image/image.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
*/

import React from 'react';
import { render, mount, ReactWrapper } from 'enzyme';
import { requiredProps as commonProps, findTestSubject } from '../../test';
import { act } from 'react-dom/test-utils';
import { keys } from '../../services';
import { fireEvent } from '@testing-library/dom';
import { render, screen } from '../../test/rtl';
import { requiredProps as commonProps } from '../../test';
import { shouldRenderCustomStyles } from '../../test/internal';
import { keys } from '../../services';

import { EuiImage } from './image';

Expand All @@ -25,85 +25,99 @@ describe('EuiImage', () => {
shouldRenderCustomStyles(<EuiImage {...requiredProps} />, {
childProps: ['wrapperProps'],
});
shouldRenderCustomStyles(<EuiImage allowFullScreen {...requiredProps} />, {
skipParentTest: true,
childProps: ['wrapperProps'],
targetSelector: '.euiImageFullScreenWrapper',
renderCallback: ({ getByTestSubject }) => {
fireEvent.click(getByTestSubject('activateFullScreenButton'));
},
});

test('is rendered', () => {
const component = render(<EuiImage {...requiredProps} />);
expect(component).toMatchSnapshot();
const { container } = render(<EuiImage {...requiredProps} />);
expect(container.firstChild).toMatchSnapshot();
});

describe('props', () => {
describe('src vs url', () => {
test('src', () => {
const component = render(<EuiImage alt="" src="/dog.jpg" />);
expect(component).toMatchSnapshot();
const { container } = render(<EuiImage alt="" src="/dog.jpg" />);
expect(container.firstChild).toMatchSnapshot();
});

test('url', () => {
const component = render(<EuiImage alt="" url="/dog.jpg" />);
expect(component).toMatchSnapshot();
const { container } = render(<EuiImage alt="" url="/dog.jpg" />);
expect(container.firstChild).toMatchSnapshot();
});

it('picks src over url when both are present (and throws a typescript error)', () => {
const component = render(
const { container } = render(
// @ts-expect-error - 'types of property url are incompatible'
<EuiImage alt="" url="/cat.jpg" src="/dog.jpg" />
);
expect(component.find('img').attr('src')).toEqual('/dog.jpg');
expect(container.querySelector('img')?.getAttribute('src')).toEqual(
'/dog.jpg'
);
});
});

test('float', () => {
const component = render(<EuiImage {...requiredProps} float="left" />);
expect(component).toMatchSnapshot();
const { container } = render(
<EuiImage {...requiredProps} float="left" />
);
expect(container.firstChild).toMatchSnapshot();
});

test('margin', () => {
const component = render(<EuiImage {...requiredProps} margin="l" />);
expect(component).toMatchSnapshot();
const { container } = render(<EuiImage {...requiredProps} margin="l" />);
expect(container.firstChild).toMatchSnapshot();
});

test('size', () => {
const component = render(<EuiImage {...requiredProps} size={50} />);
expect(component).toMatchSnapshot();
const { container } = render(<EuiImage {...requiredProps} size={50} />);
expect(container.firstChild).toMatchSnapshot();
});

test('caption', () => {
const component = render(
const { container } = render(
<EuiImage {...requiredProps} caption={<span>caption</span>} />
);
expect(component).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});

test('allowFullScreen', () => {
const component = render(<EuiImage {...requiredProps} allowFullScreen />);
expect(component).toMatchSnapshot();
const { container } = render(
<EuiImage {...requiredProps} allowFullScreen />
);
expect(container.firstChild).toMatchSnapshot();
});

test('fullScreenIconColor', () => {
const component = render(
const { container } = render(
<EuiImage
{...requiredProps}
allowFullScreen
fullScreenIconColor="dark"
/>
);
expect(component).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});

test('hasShadow', () => {
const component = render(
const { container } = render(
<EuiImage
{...requiredProps}
size="fullWidth"
allowFullScreen
hasShadow
/>
);
expect(component).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});

test('wrapperProps', () => {
const component = render(
const { container } = render(
<EuiImage
alt="alt"
caption={<span>caption</span>}
Expand All @@ -114,103 +128,55 @@ describe('EuiImage', () => {
}}
/>
);
expect(component).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});
});

describe('fullscreen behaviour', () => {
let component: ReactWrapper;

beforeAll(() => {
component = mount(
beforeEach(() => {
const { getByTestSubject } = render(
<EuiImage
{...requiredProps}
wrapperProps={requiredProps}
alt="example alt text"
allowFullScreen
/>
);
});

beforeEach(() => {
findTestSubject(component, 'activateFullScreenButton').simulate('click');
fireEvent.click(getByTestSubject('activateFullScreenButton'));
});

test('fullscreen image is rendered', () => {
const overlayMask = document.querySelectorAll(
'[data-test-subj=fullScreenOverlayMask]'
);
expect(overlayMask.length).toBe(1);

const fullScreenImage = overlayMask[0].querySelectorAll('figure img');
expect(fullScreenImage.length).toBe(1);
const overlayMask = screen.getByTestSubject('fullScreenOverlayMask');
const fullScreenImage = overlayMask.querySelector('figure img');
expect(fullScreenImage).not.toBeNull();
});

test('close using close icon', () => {
const deactivateFullScreenBtn = document.querySelectorAll(
'[data-test-subj=deactivateFullScreenButton]'
const deactivateFullScreenBtn = screen.getByTestSubject(
'deactivateFullScreenButton'
);
expect(deactivateFullScreenBtn.length).toBe(1);

act(() => {
(deactivateFullScreenBtn[0] as HTMLElement).click();
});
fireEvent.click(deactivateFullScreenBtn);

const overlayMask = document.querySelectorAll(
'[data-test-subj=fullScreenOverlayMask]'
);
expect(overlayMask.length).toBe(0);
expect(screen.queryByTestSubject('fullScreenOverlayMask')).toBeNull();
});

test('close using ESCAPE key', () => {
const deactivateFullScreenBtn = document.querySelector<HTMLElement>(
'[data-test-subj=deactivateFullScreenButton]'
const deactivateFullScreenBtn = screen.getByTestSubject(
'deactivateFullScreenButton'
);
expect(deactivateFullScreenBtn).toBeTruthy();

// Ignores non-escape keys
act(() => {
const escapeKeydownEvent = new KeyboardEvent('keydown', {
key: keys.TAB,
bubbles: true,
});
deactivateFullScreenBtn!.dispatchEvent(escapeKeydownEvent);
});
expect(deactivateFullScreenBtn).toBeTruthy();
fireEvent.keyDown(deactivateFullScreenBtn, { key: keys.TAB });

// Removes full screen overlay on escape key
act(() => {
const escapeKeydownEvent = new KeyboardEvent('keydown', {
key: keys.ESCAPE,
bubbles: true,
});
deactivateFullScreenBtn!.dispatchEvent(escapeKeydownEvent);
});
fireEvent.keyDown(deactivateFullScreenBtn, { key: keys.ESCAPE });

const overlayMask = document.querySelector(
'[data-test-subj=fullScreenOverlayMask]'
);
expect(overlayMask).toBeFalsy();
expect(screen.queryByTestSubject('fullScreenOverlayMask')).toBeNull();
});

// Clicking the mask to close is now handled by EuiFocusTrap
// and we can't use Enzyme to test this behavior.
// A Cypress test exists in the EuiFocusTrap suite that
// sufficiently covers this behavior
test.skip('close using overlay mask', () => {
let overlayMask = document.querySelectorAll(
'[data-test-subj=fullScreenOverlayMask]'
);
expect(overlayMask.length).toBe(1);

act(() => {
(overlayMask[0] as HTMLElement).click();
});

overlayMask = document.querySelectorAll(
'[data-test-subj=fullScreenOverlayMask]'
);
expect(overlayMask.length).toBe(0);
});
});
});
4 changes: 2 additions & 2 deletions src/components/image/image_fullscreen_wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const EuiImageFullScreenWrapper: FunctionComponent<

const styles = euiImageFullscreenWrapperStyles(euiTheme);

const cssStyles = [styles.euiImageFullscreenWrapper];
const cssStyles = [styles.euiImageFullscreenWrapper, wrapperProps?.css];

const classes = classNames(
'euiImageFullScreenWrapper',
Expand Down Expand Up @@ -78,9 +78,9 @@ export const EuiImageFullScreenWrapper: FunctionComponent<
<>
<figure
aria-label={optionalCaptionText}
css={cssStyles}
{...wrapperProps}
className={classes}
css={cssStyles}
>
<EuiImageButton
hasAlt={!!alt}
Expand Down
19 changes: 16 additions & 3 deletions src/test/internal/render_custom_styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ type ShouldRenderCustomStylesOptions = {
* e.g., `iconProps`, `wrapperProps`, `labelProps`, etc.
*/
childProps?: string[];
/**
* If a more specific selector needs to be passed for any reason,
* e.g. if there are multiple copies of the element on the page
*/
targetSelector?: string;
/**
* Used for components that do not allow custom inline styles, or
* components where `style` isn't on the same DOM node as `className`
Expand Down Expand Up @@ -102,8 +107,14 @@ export const shouldRenderCustomStyles = (

const assertOutputStyles = (rendered: HTMLElement, euiCss: string = '') => {
// className
const componentNode = rendered.querySelector('.hello');
expect(componentNode).not.toBeNull();
const renderedClassName = rendered.querySelector('.hello');
expect(renderedClassName).not.toBeNull();

// Set remaining assertions to use `options.targetSelector` if it exists,
// or fall back to the className selector if not
const componentNode = options.targetSelector
? rendered.querySelector(options.targetSelector)
: renderedClassName;

// css
expect(componentNode!.getAttribute('class')).toContain(
Expand All @@ -129,7 +140,9 @@ export const shouldRenderCustomStyles = (
: { className: customStyles.className };

const { baseElement, unmount } = await renderWith(testProps);
const target = baseElement.querySelector(`.${customStyles.className}`)!;
const target = baseElement.querySelector(
options.targetSelector || `.${customStyles.className}`
)!;
const classes = target.getAttribute('class')?.split(' ');
unmount(); // Ensure this baseline render doesn't pollute following renders

Expand Down

0 comments on commit 35224a8

Please sign in to comment.