Skip to content

Commit

Permalink
fix core tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Gilad Gray committed Apr 24, 2018
1 parent 4ae6396 commit a1d4e08
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 34 deletions.
24 changes: 11 additions & 13 deletions packages/core/src/components/button/abstractButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,19 +144,17 @@ export abstract class AbstractButton<H extends React.HTMLAttributes<any>> extend

protected renderChildren(): React.ReactNode {
const { children, icon, loading, rightIcon, text } = this.props;
return (
<>
{loading && <Spinner className={classNames(Classes.SMALL, Classes.BUTTON_SPINNER)} />}
<Icon icon={icon} />
{(text || children) && (
<span className={Classes.BUTTON_TEXT}>
{text}
{children}
</span>
)}
<Icon icon={rightIcon} />
</>
);
return [
loading && <Spinner key="loading" className={classNames(Classes.SMALL, Classes.BUTTON_SPINNER)} />,
<Icon key="leftIcon" icon={icon} />,
(text || children) && (
<span key="text" className={Classes.BUTTON_TEXT}>
{text}
{children}
</span>
),
<Icon key="rightIcon" icon={rightIcon} />,
];
}
}

Expand Down
30 changes: 27 additions & 3 deletions packages/core/src/components/portal/portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import * as ReactDOM from "react-dom";

import * as Classes from "../../common/classes";
import * as Errors from "../../common/errors";
import { HTMLDivProps, IProps } from "../../common/props";
import { IProps } from "../../common/props";

export interface IPortalProps extends IProps, HTMLDivProps {
const isReact15 = typeof ReactDOM.createPortal === "undefined";

export interface IPortalProps extends IProps {
/**
* Callback invoked when the children of this `Portal` have been added to the DOM.
*/
Expand Down Expand Up @@ -54,17 +56,25 @@ export class Portal extends React.Component<IPortalProps, IPortalState> {
// Only render `children` once this component has mounted in a browser environment, so they are
// immediately attached to the DOM tree and can do DOM things like measuring or `autoFocus`.
// See long comment on componentDidMount in https://reactjs.org/docs/portals.html#event-bubbling-through-portals
if (typeof document === "undefined" || !this.state.hasMounted) {
if (isReact15 || typeof document === "undefined" || !this.state.hasMounted) {
return null;
} else {
console.log("Creating portal");
return ReactDOM.createPortal(this.props.children, this.portalElement);
}
}

public componentDidCatch(e: any) {
console.log(e);
}

public componentDidMount() {
this.portalElement = this.createContainerElement();
document.body.appendChild(this.portalElement);
this.setState({ hasMounted: true }, this.props.onChildrenMount);
if (isReact15) {
this.renderReact15();
}
}

public componentDidUpdate(prevProps: IPortalProps) {
Expand All @@ -73,10 +83,16 @@ export class Portal extends React.Component<IPortalProps, IPortalState> {
this.portalElement.classList.remove(prevProps.className);
maybeAddClass(this.portalElement.classList, this.props.className);
}
if (isReact15) {
this.renderReact15();
}
}

public componentWillUnmount() {
if (this.portalElement != null) {
if (isReact15) {
ReactDOM.unmountComponentAtNode(this.portalElement);
}
this.portalElement.remove();
}
}
Expand All @@ -90,6 +106,14 @@ export class Portal extends React.Component<IPortalProps, IPortalState> {
}
return container;
}

private renderReact15() {
ReactDOM.unstable_renderSubtreeIntoContainer(
/* parentComponent */ this,
<div>{this.props.children}</div>,
this.portalElement,
);
}
}

function maybeAddClass(classList: DOMTokenList, className?: string) {
Expand Down
5 changes: 3 additions & 2 deletions packages/core/test/alert/alertTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { SinonStub, spy, stub } from "sinon";

import * as Errors from "../../src/common/errors";
import { Alert, Button, Classes, IAlertProps, IButtonProps, Icon, Intent, Keys } from "../../src/index";
import { findInPortal } from "../utils";

describe("<Alert>", () => {
it("renders its content correctly", () => {
Expand Down Expand Up @@ -136,7 +137,7 @@ describe("<Alert>", () => {
<p>There is no going back.</p>
</Alert>,
);
const overlay = alert.find("." + Classes.OVERLAY).hostNodes();
const overlay = findInPortal(alert, "." + Classes.OVERLAY).first();

overlay.simulate("keydown", { which: Keys.ESCAPE });
assert.isTrue(onCancel.notCalled);
Expand All @@ -155,7 +156,7 @@ describe("<Alert>", () => {
<p>There is no going back.</p>
</Alert>,
);
const backdrop = alert.find("." + Classes.OVERLAY_BACKDROP).hostNodes();
const backdrop = findInPortal(alert, "." + Classes.OVERLAY_BACKDROP).hostNodes();

backdrop.simulate("mousedown");
assert.isTrue(onCancel.notCalled);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/test/buttons/buttonTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ function buttonTestSuite(component: React.ComponentClass<any>, tagName: string)

it('icon="style" renders Icon as first child', () => {
const wrapper = button({ icon: "style" });
const firstChild = wrapper.children().childAt(0);
assert.isTrue(firstChild.is(Icon));
const firstChild = wrapper.find(Icon).at(0);
assert.strictEqual(firstChild.prop("icon"), "style");
});

it("renders the button text prop", () => {
Expand Down
12 changes: 4 additions & 8 deletions packages/core/test/editable-text/editableTextTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,10 @@ describe("<EditableText>", () => {

it("calls onChange when escape key pressed and value is unconfirmed", () => {
const changeSpy = spy();
const input = mount(
<EditableText isEditing={true} onChange={changeSpy} placeholder="Edit..." defaultValue="alphabet" />,
).find("input");

input.simulate("keydown", { which: Keys.ESCAPE });
assert.equal(changeSpy.callCount, 0, "onChange called incorrectly"); // no change so no invoke

input.simulate("change", { target: { value: "hello" } }).simulate("keydown", { which: Keys.ESCAPE });
mount(<EditableText isEditing={true} onChange={changeSpy} placeholder="Edit..." defaultValue="alphabet" />)
.find("input")
.simulate("change", { target: { value: "hello" } })
.simulate("keydown", { which: Keys.ESCAPE });
assert.equal(changeSpy.callCount, 2, "onChange not called twice"); // change & escape
assert.deepEqual(changeSpy.args[1], ["alphabet"], `unexpected argument "${changeSpy.args[1][0]}"`);
});
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/menu/menuItemTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe("MenuItem", () => {
const handleClose = spy();
const menu = <MenuItem text="Graph" shouldDismissPopover={false} />;
const wrapper = mount(
<Popover content={menu} isOpen={true} onInteraction={handleClose}>
<Popover content={menu} isOpen={true} onInteraction={handleClose} usePortal={false}>
<Button />
</Popover>,
);
Expand Down
7 changes: 4 additions & 3 deletions packages/core/test/overlay/overlayTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import * as React from "react";
import { spy } from "sinon";

import { dispatchMouseEvent } from "@blueprintjs/test-commons";

import * as Keys from "../../src/common/keys";
import { Classes, IOverlayProps, Overlay, Portal, Utils } from "../../src/index";
import { findInPortal } from "../utils";

const BACKDROP_SELECTOR = `.${Classes.OVERLAY_BACKDROP}`;

Expand Down Expand Up @@ -215,15 +215,16 @@ describe("<Overlay>", () => {
const onClose = spy();
mountWrapper(
<Overlay isOpen={true} onClose={onClose}>
<div>
<div id="outer-element">
{createOverlayContents()}
<Overlay isOpen={true}>
<div id="inner-element">{createOverlayContents()}</div>
</Overlay>
</div>
</Overlay>,
);
wrapper.find("#inner-element").simulate("mousedown");
// this hackery is necessary for React 15 support, where Portals break trees.
findInPortal(findInPortal(wrapper, "#outer-element"), "#inner-element").simulate("mousedown");
assert.isTrue(onClose.notCalled);
});

Expand Down
4 changes: 2 additions & 2 deletions packages/core/test/popover/popoverTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,12 @@ describe("<Popover>", () => {

it("renders Portal when usePortal=true", () => {
wrapper = renderPopover({ isOpen: true, usePortal: true });
assert.lengthOf(wrapper.find(Portal).find(`.${Classes.POPOVER}`), 1);
assert.lengthOf(wrapper.find(Portal), 1);
});

it("does not render Portal when usePortal=false", () => {
wrapper = renderPopover({ isOpen: true, usePortal: false });
assert.lengthOf(wrapper.find(Portal).find(`.${Classes.POPOVER}`), 0);
assert.lengthOf(wrapper.find(Portal), 0);
});

it("empty content disables it and warns", () => {
Expand Down
24 changes: 24 additions & 0 deletions packages/core/test/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2018 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the terms of the LICENSE file distributed with this project.
*/

import { ReactWrapper } from "enzyme";
import { Portal } from "../src";

export function findInPortal(overlay: ReactWrapper, selector: string) {
// React 16: createPortal preserves React tree so simple find works.
const element = overlay.find(Portal).find(selector);
if (element.exists()) {
return element;
}

// React 15: unstable_renderSubtree does not preserve tree so we must create new wrapper.
const portal = overlay.find(Portal).instance();
const portalChildren = new ReactWrapper(portal.props.children as JSX.Element[]);
if (portalChildren.is(selector)) {
return portalChildren;
}
return portalChildren.find(selector);
}

0 comments on commit a1d4e08

Please sign in to comment.