chore: Moves spec files to the src folder - iteration 7 (#16943)

This commit is contained in:
Michael S. Molina
2021-10-04 10:56:49 -03:00
committed by GitHub
parent 028f6c0d3f
commit 1ab36c94f3
59 changed files with 80 additions and 95 deletions

View File

@@ -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;

View 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);
});
});

View File

@@ -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) {

View 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 { 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');
});
});

View File

@@ -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,

View File

@@ -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,

View File

@@ -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();
});
});
});

View File

@@ -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,

View File

@@ -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);
});
});

View File

@@ -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);

View File

@@ -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);
});
});

View File

@@ -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;

View File

@@ -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[];

View File

@@ -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);
});
});

View File

@@ -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: {

View File

@@ -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();
});
});

View File

@@ -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;

View File

@@ -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);
});
});

View File

@@ -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();
});

View File

@@ -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,

View File

@@ -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();
});
});

View File

@@ -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);
});
});

View File

@@ -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();
});
});
});

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,
);
});
});

View File

@@ -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 {

View File

@@ -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();
});
});

View File

@@ -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;

View File

@@ -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();
});
});

View File

@@ -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,

View File

@@ -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('');
});
});

View File

@@ -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;

View File

@@ -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);
});
});

View File

@@ -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,

View File

@@ -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);
});
});

View File

@@ -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;

View File

@@ -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();
});
});
});