chore: Moves spec files to the src folder - iteration 3 (#14202)

This commit is contained in:
Michael S. Molina
2021-04-23 19:19:12 -04:00
committed by GitHub
parent 1bc73f2cba
commit aada73302f
12 changed files with 0 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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