Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

className prop consistency #1950

Merged
merged 14 commits into from
Jan 25, 2018
19 changes: 14 additions & 5 deletions packages/core/src/components/hotkeys/hotkey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
* Licensed under the terms of the LICENSE file distributed with this project.
*/

import * as classNames from "classnames";
import * as React from "react";

import { AbstractPureComponent } from "../../common";
import { AbstractPureComponent, IProps } from "../../common";
import { KeyCombo } from "./keyCombo";

export interface IHotkeyProps {
export interface IHotkeyProps extends IProps {
/**
* Whether the hotkey should be triggerable when focused in a text input.
* @default false
Expand Down Expand Up @@ -61,6 +62,12 @@ export interface IHotkeyProps {
*/
stopPropagation?: boolean;

/**
* Space-delimited string of class names applied to the
* `KeyCombo` contained in the hotkey.
*/
keyComboClassName?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need this?

Copy link
Contributor

Choose a reason for hiding this comment

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

we don't strictly need it and it's probably safe to remove. shall i do that?


/**
* `keydown` event handler.
*/
Expand All @@ -86,11 +93,13 @@ export class Hotkey extends AbstractPureComponent<IHotkeyProps, {}> {
}

public render() {
const { label, ...spreadableProps } = this.props;
const { label, className, keyComboClassName, ...spreadableProps } = this.props;

const rootClasses = classNames("pt-hotkey", className);
return (
<div className="pt-hotkey">
<div className={rootClasses}>
<div className="pt-hotkey-label">{label}</div>
<KeyCombo {...spreadableProps} />
<KeyCombo className={keyComboClassName} {...spreadableProps} />
</div>
);
}
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/components/hotkeys/hotkeys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import * as React from "react";

import * as classNames from "classnames";
import { AbstractPureComponent, IProps } from "../../common";
import { HOTKEYS_HOTKEY_CHILDREN } from "../../common/errors";
import { Hotkey, IHotkeyProps } from "./hotkey";
Expand Down Expand Up @@ -64,8 +65,8 @@ export class Hotkeys extends AbstractPureComponent<IHotkeysProps, {}> {
}
elems.push(<Hotkey key={elems.length} {...hotkey} />);
}

return <div className="pt-hotkey-column">{elems}</div>;
const rootClasses = classNames("pt-hotkey-column", this.props.className);
return <div className={rootClasses}>{elems}</div>;
}

protected validateProps(props: IHotkeysProps & { children: React.ReactNode }) {
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/components/hotkeys/keyCombo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
* Licensed under the terms of the LICENSE file distributed with this project.
*/

import * as classNames from "classnames";
import * as React from "react";
import { IProps } from "../../common";
import { normalizeKeyCombo } from "./hotkeyParser";

const KeyIcons: { [key: string]: string } = {
Expand All @@ -21,7 +23,7 @@ const KeyIcons: { [key: string]: string } = {
up: "pt-icon-arrow-up",
};

export interface IKeyComboProps {
export interface IKeyComboProps extends IProps {
allowInInput?: boolean;
combo: string;
disabled?: boolean;
Expand All @@ -33,6 +35,7 @@ export class KeyCombo extends React.Component<IKeyComboProps, {}> {
public render() {
const keys = normalizeKeyCombo(this.props.combo);
const components = [] as JSX.Element[];
const rootClasses = classNames("pt-key-combo", this.props.className);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
const icon = KeyIcons[key];
Expand All @@ -54,6 +57,6 @@ export class KeyCombo extends React.Component<IKeyComboProps, {}> {
);
}
}
return <span className="pt-key-combo">{components}</span>;
return <span className={rootClasses}>{components}</span>;
}
}
22 changes: 18 additions & 4 deletions packages/core/test/isotest.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ const Core = require("../dist");
const tooltipContent = { content: React.createElement("h1", {}, "content") };
const customProps = {
Hotkey: { combo: "mod+s", global: true, label: "save" },
Icon: { iconName: "pt-icon-build" },
KeyCombo: { combo: "?" },
Overlay: { lazy: false, inline: true },
SVGTooltip: tooltipContent,
TagInput: { values: ["foo", "bar", "baz"] },
Tooltip: tooltipContent,
Expand All @@ -29,9 +31,21 @@ const customChildren = {
Tooltip2: popoverTarget,
};

const skipList = [
"Portal", // doesn't render any DOM inline
"Tabs", // deprecated component, logs a warning
]

const classNameChildList = [
"Alert",
"Dialog",
"MenuItem",
"Popover2",
Copy link
Contributor

Choose a reason for hiding this comment

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

Will need to return and remove the 2s here after #1946 and #1370 merge.

"Portal",
"Toaster",
"Tooltip2"
]

describe("Core isomorphic rendering", () => {
generateIsomorphicTests(Core, customProps, customChildren, [
"Portal", // doesn't render any DOM inline
"Tabs", // deprecated component, logs a warning
]);
generateIsomorphicTests(Core, customProps, customChildren, skipList, classNameChildList);
});
16 changes: 15 additions & 1 deletion packages/select/test/isotest.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,24 @@ const customProps = {
}
};

const skipList = [
"QueryList", // doesn't render any DOM itself
]

const classNameChildList = [
"MultiSelect",
"Omnibar",
"QueryList",
"Select",
"Suggest"
]

describe("Select isomorphic rendering", () => {
generateIsomorphicTests(
Select,
customProps,
customChildren
customChildren,
skipList,
classNameChildList
);
});
14 changes: 9 additions & 5 deletions packages/table/src/interactions/resizeHandle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,15 @@ export class ResizeHandle extends React.PureComponent<IResizeHandleProps, IResiz
return undefined;
}

const targetClasses = classNames(Classes.TABLE_RESIZE_HANDLE_TARGET, {
[Classes.TABLE_DRAGGING]: this.state.isDragging,
[Classes.TABLE_RESIZE_HORIZONTAL]: orientation === Orientation.HORIZONTAL,
[Classes.TABLE_RESIZE_VERTICAL]: orientation === Orientation.VERTICAL,
});
const targetClasses = classNames(
Classes.TABLE_RESIZE_HANDLE_TARGET,
{
[Classes.TABLE_DRAGGING]: this.state.isDragging,
[Classes.TABLE_RESIZE_HORIZONTAL]: orientation === Orientation.HORIZONTAL,
[Classes.TABLE_RESIZE_VERTICAL]: orientation === Orientation.VERTICAL,
},
this.props.className,
);

const handleClasses = classNames(Classes.TABLE_RESIZE_HANDLE, {
[Classes.TABLE_DRAGGING]: this.state.isDragging,
Expand Down
22 changes: 15 additions & 7 deletions packages/table/test/isotest.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,31 @@ const { generateIsomorphicTests } = require("@blueprintjs/test-commons");
const React = require("react");
const Table = require("../dist");

const draggableElement = React.createElement("button");
const customChildren = {
DragSelectable: draggableElement,
Draggable: draggableElement,
};

const customProps = {
ResizeHandle: {
// needs at least one handler or it returns undefined
onDoubleClick: () => undefined,
},
};

const classNameChildList = [
"CopyCellsMenuItem",
"ResizeHandle"
]

const skipList = [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@giladgray Classname doesn't propagate for these three components, but i'm not sure of the best way to handle these. It looks like they are just wrappers for eventHandlers, in which case maybe className doesn't need to propagate?

Copy link
Contributor

Choose a reason for hiding this comment

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

ResizeHandle - add support

the other two, you're right, they're the exceptions!

// Pass-through renders
"DragSelectable",
"Draggable",
]


describe("Table isomorphic rendering", () => {
generateIsomorphicTests(
Table,
customProps,
customChildren
{},
skipList,
classNameChildList
);
});
19 changes: 19 additions & 0 deletions packages/test-commons/src/generateIsomorphicTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// TODO: make this an executable script that takes configuration from CLI arguments so we don't need
// to use the `mocha` CLI and write an isotest.js file in every project

import { assert } from "chai";
import * as Enzyme from "enzyme";
import * as Adapter from "enzyme-adapter-react-16";
import * as React from "react";
Expand All @@ -32,12 +33,15 @@ function isReactClass(Component: any): boolean {
* @param props custom props per component
* @param children custom children per component
* @param skipList array of component names to skip
* @param classNameChildList array of component names for which className is legitimately passed down to
* child element
Copy link
Contributor

Choose a reason for hiding this comment

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

array of component names where className is rendered on an arbitrary child element, rather than directly on the root element.

*/
export function generateIsomorphicTests(
Components: { [name: string]: any },
props: { [name: string]: any },
children: { [name: string]: React.ReactNode },
skipList: string[] = [],
classNameChildList: string[] = [],
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to document this new param in the function comment above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

) {
Object.keys(Components)
.sort()
Expand All @@ -54,6 +58,21 @@ export function generateIsomorphicTests(
// errors will fail the test and log full stack traces to the console. nifty!
Enzyme.render(element);
});
it(`<${componentName}> className is applied to outer element`, () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

this test name is not always accurate... might want to do the classNameChildList check outside this it() so we can change the test name?

const element = React.createElement(
Component,
{
...props[componentName],
className: "test",
},
children[componentName],
);
if (classNameChildList.includes(componentName)) {
assert.isAtLeast(Enzyme.shallow(element).find(".test").length, 1);
Copy link
Contributor

Choose a reason for hiding this comment

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

shallow and render are very different... we want to use render only here as it produces a DOM tree, whereas shallow produces some Enzyme-only tree that doesn't actually assert server-side-rendering success. should be possible to simply replace the word shallow with render, it still has a find method.

} else {
assert.isTrue(Enzyme.render(element).hasClass("test"));
}
});
}
}
});
Expand Down
10 changes: 9 additions & 1 deletion packages/timezone/test/isotest.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ const { generateIsomorphicTests } = require("@blueprintjs/test-commons");
const React = require("react");
const Timezone = require("../dist");

const skipList = []

const classNameChildList = [
"TimezonePicker"
]

describe("Timezone isomorphic rendering", () => {
generateIsomorphicTests(
Timezone,
{},
{}
{},
skipList,
classNameChildList
);
});