mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
chore: Moves spec files to the src folder - iteration 3 (#14202)
This commit is contained in:
committed by
GitHub
parent
1bc73f2cba
commit
aada73302f
@@ -1,219 +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 { Provider } from 'react-redux';
|
||||
import * as featureFlags from 'src/featureFlags';
|
||||
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import { render, screen, cleanup } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
|
||||
import DashboardList from 'src/views/CRUD/dashboard/DashboardList';
|
||||
import ListView from 'src/components/ListView';
|
||||
import ListViewCard from 'src/components/ListViewCard';
|
||||
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
|
||||
|
||||
// store needed for withToasts(DashboardTable)
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
const dashboardsInfoEndpoint = 'glob:*/api/v1/dashboard/_info*';
|
||||
const dashboardOwnersEndpoint = 'glob:*/api/v1/dashboard/related/owners*';
|
||||
const dashboardCreatedByEndpoint =
|
||||
'glob:*/api/v1/dashboard/related/created_by*';
|
||||
const dashboardFavoriteStatusEndpoint =
|
||||
'glob:*/api/v1/dashboard/favorite_status*';
|
||||
const dashboardsEndpoint = 'glob:*/api/v1/dashboard/?*';
|
||||
const dashboardEndpoint = 'glob:*/api/v1/dashboard/*';
|
||||
|
||||
const mockDashboards = [...new Array(3)].map((_, i) => ({
|
||||
id: i,
|
||||
url: 'url',
|
||||
dashboard_title: `title ${i}`,
|
||||
changed_by_name: 'user',
|
||||
changed_by_url: 'changed_by_url',
|
||||
changed_by_fk: 1,
|
||||
published: true,
|
||||
changed_on_utc: new Date().toISOString(),
|
||||
changed_on_delta_humanized: '5 minutes ago',
|
||||
owners: [{ id: 1, first_name: 'admin', last_name: 'admin_user' }],
|
||||
roles: [{ id: 1, name: 'adminUser' }],
|
||||
thumbnail_url: '/thumbnail',
|
||||
}));
|
||||
|
||||
const mockUser = {
|
||||
userId: 1,
|
||||
};
|
||||
|
||||
fetchMock.get(dashboardsInfoEndpoint, {
|
||||
permissions: ['can_read', 'can_write'],
|
||||
});
|
||||
fetchMock.get(dashboardOwnersEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
fetchMock.get(dashboardCreatedByEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
fetchMock.get(dashboardFavoriteStatusEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
|
||||
fetchMock.get(dashboardsEndpoint, {
|
||||
result: mockDashboards,
|
||||
dashboard_count: 3,
|
||||
});
|
||||
|
||||
fetchMock.get(dashboardEndpoint, {
|
||||
result: mockDashboards[0],
|
||||
});
|
||||
|
||||
global.URL.createObjectURL = jest.fn();
|
||||
fetchMock.get('/thumbnail', { body: new Blob(), sendAsJson: false });
|
||||
|
||||
describe('DashboardList', () => {
|
||||
const isFeatureEnabledMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockImplementation(feature => feature === 'LISTVIEWS_DEFAULT_CARD_VIEW');
|
||||
|
||||
afterAll(() => {
|
||||
isFeatureEnabledMock.restore();
|
||||
});
|
||||
|
||||
const mockedProps = {};
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<DashboardList {...mockedProps} user={mockUser} />
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
beforeAll(async () => {
|
||||
await waitForComponentToPaint(wrapper);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.find(DashboardList)).toExist();
|
||||
});
|
||||
|
||||
it('renders a ListView', () => {
|
||||
expect(wrapper.find(ListView)).toExist();
|
||||
});
|
||||
|
||||
it('fetches info', () => {
|
||||
const callsI = fetchMock.calls(/dashboard\/_info/);
|
||||
expect(callsI).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('fetches data', () => {
|
||||
wrapper.update();
|
||||
const callsD = fetchMock.calls(/dashboard\/\?q/);
|
||||
expect(callsD).toHaveLength(1);
|
||||
expect(callsD[0][0]).toMatchInlineSnapshot(
|
||||
`"http://localhost/api/v1/dashboard/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a card view', () => {
|
||||
expect(wrapper.find(ListViewCard)).toExist();
|
||||
});
|
||||
|
||||
it('renders a table view', async () => {
|
||||
wrapper.find('[data-test="list-view"]').first().simulate('click');
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find('table')).toExist();
|
||||
});
|
||||
|
||||
it('edits', async () => {
|
||||
expect(wrapper.find(PropertiesModal)).not.toExist();
|
||||
wrapper.find('[data-test="edit-alt"]').first().simulate('click');
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find(PropertiesModal)).toExist();
|
||||
});
|
||||
|
||||
it('card view edits', async () => {
|
||||
wrapper.find('[data-test="edit-alt"]').last().simulate('click');
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find(PropertiesModal)).toExist();
|
||||
});
|
||||
|
||||
it('delete', async () => {
|
||||
wrapper
|
||||
.find('[data-test="dashboard-list-trash-icon"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find(ConfirmStatusChange)).toExist();
|
||||
});
|
||||
|
||||
it('card view delete', async () => {
|
||||
wrapper
|
||||
.find('[data-test="dashboard-list-trash-icon"]')
|
||||
.last()
|
||||
.simulate('click');
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find(ConfirmStatusChange)).toExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('RTL', () => {
|
||||
async function renderAndWait() {
|
||||
const mounted = act(async () => {
|
||||
const mockedProps = {};
|
||||
render(
|
||||
<QueryParamProvider>
|
||||
<DashboardList {...mockedProps} user={mockUser} />
|
||||
</QueryParamProvider>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
});
|
||||
|
||||
return mounted;
|
||||
}
|
||||
|
||||
let isFeatureEnabledMock;
|
||||
beforeEach(async () => {
|
||||
isFeatureEnabledMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockImplementation(() => true);
|
||||
await renderAndWait();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
isFeatureEnabledMock.mockRestore();
|
||||
});
|
||||
|
||||
it('renders an "Import Dashboard" tooltip under import button', async () => {
|
||||
const importButton = screen.getByTestId('import-button');
|
||||
userEvent.hover(importButton);
|
||||
|
||||
await screen.findByRole('tooltip');
|
||||
const importTooltip = screen.getByRole('tooltip', {
|
||||
name: 'Import dashboards',
|
||||
});
|
||||
|
||||
expect(importTooltip).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,224 +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 { Provider } from 'react-redux';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import { render, screen, cleanup } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
import * as featureFlags from 'src/featureFlags';
|
||||
|
||||
import DatabaseList from 'src/views/CRUD/data/database/DatabaseList';
|
||||
import DatabaseModal from 'src/views/CRUD/data/database/DatabaseModal';
|
||||
import DeleteModal from 'src/components/DeleteModal';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
import ListView from 'src/components/ListView';
|
||||
import Filters from 'src/components/ListView/Filters';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
// store needed for withToasts(DatabaseList)
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
const databasesInfoEndpoint = 'glob:*/api/v1/database/_info*';
|
||||
const databasesEndpoint = 'glob:*/api/v1/database/?*';
|
||||
const databaseEndpoint = 'glob:*/api/v1/database/*';
|
||||
const databaseRelatedEndpoint = 'glob:*/api/v1/database/*/related_objects*';
|
||||
|
||||
const mockdatabases = [...new Array(3)].map((_, i) => ({
|
||||
changed_by: {
|
||||
first_name: `user`,
|
||||
last_name: `${i}`,
|
||||
},
|
||||
database_name: `db ${i}`,
|
||||
backend: 'postgresql',
|
||||
allow_run_async: true,
|
||||
allow_dml: false,
|
||||
allow_csv_upload: true,
|
||||
expose_in_sqllab: false,
|
||||
changed_on_delta_humanized: `${i} day(s) ago`,
|
||||
changed_on: new Date().toISOString,
|
||||
id: i,
|
||||
}));
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux'),
|
||||
useSelector: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockUser = {
|
||||
userId: 1,
|
||||
};
|
||||
|
||||
fetchMock.get(databasesInfoEndpoint, {
|
||||
permissions: ['can_write'],
|
||||
});
|
||||
fetchMock.get(databasesEndpoint, {
|
||||
result: mockdatabases,
|
||||
database_count: 3,
|
||||
});
|
||||
|
||||
fetchMock.delete(databaseEndpoint, {});
|
||||
fetchMock.get(databaseRelatedEndpoint, {
|
||||
charts: {
|
||||
count: 0,
|
||||
result: [],
|
||||
},
|
||||
dashboards: {
|
||||
count: 0,
|
||||
result: [],
|
||||
},
|
||||
});
|
||||
|
||||
describe('DatabaseList', () => {
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<DatabaseList user={mockUser} />
|
||||
</Provider>,
|
||||
);
|
||||
beforeAll(async () => {
|
||||
await waitForComponentToPaint(wrapper);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.find(DatabaseList)).toExist();
|
||||
});
|
||||
|
||||
it('renders a SubMenu', () => {
|
||||
expect(wrapper.find(SubMenu)).toExist();
|
||||
});
|
||||
|
||||
it('renders a DatabaseModal', () => {
|
||||
expect(wrapper.find(DatabaseModal)).toExist();
|
||||
});
|
||||
|
||||
it('renders a ListView', () => {
|
||||
expect(wrapper.find(ListView)).toExist();
|
||||
});
|
||||
|
||||
it('fetches Databases', () => {
|
||||
const callsD = fetchMock.calls(/database\/\?q/);
|
||||
expect(callsD).toHaveLength(1);
|
||||
expect(callsD[0][0]).toMatchInlineSnapshot(
|
||||
`"http://localhost/api/v1/database/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('deletes', async () => {
|
||||
act(() => {
|
||||
wrapper.find('[data-test="database-delete"]').first().props().onClick();
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
|
||||
expect(wrapper.find(DeleteModal).props().description).toMatchInlineSnapshot(
|
||||
`"The database db 0 is linked to 0 charts that appear on 0 dashboards. Are you sure you want to continue? Deleting the database will break those objects."`,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
.find('#delete')
|
||||
.first()
|
||||
.props()
|
||||
.onChange({ target: { value: 'DELETE' } });
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
act(() => {
|
||||
wrapper.find('button').last().props().onClick();
|
||||
});
|
||||
|
||||
await waitForComponentToPaint(wrapper);
|
||||
|
||||
expect(fetchMock.calls(/database\/0\/related_objects/, 'GET')).toHaveLength(
|
||||
1,
|
||||
);
|
||||
expect(fetchMock.calls(/database\/0/, 'DELETE')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('filters', async () => {
|
||||
const filtersWrapper = wrapper.find(Filters);
|
||||
act(() => {
|
||||
filtersWrapper
|
||||
.find('[name="expose_in_sqllab"]')
|
||||
.first()
|
||||
.props()
|
||||
.onSelect(true);
|
||||
|
||||
filtersWrapper
|
||||
.find('[name="allow_run_async"]')
|
||||
.first()
|
||||
.props()
|
||||
.onSelect(false);
|
||||
|
||||
filtersWrapper
|
||||
.find('[name="database_name"]')
|
||||
.first()
|
||||
.props()
|
||||
.onSubmit('fooo');
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
|
||||
expect(fetchMock.lastCall()[0]).toMatchInlineSnapshot(
|
||||
`"http://localhost/api/v1/database/?q=(filters:!((col:expose_in_sqllab,opr:eq,value:!t),(col:allow_run_async,opr:eq,value:!f),(col:database_name,opr:ct,value:fooo)),order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RTL', () => {
|
||||
async function renderAndWait() {
|
||||
const mounted = act(async () => {
|
||||
render(
|
||||
<QueryParamProvider>
|
||||
<DatabaseList user={mockUser} />
|
||||
</QueryParamProvider>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
});
|
||||
|
||||
return mounted;
|
||||
}
|
||||
|
||||
let isFeatureEnabledMock;
|
||||
beforeEach(async () => {
|
||||
isFeatureEnabledMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockImplementation(() => true);
|
||||
await renderAndWait();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
isFeatureEnabledMock.mockRestore();
|
||||
});
|
||||
|
||||
it('renders an "Import Database" tooltip under import button', async () => {
|
||||
const importButton = screen.getByTestId('import-button');
|
||||
userEvent.hover(importButton);
|
||||
|
||||
await screen.findByRole('tooltip');
|
||||
const importTooltip = screen.getByRole('tooltip', {
|
||||
name: 'Import databases',
|
||||
});
|
||||
|
||||
expect(importTooltip).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,263 +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 * as redux from 'react-redux';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import { render, screen } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Provider } from 'react-redux';
|
||||
import DatabaseModal from 'src/views/CRUD/data/database/DatabaseModal';
|
||||
import Modal from 'src/components/Modal';
|
||||
import Tabs from 'src/common/components/Tabs';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
import { initialState } from 'spec/javascripts/sqllab/fixtures';
|
||||
|
||||
// store needed for withToasts(DatabaseModal)
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
const mockedProps = {
|
||||
show: true,
|
||||
};
|
||||
const dbProps = {
|
||||
show: true,
|
||||
database: {
|
||||
id: 10,
|
||||
database_name: 'test',
|
||||
sqlalchemy_uri: 'sqllite:///user:pw/test',
|
||||
expose_in_sqllab: true,
|
||||
},
|
||||
};
|
||||
|
||||
const DATABASE_ENDPOINT = 'glob:*/api/v1/database/*';
|
||||
fetchMock.get(DATABASE_ENDPOINT, {});
|
||||
|
||||
describe('DatabaseModal', () => {
|
||||
describe('enzyme', () => {
|
||||
let wrapper;
|
||||
let spyOnUseSelector;
|
||||
beforeAll(() => {
|
||||
spyOnUseSelector = jest.spyOn(redux, 'useSelector');
|
||||
spyOnUseSelector.mockReturnValue(initialState.common.conf);
|
||||
});
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<DatabaseModal store={store} {...mockedProps} />
|
||||
</Provider>,
|
||||
);
|
||||
});
|
||||
afterEach(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
it('renders', () => {
|
||||
expect(wrapper.find(DatabaseModal)).toExist();
|
||||
});
|
||||
it('renders a Modal', () => {
|
||||
expect(wrapper.find(Modal)).toExist();
|
||||
});
|
||||
it('renders "Add database" header when no database is included', () => {
|
||||
expect(wrapper.find('h4').text()).toEqual('Add database');
|
||||
});
|
||||
it('renders "Edit database" header when database prop is included', () => {
|
||||
const editWrapper = mount(<DatabaseModal store={store} {...dbProps} />);
|
||||
waitForComponentToPaint(editWrapper);
|
||||
expect(editWrapper.find('h4').text()).toEqual('Edit database');
|
||||
editWrapper.unmount();
|
||||
});
|
||||
it('renders a Tabs menu', () => {
|
||||
expect(wrapper.find(Tabs)).toExist();
|
||||
});
|
||||
it('renders two TabPanes', () => {
|
||||
expect(wrapper.find('.ant-tabs-tab')).toExist();
|
||||
expect(wrapper.find('.ant-tabs-tab')).toHaveLength(2);
|
||||
});
|
||||
it('renders input elements for Connection section', () => {
|
||||
expect(wrapper.find('input[name="database_name"]')).toExist();
|
||||
expect(wrapper.find('input[name="sqlalchemy_uri"]')).toExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe('RTL', () => {
|
||||
describe('initial load', () => {
|
||||
it('hides the forms from the db when not selected', () => {
|
||||
render(
|
||||
<DatabaseModal
|
||||
show
|
||||
database={{
|
||||
expose_in_sqllab: false,
|
||||
allow_ctas: false,
|
||||
allow_cvas: false,
|
||||
}}
|
||||
/>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
// Select Advanced tab
|
||||
const advancedTab = screen.getByRole('tab', {
|
||||
name: /advanced/i,
|
||||
});
|
||||
userEvent.click(advancedTab);
|
||||
// Select SQL Lab tab
|
||||
const sqlLabSettingsTab = screen.getByRole('tab', {
|
||||
name: /sql lab/i,
|
||||
});
|
||||
userEvent.click(sqlLabSettingsTab);
|
||||
|
||||
const exposeInSqlLab = screen.getByText('Expose in SQL Lab');
|
||||
const exposeChoicesForm = exposeInSqlLab.parentElement.nextSibling;
|
||||
const schemaField = screen.getByText('CTAS & CVAS SCHEMA')
|
||||
.parentElement;
|
||||
expect(exposeChoicesForm).not.toHaveClass('open');
|
||||
expect(schemaField).not.toHaveClass('open');
|
||||
});
|
||||
});
|
||||
it('renders all settings when "Expose in SQL Lab" is checked', () => {
|
||||
render(<DatabaseModal {...dbProps} />, { useRedux: true });
|
||||
|
||||
// Select Advanced tab
|
||||
const advancedTab = screen.getByRole('tab', {
|
||||
name: /advanced/i,
|
||||
});
|
||||
userEvent.click(advancedTab);
|
||||
|
||||
// Select SQL Lab tab
|
||||
const sqlLabSettingsTab = screen.getByRole('tab', {
|
||||
name: /sql lab/i,
|
||||
});
|
||||
|
||||
userEvent.click(sqlLabSettingsTab);
|
||||
|
||||
// Grab all SQL Lab settings by their labels
|
||||
// const exposeInSqlLab = screen.getByText('Expose in SQL Lab');
|
||||
const exposeInSqlLab = screen.getByRole('checkbox', {
|
||||
name: /expose in sql lab/i,
|
||||
});
|
||||
|
||||
// While 'Expose in SQL Lab' is checked, all settings should display
|
||||
expect(exposeInSqlLab).not.toBeChecked();
|
||||
|
||||
// When clicked, "Expose in SQL Lab" becomes unchecked
|
||||
userEvent.click(exposeInSqlLab);
|
||||
|
||||
// While checked make sure all checkboxes are showing
|
||||
expect(exposeInSqlLab).toBeChecked();
|
||||
const checkboxes = screen
|
||||
.getAllByRole('checkbox')
|
||||
.filter(checkbox => !checkbox.checked);
|
||||
|
||||
expect(checkboxes.length).toEqual(4);
|
||||
});
|
||||
|
||||
it('renders the schema field when allowCTAS is checked', () => {
|
||||
render(<DatabaseModal {...dbProps} />, { useRedux: true });
|
||||
|
||||
// Select Advanced tab
|
||||
const advancedTab = screen.getByRole('tab', {
|
||||
name: /advanced/i,
|
||||
});
|
||||
userEvent.click(advancedTab);
|
||||
|
||||
// Select SQL Lab tab
|
||||
const sqlLabSettingsTab = screen.getByRole('tab', {
|
||||
name: /sql lab/i,
|
||||
});
|
||||
userEvent.click(sqlLabSettingsTab);
|
||||
// Grab CTAS & schema field by their labels
|
||||
const allowCTAS = screen.getByLabelText('Allow CREATE TABLE AS');
|
||||
const schemaField = screen.getByText('CTAS & CVAS SCHEMA').parentElement;
|
||||
|
||||
// While CTAS & CVAS are unchecked, schema field is not visible
|
||||
expect(schemaField).not.toHaveClass('open');
|
||||
|
||||
// Check "Allow CTAS" to reveal schema field
|
||||
userEvent.click(allowCTAS);
|
||||
expect(schemaField).toHaveClass('open');
|
||||
|
||||
// Uncheck "Allow CTAS" to hide schema field again
|
||||
userEvent.click(allowCTAS);
|
||||
expect(schemaField).not.toHaveClass('open');
|
||||
});
|
||||
|
||||
it('renders the schema field when allowCVAS is checked', () => {
|
||||
render(<DatabaseModal {...dbProps} />, { useRedux: true });
|
||||
|
||||
// Select Advanced tab
|
||||
const advancedTab = screen.getByRole('tab', {
|
||||
name: /advanced/i,
|
||||
});
|
||||
userEvent.click(advancedTab);
|
||||
|
||||
// Select SQL Lab tab
|
||||
const sqlLabSettingsTab = screen.getByRole('tab', {
|
||||
name: /sql lab/i,
|
||||
});
|
||||
userEvent.click(sqlLabSettingsTab);
|
||||
// Grab CVAS by it's label & schema field
|
||||
const allowCVAS = screen.getByText('Allow CREATE VIEW AS');
|
||||
const schemaField = screen.getByText('CTAS & CVAS SCHEMA').parentElement;
|
||||
|
||||
// While CTAS & CVAS are unchecked, schema field is not visible
|
||||
expect(schemaField).not.toHaveClass('open');
|
||||
|
||||
// Check "Allow CVAS" to reveal schema field
|
||||
userEvent.click(allowCVAS);
|
||||
expect(schemaField).toHaveClass('open');
|
||||
|
||||
// Uncheck "Allow CVAS" to hide schema field again
|
||||
userEvent.click(allowCVAS);
|
||||
expect(schemaField).not.toHaveClass('open');
|
||||
});
|
||||
|
||||
it('renders the schema field when both allowCTAS and allowCVAS are checked', () => {
|
||||
render(<DatabaseModal {...dbProps} />, { useRedux: true });
|
||||
|
||||
// Select Advanced tab
|
||||
const advancedTab = screen.getByRole('tab', {
|
||||
name: /advanced/i,
|
||||
});
|
||||
userEvent.click(advancedTab);
|
||||
|
||||
// Select SQL Lab tab
|
||||
const sqlLabSettingsTab = screen.getByRole('tab', {
|
||||
name: /sql lab/i,
|
||||
});
|
||||
userEvent.click(sqlLabSettingsTab);
|
||||
// Grab CTAS and CVAS by their labels, & schema field
|
||||
const allowCTAS = screen.getByText('Allow CREATE TABLE AS');
|
||||
const allowCVAS = screen.getByText('Allow CREATE VIEW AS');
|
||||
const schemaField = screen.getByText('CTAS & CVAS SCHEMA').parentElement;
|
||||
|
||||
// While CTAS & CVAS are unchecked, schema field is not visible
|
||||
expect(schemaField).not.toHaveClass('open');
|
||||
|
||||
// Check both "Allow CTAS" and "Allow CVAS" to reveal schema field
|
||||
userEvent.click(allowCTAS);
|
||||
userEvent.click(allowCVAS);
|
||||
expect(schemaField).toHaveClass('open');
|
||||
// Uncheck both "Allow CTAS" and "Allow CVAS" to hide schema field again
|
||||
userEvent.click(allowCTAS);
|
||||
userEvent.click(allowCVAS);
|
||||
|
||||
// Both checkboxes go unchecked, so the field should no longer render
|
||||
expect(schemaField).not.toHaveClass('open');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,222 +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 { Provider } from 'react-redux';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import { render, screen, cleanup } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
import * as featureFlags from 'src/featureFlags';
|
||||
|
||||
import DatasetList from 'src/views/CRUD/data/dataset/DatasetList';
|
||||
import ListView from 'src/components/ListView';
|
||||
import Button from 'src/components/Button';
|
||||
import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
// store needed for withToasts(DatasetList)
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
const datasetsInfoEndpoint = 'glob:*/api/v1/dataset/_info*';
|
||||
const datasetsOwnersEndpoint = 'glob:*/api/v1/dataset/related/owners*';
|
||||
const datasetsSchemaEndpoint = 'glob:*/api/v1/dataset/distinct/schema*';
|
||||
const databaseEndpoint = 'glob:*/api/v1/dataset/related/database*';
|
||||
const datasetsEndpoint = 'glob:*/api/v1/dataset/?*';
|
||||
|
||||
const mockdatasets = [...new Array(3)].map((_, i) => ({
|
||||
changed_by_name: 'user',
|
||||
kind: i === 0 ? 'virtual' : 'physical', // ensure there is 1 virtual
|
||||
changed_by_url: 'changed_by_url',
|
||||
changed_by: 'user',
|
||||
changed_on: new Date().toISOString(),
|
||||
database_name: `db ${i}`,
|
||||
explore_url: `/explore/table/${i}`,
|
||||
id: i,
|
||||
schema: `schema ${i}`,
|
||||
table_name: `coolest table ${i}`,
|
||||
}));
|
||||
|
||||
const mockUser = {
|
||||
userId: 1,
|
||||
};
|
||||
|
||||
fetchMock.get(datasetsInfoEndpoint, {
|
||||
permissions: ['can_read', 'can_write'],
|
||||
});
|
||||
fetchMock.get(datasetsOwnersEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
fetchMock.get(datasetsSchemaEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
fetchMock.get(datasetsEndpoint, {
|
||||
result: mockdatasets,
|
||||
dataset_count: 3,
|
||||
});
|
||||
fetchMock.get(databaseEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
|
||||
async function mountAndWait(props) {
|
||||
const mounted = mount(
|
||||
<Provider store={store}>
|
||||
<DatasetList {...props} user={mockUser} />
|
||||
</Provider>,
|
||||
);
|
||||
await waitForComponentToPaint(mounted);
|
||||
|
||||
return mounted;
|
||||
}
|
||||
|
||||
describe('DatasetList', () => {
|
||||
const mockedProps = {};
|
||||
let wrapper;
|
||||
|
||||
beforeAll(async () => {
|
||||
wrapper = await mountAndWait(mockedProps);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.find(DatasetList)).toExist();
|
||||
});
|
||||
|
||||
it('renders a ListView', () => {
|
||||
expect(wrapper.find(ListView)).toExist();
|
||||
});
|
||||
|
||||
it('fetches info', () => {
|
||||
const callsI = fetchMock.calls(/dataset\/_info/);
|
||||
expect(callsI).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('fetches data', () => {
|
||||
const callsD = fetchMock.calls(/dataset\/\?q/);
|
||||
expect(callsD).toHaveLength(1);
|
||||
expect(callsD[0][0]).toMatchInlineSnapshot(
|
||||
`"http://localhost/api/v1/dataset/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('fetches owner filter values', () => {
|
||||
expect(fetchMock.calls(/dataset\/related\/owners/)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('fetches schema filter values', () => {
|
||||
expect(fetchMock.calls(/dataset\/distinct\/schema/)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('shows/hides bulk actions when bulk actions is clicked', async () => {
|
||||
await waitForComponentToPaint(wrapper);
|
||||
const button = wrapper.find(Button).at(0);
|
||||
act(() => {
|
||||
button.props().onClick();
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find(IndeterminateCheckbox)).toHaveLength(
|
||||
mockdatasets.length + 1, // 1 for each row and 1 for select all
|
||||
);
|
||||
});
|
||||
|
||||
it('renders different bulk selected copy depending on type of row selected', async () => {
|
||||
// None selected
|
||||
const checkedEvent = { target: { checked: true } };
|
||||
const uncheckedEvent = { target: { checked: false } };
|
||||
expect(
|
||||
wrapper.find('[data-test="bulk-select-copy"]').text(),
|
||||
).toMatchInlineSnapshot(`"0 Selected"`);
|
||||
|
||||
// Vitual Selected
|
||||
act(() => {
|
||||
wrapper.find(IndeterminateCheckbox).at(1).props().onChange(checkedEvent);
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(
|
||||
wrapper.find('[data-test="bulk-select-copy"]').text(),
|
||||
).toMatchInlineSnapshot(`"1 Selected (Virtual)"`);
|
||||
|
||||
// Physical Selected
|
||||
act(() => {
|
||||
wrapper
|
||||
.find(IndeterminateCheckbox)
|
||||
.at(1)
|
||||
.props()
|
||||
.onChange(uncheckedEvent);
|
||||
wrapper.find(IndeterminateCheckbox).at(2).props().onChange(checkedEvent);
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(
|
||||
wrapper.find('[data-test="bulk-select-copy"]').text(),
|
||||
).toMatchInlineSnapshot(`"1 Selected (Physical)"`);
|
||||
|
||||
// All Selected
|
||||
act(() => {
|
||||
wrapper.find(IndeterminateCheckbox).at(0).props().onChange(checkedEvent);
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(
|
||||
wrapper.find('[data-test="bulk-select-copy"]').text(),
|
||||
).toMatchInlineSnapshot(`"3 Selected (2 Physical, 1 Virtual)"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RTL', () => {
|
||||
async function renderAndWait() {
|
||||
const mounted = act(async () => {
|
||||
const mockedProps = {};
|
||||
render(
|
||||
<QueryParamProvider>
|
||||
<DatasetList {...mockedProps} user={mockUser} />
|
||||
</QueryParamProvider>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
});
|
||||
|
||||
return mounted;
|
||||
}
|
||||
|
||||
let isFeatureEnabledMock;
|
||||
beforeEach(async () => {
|
||||
isFeatureEnabledMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockImplementation(() => true);
|
||||
await renderAndWait();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
isFeatureEnabledMock.mockRestore();
|
||||
});
|
||||
|
||||
it('renders an "Import Dataset" tooltip under import button', async () => {
|
||||
const importButton = screen.getByTestId('import-button');
|
||||
userEvent.hover(importButton);
|
||||
|
||||
await screen.findByRole('tooltip');
|
||||
const importTooltip = screen.getByRole('tooltip', {
|
||||
name: 'Import datasets',
|
||||
});
|
||||
|
||||
expect(importTooltip).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,341 +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 { Provider } from 'react-redux';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { styledMount as mount } from 'spec/helpers/theming';
|
||||
import { render, screen, cleanup, waitFor } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { handleBulkSavedQueryExport } from 'src/views/CRUD/utils';
|
||||
import * as featureFlags from 'src/featureFlags';
|
||||
import SavedQueryList from 'src/views/CRUD/data/savedquery/SavedQueryList';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
import ListView from 'src/components/ListView';
|
||||
import Filters from 'src/components/ListView/Filters';
|
||||
import ActionsBar from 'src/components/ListView/ActionsBar';
|
||||
import DeleteModal from 'src/components/DeleteModal';
|
||||
import Button from 'src/components/Button';
|
||||
import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
|
||||
// store needed for withToasts(DatabaseList)
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
const queriesInfoEndpoint = 'glob:*/api/v1/saved_query/_info*';
|
||||
const queriesEndpoint = 'glob:*/api/v1/saved_query/?*';
|
||||
const queryEndpoint = 'glob:*/api/v1/saved_query/*';
|
||||
const queriesRelatedEndpoint = 'glob:*/api/v1/saved_query/related/database?*';
|
||||
const queriesDistinctEndpoint = 'glob:*/api/v1/saved_query/distinct/schema?*';
|
||||
|
||||
const mockqueries = [...new Array(3)].map((_, i) => ({
|
||||
created_by: {
|
||||
id: i,
|
||||
first_name: `user`,
|
||||
last_name: `${i}`,
|
||||
},
|
||||
created_on: `${i}-2020`,
|
||||
database: {
|
||||
database_name: `db ${i}`,
|
||||
id: i,
|
||||
},
|
||||
changed_on_delta_humanized: '1 day ago',
|
||||
db_id: i,
|
||||
description: `SQL for ${i}`,
|
||||
id: i,
|
||||
label: `query ${i}`,
|
||||
schema: 'public',
|
||||
sql: `SELECT ${i} FROM table`,
|
||||
sql_tables: [
|
||||
{
|
||||
catalog: null,
|
||||
schema: null,
|
||||
table: `${i}`,
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
// ---------- For import testing ----------
|
||||
// Create an one more mocked query than the original mocked query array
|
||||
const mockOneMoreQuery = [...new Array(mockqueries.length + 1)].map((_, i) => ({
|
||||
created_by: {
|
||||
id: i,
|
||||
first_name: `user`,
|
||||
last_name: `${i}`,
|
||||
},
|
||||
created_on: `${i}-2020`,
|
||||
database: {
|
||||
database_name: `db ${i}`,
|
||||
id: i,
|
||||
},
|
||||
changed_on_delta_humanized: '1 day ago',
|
||||
db_id: i,
|
||||
description: `SQL for ${i}`,
|
||||
id: i,
|
||||
label: `query ${i}`,
|
||||
schema: 'public',
|
||||
sql: `SELECT ${i} FROM table`,
|
||||
sql_tables: [
|
||||
{
|
||||
catalog: null,
|
||||
schema: null,
|
||||
table: `${i}`,
|
||||
},
|
||||
],
|
||||
}));
|
||||
// Grab the last mocked query, to mock import
|
||||
const mockNewImportQuery = mockOneMoreQuery.pop();
|
||||
// Create a new file out of mocked import query to mock upload
|
||||
const mockImportFile = new File(
|
||||
[mockNewImportQuery],
|
||||
'saved_query_import_mock.json',
|
||||
);
|
||||
|
||||
fetchMock.get(queriesInfoEndpoint, {
|
||||
permissions: ['can_write', 'can_read'],
|
||||
});
|
||||
fetchMock.get(queriesEndpoint, {
|
||||
result: mockqueries,
|
||||
count: 3,
|
||||
});
|
||||
|
||||
fetchMock.delete(queryEndpoint, {});
|
||||
fetchMock.delete(queriesEndpoint, {});
|
||||
|
||||
fetchMock.get(queriesRelatedEndpoint, {
|
||||
count: 0,
|
||||
result: [],
|
||||
});
|
||||
|
||||
fetchMock.get(queriesDistinctEndpoint, {
|
||||
count: 0,
|
||||
result: [],
|
||||
});
|
||||
|
||||
// Mock utils module
|
||||
jest.mock('src/views/CRUD/utils');
|
||||
|
||||
describe('SavedQueryList', () => {
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<SavedQueryList />
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
beforeAll(async () => {
|
||||
await waitForComponentToPaint(wrapper);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.find(SavedQueryList)).toExist();
|
||||
});
|
||||
|
||||
it('renders a SubMenu', () => {
|
||||
expect(wrapper.find(SubMenu)).toExist();
|
||||
});
|
||||
|
||||
it('renders a ListView', () => {
|
||||
expect(wrapper.find(ListView)).toExist();
|
||||
});
|
||||
|
||||
it('fetches saved queries', () => {
|
||||
const callsQ = fetchMock.calls(/saved_query\/\?q/);
|
||||
expect(callsQ).toHaveLength(1);
|
||||
expect(callsQ[0][0]).toMatchInlineSnapshot(
|
||||
`"http://localhost/api/v1/saved_query/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders ActionsBar in table', () => {
|
||||
expect(wrapper.find(ActionsBar)).toExist();
|
||||
expect(wrapper.find(ActionsBar)).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('deletes', async () => {
|
||||
act(() => {
|
||||
wrapper.find('span[data-test="delete-action"]').first().props().onClick();
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
|
||||
expect(
|
||||
wrapper.find(DeleteModal).first().props().description,
|
||||
).toMatchInlineSnapshot(
|
||||
`"This action will permanently delete the saved query."`,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
wrapper
|
||||
.find('#delete')
|
||||
.first()
|
||||
.props()
|
||||
.onChange({ target: { value: 'DELETE' } });
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
act(() => {
|
||||
wrapper.find('button').last().props().onClick();
|
||||
});
|
||||
|
||||
await waitForComponentToPaint(wrapper);
|
||||
|
||||
expect(fetchMock.calls(/saved_query\/0/, 'DELETE')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('shows/hides bulk actions when bulk actions is clicked', async () => {
|
||||
const button = wrapper.find(Button).at(0);
|
||||
act(() => {
|
||||
button.props().onClick();
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find(IndeterminateCheckbox)).toHaveLength(
|
||||
mockqueries.length + 1, // 1 for each row and 1 for select all
|
||||
);
|
||||
});
|
||||
|
||||
it('searches', async () => {
|
||||
const filtersWrapper = wrapper.find(Filters);
|
||||
act(() => {
|
||||
filtersWrapper.find('[name="label"]').first().props().onSubmit('fooo');
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
|
||||
expect(fetchMock.lastCall()[0]).toMatchInlineSnapshot(
|
||||
`"http://localhost/api/v1/saved_query/?q=(filters:!((col:label,opr:all_text,value:fooo)),order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RTL', () => {
|
||||
async function renderAndWait() {
|
||||
const mounted = act(async () => {
|
||||
render(
|
||||
<QueryParamProvider>
|
||||
<SavedQueryList />
|
||||
</QueryParamProvider>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
});
|
||||
|
||||
return mounted;
|
||||
}
|
||||
|
||||
let isFeatureEnabledMock;
|
||||
beforeEach(async () => {
|
||||
isFeatureEnabledMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockImplementation(() => true);
|
||||
await renderAndWait();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
isFeatureEnabledMock.mockRestore();
|
||||
});
|
||||
it('renders an export button in the bulk actions', () => {
|
||||
// Grab and click the "Bulk Select" button to expose checkboxes
|
||||
const bulkSelectButton = screen.getByRole('button', {
|
||||
name: /bulk select/i,
|
||||
});
|
||||
userEvent.click(bulkSelectButton);
|
||||
|
||||
// Grab and click the "toggle all" checkbox to expose export button
|
||||
const selectAllCheckbox = screen.getByRole('checkbox', {
|
||||
name: /toggle all rows selected/i,
|
||||
});
|
||||
userEvent.click(selectAllCheckbox);
|
||||
|
||||
// Grab and assert that export button is visible
|
||||
const exportButton = screen.getByRole('button', {
|
||||
name: /export/i,
|
||||
});
|
||||
expect(exportButton).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders an export button in the actions bar', async () => {
|
||||
// Grab Export action button and mock mouse hovering over it
|
||||
const exportActionButton = screen.getAllByRole('button')[18];
|
||||
userEvent.hover(exportActionButton);
|
||||
|
||||
// Wait for the tooltip to pop up
|
||||
await screen.findByRole('tooltip');
|
||||
|
||||
// Grab and assert that "Export Query" tooltip is in the document
|
||||
const exportTooltip = screen.getByRole('tooltip', {
|
||||
name: /export query/i,
|
||||
});
|
||||
expect(exportTooltip).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('runs handleBulkSavedQueryExport when export is clicked', () => {
|
||||
// Grab Export action button and mock mouse clicking it
|
||||
const exportActionButton = screen.getAllByRole('button')[18];
|
||||
userEvent.click(exportActionButton);
|
||||
|
||||
expect(handleBulkSavedQueryExport).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders an import button in the submenu', () => {
|
||||
// Grab and assert that import saved query button is visible
|
||||
const importButton = screen.getByTestId('import-button');
|
||||
expect(importButton).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders an "Import Saved Query" tooltip under import button', async () => {
|
||||
const importButton = screen.getByTestId('import-button');
|
||||
userEvent.hover(importButton);
|
||||
waitFor(() => {
|
||||
expect(importButton).toHaveClass('ant-tooltip-open');
|
||||
screen.findByTestId('import-tooltip-test');
|
||||
const importTooltip = screen.getByRole('tooltip', {
|
||||
name: 'Import queries',
|
||||
});
|
||||
expect(importTooltip).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an import model when import button is clicked', async () => {
|
||||
// Grab and click import saved query button to reveal modal
|
||||
const importButton = screen.getByTestId('import-button');
|
||||
userEvent.click(importButton);
|
||||
|
||||
// Grab and assert that saved query import modal's heading is visible
|
||||
const importSavedQueryModalHeading = screen.getByRole('heading', {
|
||||
name: 'Import queries',
|
||||
});
|
||||
expect(importSavedQueryModalHeading).toBeVisible();
|
||||
});
|
||||
|
||||
it('imports a saved query', () => {
|
||||
// Grab and click import saved query button to reveal modal
|
||||
const importButton = screen.getByTestId('import-button');
|
||||
userEvent.click(importButton);
|
||||
|
||||
// Grab "Choose File" input from import modal
|
||||
const chooseFileInput = screen.getByLabelText(/file\*/i);
|
||||
// Upload mocked import file
|
||||
userEvent.upload(chooseFileInput, mockImportFile);
|
||||
|
||||
expect(chooseFileInput.files[0]).toStrictEqual(mockImportFile);
|
||||
expect(chooseFileInput.files.item(0)).toStrictEqual(mockImportFile);
|
||||
expect(chooseFileInput.files).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -1,137 +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 { styledMount as mount } from 'spec/helpers/theming';
|
||||
import SavedQueryPreviewModal from 'src/views/CRUD/data/savedquery/SavedQueryPreviewModal';
|
||||
import Button from 'src/components/Button';
|
||||
import Modal from 'src/components/Modal';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
// store needed for withToasts(DatabaseList)
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
const mockqueries = [...new Array(3)].map((_, i) => ({
|
||||
created_by: {
|
||||
id: i,
|
||||
first_name: `user`,
|
||||
last_name: `${i}`,
|
||||
},
|
||||
created_on: `${i}-2020`,
|
||||
database: {
|
||||
database_name: `db ${i}`,
|
||||
id: i,
|
||||
},
|
||||
changed_on_delta_humanized: '1 day ago',
|
||||
db_id: i,
|
||||
description: `SQL for ${i}`,
|
||||
id: i,
|
||||
label: `query ${i}`,
|
||||
schema: 'public',
|
||||
sql: `SELECT ${i} FROM table`,
|
||||
sql_tables: [
|
||||
{
|
||||
catalog: null,
|
||||
schema: null,
|
||||
table: `${i}`,
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const mockedProps = {
|
||||
fetchData: jest.fn(() => {}),
|
||||
openInSqlLab: jest.fn(() => {}),
|
||||
onHide: () => {},
|
||||
queries: mockqueries,
|
||||
savedQuery: mockqueries[1],
|
||||
show: true,
|
||||
};
|
||||
|
||||
const FETCH_SAVED_QUERY_ENDPOINT = 'glob:*/api/v1/saved_query/*';
|
||||
const SAVED_QUERY_PAYLOAD = { result: mockqueries[1] };
|
||||
|
||||
fetchMock.get(FETCH_SAVED_QUERY_ENDPOINT, SAVED_QUERY_PAYLOAD);
|
||||
|
||||
async function mountAndWait(props = mockedProps) {
|
||||
const mounted = mount(<SavedQueryPreviewModal store={store} {...props} />);
|
||||
await waitForComponentToPaint(mounted);
|
||||
|
||||
return mounted;
|
||||
}
|
||||
|
||||
describe('SavedQueryPreviewModal', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeAll(async () => {
|
||||
wrapper = await mountAndWait();
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.find(SavedQueryPreviewModal)).toExist();
|
||||
});
|
||||
|
||||
it('renders a Modal', () => {
|
||||
expect(wrapper.find(Modal)).toExist();
|
||||
});
|
||||
|
||||
it('renders sql from saved query', () => {
|
||||
expect(wrapper.find('pre').text()).toEqual('SELECT 1 FROM table');
|
||||
});
|
||||
|
||||
it('renders buttons with correct text', () => {
|
||||
expect(wrapper.find(Button).contains('Previous')).toBe(true);
|
||||
expect(wrapper.find(Button).contains('Next')).toBe(true);
|
||||
expect(wrapper.find(Button).contains('Open in SQL Lab')).toBe(true);
|
||||
});
|
||||
|
||||
it('handle next save query', () => {
|
||||
const button = wrapper.find('button[data-test="next-saved-query"]');
|
||||
expect(button.props().disabled).toBe(false);
|
||||
act(() => {
|
||||
button.props().onClick(false);
|
||||
});
|
||||
expect(mockedProps.fetchData).toHaveBeenCalled();
|
||||
expect(mockedProps.fetchData.mock.calls[0][0]).toEqual(2);
|
||||
});
|
||||
|
||||
it('handle previous save query', () => {
|
||||
const button = wrapper
|
||||
.find('[data-test="previous-saved-query"]')
|
||||
.find(Button);
|
||||
expect(button.props().disabled).toBe(false);
|
||||
act(() => {
|
||||
button.props().onClick(true);
|
||||
});
|
||||
wrapper.update();
|
||||
expect(mockedProps.fetchData).toHaveBeenCalled();
|
||||
expect(mockedProps.fetchData.mock.calls[0][0]).toEqual(2);
|
||||
});
|
||||
|
||||
it('handle open in sql lab', async () => {
|
||||
act(() => {
|
||||
wrapper.find('[data-test="open-in-sql-lab"]').first().props().onClick();
|
||||
});
|
||||
expect(mockedProps.openInSqlLab).toHaveBeenCalled();
|
||||
expect(mockedProps.openInSqlLab.mock.calls[0][0]).toEqual(1);
|
||||
});
|
||||
});
|
||||
@@ -1,137 +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 { act } from 'react-dom/test-utils';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { Provider } from 'react-redux';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import thunk from 'redux-thunk';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import ActivityTable from 'src/views/CRUD/welcome/ActivityTable';
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
const chartsEndpoint = 'glob:*/api/v1/chart/?*';
|
||||
const dashboardsEndpoint = 'glob:*/api/v1/dashboard/?*';
|
||||
|
||||
const mockData = {
|
||||
Viewed: [
|
||||
{
|
||||
slice_name: 'ChartyChart',
|
||||
changed_on_utc: '24 Feb 2014 10:13:14',
|
||||
url: '/fakeUrl/explore',
|
||||
id: '4',
|
||||
table: {},
|
||||
},
|
||||
],
|
||||
Created: [
|
||||
{
|
||||
dashboard_title: 'Dashboard_Test',
|
||||
changed_on_utc: '24 Feb 2014 10:13:14',
|
||||
url: '/fakeUrl/dashboard',
|
||||
id: '3',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
fetchMock.get(chartsEndpoint, {
|
||||
result: [
|
||||
{
|
||||
slice_name: 'ChartyChart',
|
||||
changed_on_utc: '24 Feb 2014 10:13:14',
|
||||
url: '/fakeUrl/explore',
|
||||
id: '4',
|
||||
table: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
fetchMock.get(dashboardsEndpoint, {
|
||||
result: [
|
||||
{
|
||||
dashboard_title: 'Dashboard_Test',
|
||||
changed_on_utc: '24 Feb 2014 10:13:14',
|
||||
url: '/fakeUrl/dashboard',
|
||||
id: '3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
describe('ActivityTable', () => {
|
||||
const activityProps = {
|
||||
activeChild: 'Created',
|
||||
activityData: mockData,
|
||||
setActiveChild: jest.fn(),
|
||||
user: { userId: '1' },
|
||||
loading: false,
|
||||
};
|
||||
|
||||
let wrapper: ReactWrapper;
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<ActivityTable {...activityProps} />
|
||||
</Provider>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('the component renders', () => {
|
||||
expect(wrapper.find(ActivityTable)).toExist();
|
||||
});
|
||||
it('renders tabs with three buttons', () => {
|
||||
expect(wrapper.find('li')).toHaveLength(3);
|
||||
});
|
||||
it('renders ActivityCards', async () => {
|
||||
expect(wrapper.find('ListViewCard')).toExist();
|
||||
});
|
||||
it('calls the getEdited batch call when edited tab is clicked', async () => {
|
||||
act(() => {
|
||||
const handler = wrapper.find('li.no-router a').at(1).prop('onClick');
|
||||
if (handler) {
|
||||
handler({} as any);
|
||||
}
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
const dashboardCall = fetchMock.calls(/dashboard\/\?q/);
|
||||
const chartCall = fetchMock.calls(/chart\/\?q/);
|
||||
expect(chartCall).toHaveLength(1);
|
||||
expect(dashboardCall).toHaveLength(1);
|
||||
});
|
||||
it('show empty state if there is data', () => {
|
||||
const activityProps = {
|
||||
activeChild: 'Created',
|
||||
activityData: {},
|
||||
setActiveChild: jest.fn(),
|
||||
user: { userId: '1' },
|
||||
loading: false,
|
||||
};
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<ActivityTable {...activityProps} />
|
||||
</Provider>,
|
||||
);
|
||||
expect(wrapper.find('EmptyState')).toExist();
|
||||
});
|
||||
});
|
||||
@@ -1,95 +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 thunk from 'redux-thunk';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import ChartTable from 'src/views/CRUD/welcome/ChartTable';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
const chartsEndpoint = 'glob:*/api/v1/chart/?*';
|
||||
const chartsInfoEndpoint = 'glob:*/api/v1/chart/_info*';
|
||||
const chartFavoriteStatusEndpoint = 'glob:*/api/v1/chart/favorite_status*';
|
||||
|
||||
const mockCharts = [...new Array(3)].map((_, i) => ({
|
||||
changed_on_utc: new Date().toISOString(),
|
||||
created_by: 'super user',
|
||||
id: i,
|
||||
slice_name: `cool chart ${i}`,
|
||||
url: 'url',
|
||||
viz_type: 'bar',
|
||||
datasource_title: `ds${i}`,
|
||||
thumbnail_url: '',
|
||||
}));
|
||||
|
||||
fetchMock.get(chartsEndpoint, {
|
||||
result: mockCharts,
|
||||
});
|
||||
|
||||
fetchMock.get(chartsInfoEndpoint, {
|
||||
permissions: ['can_add', 'can_edit', 'can_delete'],
|
||||
});
|
||||
|
||||
fetchMock.get(chartFavoriteStatusEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
|
||||
describe('ChartTable', () => {
|
||||
const mockedProps = {
|
||||
user: {
|
||||
userId: '2',
|
||||
},
|
||||
};
|
||||
|
||||
let wrapper: ReactWrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
act(() => {
|
||||
wrapper = mount(<ChartTable store={store} {...mockedProps} />);
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.find(ChartTable)).toExist();
|
||||
});
|
||||
|
||||
it('fetches chart favorites and renders chart cards', async () => {
|
||||
act(() => {
|
||||
const handler = wrapper.find('li.no-router a').at(0).prop('onClick');
|
||||
if (handler) {
|
||||
handler({} as any);
|
||||
}
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(fetchMock.calls(chartsEndpoint)).toHaveLength(1);
|
||||
expect(wrapper.find('ChartCard')).toExist();
|
||||
});
|
||||
|
||||
it('display EmptyState if there is no data', async () => {
|
||||
expect(wrapper.find('EmptyState')).toExist();
|
||||
});
|
||||
});
|
||||
@@ -1,104 +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 thunk from 'redux-thunk';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
import DashboardTable from 'src/views/CRUD/welcome/DashboardTable';
|
||||
import DashboardCard from 'src/views/CRUD/dashboard/DashboardCard';
|
||||
|
||||
// store needed for withToasts(DashboardTable)
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
const dashboardsEndpoint = 'glob:*/api/v1/dashboard/?*';
|
||||
const dashboardInfoEndpoint = 'glob:*/api/v1/dashboard/_info*';
|
||||
const dashboardFavEndpoint = 'glob:*/api/v1/dashboard/favorite_status?*';
|
||||
const mockDashboards = [
|
||||
{
|
||||
id: 1,
|
||||
url: 'url',
|
||||
dashboard_title: 'title',
|
||||
changed_on_utc: '24 Feb 2014 10:13:14',
|
||||
},
|
||||
];
|
||||
|
||||
fetchMock.get(dashboardsEndpoint, { result: mockDashboards });
|
||||
fetchMock.get(dashboardInfoEndpoint, {
|
||||
permissions: ['can_list', 'can_edit', 'can_delete'],
|
||||
});
|
||||
fetchMock.get(dashboardFavEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
|
||||
describe('DashboardTable', () => {
|
||||
const dashboardProps = {
|
||||
dashboardFilter: 'Favorite',
|
||||
user: {
|
||||
userId: '2',
|
||||
},
|
||||
mine: mockDashboards,
|
||||
};
|
||||
let wrapper = mount(<DashboardTable store={store} {...dashboardProps} />);
|
||||
|
||||
beforeAll(async () => {
|
||||
await waitForComponentToPaint(wrapper);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.find(DashboardTable)).toExist();
|
||||
});
|
||||
|
||||
it('render a submenu with clickable tabs and buttons', async () => {
|
||||
expect(wrapper.find('SubMenu')).toExist();
|
||||
expect(wrapper.find('li')).toHaveLength(2);
|
||||
expect(wrapper.find('Button')).toHaveLength(4);
|
||||
act(() => {
|
||||
const handler = wrapper.find('li.no-router a').at(1).prop('onClick');
|
||||
if (handler) {
|
||||
handler({} as any);
|
||||
}
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(fetchMock.calls(/dashboard\/\?q/)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('render DashboardCard', () => {
|
||||
expect(wrapper.find(DashboardCard)).toExist();
|
||||
});
|
||||
|
||||
it('display EmptyState if there is no data', async () => {
|
||||
await act(async () => {
|
||||
wrapper = mount(
|
||||
<DashboardTable
|
||||
dashboardFilter="Mine"
|
||||
user={{ userId: '2' }}
|
||||
mine={[]}
|
||||
store={store}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(wrapper.find('EmptyState')).toExist();
|
||||
});
|
||||
});
|
||||
@@ -1,92 +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 EmptyState from 'src/views/CRUD/welcome/EmptyState';
|
||||
|
||||
describe('EmptyState', () => {
|
||||
const variants = [
|
||||
{
|
||||
tab: 'Favorite',
|
||||
tableName: 'DASHBOARDS',
|
||||
},
|
||||
{
|
||||
tab: 'Mine',
|
||||
tableName: 'DASHBOARDS',
|
||||
},
|
||||
{
|
||||
tab: 'Favorite',
|
||||
tableName: 'CHARTS',
|
||||
},
|
||||
{
|
||||
tab: 'Mine',
|
||||
tableName: 'CHARTS',
|
||||
},
|
||||
{
|
||||
tab: 'Favorite',
|
||||
tableName: 'SAVED_QUERIES',
|
||||
},
|
||||
{
|
||||
tab: 'Mine',
|
||||
tableName: 'SAVED_QUEREIS',
|
||||
},
|
||||
];
|
||||
const recents = [
|
||||
{
|
||||
tab: 'Viewed',
|
||||
tableName: 'RECENTS',
|
||||
},
|
||||
{
|
||||
tab: 'Edited',
|
||||
tableName: 'RECENTS',
|
||||
},
|
||||
{
|
||||
tab: 'Created',
|
||||
tableName: 'RECENTS',
|
||||
},
|
||||
];
|
||||
variants.forEach(variant => {
|
||||
it(`it renders an ${variant.tab} ${variant.tableName} empty state`, () => {
|
||||
const wrapper = mount(<EmptyState {...variant} />);
|
||||
expect(wrapper).toExist();
|
||||
const textContainer = wrapper.find('.ant-empty-description');
|
||||
expect(textContainer.text()).toEqual(
|
||||
variant.tab === 'Favorite'
|
||||
? "You don't have any favorites yet!"
|
||||
: `No ${
|
||||
variant.tableName === 'SAVED_QUERIES'
|
||||
? 'saved queries'
|
||||
: variant.tableName.toLowerCase()
|
||||
} yet`,
|
||||
);
|
||||
expect(wrapper.find('button')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
recents.forEach(recent => {
|
||||
it(`it renders an ${recent.tab} ${recent.tableName} empty state`, () => {
|
||||
const wrapper = mount(<EmptyState {...recent} />);
|
||||
expect(wrapper).toExist();
|
||||
const textContainer = wrapper.find('.ant-empty-description');
|
||||
expect(wrapper.find('.ant-empty-image').children()).toHaveLength(1);
|
||||
expect(textContainer.text()).toContain(
|
||||
`Recently ${recent.tab.toLowerCase()} charts, dashboards, and saved queries will appear here`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,114 +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 { styledMount as mount } from 'spec/helpers/theming';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
import SubMenu from 'src/components/Menu/SubMenu';
|
||||
import SavedQueries from 'src/views/CRUD/welcome/SavedQueries';
|
||||
|
||||
// store needed for withToasts(DashboardTable)
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
const queriesEndpoint = 'glob:*/api/v1/saved_query/?*';
|
||||
const savedQueriesInfo = 'glob:*/api/v1/saved_query/_info*';
|
||||
|
||||
const mockqueries = [...new Array(3)].map((_, i) => ({
|
||||
created_by: {
|
||||
id: i,
|
||||
first_name: `user`,
|
||||
last_name: `${i}`,
|
||||
},
|
||||
created_on: `${i}-2020`,
|
||||
database: {
|
||||
database_name: `db ${i}`,
|
||||
id: i,
|
||||
},
|
||||
changed_on_delta_humanized: '1 day ago',
|
||||
db_id: i,
|
||||
description: `SQL for ${i}`,
|
||||
id: i,
|
||||
label: `query ${i}`,
|
||||
schema: 'public',
|
||||
sql: `SELECT ${i} FROM table`,
|
||||
sql_tables: [
|
||||
{
|
||||
catalog: null,
|
||||
schema: null,
|
||||
table: `${i}`,
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
fetchMock.get(queriesEndpoint, {
|
||||
result: mockqueries,
|
||||
});
|
||||
|
||||
fetchMock.get(savedQueriesInfo, {
|
||||
permissions: ['can_list', 'can_edit', 'can_delete'],
|
||||
});
|
||||
|
||||
describe('SavedQueries', () => {
|
||||
const savedQueryProps = {
|
||||
user: {
|
||||
userId: '1',
|
||||
},
|
||||
mine: mockqueries,
|
||||
};
|
||||
|
||||
const wrapper = mount(<SavedQueries store={store} {...savedQueryProps} />);
|
||||
|
||||
const clickTab = (idx: number) => {
|
||||
act(() => {
|
||||
const handler = wrapper.find('li.no-router a').at(idx).prop('onClick');
|
||||
if (handler) {
|
||||
handler({} as any);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await waitForComponentToPaint(wrapper);
|
||||
});
|
||||
|
||||
it('is valid', () => {
|
||||
expect(wrapper.find(SavedQueries)).toExist();
|
||||
});
|
||||
|
||||
it('fetches queries mine and renders listviewcard cards', async () => {
|
||||
clickTab(0);
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(fetchMock.calls(/saved_query\/\?q/)).toHaveLength(1);
|
||||
expect(wrapper.find('ListViewCard')).toExist();
|
||||
});
|
||||
|
||||
it('renders a submenu with clickable tables and buttons', async () => {
|
||||
expect(wrapper.find(SubMenu)).toExist();
|
||||
expect(wrapper.find('li')).toHaveLength(1);
|
||||
expect(wrapper.find('button')).toHaveLength(2);
|
||||
clickTab(0);
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(fetchMock.calls(/saved_query\/\?q/)).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
@@ -1,181 +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 { Provider } from 'react-redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import * as featureFlags from 'src/featureFlags';
|
||||
import Welcome from 'src/views/CRUD/welcome/Welcome';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
||||
const chartsEndpoint = 'glob:*/api/v1/chart/?*';
|
||||
const chartInfoEndpoint = 'glob:*/api/v1/chart/_info?*';
|
||||
const chartFavoriteStatusEndpoint = 'glob:*/api/v1/chart/favorite_status?*';
|
||||
const dashboardsEndpoint = 'glob:*/api/v1/dashboard/?*';
|
||||
const dashboardInfoEndpoint = 'glob:*/api/v1/dashboard/_info?*';
|
||||
const dashboardFavoriteStatusEndpoint =
|
||||
'glob:*/api/v1/dashboard/favorite_status?*';
|
||||
const savedQueryEndpoint = 'glob:*/api/v1/saved_query/?*';
|
||||
const savedQueryInfoEndpoint = 'glob:*/api/v1/saved_query/_info?*';
|
||||
const recentActivityEndpoint = 'glob:*/superset/recent_activity/*';
|
||||
|
||||
fetchMock.get(chartsEndpoint, {
|
||||
result: [
|
||||
{
|
||||
slice_name: 'ChartyChart',
|
||||
changed_on_utc: '24 Feb 2014 10:13:14',
|
||||
url: '/fakeUrl/explore',
|
||||
id: '4',
|
||||
table: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
fetchMock.get(dashboardsEndpoint, {
|
||||
result: [
|
||||
{
|
||||
dashboard_title: 'Dashboard_Test',
|
||||
changed_on_utc: '24 Feb 2014 10:13:14',
|
||||
url: '/fakeUrl/dashboard',
|
||||
id: '3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
fetchMock.get(savedQueryEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
|
||||
fetchMock.get(recentActivityEndpoint, {
|
||||
Created: [],
|
||||
Viewed: [],
|
||||
});
|
||||
|
||||
fetchMock.get(chartInfoEndpoint, {
|
||||
permissions: [],
|
||||
});
|
||||
|
||||
fetchMock.get(chartFavoriteStatusEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
|
||||
fetchMock.get(dashboardInfoEndpoint, {
|
||||
permissions: [],
|
||||
});
|
||||
|
||||
fetchMock.get(dashboardFavoriteStatusEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
|
||||
fetchMock.get(savedQueryInfoEndpoint, {
|
||||
permissions: [],
|
||||
});
|
||||
|
||||
const mockedProps = {
|
||||
user: {
|
||||
username: 'alpha',
|
||||
firstName: 'alpha',
|
||||
lastName: 'alpha',
|
||||
createdOn: '2016-11-11T12:34:17',
|
||||
userId: 5,
|
||||
email: 'alpha@alpha.com',
|
||||
isActive: true,
|
||||
},
|
||||
};
|
||||
|
||||
describe('Welcome', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
|
||||
beforeAll(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<Welcome {...mockedProps} />
|
||||
</Provider>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it('renders all panels on the page on page load', () => {
|
||||
expect(wrapper.find('CollapsePanel')).toHaveLength(8);
|
||||
});
|
||||
|
||||
it('calls api methods in parallel on page load', () => {
|
||||
const chartCall = fetchMock.calls(/chart\/\?q/);
|
||||
const savedQueryCall = fetchMock.calls(/saved_query\/\?q/);
|
||||
const recentCall = fetchMock.calls(/superset\/recent_activity\/*/);
|
||||
const dashboardCall = fetchMock.calls(/dashboard\/\?q/);
|
||||
expect(chartCall).toHaveLength(1);
|
||||
expect(recentCall).toHaveLength(1);
|
||||
expect(savedQueryCall).toHaveLength(1);
|
||||
expect(dashboardCall).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
async function mountAndWait(props = mockedProps) {
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<Welcome {...props} />
|
||||
</Provider>,
|
||||
);
|
||||
await waitForComponentToPaint(wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
describe('Welcome page with toggle switch', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
let isFeatureEnabledMock: any;
|
||||
|
||||
beforeAll(async () => {
|
||||
isFeatureEnabledMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockReturnValue(true);
|
||||
await act(async () => {
|
||||
wrapper = await mountAndWait();
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
isFeatureEnabledMock.restore();
|
||||
});
|
||||
|
||||
it('shows a toggle button when feature flags is turned on', async () => {
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find('Switch')).toExist();
|
||||
});
|
||||
it('does not show thumbnails when switch is off', async () => {
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
wrapper.find('button[role="switch"]').props().onClick();
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find('ImageLoader')).not.toExist();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user