mirror of
https://github.com/apache/superset.git
synced 2026-04-26 11:34:27 +00:00
chore: Moves spec files to the src folder - iteration 7 (#16943)
This commit is contained in:
committed by
GitHub
parent
028f6c0d3f
commit
1ab36c94f3
@@ -30,7 +30,7 @@ import {
|
||||
AceCompleterKeyword,
|
||||
FullSQLEditor as AceEditor,
|
||||
} from 'src/components/AsyncAceEditor';
|
||||
import { QueryEditor } from '../types';
|
||||
import { QueryEditor } from 'src/SqlLab/types';
|
||||
|
||||
type HotKey = {
|
||||
key: string;
|
||||
48
superset-frontend/src/SqlLab/components/App/App.test.jsx
Normal file
48
superset-frontend/src/SqlLab/components/App/App.test.jsx
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import App from 'src/SqlLab/components/App';
|
||||
import TabbedSqlEditors from 'src/SqlLab/components/TabbedSqlEditors';
|
||||
import sqlLabReducer from 'src/SqlLab/reducers/index';
|
||||
|
||||
describe('SqlLab App', () => {
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
const store = mockStore(sqlLabReducer(undefined, {}), {});
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<App store={store} />).dive();
|
||||
});
|
||||
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<App />)).toBe(true);
|
||||
});
|
||||
|
||||
it('should render', () => {
|
||||
const inner = wrapper.dive();
|
||||
expect(inner.find('.SqlLab')).toHaveLength(1);
|
||||
expect(inner.find(TabbedSqlEditors)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -30,9 +30,9 @@ import {
|
||||
LOCALSTORAGE_WARNING_MESSAGE_THROTTLE_MS,
|
||||
} from 'src/SqlLab/constants';
|
||||
import * as Actions from 'src/SqlLab/actions/sqlLab';
|
||||
import TabbedSqlEditors from './TabbedSqlEditors';
|
||||
import QueryAutoRefresh from './QueryAutoRefresh';
|
||||
import QuerySearch from './QuerySearch';
|
||||
import TabbedSqlEditors from '../TabbedSqlEditors';
|
||||
import QueryAutoRefresh from '../QueryAutoRefresh';
|
||||
import QuerySearch from '../QuerySearch';
|
||||
|
||||
class App extends React.PureComponent {
|
||||
constructor(props) {
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import ColumnElement from 'src/SqlLab/components/ColumnElement';
|
||||
import { mockedActions, table } from 'src/SqlLab/fixtures';
|
||||
|
||||
describe('ColumnElement', () => {
|
||||
const mockedProps = {
|
||||
actions: mockedActions,
|
||||
column: table.columns[0],
|
||||
};
|
||||
it('is valid with props', () => {
|
||||
expect(React.isValidElement(<ColumnElement {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
it('renders a proper primary key', () => {
|
||||
const wrapper = mount(<ColumnElement column={table.columns[0]} />);
|
||||
expect(wrapper.find('i.fa-key')).toExist();
|
||||
expect(wrapper.find('.col-name').first().text()).toBe('id');
|
||||
});
|
||||
it('renders a multi-key column', () => {
|
||||
const wrapper = mount(<ColumnElement column={table.columns[1]} />);
|
||||
expect(wrapper.find('i.fa-link')).toExist();
|
||||
expect(wrapper.find('i.fa-bookmark')).toExist();
|
||||
expect(wrapper.find('.col-name').first().text()).toBe('first_name');
|
||||
});
|
||||
it('renders a column with no keys', () => {
|
||||
const wrapper = mount(<ColumnElement column={table.columns[2]} />);
|
||||
expect(wrapper.find('i')).not.toExist();
|
||||
expect(wrapper.find('.col-name').first().text()).toBe('last_name');
|
||||
});
|
||||
});
|
||||
@@ -20,12 +20,11 @@ import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Alert from 'src/components/Alert';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
import TableView from 'src/components/TableView';
|
||||
import Button from 'src/components/Button';
|
||||
import Loading from '../../components/Loading';
|
||||
import ModalTrigger from '../../components/ModalTrigger';
|
||||
import { EmptyWrapperType } from '../../components/TableView/TableView';
|
||||
import Loading from 'src/components/Loading';
|
||||
import ModalTrigger from 'src/components/ModalTrigger';
|
||||
import { EmptyWrapperType } from 'src/components/TableView/TableView';
|
||||
|
||||
const propTypes = {
|
||||
dbId: PropTypes.number.isRequired,
|
||||
@@ -22,10 +22,9 @@ import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
|
||||
|
||||
import Button from 'src/components/Button';
|
||||
import { exploreChart } from 'src/explore/exploreUtils';
|
||||
import * as actions from '../actions/sqlLab';
|
||||
import * as actions from 'src/SqlLab/actions/sqlLab';
|
||||
|
||||
const propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { shallow } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import shortid from 'shortid';
|
||||
import sqlLabReducer from 'src/SqlLab/reducers/index';
|
||||
import ExploreResultsButton from 'src/SqlLab/components/ExploreResultsButton';
|
||||
import * as exploreUtils from 'src/explore/exploreUtils';
|
||||
import Button from 'src/components/Button';
|
||||
import { queries, queryWithBadColumns } from 'src/SqlLab/fixtures';
|
||||
|
||||
describe('ExploreResultsButton', () => {
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
const database = {
|
||||
allows_subquery: true,
|
||||
};
|
||||
const initialState = {
|
||||
sqlLab: {
|
||||
...sqlLabReducer(undefined, {}),
|
||||
},
|
||||
common: {
|
||||
conf: { SUPERSET_WEBSERVER_TIMEOUT: 45 },
|
||||
},
|
||||
};
|
||||
const store = mockStore(initialState);
|
||||
const mockedProps = {
|
||||
database,
|
||||
show: true,
|
||||
query: queries[0],
|
||||
onClick() {},
|
||||
};
|
||||
const mockColumns = {
|
||||
ds: {
|
||||
is_date: true,
|
||||
name: 'ds',
|
||||
type: 'STRING',
|
||||
},
|
||||
gender: {
|
||||
is_date: false,
|
||||
name: 'gender',
|
||||
type: 'STRING',
|
||||
},
|
||||
};
|
||||
const mockChartTypeBarChart = {
|
||||
label: 'Distribution - Bar Chart',
|
||||
value: 'dist_bar',
|
||||
};
|
||||
const mockChartTypeTB = {
|
||||
label: 'Time Series - Bar Chart',
|
||||
value: 'bar',
|
||||
};
|
||||
const getExploreResultsButtonWrapper = (props = mockedProps) =>
|
||||
shallow(<ExploreResultsButton store={store} {...props} />)
|
||||
.dive()
|
||||
.dive();
|
||||
|
||||
it('renders', () => {
|
||||
expect(React.isValidElement(<ExploreResultsButton />)).toBe(true);
|
||||
});
|
||||
|
||||
it('renders with props', () => {
|
||||
expect(
|
||||
React.isValidElement(<ExploreResultsButton {...mockedProps} />),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('detects bad columns', () => {
|
||||
const wrapper = getExploreResultsButtonWrapper({
|
||||
database,
|
||||
show: true,
|
||||
query: queryWithBadColumns,
|
||||
onClick() {},
|
||||
});
|
||||
|
||||
const badCols = wrapper.instance().getInvalidColumns();
|
||||
expect(badCols).toEqual(['my_dupe_col__2', '__timestamp', '__TIMESTAMP']);
|
||||
|
||||
const msgWrapper = shallow(wrapper.instance().renderInvalidColumnMessage());
|
||||
expect(msgWrapper.find('div')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a Button', () => {
|
||||
const wrapper = getExploreResultsButtonWrapper();
|
||||
expect(wrapper.find(Button)).toExist();
|
||||
});
|
||||
|
||||
describe('datasourceName', () => {
|
||||
let wrapper;
|
||||
let stub;
|
||||
beforeEach(() => {
|
||||
wrapper = getExploreResultsButtonWrapper();
|
||||
stub = sinon.stub(shortid, 'generate').returns('abcd');
|
||||
});
|
||||
afterEach(() => {
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
it('should generate data source name from query', () => {
|
||||
const sampleQuery = queries[0];
|
||||
const name = wrapper.instance().datasourceName();
|
||||
expect(name).toBe(`${sampleQuery.user}-${sampleQuery.tab}-abcd`);
|
||||
});
|
||||
it('should generate data source name with empty query', () => {
|
||||
wrapper.setProps({ query: {} });
|
||||
const name = wrapper.instance().datasourceName();
|
||||
expect(name).toBe('undefined-abcd');
|
||||
});
|
||||
|
||||
it('should build viz options', () => {
|
||||
wrapper.setState({ chartType: mockChartTypeTB });
|
||||
const spy = sinon.spy(wrapper.instance(), 'buildVizOptions');
|
||||
wrapper.instance().buildVizOptions();
|
||||
expect(spy.returnValues[0]).toEqual({
|
||||
schema: 'test_schema',
|
||||
sql: wrapper.instance().props.query.sql,
|
||||
dbId: wrapper.instance().props.query.dbId,
|
||||
columns: Object.values(mockColumns),
|
||||
templateParams: undefined,
|
||||
datasourceName: 'admin-Demo-abcd',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should build visualize advise for long query', () => {
|
||||
const longQuery = { ...queries[0], endDttm: 1476910666798 };
|
||||
const props = {
|
||||
show: true,
|
||||
query: longQuery,
|
||||
database,
|
||||
onClick() {},
|
||||
};
|
||||
const longQueryWrapper = shallow(
|
||||
<ExploreResultsButton store={store} {...props} />,
|
||||
)
|
||||
.dive()
|
||||
.dive();
|
||||
const inst = longQueryWrapper.instance();
|
||||
expect(inst.getQueryDuration()).toBe(100.7050400390625);
|
||||
});
|
||||
|
||||
describe('visualize', () => {
|
||||
const wrapper = getExploreResultsButtonWrapper();
|
||||
const mockOptions = { attr: 'mockOptions' };
|
||||
wrapper.setState({
|
||||
chartType: mockChartTypeBarChart,
|
||||
datasourceName: 'mockDatasourceName',
|
||||
});
|
||||
|
||||
const visualizeURL = '/superset/sqllab_viz/';
|
||||
const visualizeEndpoint = `glob:*${visualizeURL}`;
|
||||
const visualizationPayload = { table_id: 107 };
|
||||
fetchMock.post(visualizeEndpoint, visualizationPayload);
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.stub(exploreUtils, 'getExploreUrl').callsFake(() => 'mockURL');
|
||||
sinon.spy(exploreUtils, 'exportChart');
|
||||
sinon.spy(exploreUtils, 'exploreChart');
|
||||
sinon
|
||||
.stub(wrapper.instance(), 'buildVizOptions')
|
||||
.callsFake(() => mockOptions);
|
||||
});
|
||||
afterEach(() => {
|
||||
exploreUtils.getExploreUrl.restore();
|
||||
exploreUtils.exploreChart.restore();
|
||||
exploreUtils.exportChart.restore();
|
||||
wrapper.instance().buildVizOptions.restore();
|
||||
fetchMock.reset();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -25,9 +25,8 @@ import Alert from 'src/components/Alert';
|
||||
import { t } from '@superset-ui/core';
|
||||
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
|
||||
import shortid from 'shortid';
|
||||
|
||||
import Button from 'src/components/Button';
|
||||
import * as actions from '../actions/sqlLab';
|
||||
import * as actions from 'src/SqlLab/actions/sqlLab';
|
||||
|
||||
const propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
|
||||
import HighlightedSql from 'src/SqlLab/components/HighlightedSql';
|
||||
import ModalTrigger from 'src/components/ModalTrigger';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
|
||||
describe('HighlightedSql', () => {
|
||||
const sql =
|
||||
"SELECT * FROM test WHERE something='fkldasjfklajdslfkjadlskfjkldasjfkladsjfkdjsa'";
|
||||
it('renders with props', () => {
|
||||
expect(React.isValidElement(<HighlightedSql sql={sql} />)).toBe(true);
|
||||
});
|
||||
it('renders a ModalTrigger', () => {
|
||||
const wrapper = shallow(<HighlightedSql sql={sql} />);
|
||||
expect(wrapper.find(ModalTrigger)).toExist();
|
||||
});
|
||||
it('renders a ModalTrigger while using shrink', () => {
|
||||
const wrapper = shallow(<HighlightedSql sql={sql} shrink maxWidth={20} />);
|
||||
expect(wrapper.find(ModalTrigger)).toExist();
|
||||
});
|
||||
it('renders two SyntaxHighlighter in modal', () => {
|
||||
const wrapper = mount(
|
||||
<HighlightedSql
|
||||
sql={sql}
|
||||
rawSql="SELECT * FORM foo"
|
||||
shrink
|
||||
maxWidth={5}
|
||||
/>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
},
|
||||
);
|
||||
const pre = wrapper.find('pre');
|
||||
expect(pre).toHaveLength(1);
|
||||
pre.simulate('click');
|
||||
setTimeout(() => {
|
||||
const modalBody = mount(wrapper.state().modalBody);
|
||||
expect(modalBody.find(SyntaxHighlighter)).toHaveLength(2);
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
@@ -21,8 +21,7 @@ import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/light';
|
||||
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql';
|
||||
import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
import ModalTrigger from '../../components/ModalTrigger';
|
||||
import ModalTrigger from 'src/components/ModalTrigger';
|
||||
|
||||
SyntaxHighlighter.registerLanguage('sql', sql);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import thunk from 'redux-thunk';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import QueryAutoRefresh from 'src/SqlLab/components/QueryAutoRefresh';
|
||||
import { initialState, runningQuery } from 'src/SqlLab/fixtures';
|
||||
|
||||
describe('QueryAutoRefresh', () => {
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
const sqlLab = {
|
||||
...initialState.sqlLab,
|
||||
queries: {
|
||||
ryhMUZCGb: runningQuery,
|
||||
},
|
||||
};
|
||||
const state = {
|
||||
...initialState,
|
||||
sqlLab,
|
||||
};
|
||||
const store = mockStore(state);
|
||||
const getWrapper = () =>
|
||||
shallow(<QueryAutoRefresh store={store} />)
|
||||
.dive()
|
||||
.dive();
|
||||
let wrapper;
|
||||
|
||||
it('shouldCheckForQueries', () => {
|
||||
wrapper = getWrapper();
|
||||
expect(wrapper.instance().shouldCheckForQueries()).toBe(true);
|
||||
});
|
||||
|
||||
it('setUserOffline', () => {
|
||||
wrapper = getWrapper();
|
||||
const spy = sinon.spy(wrapper.instance().props.actions, 'setUserOffline');
|
||||
|
||||
// state not changed
|
||||
wrapper.setState({
|
||||
offline: false,
|
||||
});
|
||||
expect(spy.called).toBe(false);
|
||||
|
||||
// state is changed
|
||||
wrapper.setState({
|
||||
offline: true,
|
||||
});
|
||||
expect(spy.callCount).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -21,8 +21,7 @@ import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { SupersetClient } from '@superset-ui/core';
|
||||
|
||||
import * as Actions from '../actions/sqlLab';
|
||||
import * as Actions from 'src/SqlLab/actions/sqlLab';
|
||||
|
||||
const QUERY_UPDATE_FREQ = 2000;
|
||||
const QUERY_UPDATE_BUFFER_MS = 5000;
|
||||
@@ -19,9 +19,8 @@
|
||||
import React from 'react';
|
||||
import Alert from 'src/components/Alert';
|
||||
import { t } from '@superset-ui/core';
|
||||
|
||||
import QueryTable from './QueryTable';
|
||||
import { Query } from '../types';
|
||||
import { Query } from 'src/SqlLab/types';
|
||||
import QueryTable from 'src/SqlLab/components/QueryTable';
|
||||
|
||||
interface QueryHistoryProps {
|
||||
queries: Query[];
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import thunk from 'redux-thunk';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import QuerySearch from 'src/SqlLab/components/QuerySearch';
|
||||
import { Provider } from 'react-redux';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
import { fireEvent, render, screen, act } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { user } from 'src/SqlLab/fixtures';
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({
|
||||
sqlLab: user,
|
||||
});
|
||||
|
||||
const SEARCH_ENDPOINT = 'glob:*/superset/search_queries?*';
|
||||
const USER_ENDPOINT = 'glob:*/api/v1/query/related/user';
|
||||
const DATABASE_ENDPOINT = 'glob:*/api/v1/database/?*';
|
||||
|
||||
fetchMock.get(SEARCH_ENDPOINT, []);
|
||||
fetchMock.get(USER_ENDPOINT, []);
|
||||
fetchMock.get(DATABASE_ENDPOINT, []);
|
||||
|
||||
describe('QuerySearch', () => {
|
||||
const mockedProps = {
|
||||
actions: { addDangerToast: jest.fn() },
|
||||
displayLimit: 50,
|
||||
};
|
||||
|
||||
it('is valid', () => {
|
||||
expect(
|
||||
React.isValidElement(
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<Provider store={store}>
|
||||
<QuerySearch {...mockedProps} />
|
||||
</Provider>
|
||||
</ThemeProvider>,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// You need this await function in order to change state in the app. In fact you need it everytime you re-render.
|
||||
await act(async () => {
|
||||
render(
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<Provider store={store}>
|
||||
<QuerySearch {...mockedProps} />
|
||||
</Provider>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have three Selects', () => {
|
||||
expect(screen.getByText(/28 days ago/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/now/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/success/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('updates fromTime on user selects from time', () => {
|
||||
const role = screen.getByText(/28 days ago/i);
|
||||
fireEvent.keyDown(role, { key: 'ArrowDown', keyCode: 40 });
|
||||
userEvent.click(screen.getByText(/1 hour ago/i));
|
||||
expect(screen.getByText(/1 hour ago/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('updates toTime on user selects on time', () => {
|
||||
const role = screen.getByText(/now/i);
|
||||
fireEvent.keyDown(role, { key: 'ArrowDown', keyCode: 40 });
|
||||
userEvent.click(screen.getByText(/1 hour ago/i));
|
||||
expect(screen.getByText(/1 hour ago/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('updates status on user selects status', () => {
|
||||
const role = screen.getByText(/success/i);
|
||||
fireEvent.keyDown(role, { key: 'ArrowDown', keyCode: 40 });
|
||||
userEvent.click(screen.getByText(/failed/i));
|
||||
expect(screen.getByText(/failed/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have one input for searchText', () => {
|
||||
expect(
|
||||
screen.getByPlaceholderText(/Query search string/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('updates search text on user inputs search text', () => {
|
||||
const search = screen.getByPlaceholderText(/Query search string/i);
|
||||
userEvent.type(search, 'text');
|
||||
expect(search.value).toBe('text');
|
||||
});
|
||||
|
||||
it('should have one Button', () => {
|
||||
const button = screen.getAllByRole('button');
|
||||
expect(button.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should call API when search button is pressed', async () => {
|
||||
fetchMock.resetHistory();
|
||||
const button = screen.getByRole('button');
|
||||
await act(async () => {
|
||||
userEvent.click(button);
|
||||
});
|
||||
expect(fetchMock.calls(SEARCH_ENDPOINT)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should call API when (only)enter key is pressed', async () => {
|
||||
fetchMock.resetHistory();
|
||||
const search = screen.getByPlaceholderText(/Query search string/i);
|
||||
await act(async () => {
|
||||
userEvent.type(search, 'a');
|
||||
});
|
||||
expect(fetchMock.calls(SEARCH_ENDPOINT)).toHaveLength(0);
|
||||
await act(async () => {
|
||||
userEvent.type(search, '{enter}');
|
||||
});
|
||||
expect(fetchMock.calls(SEARCH_ENDPOINT)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -21,17 +21,17 @@ import Button from 'src/components/Button';
|
||||
import Select from 'src/components/Select';
|
||||
import { styled, t, SupersetClient } from '@superset-ui/core';
|
||||
import { debounce } from 'lodash';
|
||||
import Loading from '../../components/Loading';
|
||||
import QueryTable from './QueryTable';
|
||||
import Loading from 'src/components/Loading';
|
||||
import {
|
||||
now,
|
||||
epochTimeXHoursAgo,
|
||||
epochTimeXDaysAgo,
|
||||
epochTimeXYearsAgo,
|
||||
} from '../../modules/dates';
|
||||
import { STATUS_OPTIONS, TIME_OPTIONS } from '../constants';
|
||||
import AsyncSelect from '../../components/AsyncSelect';
|
||||
import { Query } from '../types';
|
||||
} from 'src/modules/dates';
|
||||
import AsyncSelect from 'src/components/AsyncSelect';
|
||||
import { Query } from 'src/SqlLab/types';
|
||||
import { STATUS_OPTIONS, TIME_OPTIONS } from 'src/SqlLab/constants';
|
||||
import QueryTable from '../QueryTable';
|
||||
|
||||
interface QuerySearchProps {
|
||||
actions: {
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Label from 'src/components/Label';
|
||||
import QueryStateLabel from 'src/SqlLab/components/QueryStateLabel';
|
||||
|
||||
describe('SavedQuery', () => {
|
||||
const mockedProps = {
|
||||
query: {
|
||||
state: 'running',
|
||||
},
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<QueryStateLabel {...mockedProps} />)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
it('has an Overlay and a Popover', () => {
|
||||
const wrapper = shallow(<QueryStateLabel {...mockedProps} />);
|
||||
expect(wrapper.find(Label)).toExist();
|
||||
});
|
||||
});
|
||||
@@ -18,9 +18,8 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import Label from 'src/components/Label';
|
||||
|
||||
import { STATE_TYPE_MAP } from '../constants';
|
||||
import { Query } from '../types';
|
||||
import { STATE_TYPE_MAP } from 'src/SqlLab/constants';
|
||||
import { Query } from 'src/SqlLab/types';
|
||||
|
||||
interface QueryStateLabelProps {
|
||||
query: Query;
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import thunk from 'redux-thunk';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import QueryTable from 'src/SqlLab/components/QueryTable';
|
||||
import TableView from 'src/components/TableView';
|
||||
import { TableCollection } from 'src/components/dataViewCommon';
|
||||
import { Provider } from 'react-redux';
|
||||
import { queries, user } from 'src/SqlLab/fixtures';
|
||||
|
||||
describe('QueryTable', () => {
|
||||
const mockedProps = {
|
||||
queries,
|
||||
displayLimit: 100,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<QueryTable displayLimit={100} />)).toBe(true);
|
||||
});
|
||||
it('is valid with props', () => {
|
||||
expect(React.isValidElement(<QueryTable {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
it('renders a proper table', () => {
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({
|
||||
sqlLab: user,
|
||||
});
|
||||
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<QueryTable {...mockedProps} />
|
||||
</Provider>,
|
||||
);
|
||||
const tableWrapper = wrapper.find(TableView).find(TableCollection);
|
||||
|
||||
expect(wrapper.find(TableView)).toExist();
|
||||
expect(tableWrapper.find('table')).toExist();
|
||||
expect(tableWrapper.find('table').find('thead').find('tr')).toHaveLength(1);
|
||||
expect(tableWrapper.find('table').find('tbody').find('tr')).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { styledMount } from 'spec/helpers/theming';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import { Provider } from 'react-redux';
|
||||
import sinon from 'sinon';
|
||||
import Alert from 'src/components/Alert';
|
||||
import ProgressBar from 'src/components/ProgressBar';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import FilterableTable from 'src/components/FilterableTable/FilterableTable';
|
||||
import ExploreResultsButton from 'src/SqlLab/components/ExploreResultsButton';
|
||||
import ResultSet from 'src/SqlLab/components/ResultSet';
|
||||
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||
import {
|
||||
cachedQuery,
|
||||
failedQueryWithErrorMessage,
|
||||
failedQueryWithErrors,
|
||||
queries,
|
||||
runningQuery,
|
||||
stoppedQuery,
|
||||
initialState,
|
||||
user,
|
||||
queryWithNoQueryLimit,
|
||||
} from 'src/SqlLab/fixtures';
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore(initialState);
|
||||
const clearQuerySpy = sinon.spy();
|
||||
const fetchQuerySpy = sinon.spy();
|
||||
const reRunQuerySpy = sinon.spy();
|
||||
const mockedProps = {
|
||||
actions: {
|
||||
clearQueryResults: clearQuerySpy,
|
||||
fetchQueryResults: fetchQuerySpy,
|
||||
reRunQuery: reRunQuerySpy,
|
||||
},
|
||||
cache: true,
|
||||
query: queries[0],
|
||||
height: 140,
|
||||
database: { allows_virtual_table_explore: true },
|
||||
user,
|
||||
defaultQueryLimit: 1000,
|
||||
};
|
||||
const stoppedQueryProps = { ...mockedProps, query: stoppedQuery };
|
||||
const runningQueryProps = { ...mockedProps, query: runningQuery };
|
||||
const cachedQueryProps = { ...mockedProps, query: cachedQuery };
|
||||
const failedQueryWithErrorMessageProps = {
|
||||
...mockedProps,
|
||||
query: failedQueryWithErrorMessage,
|
||||
};
|
||||
const failedQueryWithErrorsProps = {
|
||||
...mockedProps,
|
||||
query: failedQueryWithErrors,
|
||||
};
|
||||
const newProps = {
|
||||
query: {
|
||||
cached: false,
|
||||
resultsKey: 'new key',
|
||||
results: {
|
||||
data: [{ a: 1 }],
|
||||
},
|
||||
},
|
||||
};
|
||||
fetchMock.get('glob:*/api/v1/dataset?*', { result: [] });
|
||||
|
||||
test('is valid', () => {
|
||||
expect(React.isValidElement(<ResultSet {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
|
||||
test('renders a Table', () => {
|
||||
const wrapper = shallow(<ResultSet {...mockedProps} />);
|
||||
expect(wrapper.find(FilterableTable)).toExist();
|
||||
});
|
||||
|
||||
describe('componentDidMount', () => {
|
||||
const propsWithError = {
|
||||
...mockedProps,
|
||||
query: { ...queries[0], errorMessage: 'Your session timed out' },
|
||||
};
|
||||
let spy;
|
||||
beforeEach(() => {
|
||||
reRunQuerySpy.resetHistory();
|
||||
spy = sinon.spy(ResultSet.prototype, 'componentDidMount');
|
||||
});
|
||||
afterEach(() => {
|
||||
spy.restore();
|
||||
});
|
||||
it('should call reRunQuery if timed out', () => {
|
||||
shallow(<ResultSet {...propsWithError} />);
|
||||
expect(reRunQuerySpy.callCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should not call reRunQuery if no error', () => {
|
||||
shallow(<ResultSet {...mockedProps} />);
|
||||
expect(reRunQuerySpy.callCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UNSAFE_componentWillReceiveProps', () => {
|
||||
const wrapper = shallow(<ResultSet {...mockedProps} />);
|
||||
let spy;
|
||||
beforeEach(() => {
|
||||
clearQuerySpy.resetHistory();
|
||||
fetchQuerySpy.resetHistory();
|
||||
spy = sinon.spy(ResultSet.prototype, 'UNSAFE_componentWillReceiveProps');
|
||||
});
|
||||
afterEach(() => {
|
||||
spy.restore();
|
||||
});
|
||||
it('should update cached data', () => {
|
||||
wrapper.setProps(newProps);
|
||||
|
||||
expect(wrapper.state().data).toEqual(newProps.query.results.data);
|
||||
expect(clearQuerySpy.callCount).toBe(1);
|
||||
expect(clearQuerySpy.getCall(0).args[0]).toEqual(newProps.query);
|
||||
expect(fetchQuerySpy.callCount).toBe(1);
|
||||
expect(fetchQuerySpy.getCall(0).args[0]).toEqual(newProps.query);
|
||||
});
|
||||
});
|
||||
|
||||
test('should render success query', () => {
|
||||
const wrapper = shallow(<ResultSet {...mockedProps} />);
|
||||
const filterableTable = wrapper.find(FilterableTable);
|
||||
expect(filterableTable.props().data).toBe(mockedProps.query.results.data);
|
||||
expect(wrapper.find(ExploreResultsButton)).toExist();
|
||||
});
|
||||
test('should render empty results', () => {
|
||||
const props = {
|
||||
...mockedProps,
|
||||
query: { ...mockedProps.query, results: { data: [] } },
|
||||
};
|
||||
const wrapper = styledMount(
|
||||
<Provider store={store}>
|
||||
<ResultSet {...props} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(wrapper.find(FilterableTable)).not.toExist();
|
||||
expect(wrapper.find(Alert)).toExist();
|
||||
expect(wrapper.find(Alert).render().text()).toBe(
|
||||
'The query returned no data',
|
||||
);
|
||||
});
|
||||
|
||||
test('should render cached query', () => {
|
||||
const wrapper = shallow(<ResultSet {...cachedQueryProps} />);
|
||||
const cachedData = [{ col1: 'a', col2: 'b' }];
|
||||
wrapper.setState({ data: cachedData });
|
||||
const filterableTable = wrapper.find(FilterableTable);
|
||||
expect(filterableTable.props().data).toBe(cachedData);
|
||||
});
|
||||
|
||||
test('should render stopped query', () => {
|
||||
const wrapper = shallow(<ResultSet {...stoppedQueryProps} />);
|
||||
expect(wrapper.find(Alert)).toExist();
|
||||
});
|
||||
|
||||
test('should render running/pending/fetching query', () => {
|
||||
const wrapper = shallow(<ResultSet {...runningQueryProps} />);
|
||||
expect(wrapper.find(ProgressBar)).toExist();
|
||||
});
|
||||
|
||||
test('should render a failed query with an error message', () => {
|
||||
const wrapper = shallow(<ResultSet {...failedQueryWithErrorMessageProps} />);
|
||||
expect(wrapper.find(ErrorMessageWithStackTrace)).toExist();
|
||||
});
|
||||
|
||||
test('should render a failed query with an errors object', () => {
|
||||
const wrapper = shallow(<ResultSet {...failedQueryWithErrorsProps} />);
|
||||
expect(wrapper.find(ErrorMessageWithStackTrace)).toExist();
|
||||
});
|
||||
|
||||
test('renders if there is no limit in query.results but has queryLimit', () => {
|
||||
render(<ResultSet {...mockedProps} />, { useRedux: true });
|
||||
expect(screen.getByRole('grid')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders if there is a limit in query.results but not queryLimit', () => {
|
||||
const props = { ...mockedProps, query: queryWithNoQueryLimit };
|
||||
render(<ResultSet {...props} />, { useRedux: true });
|
||||
expect(screen.getByRole('grid')).toBeInTheDocument();
|
||||
});
|
||||
@@ -36,17 +36,17 @@ import { debounce } from 'lodash';
|
||||
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
|
||||
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
||||
import Loading from '../../components/Loading';
|
||||
import ExploreCtasResultsButton from './ExploreCtasResultsButton';
|
||||
import ExploreResultsButton from './ExploreResultsButton';
|
||||
import HighlightedSql from './HighlightedSql';
|
||||
import FilterableTable from '../../components/FilterableTable/FilterableTable';
|
||||
import QueryStateLabel from './QueryStateLabel';
|
||||
import CopyToClipboard from '../../components/CopyToClipboard';
|
||||
import { prepareCopyToClipboardTabularData } from '../../utils/common';
|
||||
import { exploreChart } from '../../explore/exploreUtils';
|
||||
import { CtasEnum } from '../actions/sqlLab';
|
||||
import { Query } from '../types';
|
||||
import Loading from 'src/components/Loading';
|
||||
import FilterableTable from 'src/components/FilterableTable/FilterableTable';
|
||||
import CopyToClipboard from 'src/components/CopyToClipboard';
|
||||
import { prepareCopyToClipboardTabularData } from 'src/utils/common';
|
||||
import { exploreChart } from 'src/explore/exploreUtils';
|
||||
import { CtasEnum } from 'src/SqlLab/actions/sqlLab';
|
||||
import { Query } from 'src/SqlLab/types';
|
||||
import ExploreCtasResultsButton from '../ExploreCtasResultsButton';
|
||||
import ExploreResultsButton from '../ExploreResultsButton';
|
||||
import HighlightedSql from '../HighlightedSql';
|
||||
import QueryStateLabel from '../QueryStateLabel';
|
||||
|
||||
enum DatasetRadioState {
|
||||
SAVE_NEW = 1,
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Radio } from 'src/components/Radio';
|
||||
import { AutoComplete, Input } from 'src/common/components';
|
||||
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
|
||||
|
||||
describe('SaveDatasetModal', () => {
|
||||
const mockedProps = {
|
||||
visible: false,
|
||||
onOk: () => {},
|
||||
onHide: () => {},
|
||||
handleDatasetNameChange: () => {},
|
||||
handleSaveDatasetRadioBtnState: () => {},
|
||||
saveDatasetRadioBtnState: 1,
|
||||
handleOverwriteCancel: () => {},
|
||||
handleOverwriteDataset: () => {},
|
||||
handleOverwriteDatasetOption: () => {},
|
||||
defaultCreateDatasetValue: 'someDatasets',
|
||||
shouldOverwriteDataset: false,
|
||||
userDatasetOptions: [],
|
||||
disableSaveAndExploreBtn: false,
|
||||
handleSaveDatasetModalSearch: () => Promise,
|
||||
filterAutocompleteOption: () => false,
|
||||
onChangeAutoComplete: () => {},
|
||||
};
|
||||
it('renders a radio group btn', () => {
|
||||
// @ts-ignore
|
||||
const wrapper = shallow(<SaveDatasetModal {...mockedProps} />);
|
||||
expect(wrapper.find(Radio.Group)).toExist();
|
||||
});
|
||||
it('renders a autocomplete', () => {
|
||||
// @ts-ignore
|
||||
const wrapper = shallow(<SaveDatasetModal {...mockedProps} />);
|
||||
expect(wrapper.find(AutoComplete)).toExist();
|
||||
});
|
||||
it('renders an input form', () => {
|
||||
// @ts-ignore
|
||||
const wrapper = shallow(<SaveDatasetModal {...mockedProps} />);
|
||||
expect(wrapper.find(Input)).toExist();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import * as sinon from 'sinon';
|
||||
import SaveQuery from 'src/SqlLab/components/SaveQuery';
|
||||
import Modal from 'src/components/Modal';
|
||||
import Button from 'src/components/Button';
|
||||
import { FormItem } from 'src/components/Form';
|
||||
|
||||
describe('SavedQuery', () => {
|
||||
const mockedProps = {
|
||||
query: {
|
||||
dbId: 1,
|
||||
schema: 'main',
|
||||
sql: 'SELECT * FROM t',
|
||||
},
|
||||
defaultLabel: 'untitled',
|
||||
animation: false,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<SaveQuery />)).toBe(true);
|
||||
});
|
||||
it('is valid with props', () => {
|
||||
expect(React.isValidElement(<SaveQuery {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
it('has a Modal', () => {
|
||||
const wrapper = shallow(<SaveQuery {...mockedProps} />);
|
||||
expect(wrapper.find(Modal)).toExist();
|
||||
});
|
||||
// TODO: eschutho convert test to RTL
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('has a cancel button', () => {
|
||||
const wrapper = shallow(<SaveQuery {...mockedProps} />);
|
||||
const modal = wrapper.find(Modal);
|
||||
|
||||
expect(modal.find('[data-test="cancel-query"]')).toHaveLength(1);
|
||||
});
|
||||
it('has 2 FormItem', () => {
|
||||
const wrapper = shallow(<SaveQuery {...mockedProps} />);
|
||||
const modal = wrapper.find(Modal);
|
||||
|
||||
expect(modal.find(FormItem)).toHaveLength(2);
|
||||
});
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('has a save button if this is a new query', () => {
|
||||
const saveSpy = sinon.spy();
|
||||
const wrapper = shallow(<SaveQuery {...mockedProps} onSave={saveSpy} />);
|
||||
const modal = wrapper.find(Modal);
|
||||
|
||||
expect(modal.find(Button)).toHaveLength(2);
|
||||
modal.find(Button).at(0).simulate('click');
|
||||
expect(saveSpy.calledOnce).toBe(true);
|
||||
});
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('has an update button if this is an existing query', () => {
|
||||
const updateSpy = sinon.spy();
|
||||
const props = {
|
||||
...mockedProps,
|
||||
query: {
|
||||
...mockedProps.query,
|
||||
remoteId: '42',
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(<SaveQuery {...props} onUpdate={updateSpy} />);
|
||||
const modal = wrapper.find(Modal);
|
||||
|
||||
expect(modal.find(Button)).toHaveLength(3);
|
||||
modal.find(Button).at(0).simulate('click');
|
||||
expect(updateSpy.calledOnce).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import * as featureFlags from 'src/featureFlags';
|
||||
import { Provider } from 'react-redux';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
import { render, screen, act } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import * as utils from 'src/utils/common';
|
||||
import ShareSqlLabQuery from 'src/SqlLab/components/ShareSqlLabQuery';
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
let isFeatureEnabledMock;
|
||||
|
||||
const standardProvider = ({ children }) => (
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
const defaultProps = {
|
||||
queryEditor: {
|
||||
dbId: 0,
|
||||
title: 'query title',
|
||||
schema: 'query_schema',
|
||||
autorun: false,
|
||||
sql: 'SELECT * FROM ...',
|
||||
remoteId: 999,
|
||||
},
|
||||
addDangerToast: jest.fn(),
|
||||
};
|
||||
|
||||
describe('ShareSqlLabQuery', () => {
|
||||
const storeQueryUrl = 'glob:*/kv/store/';
|
||||
const storeQueryMockId = '123';
|
||||
|
||||
beforeEach(async () => {
|
||||
fetchMock.post(storeQueryUrl, () => ({ id: storeQueryMockId }), {
|
||||
overwriteRoutes: true,
|
||||
});
|
||||
fetchMock.resetHistory();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(fetchMock.reset);
|
||||
|
||||
describe('via /kv/store', () => {
|
||||
beforeAll(() => {
|
||||
isFeatureEnabledMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockImplementation(() => true);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
isFeatureEnabledMock.restore();
|
||||
});
|
||||
|
||||
it('calls storeQuery() with the query when getCopyUrl() is called', async () => {
|
||||
await act(async () => {
|
||||
render(<ShareSqlLabQuery {...defaultProps} />, {
|
||||
wrapper: standardProvider,
|
||||
});
|
||||
});
|
||||
const button = screen.getByRole('button');
|
||||
const storeQuerySpy = jest.spyOn(utils, 'storeQuery');
|
||||
userEvent.click(button);
|
||||
expect(storeQuerySpy.mock.calls).toHaveLength(1);
|
||||
storeQuerySpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('via saved query', () => {
|
||||
beforeAll(() => {
|
||||
isFeatureEnabledMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockImplementation(() => false);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
isFeatureEnabledMock.restore();
|
||||
});
|
||||
|
||||
it('does not call storeQuery() with the query when getCopyUrl() is called and feature is not enabled', async () => {
|
||||
await act(async () => {
|
||||
render(<ShareSqlLabQuery {...defaultProps} />, {
|
||||
wrapper: standardProvider,
|
||||
});
|
||||
});
|
||||
const storeQuerySpy = jest.spyOn(utils, 'storeQuery');
|
||||
const button = screen.getByRole('button');
|
||||
userEvent.click(button);
|
||||
expect(storeQuerySpy.mock.calls).toHaveLength(0);
|
||||
storeQuerySpy.mockRestore();
|
||||
});
|
||||
|
||||
it('button is disabled and there is a request to save the query', async () => {
|
||||
const updatedProps = {
|
||||
queryEditor: {
|
||||
...defaultProps.queryEditor,
|
||||
remoteId: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
render(<ShareSqlLabQuery {...updatedProps} />, {
|
||||
wrapper: standardProvider,
|
||||
});
|
||||
const button = await screen.findByRole('button', { name: /copy link/i });
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -24,8 +24,8 @@ import withToasts from 'src/components/MessageToasts/withToasts';
|
||||
import CopyToClipboard from 'src/components/CopyToClipboard';
|
||||
import { storeQuery } from 'src/utils/common';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { FeatureFlag, isFeatureEnabled } from '../../featureFlags';
|
||||
import { QueryEditor } from '../types';
|
||||
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import { QueryEditor } from 'src/SqlLab/types';
|
||||
|
||||
interface ShareSqlLabQueryPropTypes {
|
||||
queryEditor: QueryEditor;
|
||||
@@ -20,9 +20,8 @@ import React from 'react';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/light';
|
||||
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql';
|
||||
import github from 'react-syntax-highlighter/dist/cjs/styles/hljs/github';
|
||||
|
||||
import { IconTooltip } from '../../components/IconTooltip';
|
||||
import ModalTrigger from '../../components/ModalTrigger';
|
||||
import { IconTooltip } from 'src/components/IconTooltip';
|
||||
import ModalTrigger from 'src/components/ModalTrigger';
|
||||
|
||||
SyntaxHighlighter.registerLanguage('sql', sql);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import { styledShallow as shallow } from 'spec/helpers/theming';
|
||||
import SouthPaneContainer from 'src/SqlLab/components/SouthPane/state';
|
||||
import ResultSet from 'src/SqlLab/components/ResultSet';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { STATUS_OPTIONS } from 'src/SqlLab/constants';
|
||||
import { initialState } from 'src/SqlLab/fixtures';
|
||||
|
||||
const mockedProps = {
|
||||
editorQueries: [
|
||||
{
|
||||
cached: false,
|
||||
changedOn: Date.now(),
|
||||
db: 'main',
|
||||
dbId: 1,
|
||||
id: 'LCly_kkIN',
|
||||
startDttm: Date.now(),
|
||||
},
|
||||
{
|
||||
cached: false,
|
||||
changedOn: 1559238500401,
|
||||
db: 'main',
|
||||
dbId: 1,
|
||||
id: 'lXJa7F9_r',
|
||||
startDttm: 1559238500401,
|
||||
},
|
||||
{
|
||||
cached: false,
|
||||
changedOn: 1559238506925,
|
||||
db: 'main',
|
||||
dbId: 1,
|
||||
id: '2g2_iRFMl',
|
||||
startDttm: 1559238506925,
|
||||
},
|
||||
{
|
||||
cached: false,
|
||||
changedOn: 1559238516395,
|
||||
db: 'main',
|
||||
dbId: 1,
|
||||
id: 'erWdqEWPm',
|
||||
startDttm: 1559238516395,
|
||||
},
|
||||
],
|
||||
latestQueryId: 'LCly_kkIN',
|
||||
dataPreviewQueries: [],
|
||||
actions: {},
|
||||
activeSouthPaneTab: '',
|
||||
height: 1,
|
||||
displayLimit: 1,
|
||||
databases: {},
|
||||
offline: false,
|
||||
};
|
||||
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
const store = mockStore(initialState);
|
||||
|
||||
describe('SouthPane', () => {
|
||||
const getWrapper = () =>
|
||||
shallow(<SouthPaneContainer store={store} {...mockedProps} />).dive();
|
||||
|
||||
let wrapper;
|
||||
|
||||
it('should render offline when the state is offline', () => {
|
||||
wrapper = getWrapper().dive();
|
||||
wrapper.setProps({ offline: true });
|
||||
expect(wrapper.childAt(0).text()).toBe(STATUS_OPTIONS.offline);
|
||||
});
|
||||
|
||||
it('should pass latest query down to ResultSet component', () => {
|
||||
wrapper = getWrapper().dive();
|
||||
expect(wrapper.find(ResultSet)).toExist();
|
||||
expect(wrapper.find(ResultSet).props().query.id).toEqual(
|
||||
mockedProps.latestQueryId,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -18,8 +18,8 @@
|
||||
*/
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import * as Actions from '../../actions/sqlLab';
|
||||
import SouthPane from './SouthPane';
|
||||
import * as Actions from 'src/SqlLab/actions/sqlLab';
|
||||
import SouthPane from '.';
|
||||
|
||||
function mapStateToProps({ sqlLab }: Record<string, any>) {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
import { Provider } from 'react-redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import {
|
||||
SQL_EDITOR_GUTTER_HEIGHT,
|
||||
SQL_EDITOR_GUTTER_MARGIN,
|
||||
SQL_TOOLBAR_HEIGHT,
|
||||
} from 'src/SqlLab/constants';
|
||||
import AceEditorWrapper from 'src/SqlLab/components/AceEditorWrapper';
|
||||
import ConnectedSouthPane from 'src/SqlLab/components/SouthPane/state';
|
||||
import SqlEditor from 'src/SqlLab/components/SqlEditor';
|
||||
import SqlEditorLeftBar from 'src/SqlLab/components/SqlEditorLeftBar';
|
||||
import { Dropdown } from 'src/common/components';
|
||||
import {
|
||||
queryEditorSetFunctionNames,
|
||||
queryEditorSetSelectedText,
|
||||
queryEditorSetSchemaOptions,
|
||||
} from 'src/SqlLab/actions/sqlLab';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
import { initialState, queries, table } from 'src/SqlLab/fixtures';
|
||||
|
||||
const MOCKED_SQL_EDITOR_HEIGHT = 500;
|
||||
|
||||
fetchMock.get('glob:*/api/v1/database/*', { result: [] });
|
||||
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
const store = mockStore(initialState);
|
||||
|
||||
describe('SqlEditor', () => {
|
||||
const mockedProps = {
|
||||
actions: {
|
||||
queryEditorSetFunctionNames,
|
||||
queryEditorSetSelectedText,
|
||||
queryEditorSetSchemaOptions,
|
||||
addDangerToast: jest.fn(),
|
||||
},
|
||||
database: {},
|
||||
queryEditorId: initialState.sqlLab.queryEditors[0].id,
|
||||
latestQuery: queries[0],
|
||||
tables: [table],
|
||||
getHeight: () => '100px',
|
||||
editorQueries: [],
|
||||
dataPreviewQueries: [],
|
||||
defaultQueryLimit: 1000,
|
||||
maxRow: 100000,
|
||||
displayLimit: 100,
|
||||
};
|
||||
|
||||
const buildWrapper = (props = {}) =>
|
||||
mount(
|
||||
<Provider store={store}>
|
||||
<SqlEditor {...mockedProps} {...props} />
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
},
|
||||
);
|
||||
|
||||
it('render a SqlEditorLeftBar', async () => {
|
||||
const wrapper = buildWrapper();
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find(SqlEditorLeftBar)).toExist();
|
||||
});
|
||||
it('render an AceEditorWrapper', async () => {
|
||||
const wrapper = buildWrapper();
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find(AceEditorWrapper)).toExist();
|
||||
});
|
||||
it('render a SouthPane', async () => {
|
||||
const wrapper = buildWrapper();
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find(ConnectedSouthPane)).toExist();
|
||||
});
|
||||
// TODO eschutho convert tests to RTL
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('does not overflow the editor window', async () => {
|
||||
const wrapper = buildWrapper();
|
||||
await waitForComponentToPaint(wrapper);
|
||||
const totalSize =
|
||||
parseFloat(wrapper.find(AceEditorWrapper).props().height) +
|
||||
wrapper.find(ConnectedSouthPane).props().height +
|
||||
SQL_TOOLBAR_HEIGHT +
|
||||
SQL_EDITOR_GUTTER_MARGIN * 2 +
|
||||
SQL_EDITOR_GUTTER_HEIGHT;
|
||||
expect(totalSize).toEqual(MOCKED_SQL_EDITOR_HEIGHT);
|
||||
});
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('does not overflow the editor window after resizing', async () => {
|
||||
const wrapper = buildWrapper();
|
||||
wrapper.setState({ height: 450 });
|
||||
await waitForComponentToPaint(wrapper);
|
||||
const totalSize =
|
||||
parseFloat(wrapper.find(AceEditorWrapper).props().height) +
|
||||
wrapper.find(ConnectedSouthPane).props().height +
|
||||
SQL_TOOLBAR_HEIGHT +
|
||||
SQL_EDITOR_GUTTER_MARGIN * 2 +
|
||||
SQL_EDITOR_GUTTER_HEIGHT;
|
||||
expect(totalSize).toEqual(450);
|
||||
});
|
||||
it('render a Limit Dropdown', async () => {
|
||||
const defaultQueryLimit = 101;
|
||||
const updatedProps = { ...mockedProps, defaultQueryLimit };
|
||||
const wrapper = buildWrapper(updatedProps);
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find(Dropdown)).toExist();
|
||||
});
|
||||
});
|
||||
@@ -64,15 +64,15 @@ import {
|
||||
SQL_TOOLBAR_HEIGHT,
|
||||
} from 'src/SqlLab/constants';
|
||||
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import TemplateParamsEditor from './TemplateParamsEditor';
|
||||
import ConnectedSouthPane from './SouthPane/state';
|
||||
import SaveQuery from './SaveQuery';
|
||||
import ScheduleQueryButton from './ScheduleQueryButton';
|
||||
import EstimateQueryCostButton from './EstimateQueryCostButton';
|
||||
import ShareSqlLabQuery from './ShareSqlLabQuery';
|
||||
import SqlEditorLeftBar from './SqlEditorLeftBar';
|
||||
import AceEditorWrapper from './AceEditorWrapper';
|
||||
import RunQueryActionButton from './RunQueryActionButton';
|
||||
import TemplateParamsEditor from '../TemplateParamsEditor';
|
||||
import ConnectedSouthPane from '../SouthPane/state';
|
||||
import SaveQuery from '../SaveQuery';
|
||||
import ScheduleQueryButton from '../ScheduleQueryButton';
|
||||
import EstimateQueryCostButton from '../EstimateQueryCostButton';
|
||||
import ShareSqlLabQuery from '../ShareSqlLabQuery';
|
||||
import SqlEditorLeftBar from '../SqlEditorLeftBar';
|
||||
import AceEditorWrapper from '../AceEditorWrapper';
|
||||
import RunQueryActionButton from '../RunQueryActionButton';
|
||||
|
||||
const LIMIT_DROPDOWN = [10, 100, 1000, 10000, 100000];
|
||||
const SQL_EDITOR_PADDING = 10;
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { shallow } from 'enzyme';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Provider } from 'react-redux';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import thunk from 'redux-thunk';
|
||||
import SqlEditorLeftBar from 'src/SqlLab/components/SqlEditorLeftBar';
|
||||
import TableElement from 'src/SqlLab/components/TableElement';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
import {
|
||||
table,
|
||||
initialState,
|
||||
databases,
|
||||
defaultQueryEditor,
|
||||
mockedActions,
|
||||
} from 'src/SqlLab/fixtures';
|
||||
|
||||
const mockedProps = {
|
||||
actions: mockedActions,
|
||||
tables: [table],
|
||||
queryEditor: defaultQueryEditor,
|
||||
database: databases,
|
||||
height: 0,
|
||||
};
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
const store = mockStore(initialState);
|
||||
fetchMock.get('glob:*/api/v1/database/*/schemas/?*', { result: [] });
|
||||
describe('SqlEditorLeftBar', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<SqlEditorLeftBar {...mockedProps} />, {
|
||||
context: { store },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<SqlEditorLeftBar {...mockedProps} />)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a TableElement', () => {
|
||||
expect(wrapper.find(TableElement)).toExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Left Panel Expansion', () => {
|
||||
it('table should be visible when expanded is true', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<Provider store={store}>
|
||||
<SqlEditorLeftBar {...mockedProps} />
|
||||
</Provider>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
const dbSelect = screen.getByRole('combobox', {
|
||||
name: 'Select database or type database name',
|
||||
});
|
||||
const schemaSelect = screen.getByRole('combobox', {
|
||||
name: 'Select schema or type schema name',
|
||||
});
|
||||
const dropdown = screen.getByText(/Select table or type table name/i);
|
||||
const abUser = screen.getByText(/ab_user/i);
|
||||
expect(dbSelect).toBeInTheDocument();
|
||||
expect(schemaSelect).toBeInTheDocument();
|
||||
expect(dropdown).toBeInTheDocument();
|
||||
expect(abUser).toBeInTheDocument();
|
||||
expect(
|
||||
container.querySelector('.ant-collapse-content-active'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should toggle the table when the header is clicked', async () => {
|
||||
const collapseMock = jest.fn();
|
||||
render(
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<Provider store={store}>
|
||||
<SqlEditorLeftBar
|
||||
actions={{ ...mockedActions, collapseTable: collapseMock }}
|
||||
tables={[table]}
|
||||
queryEditor={defaultQueryEditor}
|
||||
database={databases}
|
||||
height={0}
|
||||
/>
|
||||
</Provider>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
const header = screen.getByText(/ab_user/);
|
||||
userEvent.click(header);
|
||||
expect(collapseMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -22,9 +22,9 @@ import Button from 'src/components/Button';
|
||||
import { t, styled, css } from '@superset-ui/core';
|
||||
import Collapse from 'src/components/Collapse';
|
||||
import Icons from 'src/components/Icons';
|
||||
import TableElement from './TableElement';
|
||||
import TableSelector from '../../components/TableSelector';
|
||||
import { IconTooltip } from '../../components/IconTooltip';
|
||||
import TableSelector from 'src/components/TableSelector';
|
||||
import { IconTooltip } from 'src/components/IconTooltip';
|
||||
import TableElement from '../TableElement';
|
||||
|
||||
const propTypes = {
|
||||
queryEditor: PropTypes.object.isRequired,
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import TabStatusIcon from 'src/SqlLab/components/TabStatusIcon';
|
||||
|
||||
function setup() {
|
||||
const onClose = sinon.spy();
|
||||
const wrapper = shallow(
|
||||
<TabStatusIcon onClose={onClose} tabState="running" />,
|
||||
);
|
||||
return { wrapper, onClose };
|
||||
}
|
||||
|
||||
describe('TabStatusIcon', () => {
|
||||
it('renders a circle without an x when hovered', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper.find('div.circle')).toExist();
|
||||
expect(wrapper.text()).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -17,8 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import { QueryState } from '../types';
|
||||
import { QueryState } from 'src/SqlLab/types';
|
||||
|
||||
interface TabStatusIconProps {
|
||||
tabState: QueryState;
|
||||
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import URI from 'urijs';
|
||||
import { Provider } from 'react-redux';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
import { EditableTabs } from 'src/components/Tabs';
|
||||
import TabbedSqlEditors from 'src/SqlLab/components/TabbedSqlEditors';
|
||||
import SqlEditor from 'src/SqlLab/components/SqlEditor';
|
||||
import { table, initialState } from 'src/SqlLab/fixtures';
|
||||
|
||||
fetchMock.get('glob:*/api/v1/database/*', {});
|
||||
fetchMock.get('glob:*/savedqueryviewapi/api/get/*', {});
|
||||
fetchMock.get('glob:*/kv/*', {});
|
||||
|
||||
describe('TabbedSqlEditors', () => {
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureStore(middlewares);
|
||||
const store = mockStore(initialState);
|
||||
|
||||
const tabHistory = ['dfsadfs', 'newEditorId'];
|
||||
|
||||
const tables = [
|
||||
{ ...table, dataPreviewQueryId: 'B1-VQU1zW', queryEditorId: 'newEditorId' },
|
||||
];
|
||||
|
||||
const queryEditors = [
|
||||
{
|
||||
autorun: false,
|
||||
dbId: 1,
|
||||
id: 'newEditorId',
|
||||
latestQueryId: 'B1-VQU1zW',
|
||||
schema: null,
|
||||
selectedText: null,
|
||||
sql: 'SELECT ds...',
|
||||
title: 'Untitled Query',
|
||||
},
|
||||
];
|
||||
const queries = {
|
||||
'B1-VQU1zW': {
|
||||
id: 'B1-VQU1zW',
|
||||
sqlEditorId: 'newEditorId',
|
||||
tableName: 'ab_user',
|
||||
state: 'success',
|
||||
},
|
||||
};
|
||||
const mockedProps = {
|
||||
actions: {},
|
||||
databases: {},
|
||||
tables: [],
|
||||
queries: {},
|
||||
queryEditors: initialState.sqlLab.queryEditors,
|
||||
tabHistory: initialState.sqlLab.tabHistory,
|
||||
editorHeight: '',
|
||||
getHeight: () => '100px',
|
||||
database: {},
|
||||
defaultQueryLimit: 1000,
|
||||
maxRow: 100000,
|
||||
};
|
||||
const getWrapper = () =>
|
||||
shallow(<TabbedSqlEditors store={store} {...mockedProps} />)
|
||||
.dive()
|
||||
.dive();
|
||||
|
||||
const mountWithAct = async () =>
|
||||
act(async () => {
|
||||
mount(
|
||||
<Provider store={store}>
|
||||
<TabbedSqlEditors {...mockedProps} />
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let wrapper;
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<TabbedSqlEditors {...mockedProps} />)).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
describe('componentDidMount', () => {
|
||||
let uriStub;
|
||||
beforeEach(() => {
|
||||
sinon.stub(window.history, 'replaceState');
|
||||
uriStub = sinon.stub(URI.prototype, 'search');
|
||||
});
|
||||
afterEach(() => {
|
||||
window.history.replaceState.restore();
|
||||
uriStub.restore();
|
||||
});
|
||||
it('should handle id', async () => {
|
||||
uriStub.returns({ id: 1 });
|
||||
await mountWithAct();
|
||||
expect(window.history.replaceState.getCall(0).args[2]).toBe(
|
||||
'/superset/sqllab',
|
||||
);
|
||||
});
|
||||
it('should handle savedQueryId', async () => {
|
||||
uriStub.returns({ savedQueryId: 1 });
|
||||
await mountWithAct();
|
||||
expect(window.history.replaceState.getCall(0).args[2]).toBe(
|
||||
'/superset/sqllab',
|
||||
);
|
||||
});
|
||||
it('should handle sql', async () => {
|
||||
uriStub.returns({ sql: 1, dbid: 1 });
|
||||
await mountWithAct();
|
||||
expect(window.history.replaceState.getCall(0).args[2]).toBe(
|
||||
'/superset/sqllab',
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('UNSAFE_componentWillReceiveProps', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = getWrapper();
|
||||
wrapper.setProps({ queryEditors, queries, tabHistory, tables });
|
||||
});
|
||||
it('should update queriesArray and dataPreviewQueries', () => {
|
||||
expect(wrapper.state().queriesArray.slice(-1)[0]).toBe(
|
||||
queries['B1-VQU1zW'],
|
||||
);
|
||||
expect(wrapper.state().dataPreviewQueries.slice(-1)[0]).toEqual(
|
||||
queries['B1-VQU1zW'],
|
||||
);
|
||||
});
|
||||
});
|
||||
it('should rename Tab', () => {
|
||||
global.prompt = () => 'new title';
|
||||
wrapper = getWrapper();
|
||||
sinon.stub(wrapper.instance().props.actions, 'queryEditorSetTitle');
|
||||
|
||||
wrapper.instance().renameTab(queryEditors[0]);
|
||||
expect(
|
||||
wrapper.instance().props.actions.queryEditorSetTitle.getCall(0).args[1],
|
||||
).toBe('new title');
|
||||
|
||||
delete global.prompt;
|
||||
});
|
||||
it('should removeQueryEditor', () => {
|
||||
wrapper = getWrapper();
|
||||
sinon.stub(wrapper.instance().props.actions, 'removeQueryEditor');
|
||||
|
||||
wrapper.instance().removeQueryEditor(queryEditors[0]);
|
||||
expect(
|
||||
wrapper.instance().props.actions.removeQueryEditor.getCall(0).args[0],
|
||||
).toBe(queryEditors[0]);
|
||||
});
|
||||
it('should add new query editor', () => {
|
||||
wrapper = getWrapper();
|
||||
sinon.stub(wrapper.instance().props.actions, 'addQueryEditor');
|
||||
|
||||
wrapper.instance().newQueryEditor();
|
||||
expect(
|
||||
wrapper.instance().props.actions.addQueryEditor.getCall(0).args[0].title,
|
||||
).toContain('Untitled Query');
|
||||
});
|
||||
it('should duplicate query editor', () => {
|
||||
wrapper = getWrapper();
|
||||
sinon.stub(wrapper.instance().props.actions, 'cloneQueryToNewTab');
|
||||
|
||||
wrapper.instance().duplicateQueryEditor(queryEditors[0]);
|
||||
expect(
|
||||
wrapper.instance().props.actions.cloneQueryToNewTab.getCall(0).args[0],
|
||||
).toBe(queryEditors[0]);
|
||||
});
|
||||
it('should handle select', () => {
|
||||
const mockEvent = {
|
||||
target: {
|
||||
getAttribute: () => null,
|
||||
},
|
||||
};
|
||||
wrapper = getWrapper();
|
||||
sinon.stub(wrapper.instance().props.actions, 'switchQueryEditor');
|
||||
|
||||
// cannot switch to current tab, switchQueryEditor never gets called
|
||||
wrapper.instance().handleSelect('dfsadfs', mockEvent);
|
||||
expect(
|
||||
wrapper.instance().props.actions.switchQueryEditor.callCount,
|
||||
).toEqual(0);
|
||||
});
|
||||
it('should handle add tab', () => {
|
||||
wrapper = getWrapper();
|
||||
sinon.spy(wrapper.instance(), 'newQueryEditor');
|
||||
|
||||
wrapper.instance().handleEdit('1', 'add');
|
||||
expect(wrapper.instance().newQueryEditor.callCount).toBe(1);
|
||||
wrapper.instance().newQueryEditor.restore();
|
||||
});
|
||||
it('should render', () => {
|
||||
wrapper = getWrapper();
|
||||
wrapper.setState({ hideLeftBar: true });
|
||||
|
||||
const firstTab = wrapper.find(EditableTabs.TabPane).first();
|
||||
expect(firstTab.props()['data-key']).toContain(
|
||||
initialState.sqlLab.queryEditors[0].id,
|
||||
);
|
||||
expect(firstTab.find(SqlEditor)).toHaveLength(1);
|
||||
});
|
||||
it('should disable new tab when offline', () => {
|
||||
wrapper = getWrapper();
|
||||
expect(wrapper.find(EditableTabs).props().hideAdd).toBe(false);
|
||||
wrapper.setProps({ offline: true });
|
||||
expect(wrapper.find(EditableTabs).props().hideAdd).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -26,13 +26,12 @@ import { bindActionCreators } from 'redux';
|
||||
import URI from 'urijs';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
|
||||
|
||||
import { areArraysShallowEqual } from 'src/reduxUtils';
|
||||
import { Tooltip } from 'src/components/Tooltip';
|
||||
import { detectOS } from 'src/utils/common';
|
||||
import * as Actions from '../actions/sqlLab';
|
||||
import SqlEditor from './SqlEditor';
|
||||
import TabStatusIcon from './TabStatusIcon';
|
||||
import * as Actions from 'src/SqlLab/actions/sqlLab';
|
||||
import SqlEditor from '../SqlEditor';
|
||||
import TabStatusIcon from '../TabStatusIcon';
|
||||
|
||||
const propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
import Collapse from 'src/components/Collapse';
|
||||
import { IconTooltip } from 'src/components/IconTooltip';
|
||||
import TableElement from 'src/SqlLab/components/TableElement';
|
||||
import ColumnElement from 'src/SqlLab/components/ColumnElement';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
import { mockedActions, table } from 'src/SqlLab/fixtures';
|
||||
|
||||
describe('TableElement', () => {
|
||||
const mockStore = configureStore([]);
|
||||
const store = mockStore({});
|
||||
const mockedProps = {
|
||||
actions: mockedActions,
|
||||
table,
|
||||
timeout: 0,
|
||||
};
|
||||
it('renders', () => {
|
||||
expect(React.isValidElement(<TableElement />)).toBe(true);
|
||||
});
|
||||
it('renders with props', () => {
|
||||
expect(React.isValidElement(<TableElement {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
it('has 5 IconTooltip elements', () => {
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<TableElement {...mockedProps} />
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(wrapper.find(IconTooltip)).toHaveLength(4);
|
||||
});
|
||||
it('has 14 columns', () => {
|
||||
const wrapper = shallow(<TableElement {...mockedProps} />);
|
||||
expect(wrapper.find(ColumnElement)).toHaveLength(14);
|
||||
});
|
||||
it('mounts', () => {
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<TableElement {...mockedProps} />
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(wrapper.find(TableElement)).toHaveLength(1);
|
||||
});
|
||||
it('fades table', async () => {
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<TableElement {...mockedProps} />
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(wrapper.find('[data-test="fade"]').first().props().hovered).toBe(
|
||||
false,
|
||||
);
|
||||
wrapper.find('.header-container').hostNodes().simulate('mouseEnter');
|
||||
await waitForComponentToPaint(wrapper, 300);
|
||||
expect(wrapper.find('[data-test="fade"]').first().props().hovered).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
it('sorts columns', () => {
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<Collapse>
|
||||
<TableElement {...mockedProps} />
|
||||
</Collapse>
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(
|
||||
wrapper.find(IconTooltip).at(1).hasClass('fa-sort-alpha-asc'),
|
||||
).toEqual(true);
|
||||
expect(
|
||||
wrapper.find(IconTooltip).at(1).hasClass('fa-sort-numeric-asc'),
|
||||
).toEqual(false);
|
||||
wrapper.find('.header-container').hostNodes().simulate('click');
|
||||
expect(wrapper.find(ColumnElement).first().props().column.name).toBe('id');
|
||||
wrapper.find('.header-container').simulate('mouseEnter');
|
||||
wrapper.find('.sort-cols').hostNodes().simulate('click');
|
||||
expect(
|
||||
wrapper.find(IconTooltip).at(1).hasClass('fa-sort-numeric-asc'),
|
||||
).toEqual(true);
|
||||
expect(
|
||||
wrapper.find(IconTooltip).at(1).hasClass('fa-sort-alpha-asc'),
|
||||
).toEqual(false);
|
||||
expect(wrapper.find(ColumnElement).first().props().column.name).toBe(
|
||||
'active',
|
||||
);
|
||||
});
|
||||
it('removes the table', () => {
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<TableElement {...mockedProps} />
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
},
|
||||
);
|
||||
wrapper.find('.table-remove').hostNodes().simulate('click');
|
||||
expect(mockedActions.removeDataPreview.called).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -24,12 +24,12 @@ import { t, styled } from '@superset-ui/core';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { Tooltip } from 'src/components/Tooltip';
|
||||
import CopyToClipboard from '../../components/CopyToClipboard';
|
||||
import { IconTooltip } from '../../components/IconTooltip';
|
||||
import ColumnElement, { ColumnKeyTypeType } from './ColumnElement';
|
||||
import ShowSQL from './ShowSQL';
|
||||
import ModalTrigger from '../../components/ModalTrigger';
|
||||
import Loading from '../../components/Loading';
|
||||
import CopyToClipboard from 'src/components/CopyToClipboard';
|
||||
import { IconTooltip } from 'src/components/IconTooltip';
|
||||
import ModalTrigger from 'src/components/ModalTrigger';
|
||||
import Loading from 'src/components/Loading';
|
||||
import ColumnElement, { ColumnKeyTypeType } from '../ColumnElement';
|
||||
import ShowSQL from '../ShowSQL';
|
||||
|
||||
interface Column {
|
||||
name: string;
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import {
|
||||
render,
|
||||
fireEvent,
|
||||
getByText,
|
||||
waitFor,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import brace from 'brace';
|
||||
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
||||
|
||||
import TemplateParamsEditor from 'src/SqlLab/components/TemplateParamsEditor';
|
||||
|
||||
const ThemeWrapper = ({ children }: { children: ReactNode }) => (
|
||||
<ThemeProvider theme={supersetTheme}>{children}</ThemeProvider>
|
||||
);
|
||||
|
||||
describe('TemplateParamsEditor', () => {
|
||||
it('should render with a title', () => {
|
||||
const { container } = render(
|
||||
<TemplateParamsEditor code="FOO" language="json" onChange={() => {}} />,
|
||||
{ wrapper: ThemeWrapper },
|
||||
);
|
||||
expect(container.querySelector('div[role="button"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should open a modal with the ace editor', async () => {
|
||||
const { container, baseElement } = render(
|
||||
<TemplateParamsEditor code="FOO" language="json" onChange={() => {}} />,
|
||||
{ wrapper: ThemeWrapper },
|
||||
);
|
||||
fireEvent.click(getByText(container, 'Parameters'));
|
||||
const spy = jest.spyOn(brace, 'acequire');
|
||||
spy.mockReturnValue({ setCompleters: () => 'foo' });
|
||||
await waitFor(() => {
|
||||
expect(baseElement.querySelector('#brace-editor')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user