mirror of https://github.com/apache/superset.git
[sql lab] Make sql editor resizable (#3242)
* Update to the version of react-ace with the fixed sizing issues * Make ace editor resizable * Use small util method for offset calculation instead of $ * Test ResizableAceEditor * Make the right pane of the Sql Lab scrollable * Add default and min height to the ResizableAceEditor * Implement SplitPane * Make Splitter fullscreen * React on resize of the window * Implement min and max * Get rid of a magic number + add margin * Handle resize event with delay + cleanup the code * Make ResultSet adjustable * Make QueryHistory adjustable * Remove ResizableAceEditor * Make linter happy * Test SplitPane * Init sizes properly
This commit is contained in:
parent
6fc837db51
commit
75e69f02e8
|
@ -32,6 +32,7 @@ const propTypes = {
|
||||||
sql: PropTypes.string.isRequired,
|
sql: PropTypes.string.isRequired,
|
||||||
tables: PropTypes.array,
|
tables: PropTypes.array,
|
||||||
queryEditor: PropTypes.object.isRequired,
|
queryEditor: PropTypes.object.isRequired,
|
||||||
|
height: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
|
@ -119,14 +120,15 @@ class AceEditorWrapper extends React.PureComponent {
|
||||||
this.setState({ sql: text });
|
this.setState({ sql: text });
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
|
const { height } = this.props;
|
||||||
return (
|
return (
|
||||||
<AceEditor
|
<AceEditor
|
||||||
|
ref="editor"
|
||||||
mode="sql"
|
mode="sql"
|
||||||
theme="github"
|
theme="github"
|
||||||
onLoad={this.onEditorLoad.bind(this)}
|
onLoad={this.onEditorLoad.bind(this)}
|
||||||
onBlur={this.onBlur.bind(this)}
|
onBlur={this.onBlur.bind(this)}
|
||||||
minLines={12}
|
height={height}
|
||||||
maxLines={12}
|
|
||||||
onChange={this.textChange.bind(this)}
|
onChange={this.textChange.bind(this)}
|
||||||
width="100%"
|
width="100%"
|
||||||
editorProps={{ $blockScrolling: true }}
|
editorProps={{ $blockScrolling: true }}
|
||||||
|
|
|
@ -36,7 +36,6 @@ export default class ResultSet extends React.PureComponent {
|
||||||
searchText: '',
|
searchText: '',
|
||||||
showModal: false,
|
showModal: false,
|
||||||
data: null,
|
data: null,
|
||||||
height: props.search ? props.height - RESULT_SET_CONTROLS_HEIGHT : props.height,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -143,7 +142,8 @@ export default class ResultSet extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const query = this.props.query;
|
const { query, search, height } = this.props;
|
||||||
|
const tableHeight = search ? height - RESULT_SET_CONTROLS_HEIGHT : height;
|
||||||
let sql;
|
let sql;
|
||||||
|
|
||||||
if (this.props.showSql) {
|
if (this.props.showSql) {
|
||||||
|
@ -190,7 +190,7 @@ export default class ResultSet extends React.PureComponent {
|
||||||
<FilterableTable
|
<FilterableTable
|
||||||
data={data}
|
data={data}
|
||||||
orderedColumnKeys={results.columns.map(col => col.name)}
|
orderedColumnKeys={results.columns.map(col => col.name)}
|
||||||
height={this.state.height}
|
height={tableHeight}
|
||||||
filterText={this.state.searchText}
|
filterText={this.state.searchText}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import shortid from 'shortid';
|
||||||
import { Alert, Tab, Tabs } from 'react-bootstrap';
|
import { Alert, Tab, Tabs } from 'react-bootstrap';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { AutoSizer } from 'react-virtualized';
|
||||||
|
|
||||||
import * as Actions from '../actions';
|
import * as Actions from '../actions';
|
||||||
import QueryHistory from './QueryHistory';
|
import QueryHistory from './QueryHistory';
|
||||||
|
@ -55,6 +56,7 @@ class SouthPane extends React.PureComponent {
|
||||||
|
|
||||||
return window.innerHeight - sum - 95;
|
return window.innerHeight - sum - 95;
|
||||||
}
|
}
|
||||||
|
|
||||||
switchTab(id) {
|
switchTab(id) {
|
||||||
this.props.actions.setActiveSouthPaneTab(id);
|
this.props.actions.setActiveSouthPaneTab(id);
|
||||||
}
|
}
|
||||||
|
@ -67,13 +69,28 @@ class SouthPane extends React.PureComponent {
|
||||||
let results;
|
let results;
|
||||||
if (latestQuery) {
|
if (latestQuery) {
|
||||||
results = (
|
results = (
|
||||||
<ResultSet
|
<AutoSizer
|
||||||
showControls
|
disableWidth
|
||||||
search
|
>
|
||||||
query={latestQuery}
|
{({ height }) => {
|
||||||
actions={props.actions}
|
/*
|
||||||
height={this.state.innerTabHeight}
|
checking of the height probably won't be necessary
|
||||||
/>
|
after release of react-virtualized v10
|
||||||
|
*/
|
||||||
|
if (height !== 0) {
|
||||||
|
return (
|
||||||
|
<ResultSet
|
||||||
|
showControls
|
||||||
|
search
|
||||||
|
query={latestQuery}
|
||||||
|
actions={props.actions}
|
||||||
|
height={height}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <div />;
|
||||||
|
}}
|
||||||
|
</AutoSizer>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
results = <Alert bsStyle="info">Run a query to display results here</Alert>;
|
results = <Alert bsStyle="info">Run a query to display results here</Alert>;
|
||||||
|
@ -85,20 +102,36 @@ class SouthPane extends React.PureComponent {
|
||||||
eventKey={query.id}
|
eventKey={query.id}
|
||||||
key={query.id}
|
key={query.id}
|
||||||
>
|
>
|
||||||
<ResultSet
|
<AutoSizer
|
||||||
query={query}
|
disableWidth
|
||||||
visualize={false}
|
>
|
||||||
csv={false}
|
{({ height }) => {
|
||||||
actions={props.actions}
|
/*
|
||||||
cache
|
checking of the height probably won't be necessary
|
||||||
height={this.state.innerTabHeight}
|
after release of react-virtualized v10
|
||||||
/>
|
*/
|
||||||
|
if (height !== 0) {
|
||||||
|
return (
|
||||||
|
<ResultSet
|
||||||
|
query={query}
|
||||||
|
visualize={false}
|
||||||
|
csv={false}
|
||||||
|
actions={props.actions}
|
||||||
|
cache
|
||||||
|
height={height}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <div />;
|
||||||
|
}}
|
||||||
|
</AutoSizer>
|
||||||
</Tab>
|
</Tab>
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="SouthPane">
|
<div className="SouthPane">
|
||||||
<Tabs
|
<Tabs
|
||||||
|
className="Tabs"
|
||||||
bsStyle="tabs"
|
bsStyle="tabs"
|
||||||
id={shortid.generate()}
|
id={shortid.generate()}
|
||||||
activeKey={this.props.activeSouthPaneTab}
|
activeKey={this.props.activeSouthPaneTab}
|
||||||
|
@ -114,7 +147,7 @@ class SouthPane extends React.PureComponent {
|
||||||
title="Query History"
|
title="Query History"
|
||||||
eventKey="History"
|
eventKey="History"
|
||||||
>
|
>
|
||||||
<div style={{ height: `${this.state.innerTabHeight}px`, overflow: 'scroll' }}>
|
<div className="QueryHistoryWrapper">
|
||||||
<QueryHistory queries={props.editorQueries} actions={props.actions} />
|
<QueryHistory queries={props.editorQueries} actions={props.actions} />
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import delay from 'lodash.delay';
|
||||||
|
import { getTopOffset } from '../../../utils/common';
|
||||||
|
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
north: PropTypes.object.isRequired,
|
||||||
|
south: PropTypes.object.isRequired,
|
||||||
|
minHeight: PropTypes.number,
|
||||||
|
onSizeChange: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
class SplitPane extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
dragging: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleDraggingStart = this.handleDraggingStart.bind(this);
|
||||||
|
this.handleMouseUp = this.handleMouseUp.bind(this);
|
||||||
|
this.handleMouseMove = this.handleMouseMove.bind(this);
|
||||||
|
this.handleResize = this.handleResize.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
window.addEventListener('mouseup', this.handleMouseUp);
|
||||||
|
window.addEventListener('mousemove', this.handleMouseMove);
|
||||||
|
window.addEventListener('resize', this.handleResize);
|
||||||
|
|
||||||
|
this.initSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||||
|
window.removeEventListener('mousemove', this.handleMouseMove);
|
||||||
|
window.removeEventListener('resize', this.handleResize);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSize(northInPercent, southInPercent) {
|
||||||
|
const totalHeight = this.refs.splitter.clientHeight - this.refs.dragBar.clientHeight;
|
||||||
|
|
||||||
|
const heightNorthInPixels = northInPercent * totalHeight / 100;
|
||||||
|
const heightSouthInPixels = southInPercent * totalHeight / 100;
|
||||||
|
|
||||||
|
if (this.props.onSizeChange) {
|
||||||
|
this.props.onSizeChange({
|
||||||
|
north: heightNorthInPixels,
|
||||||
|
south: heightSouthInPixels,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initSize() {
|
||||||
|
const totalHeight = this.refs.splitter.clientHeight;
|
||||||
|
const dragBarHeight = this.refs.dragBar.clientHeight;
|
||||||
|
|
||||||
|
const heightInPixels = (totalHeight - dragBarHeight) / 2;
|
||||||
|
const heightInPercent = heightInPixels * 100 / totalHeight;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
heightNorth: heightInPercent,
|
||||||
|
heightSouth: heightInPercent,
|
||||||
|
});
|
||||||
|
this.setSize(heightInPercent, heightInPercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseMove(e) {
|
||||||
|
if (!this.state.dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minHeight = this.props.minHeight || 0;
|
||||||
|
|
||||||
|
const offset = getTopOffset(this.refs.splitter);
|
||||||
|
const totalHeight = this.refs.splitter.clientHeight;
|
||||||
|
const dragBarHeight = this.refs.dragBar.clientHeight;
|
||||||
|
|
||||||
|
const heightNorthInPixels = e.pageY - offset;
|
||||||
|
const heightSouthInPixels = totalHeight - heightNorthInPixels - dragBarHeight;
|
||||||
|
|
||||||
|
const heightNorthInPercent = 100 * heightNorthInPixels / totalHeight;
|
||||||
|
const heightSouthInPercent = 100 * heightSouthInPixels / totalHeight;
|
||||||
|
|
||||||
|
if (heightNorthInPercent >= minHeight
|
||||||
|
&& heightSouthInPercent >= minHeight) {
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
heightNorth: heightNorthInPercent,
|
||||||
|
heightSouth: heightSouthInPercent,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setSize(heightNorthInPercent, heightSouthInPercent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDraggingStart() {
|
||||||
|
this.setState({ ...this.state, dragging: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResize() {
|
||||||
|
const { heightNorth, heightSouth } = this.state;
|
||||||
|
/*
|
||||||
|
The `delay` is needed since some events like 'onresize' happen before rendering.
|
||||||
|
That means that we can't calculate the sizes right.
|
||||||
|
*/
|
||||||
|
delay(() => {
|
||||||
|
this.setSize(heightNorth, heightSouth);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseUp() {
|
||||||
|
if (this.state.dragging) {
|
||||||
|
this.setState({ ...this.state, dragging: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div ref="splitter" className="Splitter">
|
||||||
|
<div
|
||||||
|
style={{ height: this.state.heightNorth + '%' }}
|
||||||
|
>
|
||||||
|
{this.props.north}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref="dragBar"
|
||||||
|
className="DragBar"
|
||||||
|
onMouseDown={this.handleDraggingStart}
|
||||||
|
>
|
||||||
|
<div className="DragBarVisible" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{ height: this.state.heightSouth + '%' }}
|
||||||
|
>
|
||||||
|
{this.props.south}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SplitPane.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default SplitPane;
|
|
@ -15,14 +15,16 @@ import {
|
||||||
|
|
||||||
import Button from '../../components/Button';
|
import Button from '../../components/Button';
|
||||||
|
|
||||||
|
import AceEditorWrapper from './AceEditorWrapper';
|
||||||
import SouthPane from './SouthPane';
|
import SouthPane from './SouthPane';
|
||||||
|
import SplitPane from './SplitPane';
|
||||||
import SaveQuery from './SaveQuery';
|
import SaveQuery from './SaveQuery';
|
||||||
import Timer from '../../components/Timer';
|
import Timer from '../../components/Timer';
|
||||||
import SqlEditorLeftBar from './SqlEditorLeftBar';
|
import SqlEditorLeftBar from './SqlEditorLeftBar';
|
||||||
import AceEditorWrapper from './AceEditorWrapper';
|
|
||||||
import { STATE_BSSTYLE_MAP } from '../constants';
|
import { STATE_BSSTYLE_MAP } from '../constants';
|
||||||
import RunQueryActionButton from './RunQueryActionButton';
|
import RunQueryActionButton from './RunQueryActionButton';
|
||||||
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
actions: PropTypes.object.isRequired,
|
actions: PropTypes.object.isRequired,
|
||||||
height: PropTypes.string.isRequired,
|
height: PropTypes.string.isRequired,
|
||||||
|
@ -49,6 +51,7 @@ class SqlEditor extends React.PureComponent {
|
||||||
autorun: props.queryEditor.autorun,
|
autorun: props.queryEditor.autorun,
|
||||||
ctas: '',
|
ctas: '',
|
||||||
};
|
};
|
||||||
|
this.onSizeChange = this.onSizeChange.bind(this);
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.onMount();
|
this.onMount();
|
||||||
|
@ -60,6 +63,13 @@ class SqlEditor extends React.PureComponent {
|
||||||
this.startQuery();
|
this.startQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onSizeChange(newSizes) {
|
||||||
|
const bottomBarHeight = this.refs.editorBottomBar.clientHeight;
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
editorHeight: newSizes.north - bottomBarHeight,
|
||||||
|
});
|
||||||
|
}
|
||||||
setQueryEditorSql(sql) {
|
setQueryEditorSql(sql) {
|
||||||
this.props.actions.queryEditorSetSql(this.props.queryEditor, sql);
|
this.props.actions.queryEditorSetSql(this.props.queryEditor, sql);
|
||||||
}
|
}
|
||||||
|
@ -147,7 +157,7 @@ class SqlEditor extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const editorBottomBar = (
|
const editorBottomBar = (
|
||||||
<div className="sql-toolbar clearfix" id="js-sql-toolbar">
|
<div ref="editorBottomBar" className="sql-toolbar clearfix" id="js-sql-toolbar">
|
||||||
<div className="pull-left">
|
<div className="pull-left">
|
||||||
<Form inline>
|
<Form inline>
|
||||||
<RunQueryActionButton
|
<RunQueryActionButton
|
||||||
|
@ -212,21 +222,34 @@ class SqlEditor extends React.PureComponent {
|
||||||
sm={this.props.hideLeftBar ? 12 : 7}
|
sm={this.props.hideLeftBar ? 12 : 7}
|
||||||
md={this.props.hideLeftBar ? 12 : 8}
|
md={this.props.hideLeftBar ? 12 : 8}
|
||||||
lg={this.props.hideLeftBar ? 12 : 9}
|
lg={this.props.hideLeftBar ? 12 : 9}
|
||||||
|
style={{
|
||||||
|
height: this.sqlEditorHeight(),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<AceEditorWrapper
|
<SplitPane
|
||||||
actions={this.props.actions}
|
onSizeChange={this.onSizeChange}
|
||||||
onBlur={this.setQueryEditorSql.bind(this)}
|
minHeight={25}
|
||||||
queryEditor={this.props.queryEditor}
|
north={
|
||||||
onAltEnter={this.runQuery.bind(this)}
|
<div>
|
||||||
sql={this.props.queryEditor.sql}
|
<AceEditorWrapper
|
||||||
tables={this.props.tables}
|
actions={this.props.actions}
|
||||||
/>
|
onBlur={this.setQueryEditorSql.bind(this)}
|
||||||
{editorBottomBar}
|
queryEditor={this.props.queryEditor}
|
||||||
<br />
|
onAltEnter={this.runQuery.bind(this)}
|
||||||
<SouthPane
|
sql={this.props.queryEditor.sql}
|
||||||
editorQueries={this.props.editorQueries}
|
tables={this.props.tables}
|
||||||
dataPreviewQueries={this.props.dataPreviewQueries}
|
height={this.state.editorHeight + 'px'}
|
||||||
actions={this.props.actions}
|
/>
|
||||||
|
{editorBottomBar}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
south={
|
||||||
|
<SouthPane
|
||||||
|
editorQueries={this.props.editorQueries}
|
||||||
|
dataPreviewQueries={this.props.dataPreviewQueries}
|
||||||
|
actions={this.props.actions}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -31,6 +31,40 @@ body {
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
.DragBar {
|
||||||
|
padding: 10px 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.DragBarVisible {
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
|
background-color: #ccc;
|
||||||
|
cursor: row-resize;
|
||||||
|
}
|
||||||
|
.Splitter {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.SqlEditor .SouthPane{
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
.SqlEditor .SouthPane .Tabs{
|
||||||
|
height:100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.SqlEditor .SouthPane .Tabs .tab-content{
|
||||||
|
height:100%;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1;
|
||||||
|
}
|
||||||
|
.SqlEditor .SouthPane .Tabs .tab-pane{
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
.SqlEditor .QueryHistoryWrapper{
|
||||||
|
height: 100%;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
.scrollbar-container {
|
.scrollbar-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -237,8 +271,8 @@ div.tablePopover:hover {
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
.ace_editor {
|
.ace_editor {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
margin: 0px 0px 10px 0px;
|
margin: 0px 0px 10px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Select-menu-outer {
|
.Select-menu-outer {
|
||||||
|
@ -253,6 +287,10 @@ div.tablePopover:hover {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.NorthPane {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.TableElement {
|
.TableElement {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,13 +55,14 @@
|
||||||
"datatables.net-bs": "^1.10.12",
|
"datatables.net-bs": "^1.10.12",
|
||||||
"immutable": "^3.8.1",
|
"immutable": "^3.8.1",
|
||||||
"jquery": "^3.2.1",
|
"jquery": "^3.2.1",
|
||||||
|
"lodash.delay": "^4.1.1",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"moment": "^2.14.1",
|
"moment": "^2.14.1",
|
||||||
"mustache": "^2.2.1",
|
"mustache": "^2.2.1",
|
||||||
"nvd3": "1.8.5",
|
"nvd3": "1.8.5",
|
||||||
"prop-types": "^15.5.8",
|
"prop-types": "^15.5.8",
|
||||||
"react": "^15.5.1",
|
"react": "^15.5.1",
|
||||||
"react-ace": "^5.0.1",
|
"react-ace": "^5.1.2",
|
||||||
"react-addons-css-transition-group": "^15.6.0",
|
"react-addons-css-transition-group": "^15.6.0",
|
||||||
"react-addons-shallow-compare": "^15.4.2",
|
"react-addons-shallow-compare": "^15.4.2",
|
||||||
"react-alert": "^1.0.14",
|
"react-alert": "^1.0.14",
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { shallow, mount } from 'enzyme';
|
||||||
|
import { describe, it } from 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import SplitPane from '../../../javascripts/SqlLab/components/SplitPane';
|
||||||
|
|
||||||
|
function simulateWindowEvent(eventName, customProps) {
|
||||||
|
const evt = document.createEvent('Event');
|
||||||
|
evt.initEvent(eventName, true, true);
|
||||||
|
global.window.dispatchEvent(Object.assign(evt, customProps));
|
||||||
|
}
|
||||||
|
|
||||||
|
const TestComponent = () => (<div className="test" />);
|
||||||
|
|
||||||
|
describe('ResizableAceEditor', () => {
|
||||||
|
const mockedProps = {
|
||||||
|
north: () => <div className="stub north" />,
|
||||||
|
south: () => <div className="stub south" />,
|
||||||
|
};
|
||||||
|
let clock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
clock = sinon.useFakeTimers();
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
clock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('is valid', () => {
|
||||||
|
expect(
|
||||||
|
React.isValidElement(<SplitPane {...mockedProps} />),
|
||||||
|
).to.equal(true);
|
||||||
|
});
|
||||||
|
it('renders what you provide in north', () => {
|
||||||
|
const wrapper = shallow(<SplitPane {...mockedProps} north={<TestComponent />} />);
|
||||||
|
expect(wrapper.find(TestComponent)).to.have.length(1);
|
||||||
|
});
|
||||||
|
it('renders what you provide in south', () => {
|
||||||
|
const wrapper = shallow(<SplitPane {...mockedProps} south={<TestComponent />} />);
|
||||||
|
expect(wrapper.find(TestComponent)).to.have.length(1);
|
||||||
|
});
|
||||||
|
it('render a DragBar', () => {
|
||||||
|
const wrapper = shallow(<SplitPane {...mockedProps} />);
|
||||||
|
expect(wrapper.find('.DragBar')).to.have.length(1);
|
||||||
|
});
|
||||||
|
it('has dragging set to false by default', () => {
|
||||||
|
const wrapper = shallow(<SplitPane {...mockedProps} />);
|
||||||
|
expect(wrapper.state().dragging).to.be.equal(false);
|
||||||
|
});
|
||||||
|
it('has dragging set to true when dragged', () => {
|
||||||
|
const wrapper = shallow(<SplitPane {...mockedProps} />);
|
||||||
|
const dragbar = wrapper.find('.DragBar');
|
||||||
|
dragbar.simulate('mouseDown');
|
||||||
|
expect(wrapper.state().dragging).to.be.equal(true);
|
||||||
|
});
|
||||||
|
it('has dragging set to false when dropped', () => {
|
||||||
|
const wrapper = mount(<SplitPane {...mockedProps} />);
|
||||||
|
const dragbar = wrapper.find('.DragBar');
|
||||||
|
dragbar.simulate('mouseDown');
|
||||||
|
simulateWindowEvent('mouseup');
|
||||||
|
expect(wrapper.state().dragging).to.be.equal(false);
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,6 +6,7 @@ import { expect } from 'chai';
|
||||||
import { initialState, queries, table } from './fixtures';
|
import { initialState, queries, table } from './fixtures';
|
||||||
import SqlEditor from '../../../javascripts/SqlLab/components/SqlEditor';
|
import SqlEditor from '../../../javascripts/SqlLab/components/SqlEditor';
|
||||||
import SqlEditorLeftBar from '../../../javascripts/SqlLab/components/SqlEditorLeftBar';
|
import SqlEditorLeftBar from '../../../javascripts/SqlLab/components/SqlEditorLeftBar';
|
||||||
|
import SplitPane from '../../../javascripts/SqlLab/components/SplitPane';
|
||||||
|
|
||||||
describe('SqlEditor', () => {
|
describe('SqlEditor', () => {
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
|
@ -28,4 +29,8 @@ describe('SqlEditor', () => {
|
||||||
const wrapper = shallow(<SqlEditor {...mockedProps} />);
|
const wrapper = shallow(<SqlEditor {...mockedProps} />);
|
||||||
expect(wrapper.find(SqlEditorLeftBar)).to.have.length(1);
|
expect(wrapper.find(SqlEditorLeftBar)).to.have.length(1);
|
||||||
});
|
});
|
||||||
|
it('render a SplitPane', () => {
|
||||||
|
const wrapper = shallow(<SqlEditor {...mockedProps} />);
|
||||||
|
expect(wrapper.find(SplitPane)).to.have.length(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -86,3 +86,8 @@ export function getShortUrl(longUrl, callback) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getTopOffset(element) {
|
||||||
|
const box = element.getBoundingClientRect();
|
||||||
|
return box.top + window.pageYOffset - document.documentElement.clientTop;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue