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

@@ -36,7 +36,7 @@ import { sliceId } from 'spec/fixtures/mockChartQueries';
import dashboardInfo from 'spec/fixtures/mockDashboardInfo';
import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout';
import { sliceEntitiesForChart } from 'spec/fixtures/mockSliceEntities';
import { initialState } from 'spec/javascripts/sqllab/fixtures';
import { initialState } from 'src/SqlLab/fixtures';
import { nativeFiltersInfo } from '../../fixtures/mockNativeFilters';
describe('ChartHolder', () => {

View File

@@ -36,7 +36,7 @@ import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu';
import { getMockStore } from 'spec/fixtures/mockStore';
import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout';
import { initialState } from 'spec/javascripts/sqllab/fixtures';
import { initialState } from 'src/SqlLab/fixtures';
describe('Column', () => {
const columnWithoutChildren = {

View File

@@ -36,7 +36,7 @@ import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import { getMockStore } from 'spec/fixtures/mockStore';
import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout';
import { initialState } from 'spec/javascripts/sqllab/fixtures';
import { initialState } from 'src/SqlLab/fixtures';
describe('Row', () => {
const rowWithoutChildren = { ...mockLayout.present.ROW_ID, children: [] };

View File

@@ -32,7 +32,7 @@ import Tab, {
} from 'src/dashboard/components/gridComponents/Tab';
import { dashboardLayoutWithTabs } from 'spec/fixtures/mockDashboardLayout';
import { getMockStore } from 'spec/fixtures/mockStore';
import { initialState } from 'spec/javascripts/sqllab/fixtures';
import { initialState } from 'src/SqlLab/fixtures';
describe('Tabs', () => {
const props = {

View File

@@ -37,7 +37,7 @@ import emptyDashboardLayout from 'src/dashboard/fixtures/emptyDashboardLayout';
import { dashboardLayoutWithTabs } from 'spec/fixtures/mockDashboardLayout';
import { getMockStore } from 'spec/fixtures/mockStore';
import { nativeFilters } from 'spec/fixtures/mockNativeFilters';
import { initialState } from 'spec/javascripts/sqllab/fixtures';
import { initialState } from 'src/SqlLab/fixtures';
describe('Tabs', () => {
fetchMock.post('glob:*/r/shortner/', {});

View File

@@ -1,48 +0,0 @@
/**
* 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

@@ -1,49 +0,0 @@
/**
* 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 './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

@@ -1,192 +0,0 @@
/**
* 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 './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

@@ -1,64 +0,0 @@
/**
* 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

@@ -1,69 +0,0 @@
/**
* 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 './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

@@ -1,140 +0,0 @@
/**
* 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 './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

@@ -1,40 +0,0 @@
/**
* 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

@@ -1,58 +0,0 @@
/**
* 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 './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

@@ -1,201 +0,0 @@
/**
* 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 './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

@@ -1,59 +0,0 @@
/**
* 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

@@ -1,88 +0,0 @@
/**
* 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

@@ -1,132 +0,0 @@
/**
* 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

@@ -1,97 +0,0 @@
/**
* 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 './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

@@ -1,119 +0,0 @@
/**
* 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 './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

@@ -1,131 +0,0 @@
/**
* 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 './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

@@ -1,39 +0,0 @@
/**
* 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

@@ -1,231 +0,0 @@
/**
* 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 './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

@@ -1,149 +0,0 @@
/**
* 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 './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

@@ -1,57 +0,0 @@
/**
* 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();
});
});
});

View File

@@ -1,876 +0,0 @@
/**
* 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.
*/
/* eslint no-unused-expressions: 0 */
import sinon from 'sinon';
import fetchMock from 'fetch-mock';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import shortid from 'shortid';
import * as featureFlags from 'src/featureFlags';
import { ADD_TOAST } from 'src/components/MessageToasts/actions';
import * as actions from 'src/SqlLab/actions/sqlLab';
import { defaultQueryEditor, query } from '../fixtures';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('async actions', () => {
const mockBigNumber = '9223372036854775807';
const queryEditor = {
id: 'abcd',
autorun: false,
dbId: null,
latestQueryId: null,
selectedText: null,
sql: 'SELECT *\nFROM\nWHERE',
title: 'Untitled Query',
schemaOptions: [{ value: 'main', label: 'main', title: 'main' }],
};
let dispatch;
beforeEach(() => {
dispatch = sinon.spy();
});
afterEach(fetchMock.resetHistory);
const fetchQueryEndpoint = 'glob:*/superset/results/*';
fetchMock.get(
fetchQueryEndpoint,
JSON.stringify({ data: mockBigNumber, query: { sqlEditorId: 'dfsadfs' } }),
);
const runQueryEndpoint = 'glob:*/superset/sql_json/*';
fetchMock.post(runQueryEndpoint, `{ "data": ${mockBigNumber} }`);
describe('saveQuery', () => {
const saveQueryEndpoint = 'glob:*/savedqueryviewapi/api/create';
fetchMock.post(saveQueryEndpoint, { results: { json: {} } });
const makeRequest = () => {
const request = actions.saveQuery(query);
return request(dispatch);
};
it('posts to the correct url', () => {
expect.assertions(1);
const store = mockStore({});
return store.dispatch(actions.saveQuery(query)).then(() => {
expect(fetchMock.calls(saveQueryEndpoint)).toHaveLength(1);
});
});
it('posts the correct query object', () => {
const store = mockStore({});
return store.dispatch(actions.saveQuery(query)).then(() => {
const call = fetchMock.calls(saveQueryEndpoint)[0];
const formData = call[1].body;
Object.keys(query).forEach(key => {
expect(formData.get(key)).toBeDefined();
});
});
});
it('calls 3 dispatch actions', () => {
expect.assertions(1);
return makeRequest().then(() => {
expect(dispatch.callCount).toBe(3);
});
});
it('calls QUERY_EDITOR_SAVED after making a request', () => {
expect.assertions(1);
return makeRequest().then(() => {
expect(dispatch.args[0][0].type).toBe(actions.QUERY_EDITOR_SAVED);
});
});
it('onSave calls QUERY_EDITOR_SAVED and QUERY_EDITOR_SET_TITLE', () => {
expect.assertions(1);
const store = mockStore({});
const expectedActionTypes = [
actions.QUERY_EDITOR_SAVED,
ADD_TOAST,
actions.QUERY_EDITOR_SET_TITLE,
];
return store.dispatch(actions.saveQuery(query)).then(() => {
expect(store.getActions().map(a => a.type)).toEqual(
expectedActionTypes,
);
});
});
});
describe('fetchQueryResults', () => {
const makeRequest = () => {
const request = actions.fetchQueryResults(query);
return request(dispatch);
};
it('makes the fetch request', () => {
expect.assertions(1);
return makeRequest().then(() => {
expect(fetchMock.calls(fetchQueryEndpoint)).toHaveLength(1);
});
});
it('calls requestQueryResults', () => {
expect.assertions(1);
return makeRequest().then(() => {
expect(dispatch.args[0][0].type).toBe(actions.REQUEST_QUERY_RESULTS);
});
});
it.skip('parses large number result without losing precision', () =>
makeRequest().then(() => {
expect(fetchMock.calls(fetchQueryEndpoint)).toHaveLength(1);
expect(dispatch.callCount).toBe(2);
expect(dispatch.getCall(1).lastArg.results.data.toString()).toBe(
mockBigNumber,
);
}));
it('calls querySuccess on fetch success', () => {
expect.assertions(1);
const store = mockStore({});
const expectedActionTypes = [
actions.REQUEST_QUERY_RESULTS,
actions.QUERY_SUCCESS,
];
return store.dispatch(actions.fetchQueryResults(query)).then(() => {
expect(store.getActions().map(a => a.type)).toEqual(
expectedActionTypes,
);
});
});
it('calls queryFailed on fetch error', () => {
expect.assertions(1);
fetchMock.get(
fetchQueryEndpoint,
{ throws: { message: 'error text' } },
{ overwriteRoutes: true },
);
const store = mockStore({});
const expectedActionTypes = [
actions.REQUEST_QUERY_RESULTS,
actions.QUERY_FAILED,
];
return store.dispatch(actions.fetchQueryResults(query)).then(() => {
expect(store.getActions().map(a => a.type)).toEqual(
expectedActionTypes,
);
});
});
});
describe('runQuery', () => {
const makeRequest = () => {
const request = actions.runQuery(query);
return request(dispatch);
};
it('makes the fetch request', () => {
expect.assertions(1);
return makeRequest().then(() => {
expect(fetchMock.calls(runQueryEndpoint)).toHaveLength(1);
});
});
it('calls startQuery', () => {
expect.assertions(1);
return makeRequest().then(() => {
expect(dispatch.args[0][0].type).toBe(actions.START_QUERY);
});
});
it.skip('parses large number result without losing precision', () =>
makeRequest().then(() => {
expect(fetchMock.calls(runQueryEndpoint)).toHaveLength(1);
expect(dispatch.callCount).toBe(2);
expect(dispatch.getCall(1).lastArg.results.data.toString()).toBe(
mockBigNumber,
);
}));
it('calls querySuccess on fetch success', () => {
expect.assertions(1);
const store = mockStore({});
const expectedActionTypes = [actions.START_QUERY, actions.QUERY_SUCCESS];
return store.dispatch(actions.runQuery(query)).then(() => {
expect(store.getActions().map(a => a.type)).toEqual(
expectedActionTypes,
);
});
});
it('calls queryFailed on fetch error', () => {
expect.assertions(1);
fetchMock.post(
runQueryEndpoint,
{ throws: { message: 'error text' } },
{ overwriteRoutes: true },
);
const store = mockStore({});
const expectedActionTypes = [actions.START_QUERY, actions.QUERY_FAILED];
return store.dispatch(actions.runQuery(query)).then(() => {
expect(store.getActions().map(a => a.type)).toEqual(
expectedActionTypes,
);
});
});
});
describe('reRunQuery', () => {
let stub;
beforeEach(() => {
stub = sinon.stub(shortid, 'generate').returns('abcd');
});
afterEach(() => {
stub.restore();
});
it('creates new query with a new id', () => {
const id = 'id';
const state = {
sqlLab: {
tabHistory: [id],
queryEditors: [{ id, title: 'Dummy query editor' }],
},
};
const store = mockStore(state);
store.dispatch(actions.reRunQuery(query));
expect(store.getActions()[0].query.id).toEqual('abcd');
});
});
describe('postStopQuery', () => {
const stopQueryEndpoint = 'glob:*/superset/stop_query/*';
fetchMock.post(stopQueryEndpoint, {});
const makeRequest = () => {
const request = actions.postStopQuery(query);
return request(dispatch);
};
it('makes the fetch request', () => {
expect.assertions(1);
return makeRequest().then(() => {
expect(fetchMock.calls(stopQueryEndpoint)).toHaveLength(1);
});
});
it('calls stopQuery', () => {
expect.assertions(1);
return makeRequest().then(() => {
expect(dispatch.getCall(0).args[0].type).toBe(actions.STOP_QUERY);
});
});
it('sends the correct data', () => {
expect.assertions(1);
return makeRequest().then(() => {
const call = fetchMock.calls(stopQueryEndpoint)[0];
expect(call[1].body.get('client_id')).toBe(query.id);
});
});
});
describe('cloneQueryToNewTab', () => {
let stub;
beforeEach(() => {
stub = sinon.stub(shortid, 'generate').returns('abcd');
});
afterEach(() => {
stub.restore();
});
it('creates new query editor', () => {
expect.assertions(1);
const id = 'id';
const state = {
sqlLab: {
tabHistory: [id],
queryEditors: [{ id, title: 'Dummy query editor' }],
},
};
const store = mockStore(state);
const expectedActions = [
{
type: actions.ADD_QUERY_EDITOR,
queryEditor: {
title: 'Copy of Dummy query editor',
dbId: 1,
schema: null,
autorun: true,
sql: 'SELECT * FROM something',
queryLimit: undefined,
maxRow: undefined,
id: 'abcd',
},
},
];
return store
.dispatch(actions.cloneQueryToNewTab(query, true))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});
describe('addQueryEditor', () => {
let stub;
beforeEach(() => {
stub = sinon.stub(shortid, 'generate').returns('abcd');
});
afterEach(() => {
stub.restore();
});
it('creates new query editor', () => {
expect.assertions(1);
const store = mockStore({});
const expectedActions = [
{
type: actions.ADD_QUERY_EDITOR,
queryEditor,
},
];
return store
.dispatch(actions.addQueryEditor(defaultQueryEditor))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});
describe('backend sync', () => {
const updateTabStateEndpoint = 'glob:*/tabstateview/*';
fetchMock.put(updateTabStateEndpoint, {});
fetchMock.delete(updateTabStateEndpoint, {});
fetchMock.post(updateTabStateEndpoint, JSON.stringify({ id: 1 }));
const updateTableSchemaEndpoint = 'glob:*/tableschemaview/*';
fetchMock.put(updateTableSchemaEndpoint, {});
fetchMock.delete(updateTableSchemaEndpoint, {});
fetchMock.post(updateTableSchemaEndpoint, JSON.stringify({ id: 1 }));
const getTableMetadataEndpoint = 'glob:*/api/v1/database/*';
fetchMock.get(getTableMetadataEndpoint, {});
const getExtraTableMetadataEndpoint =
'glob:*/superset/extra_table_metadata/*';
fetchMock.get(getExtraTableMetadataEndpoint, {});
let isFeatureEnabledMock;
beforeEach(() => {
isFeatureEnabledMock = jest
.spyOn(featureFlags, 'isFeatureEnabled')
.mockImplementation(
feature => feature === 'SQLLAB_BACKEND_PERSISTENCE',
);
});
afterEach(() => {
isFeatureEnabledMock.mockRestore();
});
afterEach(fetchMock.resetHistory);
describe('querySuccess', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const store = mockStore({});
const results = { query: { sqlEditorId: 'abcd' } };
const expectedActions = [
{
type: actions.QUERY_SUCCESS,
query,
results,
},
];
return store.dispatch(actions.querySuccess(query, results)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('fetchQueryResults', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const results = {
data: mockBigNumber,
query: { sqlEditorId: 'abcd' },
query_id: 'efgh',
};
fetchMock.get(fetchQueryEndpoint, JSON.stringify(results), {
overwriteRoutes: true,
});
const store = mockStore({});
const expectedActions = [
{
type: actions.REQUEST_QUERY_RESULTS,
query,
},
// missing below
{
type: actions.QUERY_SUCCESS,
query,
results,
},
];
return store.dispatch(actions.fetchQueryResults(query)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('addQueryEditor', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const store = mockStore({});
const expectedActions = [
{
type: actions.ADD_QUERY_EDITOR,
queryEditor: { ...queryEditor, id: '1' },
},
];
return store.dispatch(actions.addQueryEditor(queryEditor)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('setActiveQueryEditor', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const store = mockStore({});
const expectedActions = [
{
type: actions.SET_ACTIVE_QUERY_EDITOR,
queryEditor,
},
];
return store
.dispatch(actions.setActiveQueryEditor(queryEditor))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('removeQueryEditor', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const store = mockStore({});
const expectedActions = [
{
type: actions.REMOVE_QUERY_EDITOR,
queryEditor,
},
];
return store
.dispatch(actions.removeQueryEditor(queryEditor))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('queryEditorSetDb', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const dbId = 42;
const store = mockStore({});
const expectedActions = [
{
type: actions.QUERY_EDITOR_SETDB,
queryEditor,
dbId,
},
];
return store
.dispatch(actions.queryEditorSetDb(queryEditor, dbId))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('queryEditorSetSchema', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const schema = 'schema';
const store = mockStore({});
const expectedActions = [
{
type: actions.QUERY_EDITOR_SET_SCHEMA,
queryEditor,
schema,
},
];
return store
.dispatch(actions.queryEditorSetSchema(queryEditor, schema))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('queryEditorSetAutorun', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const autorun = true;
const store = mockStore({});
const expectedActions = [
{
type: actions.QUERY_EDITOR_SET_AUTORUN,
queryEditor,
autorun,
},
];
return store
.dispatch(actions.queryEditorSetAutorun(queryEditor, autorun))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('queryEditorSetTitle', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const title = 'title';
const store = mockStore({});
const expectedActions = [
{
type: actions.QUERY_EDITOR_SET_TITLE,
queryEditor,
title,
},
];
return store
.dispatch(actions.queryEditorSetTitle(queryEditor, title))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('queryEditorSetSql', () => {
const sql = 'SELECT * ';
const expectedActions = [
{
type: actions.QUERY_EDITOR_SET_SQL,
queryEditor,
sql,
},
];
describe('with backend persistence flag on', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const store = mockStore({});
return store
.dispatch(actions.queryEditorSetSql(queryEditor, sql))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('with backend persistence flag off', () => {
it('does not update the tab state in the backend', () => {
const backendPersistenceOffMock = jest
.spyOn(featureFlags, 'isFeatureEnabled')
.mockImplementation(
feature => !(feature === 'SQLLAB_BACKEND_PERSISTENCE'),
);
const store = mockStore({});
store.dispatch(actions.queryEditorSetSql(queryEditor, sql));
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(0);
backendPersistenceOffMock.mockRestore();
});
});
});
describe('queryEditorSetQueryLimit', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const queryLimit = 10;
const store = mockStore({});
const expectedActions = [
{
type: actions.QUERY_EDITOR_SET_QUERY_LIMIT,
queryEditor,
queryLimit,
},
];
return store
.dispatch(actions.queryEditorSetQueryLimit(queryEditor, queryLimit))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('queryEditorSetTemplateParams', () => {
it('updates the tab state in the backend', () => {
expect.assertions(2);
const templateParams = '{"foo": "bar"}';
const store = mockStore({});
const expectedActions = [
{
type: actions.QUERY_EDITOR_SET_TEMPLATE_PARAMS,
queryEditor,
templateParams,
},
];
return store
.dispatch(
actions.queryEditorSetTemplateParams(queryEditor, templateParams),
)
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(1);
});
});
});
describe('addTable', () => {
it('updates the table schema state in the backend', () => {
expect.assertions(5);
const results = {
data: mockBigNumber,
query: { sqlEditorId: 'null' },
query_id: 'efgh',
};
fetchMock.post(runQueryEndpoint, JSON.stringify(results), {
overwriteRoutes: true,
});
const tableName = 'table';
const schemaName = 'schema';
const store = mockStore({});
const expectedActionTypes = [
actions.MERGE_TABLE, // addTable
actions.MERGE_TABLE, // getTableMetadata
actions.START_QUERY, // runQuery (data preview)
actions.MERGE_TABLE, // getTableExtendedMetadata
actions.QUERY_SUCCESS, // querySuccess
actions.MERGE_TABLE, // addTable
];
return store
.dispatch(actions.addTable(query, tableName, schemaName))
.then(() => {
expect(store.getActions().map(a => a.type)).toEqual(
expectedActionTypes,
);
expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(1);
expect(fetchMock.calls(getTableMetadataEndpoint)).toHaveLength(1);
expect(fetchMock.calls(getExtraTableMetadataEndpoint)).toHaveLength(
1,
);
// tab state is not updated, since the query is a data preview
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(0);
});
});
});
describe('expandTable', () => {
it('updates the table schema state in the backend', () => {
expect.assertions(2);
const table = { id: 1 };
const store = mockStore({});
const expectedActions = [
{
type: actions.EXPAND_TABLE,
table,
},
];
return store.dispatch(actions.expandTable(table)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(1);
});
});
});
describe('collapseTable', () => {
it('updates the table schema state in the backend', () => {
expect.assertions(2);
const table = { id: 1 };
const store = mockStore({});
const expectedActions = [
{
type: actions.COLLAPSE_TABLE,
table,
},
];
return store.dispatch(actions.collapseTable(table)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(1);
});
});
});
describe('removeTable', () => {
it('updates the table schema state in the backend', () => {
expect.assertions(2);
const table = { id: 1 };
const store = mockStore({});
const expectedActions = [
{
type: actions.REMOVE_TABLE,
table,
},
];
return store.dispatch(actions.removeTable(table)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(1);
});
});
});
describe('migrateQueryEditorFromLocalStorage', () => {
it('updates the tab state in the backend', () => {
expect.assertions(3);
const results = {
data: mockBigNumber,
query: { sqlEditorId: 'null' },
query_id: 'efgh',
};
fetchMock.post(runQueryEndpoint, JSON.stringify(results), {
overwriteRoutes: true,
});
const tables = [
{ id: 'one', dataPreviewQueryId: 'previewOne' },
{ id: 'two', dataPreviewQueryId: 'previewTwo' },
];
const queries = [
{ ...query, id: 'previewOne' },
{ ...query, id: 'previewTwo' },
];
const store = mockStore({});
const expectedActions = [
{
type: actions.MIGRATE_QUERY_EDITOR,
oldQueryEditor: queryEditor,
// new qe has a different id
newQueryEditor: { ...queryEditor, id: '1' },
},
{
type: actions.MIGRATE_TAB_HISTORY,
newId: '1',
oldId: 'abcd',
},
{
type: actions.MIGRATE_TABLE,
oldTable: tables[0],
// new table has a different id and points to new query editor
newTable: { ...tables[0], id: 1, queryEditorId: '1' },
},
{
type: actions.MIGRATE_TABLE,
oldTable: tables[1],
// new table has a different id and points to new query editor
newTable: { ...tables[1], id: 1, queryEditorId: '1' },
},
{
type: actions.MIGRATE_QUERY,
queryId: 'previewOne',
queryEditorId: '1',
},
{
type: actions.MIGRATE_QUERY,
queryId: 'previewTwo',
queryEditorId: '1',
},
];
return store
.dispatch(
actions.migrateQueryEditorFromLocalStorage(
queryEditor,
tables,
queries,
),
)
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(3);
// query editor has 2 tables loaded in the schema viewer
expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(2);
});
});
});
});
});

View File

@@ -1,574 +0,0 @@
/**
* 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 sinon from 'sinon';
import * as actions from 'src/SqlLab/actions/sqlLab';
import { ColumnKeyTypeType } from 'src/SqlLab/components/ColumnElement';
export const mockedActions = sinon.stub({ ...actions });
export const alert = { bsStyle: 'danger', msg: 'Ooops', id: 'lksvmcx32' };
export const table = {
dbId: 1,
selectStar: 'SELECT * FROM ab_user',
queryEditorId: 'rJ-KP47a',
schema: 'superset',
name: 'ab_user',
id: 'r11Vgt60',
dataPreviewQueryId: null,
partitions: {
cols: ['username'],
latest: 'bob',
partitionQuery: 'SHOW PARTITIONS FROM ab_user',
},
indexes: [
{
unique: true,
column_names: ['username'],
type: 'UNIQUE',
name: 'username',
},
{
unique: true,
column_names: ['email'],
type: 'UNIQUE',
name: 'email',
},
{
unique: false,
column_names: ['created_by_fk'],
name: 'created_by_fk',
},
{
unique: false,
column_names: ['changed_by_fk'],
name: 'changed_by_fk',
},
],
columns: [
{
indexed: false,
longType: 'INTEGER(11)',
type: 'INTEGER',
name: 'id',
keys: [
{
column_names: ['id'],
type: 'pk' as ColumnKeyTypeType,
name: null,
},
],
},
{
indexed: false,
longType: 'VARCHAR(64)',
type: 'VARCHAR',
name: 'first_name',
keys: [
{
column_names: ['first_name'],
name: 'slices_ibfk_1',
referred_columns: ['id'],
referred_table: 'datasources',
type: 'fk' as ColumnKeyTypeType,
referred_schema: 'carapal',
options: {},
},
{
unique: false,
column_names: ['druid_datasource_id'],
type: 'index' as ColumnKeyTypeType,
name: 'druid_datasource_id',
},
],
},
{
indexed: false,
longType: 'VARCHAR(64)',
type: 'VARCHAR',
name: 'last_name',
},
{
indexed: true,
longType: 'VARCHAR(64)',
type: 'VARCHAR',
name: 'username',
},
{
indexed: false,
longType: 'VARCHAR(256)',
type: 'VARCHAR',
name: 'password',
},
{
indexed: false,
longType: 'TINYINT(1)',
type: 'TINYINT',
name: 'active',
},
{
indexed: true,
longType: 'VARCHAR(64)',
type: 'VARCHAR',
name: 'email',
},
{
indexed: false,
longType: 'DATETIME',
type: 'DATETIME',
name: 'last_login',
},
{
indexed: false,
longType: 'INTEGER(11)',
type: 'INTEGER',
name: 'login_count',
},
{
indexed: false,
longType: 'INTEGER(11)',
type: 'INTEGER',
name: 'fail_login_count',
},
{
indexed: false,
longType: 'DATETIME',
type: 'DATETIME',
name: 'created_on',
},
{
indexed: false,
longType: 'DATETIME',
type: 'DATETIME',
name: 'changed_on',
},
{
indexed: true,
longType: 'INTEGER(11)',
type: 'INTEGER',
name: 'created_by_fk',
},
{
indexed: true,
longType: 'INTEGER(11)',
type: 'INTEGER',
name: 'changed_by_fk',
},
],
expanded: true,
};
export const defaultQueryEditor = {
id: 'dfsadfs',
autorun: false,
dbId: null,
latestQueryId: null,
selectedText: null,
sql: 'SELECT *\nFROM\nWHERE',
title: 'Untitled Query',
schemaOptions: [
{
value: 'main',
label: 'main',
title: 'main',
},
],
};
export const queries = [
{
dbId: 1,
sql: 'SELECT * FROM superset.slices',
sqlEditorId: 'SJ8YO72R',
tab: 'Demo',
runAsync: false,
ctas: false,
cached: false,
id: 'BkA1CLrJg',
progress: 100,
startDttm: 1476910566092.96,
state: 'success',
changedOn: 1476910566000,
tempTable: null,
userId: 1,
executedSql: null,
changed_on: '2016-10-19T20:56:06',
rows: 42,
queryLimit: 100,
endDttm: 1476910566798,
limit_reached: false,
schema: 'test_schema',
errorMessage: null,
db: 'main',
user: 'admin',
limit: 1000,
serverId: 141,
resultsKey: null,
results: {
columns: [
{
is_date: true,
name: 'ds',
type: 'STRING',
},
{
is_date: false,
name: 'gender',
type: 'STRING',
},
],
selected_columns: [
{
is_date: true,
name: 'ds',
type: 'STRING',
},
{
is_date: false,
name: 'gender',
type: 'STRING',
},
],
data: [
{ col1: 0, col2: 1 },
{ col1: 2, col2: 3 },
],
},
},
{
dbId: 1,
sql: 'SELECT *FROM superset.slices',
sqlEditorId: 'SJ8YO72R',
tab: 'Demo',
runAsync: true,
ctas: false,
cached: false,
id: 'S1zeAISkx',
progress: 100,
startDttm: 1476910570802.2,
state: 'success',
changedOn: 1476910572000,
tempTable: null,
userId: 1,
executedSql:
'SELECT * \nFROM (SELECT created_on, changed_on, id, slice_name, ' +
'druid_datasource_id, table_id, datasource_type, datasource_name, ' +
'viz_type, params, created_by_fk, changed_by_fk, description, ' +
'cache_timeout, perm\nFROM superset.slices) AS inner_qry \n LIMIT 1000',
changed_on: '2016-10-19T20:56:12',
rows: 42,
endDttm: 1476910579693,
limit_reached: false,
schema: null,
errorMessage: null,
db: 'main',
user: 'admin',
limit: 1000,
serverId: 142,
resultsKey: '417149f4-cd27-4f80-91f3-c45c871003f7',
results: null,
},
];
export const queryWithNoQueryLimit = {
dbId: 1,
sql: 'SELECT * FROM superset.slices',
sqlEditorId: 'SJ8YO72R',
tab: 'Demo',
runAsync: false,
ctas: false,
cached: false,
id: 'BkA1CLrJg',
progress: 100,
startDttm: 1476910566092.96,
state: 'success',
changedOn: 1476910566000,
tempTable: null,
userId: 1,
executedSql: null,
changed_on: '2016-10-19T20:56:06',
rows: 42,
endDttm: 1476910566798,
limit_reached: false,
schema: 'test_schema',
errorMessage: null,
db: 'main',
user: 'admin',
limit: 1000,
serverId: 141,
resultsKey: null,
results: {
columns: [
{
is_date: true,
name: 'ds',
type: 'STRING',
},
{
is_date: false,
name: 'gender',
type: 'STRING',
},
],
selected_columns: [
{
is_date: true,
name: 'ds',
type: 'STRING',
},
{
is_date: false,
name: 'gender',
type: 'STRING',
},
],
data: [
{ col1: 0, col2: 1 },
{ col1: 2, col2: 3 },
],
query: {
limit: 100,
},
},
};
export const queryWithBadColumns = {
...queries[0],
results: {
data: queries[0].results?.data,
selected_columns: [
{
is_date: true,
name: 'COUNT(*)',
type: 'STRING',
},
{
is_date: false,
name: 'this_col_is_ok',
type: 'STRING',
},
{
is_date: false,
name: 'a',
type: 'STRING',
},
{
is_date: false,
name: '1',
type: 'STRING',
},
{
is_date: false,
name: '123',
type: 'STRING',
},
{
is_date: false,
name: 'CASE WHEN 1=1 THEN 1 ELSE 0 END',
type: 'STRING',
},
{
is_date: true,
name: '_TIMESTAMP',
type: 'TIMESTAMP',
},
{
is_date: true,
name: '__TIME',
type: 'TIMESTAMP',
},
{
is_date: false,
name: 'my_dupe_col__2',
type: 'STRING',
},
{
is_date: true,
name: '__timestamp',
type: 'TIMESTAMP',
},
{
is_date: true,
name: '__TIMESTAMP',
type: 'TIMESTAMP',
},
],
},
};
export const databases = {
result: [
{
allow_ctas: true,
allow_dml: true,
allow_run_async: false,
database_name: 'main',
expose_in_sqllab: true,
force_ctas_schema: '',
id: 1,
},
{
allow_ctas: true,
allow_dml: false,
allow_run_async: true,
database_name: 'Presto - Gold',
expose_in_sqllab: true,
force_ctas_schema: 'tmp',
id: 208,
},
],
};
export const tables = {
options: [
{
value: 'birth_names',
schema: 'main',
label: 'birth_names',
title: 'birth_names',
},
{
value: 'energy_usage',
schema: 'main',
label: 'energy_usage',
title: 'energy_usage',
},
{
value: 'wb_health_population',
schema: 'main',
label: 'wb_health_population',
title: 'wb_health_population',
},
],
};
export const stoppedQuery = {
dbId: 1,
cached: false,
ctas: false,
id: 'ryhMUZCGb',
progress: 0,
results: [],
runAsync: false,
schema: 'main',
sql: 'SELECT ...',
sqlEditorId: 'rJaf5u9WZ',
startDttm: 1497400851936,
state: 'stopped',
tab: 'Untitled Query 2',
tempTable: '',
};
export const failedQueryWithErrorMessage = {
dbId: 1,
cached: false,
ctas: false,
errorMessage: 'Something went wrong',
id: 'ryhMUZCGb',
progress: 0,
results: [],
runAsync: false,
schema: 'main',
sql: 'SELECT ...',
sqlEditorId: 'rJaf5u9WZ',
startDttm: 1497400851936,
state: 'failed',
tab: 'Untitled Query 2',
tempTable: '',
};
export const failedQueryWithErrors = {
dbId: 1,
cached: false,
ctas: false,
errors: [
{
message: 'Something went wrong',
error_type: 'TEST_ERROR',
level: 'error',
extra: null,
},
],
id: 'ryhMUZCGb',
progress: 0,
results: [],
runAsync: false,
schema: 'main',
sql: 'SELECT ...',
sqlEditorId: 'rJaf5u9WZ',
startDttm: 1497400851936,
state: 'failed',
tab: 'Untitled Query 2',
tempTable: '',
};
export const runningQuery = {
dbId: 1,
cached: false,
ctas: false,
id: 'ryhMUZCGb',
progress: 90,
state: 'running',
startDttm: Date.now() - 500,
};
export const cachedQuery = { ...queries[0], cached: true };
export const user = {
createdOn: '2021-04-27T18:12:38.952304',
email: 'admin',
firstName: 'admin',
isActive: true,
lastName: 'admin',
permissions: {},
roles: { Admin: Array(173) },
userId: 1,
username: 'admin',
};
export const initialState = {
sqlLab: {
offline: false,
alerts: [],
queries: {},
databases: {},
queryEditors: [defaultQueryEditor],
tabHistory: [defaultQueryEditor.id],
tables: [],
workspaceQueries: [],
queriesLastUpdate: 0,
activeSouthPaneTab: 'Results',
user: { user },
},
messageToasts: [],
common: {
conf: {
DEFAULT_SQLLAB_LIMIT: 1000,
SQL_MAX_ROW: 100000,
DISPLAY_MAX_ROW: 100,
SQLALCHEMY_DOCS_URL: 'test_SQLALCHEMY_DOCS_URL',
SQLALCHEMY_DISPLAY_TEXT: 'test_SQLALCHEMY_DISPLAY_TEXT',
},
},
};
export const query = {
id: 'clientId2353',
dbId: 1,
sql: 'SELECT * FROM something',
sqlEditorId: defaultQueryEditor.id,
tab: 'unimportant',
tempTable: null,
runAsync: false,
ctas: false,
cached: false,
};

View File

@@ -1,252 +0,0 @@
/**
* 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 sqlLabReducer from 'src/SqlLab/reducers/sqlLab';
import * as actions from 'src/SqlLab/actions/sqlLab';
import { now } from 'src/modules/dates';
import { table, initialState as mockState } from '../fixtures';
const initialState = mockState.sqlLab;
describe('sqlLabReducer', () => {
describe('Query editors actions', () => {
let newState;
let defaultQueryEditor;
let qe;
beforeEach(() => {
newState = { ...initialState };
defaultQueryEditor = newState.queryEditors[0];
const action = {
type: actions.ADD_QUERY_EDITOR,
queryEditor: { ...initialState.queryEditors[0], id: 'abcd' },
};
newState = sqlLabReducer(newState, action);
qe = newState.queryEditors.find(e => e.id === 'abcd');
});
it('should add a query editor', () => {
expect(newState.queryEditors).toHaveLength(2);
});
it('should remove a query editor', () => {
expect(newState.queryEditors).toHaveLength(2);
const action = {
type: actions.REMOVE_QUERY_EDITOR,
queryEditor: qe,
};
newState = sqlLabReducer(newState, action);
expect(newState.queryEditors).toHaveLength(1);
});
it('should set q query editor active', () => {
const addQueryEditorAction = {
type: actions.ADD_QUERY_EDITOR,
queryEditor: { ...initialState.queryEditors[0], id: 'abcd' },
};
newState = sqlLabReducer(newState, addQueryEditorAction);
const setActiveQueryEditorAction = {
type: actions.SET_ACTIVE_QUERY_EDITOR,
queryEditor: defaultQueryEditor,
};
newState = sqlLabReducer(newState, setActiveQueryEditorAction);
expect(newState.tabHistory[newState.tabHistory.length - 1]).toBe(
defaultQueryEditor.id,
);
});
it('should not fail while setting DB', () => {
const dbId = 9;
const action = {
type: actions.QUERY_EDITOR_SETDB,
queryEditor: qe,
dbId,
};
newState = sqlLabReducer(newState, action);
expect(newState.queryEditors[1].dbId).toBe(dbId);
});
it('should not fail while setting schema', () => {
const schema = 'foo';
const action = {
type: actions.QUERY_EDITOR_SET_SCHEMA,
queryEditor: qe,
schema,
};
newState = sqlLabReducer(newState, action);
expect(newState.queryEditors[1].schema).toBe(schema);
});
it('should not fail while setting autorun', () => {
const action = {
type: actions.QUERY_EDITOR_SET_AUTORUN,
queryEditor: qe,
};
newState = sqlLabReducer(newState, { ...action, autorun: false });
expect(newState.queryEditors[1].autorun).toBe(false);
newState = sqlLabReducer(newState, { ...action, autorun: true });
expect(newState.queryEditors[1].autorun).toBe(true);
});
it('should not fail while setting title', () => {
const title = 'a new title';
const action = {
type: actions.QUERY_EDITOR_SET_TITLE,
queryEditor: qe,
title,
};
newState = sqlLabReducer(newState, action);
expect(newState.queryEditors[1].title).toBe(title);
});
it('should not fail while setting Sql', () => {
const sql = 'SELECT nothing from dev_null';
const action = {
type: actions.QUERY_EDITOR_SET_SQL,
queryEditor: qe,
sql,
};
newState = sqlLabReducer(newState, action);
expect(newState.queryEditors[1].sql).toBe(sql);
});
it('should not fail while setting queryLimit', () => {
const queryLimit = 101;
const action = {
type: actions.QUERY_EDITOR_SET_QUERY_LIMIT,
queryEditor: qe,
queryLimit,
};
newState = sqlLabReducer(newState, action);
expect(newState.queryEditors[1].queryLimit).toEqual(queryLimit);
});
it('should set selectedText', () => {
const selectedText = 'TEST';
const action = {
type: actions.QUERY_EDITOR_SET_SELECTED_TEXT,
queryEditor: newState.queryEditors[0],
sql: selectedText,
};
expect(newState.queryEditors[0].selectedText).toBeNull();
newState = sqlLabReducer(newState, action);
expect(newState.queryEditors[0].selectedText).toBe(selectedText);
});
});
describe('Tables', () => {
let newState;
let newTable;
beforeEach(() => {
newTable = { ...table };
const action = {
type: actions.MERGE_TABLE,
table: newTable,
};
newState = sqlLabReducer(initialState, action);
newTable = newState.tables[0];
});
it('should add a table', () => {
// Testing that beforeEach actually added the table
expect(newState.tables).toHaveLength(1);
expect(newState.tables[0].expanded).toBe(true);
});
it('should merge the table attributes', () => {
// Merging the extra attribute
newTable.extra = true;
const action = {
type: actions.MERGE_TABLE,
table: newTable,
};
newState = sqlLabReducer(newState, action);
expect(newState.tables).toHaveLength(1);
expect(newState.tables[0].extra).toBe(true);
});
it('should expand and collapse a table', () => {
const collapseTableAction = {
type: actions.COLLAPSE_TABLE,
table: newTable,
};
newState = sqlLabReducer(newState, collapseTableAction);
expect(newState.tables[0].expanded).toBe(false);
const expandTableAction = {
type: actions.EXPAND_TABLE,
table: newTable,
};
newState = sqlLabReducer(newState, expandTableAction);
expect(newState.tables[0].expanded).toBe(true);
});
it('should remove a table', () => {
const action = {
type: actions.REMOVE_TABLE,
table: newTable,
};
newState = sqlLabReducer(newState, action);
expect(newState.tables).toHaveLength(0);
});
});
describe('Run Query', () => {
let newState;
let query;
beforeEach(() => {
newState = { ...initialState };
query = {
id: 'abcd',
progress: 0,
startDttm: now(),
state: 'running',
cached: false,
sqlEditorId: 'dfsadfs',
};
});
it('should start a query', () => {
const action = {
type: actions.START_QUERY,
query: {
id: 'abcd',
progress: 0,
startDttm: now(),
state: 'running',
cached: false,
sqlEditorId: 'dfsadfs',
},
};
newState = sqlLabReducer(newState, action);
expect(Object.keys(newState.queries)).toHaveLength(1);
});
it('should stop the query', () => {
const startQueryAction = {
type: actions.START_QUERY,
query,
};
newState = sqlLabReducer(newState, startQueryAction);
const stopQueryAction = {
type: actions.STOP_QUERY,
query,
};
newState = sqlLabReducer(newState, stopQueryAction);
const q = newState.queries[Object.keys(newState.queries)[0]];
expect(q.state).toBe('stopped');
});
it('should remove a query', () => {
const startQueryAction = {
type: actions.START_QUERY,
query,
};
newState = sqlLabReducer(newState, startQueryAction);
const removeQueryAction = {
type: actions.REMOVE_QUERY,
query,
};
newState = sqlLabReducer(newState, removeQueryAction);
expect(Object.keys(newState.queries)).toHaveLength(0);
});
it('should refresh queries when polling returns empty', () => {
newState = sqlLabReducer(newState, actions.refreshQueries({}));
});
});
});

View File

@@ -1,55 +0,0 @@
/**
* 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 {
emptyQueryResults,
clearQueryEditors,
} from 'src/SqlLab/utils/reduxStateToLocalStorageHelper';
import { LOCALSTORAGE_MAX_QUERY_AGE_MS } from 'src/SqlLab/constants';
import { queries, defaultQueryEditor } from '../fixtures';
describe('reduxStateToLocalStorageHelper', () => {
const queriesObj = {};
beforeEach(() => {
queries.forEach(q => {
queriesObj[q.id] = q;
});
});
it('should empty query.results if query.startDttm is > LOCALSTORAGE_MAX_QUERY_AGE_MS', () => {
// make sure sample data contains old query
const oldQuery = queries[0];
const { id, startDttm } = oldQuery;
expect(Date.now() - startDttm).toBeGreaterThan(
LOCALSTORAGE_MAX_QUERY_AGE_MS,
);
expect(Object.keys(oldQuery.results)).toContain('data');
const emptiedQuery = emptyQueryResults(queriesObj);
expect(emptiedQuery[id].startDttm).toBe(startDttm);
expect(emptiedQuery[id].results).toEqual({});
});
it('should only return selected keys for query editor', () => {
const queryEditors = [defaultQueryEditor];
expect(Object.keys(queryEditors[0])).toContain('schemaOptions');
const clearedQueryEditors = clearQueryEditors(queryEditors);
expect(Object.keys(clearedQueryEditors)[0]).not.toContain('schemaOptions');
});
});