Skip to content
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
1 change: 1 addition & 0 deletions UNRELEASED.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Added high contrast outline to `ActionList` ([#2713](https://github.com/Shopify/polaris-react/pull/2713))
- Added high contrast border to `Button` ([#2712](https://github.com/Shopify/polaris-react/pull/2712))
- Added styled placeholder image to `Avatar` when initials are blank ([#2693](https://github.com/Shopify/polaris-react/pull/2693))
- Added a `preferInputActivator` prop to `Popover` to allow better positioning of the overlay ([#2754](https://github.com/Shopify/polaris-react/pull/2754))

### Bug fixes

Expand Down
39 changes: 14 additions & 25 deletions src/components/Card/tests/Card.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import React from 'react';
// eslint-disable-next-line no-restricted-imports
import {
mountWithAppProvider,
trigger,
findByTestID,
} from 'test-utilities/legacy';
import {mountWithAppProvider} from 'test-utilities/legacy';
import {mountWithApp} from 'test-utilities';
import {Card, Badge, Button, Popover, ActionList} from 'components';
import {WithinContentContext} from '../../../utilities/within-content-context';
Expand Down Expand Up @@ -177,35 +173,28 @@ describe('<Card />', () => {
{content: 'Most important action'},
{content: 'Second most important action'},
];
const card = mountWithAppProvider(
const card = mountWithApp(
<Card secondaryFooterActions={footerActions}>
<p>Some card content.</p>
</Card>,
);

const disclosureButton = card.find(Button).first();
expect(disclosureButton).toHaveLength(1);
expect(disclosureButton.text()).toBe('More');

const popover = card.find(Popover).first();
expect(popover).toHaveLength(1);
expect(popover.prop('active')).toBe(false);
const disclosureButton = card.findAll(Button)[0];
expect(disclosureButton).toContainReactText('More');

trigger(disclosureButton, 'onClick');
expect(card).toContainReactComponent(Popover, {
active: false,
});

expect(
card
.find(Popover)
.first()
.prop('active'),
).toBe(true);
disclosureButton.trigger('onClick');

const overlay = findByTestID(card, 'popoverOverlay');
expect(overlay).toHaveLength(1);
expect(card).toContainReactComponent(Popover, {
active: true,
});

const actionList = overlay.find(ActionList).first();
expect(actionList).toHaveLength(1);
expect(actionList.prop('items')).toBe(footerActions);
expect(card).toContainReactComponent(ActionList, {
items: footerActions,
});
});

it('sets the disclosure button content to the value set on secondaryFooterActionsDisclosureText', () => {
Expand Down
10 changes: 8 additions & 2 deletions src/components/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ export interface PopoverProps {
active: boolean;
/** The element to activate the Popover */
activator: React.ReactElement;
/**
* Use the activator's input element to calculate the Popover position
* @default true
*/
preferInputActivator?: PopoverOverlayProps['preferInputActivator'];
/**
* The element type to wrap the activator with
* @default 'div'
Expand Down Expand Up @@ -71,6 +76,7 @@ export const Popover: React.FunctionComponent<PopoverProps> & {
active,
fixed,
ariaHaspopup,
preferInputActivator = true,
...rest
}: PopoverProps) {
const [activatorNode, setActivatorNode] = useState();
Expand Down Expand Up @@ -131,11 +137,11 @@ export const Popover: React.FunctionComponent<PopoverProps> & {
}, [activatorNode, setAccessibilityAttributes]);

const portal = activatorNode ? (
<Portal idPrefix="popover" testID="portal">
<Portal idPrefix="popover">
<PopoverOverlay
testID="popoverOverlay"
id={id}
activator={activatorNode}
preferInputActivator={preferInputActivator}
onClose={handleClose}
active={active}
fixed={fixed}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface PopoverOverlayProps {
active: boolean;
id: string;
activator: HTMLElement;
preferInputActivator?: PositionedOverlayProps['preferInputActivator'];
preventAutofocus?: boolean;
sectioned?: boolean;
fixed?: boolean;
Expand Down Expand Up @@ -115,6 +116,7 @@ export class PopoverOverlay extends React.PureComponent<
fullWidth,
preferredPosition = 'below',
preferredAlignment = 'center',
preferInputActivator = true,
fixed,
} = this.props;
const {transitionStatus} = this.state;
Expand All @@ -136,6 +138,7 @@ export class PopoverOverlay extends React.PureComponent<
fullWidth={fullWidth}
active={active}
activator={activator}
preferInputActivator={preferInputActivator}
preferredPosition={preferredPosition}
preferredAlignment={preferredAlignment}
render={this.renderPopover.bind(this)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,25 @@ describe('<PopoverOverlay />', () => {
).toBe('center');
});

it('passes preferInputActivator to PositionedOverlay when false', () => {
const popoverOverlay = mountWithAppProvider(
<PopoverOverlay
active
id="PopoverOverlay-1"
activator={activator}
onClose={noop}
fixed
preferInputActivator={false}
>
{children}
</PopoverOverlay>,
);

expect(
popoverOverlay.find(PositionedOverlay).prop('preferInputActivator'),
).toBe(false);
});

it('calls the onClose callback when the escape key is pressed', () => {
const spy = jest.fn();

Expand Down
103 changes: 63 additions & 40 deletions src/components/Popover/tests/Popover.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {useState, useCallback} from 'react';
// eslint-disable-next-line no-restricted-imports
import {mountWithAppProvider, findByTestID} from 'test-utilities/legacy';
import {mountWithApp} from 'test-utilities';
import {PositionedOverlay} from 'components/PositionedOverlay';
import {Portal} from 'components';
import {Popover} from '../Popover';
import {PopoverOverlay} from '../components';
import * as setActivatorAttributes from '../set-activator-attributes';
Expand All @@ -22,7 +22,7 @@ describe('<Popover />', () => {
});

it('invokes setActivatorAttributes with active, ariaHasPopup and id', () => {
mountWithAppProvider(
mountWithApp(
<Popover active={false} activator={<div>Activator</div>} onClose={spy} />,
);

Expand All @@ -33,56 +33,54 @@ describe('<Popover />', () => {
});

it('renders a portal', () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover active={false} activator={<div>Activator</div>} onClose={spy} />,
);
const portal = findByTestID(popover, 'portal');
expect(portal.exists()).toBeTruthy();
expect(popover).toContainReactComponent(Portal);
});

it('renders an activator', () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover
active
activator={<div testID="activator">Activator</div>}
onClose={spy}
/>,
);
const activator = findByTestID(popover, 'activator');
expect(activator.exists()).toBeTruthy();
expect(popover).toContainReactComponent('div', {testID: 'activator'});
});

it('renders a positionedOverlay when active is true', () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover active activator={<div>Activator</div>} onClose={spy} />,
);
const positionedOverlay = findByTestID(popover, 'positionedOverlay');
expect(positionedOverlay.exists()).toBeTruthy();
expect(popover).toContainReactComponent(PositionedOverlay);
});

it('doesn’t render a popover when active is false', () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover active={false} activator={<div>Activator</div>} onClose={spy} />,
);
const positionedOverlay = findByTestID(popover, 'positionedOverlay');
expect(positionedOverlay.exists()).toBeFalsy();
expect(popover).not.toContainReactComponent(PositionedOverlay);
});

it("passes 'preferredPosition' to PopoverOverlay", () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover
active={false}
preferredPosition="above"
activator={<div>Activator</div>}
onClose={spy}
/>,
);
const popoverOverlay = findByTestID(popover, 'popoverOverlay');
expect(popoverOverlay.prop('preferredPosition')).toBe('above');

expect(popover).toContainReactComponent(PopoverOverlay, {
preferredPosition: 'above',
});
});

it("passes 'preferredAlignment' to PopoverOverlay", () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover
active={false}
preferredPosition="above"
Expand All @@ -91,24 +89,42 @@ describe('<Popover />', () => {
preferredAlignment="left"
/>,
);
const popoverOverlay = findByTestID(popover, 'popoverOverlay');
expect(popoverOverlay.prop('preferredAlignment')).toBe('left');

expect(popover).toContainReactComponent(PopoverOverlay, {
preferredAlignment: 'left',
});
});

it("passes 'preferInputActivator' to PopoverOverlay", () => {
const popover = mountWithApp(
<Popover
active={false}
preferredPosition="above"
activator={<div>Activator</div>}
onClose={spy}
preferInputActivator={false}
/>,
);

expect(popover).toContainReactComponent(PopoverOverlay, {
preferInputActivator: false,
});
});

it('has a div as activatorWrapper by default', () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover
active={false}
preferredPosition="above"
activator={<div>Activator</div>}
onClose={spy}
/>,
);
expect(popover.childAt(0).type()).toBe('div');
expect(popover.children[0].type).toBe('div');
});

it('has a span as activatorWrapper when activatorWrapper prop is set to span', () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover
active={false}
activatorWrapper="span"
Expand All @@ -117,63 +133,70 @@ describe('<Popover />', () => {
onClose={spy}
/>,
);
expect(popover.childAt(0).type()).toBe('span');
expect(popover.children[0].type).toBe('span');
});

it('passes preventAutofocus to PopoverOverlay', () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover
active={false}
preventAutofocus
activator={<div>Activator</div>}
onClose={spy}
/>,
);
const popoverOverlay = findByTestID(popover, 'popoverOverlay');
expect(popoverOverlay.prop('preventAutofocus')).toBe(true);

expect(popover).toContainReactComponent(PopoverOverlay, {
preventAutofocus: true,
});
});

it('passes sectioned to PopoverOverlay', () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover
active={false}
sectioned
activator={<div>Activator</div>}
onClose={spy}
/>,
);
const popoverOverlay = findByTestID(popover, 'popoverOverlay');
expect(popoverOverlay.prop('sectioned')).toBe(true);

expect(popover).toContainReactComponent(PopoverOverlay, {
sectioned: true,
});
});

it('passes fullWidth to PopoverOverlay', () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover
active={false}
fullWidth
activator={<div>Activator</div>}
onClose={spy}
/>,
);
const popoverOverlay = findByTestID(popover, 'popoverOverlay');
expect(popoverOverlay.prop('fullWidth')).toBe(true);

expect(popover).toContainReactComponent(PopoverOverlay, {
fullWidth: true,
});
});

it('passes fluidContent to PopoverOverlay', () => {
const popover = mountWithAppProvider(
const popover = mountWithApp(
<Popover
active
fluidContent
activator={<div>Activator</div>}
onClose={spy}
/>,
);
const popoverOverlay = findByTestID(popover, 'popoverOverlay');
expect(popoverOverlay.prop('fluidContent')).toBe(true);
expect(popover).toContainReactComponent(PopoverOverlay, {
fluidContent: true,
});
});

it('calls onClose when you click outside the Popover', () => {
mountWithAppProvider(
mountWithApp(
<Popover
active
fullWidth
Expand Down Expand Up @@ -201,11 +224,11 @@ describe('<Popover />', () => {

const onCloseSpy = jest.fn();

const popoverWithDisconnectedActivator = mountWithAppProvider(
const popoverWithDisconnectedActivator = mountWithApp(
<PopoverWithDisconnectedActivator />,
);

popoverWithDisconnectedActivator.find('button').simulate('click');
popoverWithDisconnectedActivator.find('button')!.trigger('onClick');
const evt = new CustomEvent('click');
window.dispatchEvent(evt);

Expand Down
Loading