mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
test: RTL overhaul - hackathon (#16626)
* CachedLabel_spec fully converted to RTL * ColumnTypeLabel_spec fully converted to RTL * MissingChart_spec fully converted to RTL * RefreshIntervalModal_spec mostly converted to RTL * HoverMenu_spec mostly converted to RTL * ResizableContainer_spec fully converted to RTL * ResizableHandle_spec fully converted to RTL * AggregateOption_spec fully converted to RTL * CheckboxControl_spec fully converted to RTL * ColorPickerControl_spec to RTL * Finished converting ColorPickerControl_spec to RTL/TS, also converted RefreshIntervalModal_spec to TS * Added unknown type to ColumnTypeLabelProps * Fixed ColumnTypeLabel_spec
This commit is contained in:
parent
4af5ae08f9
commit
63aadd3fe4
@ -17,22 +17,26 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
|
|
||||||
import Label from 'src/components/Label';
|
import CachedLabel, { CacheLabelProps } from 'src/components/CachedLabel';
|
||||||
import CachedLabel from 'src/components/CachedLabel';
|
|
||||||
|
const defaultProps = {
|
||||||
|
onClick: () => {},
|
||||||
|
cachedTimestamp: '2017-01-01',
|
||||||
|
};
|
||||||
|
|
||||||
|
const setup = (props: CacheLabelProps) => <CachedLabel {...props} />;
|
||||||
|
|
||||||
describe('CachedLabel', () => {
|
describe('CachedLabel', () => {
|
||||||
const defaultProps = {
|
|
||||||
onClick: () => {},
|
|
||||||
cachedTimestamp: '2017-01-01',
|
|
||||||
};
|
|
||||||
|
|
||||||
it('is valid', () => {
|
it('is valid', () => {
|
||||||
expect(React.isValidElement(<CachedLabel {...defaultProps} />)).toBe(true);
|
expect(React.isValidElement(<CachedLabel {...defaultProps} />)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
const wrapper = shallow(<CachedLabel {...defaultProps} />);
|
render(setup(defaultProps));
|
||||||
expect(wrapper.find(Label)).toExist();
|
|
||||||
|
const label = screen.getByText(/cached/i);
|
||||||
|
expect(label).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -17,61 +17,71 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { render, screen, cleanup } from 'spec/helpers/testing-library';
|
||||||
|
|
||||||
import { ColumnTypeLabel } from '@superset-ui/chart-controls';
|
import {
|
||||||
|
ColumnTypeLabel,
|
||||||
|
ColumnTypeLabelProps,
|
||||||
|
} from '@superset-ui/chart-controls';
|
||||||
import { GenericDataType } from '@superset-ui/core';
|
import { GenericDataType } from '@superset-ui/core';
|
||||||
|
|
||||||
describe('ColumnOption', () => {
|
const defaultProps = {
|
||||||
const defaultProps = {
|
type: GenericDataType.STRING,
|
||||||
type: GenericDataType.STRING,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const props = { ...defaultProps };
|
const setup = (overrides?: ColumnTypeLabelProps) => (
|
||||||
|
<div className="type-label">
|
||||||
function getWrapper(overrides) {
|
<ColumnTypeLabel {...defaultProps} {...overrides} />
|
||||||
const wrapper = shallow(<ColumnTypeLabel {...props} {...overrides} />);
|
</div>
|
||||||
return wrapper;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
|
describe('ColumnOption RTL', () => {
|
||||||
|
afterEach(cleanup);
|
||||||
it('is a valid element', () => {
|
it('is a valid element', () => {
|
||||||
expect(React.isValidElement(<ColumnTypeLabel {...defaultProps} />)).toBe(
|
expect(React.isValidElement(<ColumnTypeLabel {...defaultProps} />)).toBe(
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('string type shows ABC icon', () => {
|
it('string type shows ABC icon', () => {
|
||||||
const lbl = getWrapper({}).find('.type-label');
|
render(setup(defaultProps));
|
||||||
expect(lbl).toHaveLength(1);
|
|
||||||
expect(lbl.first().text()).toBe('ABC');
|
const labelIcon = screen.getByText(/abc/i);
|
||||||
|
expect(labelIcon.innerHTML).toMatch(/abc/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('int type shows # icon', () => {
|
it('int type shows # icon', () => {
|
||||||
const lbl = getWrapper({
|
render(setup({ type: GenericDataType.NUMERIC }));
|
||||||
type: GenericDataType.NUMERIC,
|
|
||||||
}).find('.type-label');
|
const labelIcon = screen.getByText(/#/i);
|
||||||
expect(lbl).toHaveLength(1);
|
expect(labelIcon.innerHTML).toMatch(/#/i);
|
||||||
expect(lbl.first().text()).toBe('#');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('bool type shows T/F icon', () => {
|
it('bool type shows T/F icon', () => {
|
||||||
const lbl = getWrapper({
|
render(setup({ type: GenericDataType.BOOLEAN }));
|
||||||
type: GenericDataType.BOOLEAN,
|
|
||||||
}).find('.type-label');
|
const labelIcon = screen.getByText(/t\/f/i);
|
||||||
expect(lbl).toHaveLength(1);
|
expect(labelIcon.innerHTML).toMatch(/t\/f/i);
|
||||||
expect(lbl.first().text()).toBe('T/F');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('expression type shows function icon', () => {
|
it('expression type shows function icon', () => {
|
||||||
const lbl = getWrapper({ type: 'expression' }).find('.type-label');
|
render(setup({ type: 'expression' }));
|
||||||
expect(lbl).toHaveLength(1);
|
|
||||||
expect(lbl.first().text()).toBe('ƒ');
|
const labelIcon = screen.getByText('ƒ');
|
||||||
|
expect(labelIcon.innerHTML).toMatch('ƒ');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('unknown type shows question mark', () => {
|
it('unknown type shows question mark', () => {
|
||||||
const lbl = getWrapper({ type: 'unknown' }).find('.type-label');
|
render(setup({ type: undefined }));
|
||||||
expect(lbl).toHaveLength(1);
|
|
||||||
expect(lbl.first().text()).toBe('?');
|
const labelIcon = screen.getByText('?');
|
||||||
|
expect(labelIcon.innerHTML).toMatch('?');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('datetime type displays', () => {
|
it('datetime type displays', () => {
|
||||||
const lbl = getWrapper({
|
const rendered = render(setup({ type: GenericDataType.TEMPORAL }));
|
||||||
type: GenericDataType.TEMPORAL,
|
|
||||||
}).find('.fa-clock-o');
|
const clockIcon = rendered.container.querySelector('.fa-clock-o');
|
||||||
expect(lbl).toHaveLength(1);
|
expect(clockIcon).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -17,23 +17,38 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { render } from 'spec/helpers/testing-library';
|
||||||
|
|
||||||
import MissingChart from 'src/dashboard/components/MissingChart';
|
import MissingChart from 'src/dashboard/components/MissingChart';
|
||||||
|
|
||||||
describe('MissingChart', () => {
|
type MissingChartProps = {
|
||||||
function setup(overrideProps) {
|
height: number;
|
||||||
const wrapper = shallow(<MissingChart height={100} {...overrideProps} />);
|
};
|
||||||
return wrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const setup = (overrides?: MissingChartProps) => (
|
||||||
|
<MissingChart height={100} {...overrides} />
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('MissingChart', () => {
|
||||||
it('renders a .missing-chart-container', () => {
|
it('renders a .missing-chart-container', () => {
|
||||||
const wrapper = setup();
|
const rendered = render(setup());
|
||||||
expect(wrapper.find('.missing-chart-container')).toExist();
|
|
||||||
|
const missingChartContainer = rendered.container.querySelector(
|
||||||
|
'.missing-chart-container',
|
||||||
|
);
|
||||||
|
expect(missingChartContainer).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a .missing-chart-body', () => {
|
it('renders a .missing-chart-body', () => {
|
||||||
const wrapper = setup();
|
const rendered = render(setup());
|
||||||
expect(wrapper.find('.missing-chart-body')).toExist();
|
|
||||||
|
const missingChartBody = rendered.container.querySelector(
|
||||||
|
'.missing-chart-body',
|
||||||
|
);
|
||||||
|
const bodyText =
|
||||||
|
'There is no chart definition associated with this component, could it have been deleted?<br><br>Delete this container and save to remove this message.';
|
||||||
|
|
||||||
|
expect(missingChartBody).toBeVisible();
|
||||||
|
expect(missingChartBody?.innerHTML).toMatch(bodyText);
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,80 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
import React from 'react';
|
|
||||||
import { mount } from 'enzyme';
|
|
||||||
|
|
||||||
import ModalTrigger from 'src/components/ModalTrigger';
|
|
||||||
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
|
|
||||||
import Alert from 'src/components/Alert';
|
|
||||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
|
||||||
|
|
||||||
const getMountWrapper = props =>
|
|
||||||
mount(<RefreshIntervalModal {...props} />, {
|
|
||||||
wrappingComponent: ThemeProvider,
|
|
||||||
wrappingComponentProps: {
|
|
||||||
theme: supersetTheme,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('RefreshIntervalModal', () => {
|
|
||||||
const mockedProps = {
|
|
||||||
triggerNode: <i className="fa fa-edit" />,
|
|
||||||
refreshFrequency: 10,
|
|
||||||
onChange: jest.fn(),
|
|
||||||
editMode: true,
|
|
||||||
};
|
|
||||||
it('is valid', () => {
|
|
||||||
expect(
|
|
||||||
React.isValidElement(<RefreshIntervalModal {...mockedProps} />),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
it('renders the trigger node', () => {
|
|
||||||
const wrapper = getMountWrapper(mockedProps);
|
|
||||||
expect(wrapper.find('.fa-edit')).toExist();
|
|
||||||
});
|
|
||||||
it('should render a interval seconds', () => {
|
|
||||||
const wrapper = getMountWrapper(mockedProps);
|
|
||||||
expect(wrapper.prop('refreshFrequency')).toEqual(10);
|
|
||||||
});
|
|
||||||
it('should change refreshFrequency with edit mode', () => {
|
|
||||||
const wrapper = getMountWrapper(mockedProps);
|
|
||||||
wrapper.instance().handleFrequencyChange(30);
|
|
||||||
wrapper.instance().onSave();
|
|
||||||
expect(mockedProps.onChange).toHaveBeenCalled();
|
|
||||||
expect(mockedProps.onChange).toHaveBeenCalledWith(30, mockedProps.editMode);
|
|
||||||
});
|
|
||||||
it('should show warning message', () => {
|
|
||||||
const props = {
|
|
||||||
...mockedProps,
|
|
||||||
refreshLimit: 3600,
|
|
||||||
refreshWarning: 'Show warning',
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = getMountWrapper(props);
|
|
||||||
wrapper.find('span[role="button"]').simulate('click');
|
|
||||||
|
|
||||||
wrapper.instance().handleFrequencyChange(30);
|
|
||||||
wrapper.update();
|
|
||||||
expect(wrapper.find(ModalTrigger).find(Alert)).toExist();
|
|
||||||
|
|
||||||
wrapper.instance().handleFrequencyChange(3601);
|
|
||||||
wrapper.update();
|
|
||||||
expect(wrapper.find(ModalTrigger).find(Alert)).not.toExist();
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,237 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import fetchMock from 'fetch-mock';
|
||||||
|
|
||||||
|
import ModalTrigger from 'src/components/ModalTrigger';
|
||||||
|
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
|
||||||
|
import HeaderActionsDropdown from 'src/dashboard/components/Header/HeaderActionsDropdown';
|
||||||
|
import Alert from 'src/components/Alert';
|
||||||
|
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||||
|
|
||||||
|
describe('RefreshIntervalModal - Enzyme', () => {
|
||||||
|
const getMountWrapper = (props: any) =>
|
||||||
|
mount(<RefreshIntervalModal {...props} />, {
|
||||||
|
wrappingComponent: ThemeProvider,
|
||||||
|
wrappingComponentProps: {
|
||||||
|
theme: supersetTheme,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const mockedProps = {
|
||||||
|
triggerNode: <i className="fa fa-edit" />,
|
||||||
|
refreshFrequency: 10,
|
||||||
|
onChange: jest.fn(),
|
||||||
|
editMode: true,
|
||||||
|
};
|
||||||
|
it('should show warning message', () => {
|
||||||
|
const props = {
|
||||||
|
...mockedProps,
|
||||||
|
refreshLimit: 3600,
|
||||||
|
refreshWarning: 'Show warning',
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = getMountWrapper(props);
|
||||||
|
wrapper.find('span[role="button"]').simulate('click');
|
||||||
|
|
||||||
|
// @ts-ignore (for handleFrequencyChange)
|
||||||
|
wrapper.instance().handleFrequencyChange(30);
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find(ModalTrigger).find(Alert)).toExist();
|
||||||
|
|
||||||
|
// @ts-ignore (for handleFrequencyChange)
|
||||||
|
wrapper.instance().handleFrequencyChange(3601);
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find(ModalTrigger).find(Alert)).not.toExist();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const createProps = () => ({
|
||||||
|
addSuccessToast: jest.fn(),
|
||||||
|
addDangerToast: jest.fn(),
|
||||||
|
customCss: '#save-dash-split-button{margin-left: 100px;}',
|
||||||
|
dashboardId: 1,
|
||||||
|
dashboardInfo: {
|
||||||
|
id: 1,
|
||||||
|
dash_edit_perm: true,
|
||||||
|
dash_save_perm: true,
|
||||||
|
userId: '1',
|
||||||
|
metadata: {},
|
||||||
|
common: {
|
||||||
|
conf: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dashboardTitle: 'Title',
|
||||||
|
editMode: false,
|
||||||
|
expandedSlices: {},
|
||||||
|
forceRefreshAllCharts: jest.fn(),
|
||||||
|
hasUnsavedChanges: false,
|
||||||
|
isLoading: false,
|
||||||
|
layout: {},
|
||||||
|
dataMask: {},
|
||||||
|
onChange: jest.fn(),
|
||||||
|
onSave: jest.fn(),
|
||||||
|
refreshFrequency: 0,
|
||||||
|
setRefreshFrequency: jest.fn(),
|
||||||
|
shouldPersistRefreshFrequency: false,
|
||||||
|
showPropertiesModal: jest.fn(),
|
||||||
|
startPeriodicRender: jest.fn(),
|
||||||
|
updateCss: jest.fn(),
|
||||||
|
userCanEdit: false,
|
||||||
|
userCanSave: false,
|
||||||
|
userCanShare: false,
|
||||||
|
lastModifiedTime: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const editModeOnProps = {
|
||||||
|
...createProps(),
|
||||||
|
editMode: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const setup = (overrides?: any) => (
|
||||||
|
<div className="dashboard-header">
|
||||||
|
<HeaderActionsDropdown {...editModeOnProps} {...overrides} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {});
|
||||||
|
|
||||||
|
const openRefreshIntervalModal = async () => {
|
||||||
|
const headerActionsButton = screen.getByRole('img', { name: 'more-horiz' });
|
||||||
|
userEvent.click(headerActionsButton);
|
||||||
|
|
||||||
|
const autoRefreshOption = screen.getByText('Set auto-refresh interval');
|
||||||
|
userEvent.click(autoRefreshOption);
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayOptions = async () => {
|
||||||
|
// Click default refresh interval option to display other options
|
||||||
|
userEvent.click(screen.getByText(/don't refresh/i));
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultRefreshIntervalModalProps = {
|
||||||
|
triggerNode: <i className="fa fa-edit" />,
|
||||||
|
refreshFrequency: 0,
|
||||||
|
onChange: jest.fn(),
|
||||||
|
editMode: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('RefreshIntervalModal - RTL', () => {
|
||||||
|
it('is valid', () => {
|
||||||
|
expect(
|
||||||
|
React.isValidElement(
|
||||||
|
<RefreshIntervalModal {...defaultRefreshIntervalModalProps} />,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders refresh interval modal', async () => {
|
||||||
|
render(setup(editModeOnProps));
|
||||||
|
await openRefreshIntervalModal();
|
||||||
|
|
||||||
|
// Assert that modal exists by checking for the modal title
|
||||||
|
expect(screen.getByText('Refresh interval')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders refresh interval options', async () => {
|
||||||
|
render(setup(editModeOnProps));
|
||||||
|
await openRefreshIntervalModal();
|
||||||
|
await displayOptions();
|
||||||
|
|
||||||
|
// Assert that both "Don't refresh" instances exist
|
||||||
|
// - There will be two at this point, the default option and the dropdown option
|
||||||
|
const dontRefreshInstances = screen.getAllByText(/don't refresh/i);
|
||||||
|
expect(dontRefreshInstances).toHaveLength(2);
|
||||||
|
dontRefreshInstances.forEach(option => {
|
||||||
|
expect(option).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert that all the other options exist
|
||||||
|
const options = [
|
||||||
|
screen.getByText(/10 seconds/i),
|
||||||
|
screen.getByText(/30 seconds/i),
|
||||||
|
screen.getByText(/1 minute/i),
|
||||||
|
screen.getByText(/5 minutes/i),
|
||||||
|
screen.getByText(/30 minutes/i),
|
||||||
|
screen.getByText(/1 hour/i),
|
||||||
|
screen.getByText(/6 hours/i),
|
||||||
|
screen.getByText(/12 hours/i),
|
||||||
|
screen.getByText(/24 hours/i),
|
||||||
|
];
|
||||||
|
options.forEach(option => {
|
||||||
|
expect(option).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change selected value', async () => {
|
||||||
|
render(setup(editModeOnProps));
|
||||||
|
await openRefreshIntervalModal();
|
||||||
|
|
||||||
|
// Initial selected value should be "Don't refresh"
|
||||||
|
const selectedValue = screen.getByText(/don't refresh/i);
|
||||||
|
expect(selectedValue.title).toMatch(/don't refresh/i);
|
||||||
|
|
||||||
|
// Display options and select "10 seconds"
|
||||||
|
await displayOptions();
|
||||||
|
userEvent.click(screen.getByText(/10 seconds/i));
|
||||||
|
|
||||||
|
// Selected value should now be "10 seconds"
|
||||||
|
expect(selectedValue.title).toMatch(/10 seconds/i);
|
||||||
|
expect(selectedValue.title).not.toMatch(/don't refresh/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save a newly-selected value', async () => {
|
||||||
|
render(setup(editModeOnProps));
|
||||||
|
await openRefreshIntervalModal();
|
||||||
|
await displayOptions();
|
||||||
|
|
||||||
|
screen.logTestingPlaygroundURL();
|
||||||
|
// Select a new interval and click save
|
||||||
|
userEvent.click(screen.getByText(/10 seconds/i));
|
||||||
|
userEvent.click(screen.getByRole('button', { name: /save/i }));
|
||||||
|
|
||||||
|
expect(editModeOnProps.setRefreshFrequency).toHaveBeenCalled();
|
||||||
|
expect(editModeOnProps.setRefreshFrequency).toHaveBeenCalledWith(
|
||||||
|
10,
|
||||||
|
editModeOnProps.editMode,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show warning message', async () => {
|
||||||
|
// TODO (lyndsiWilliams): This test is incomplete
|
||||||
|
const warningProps = {
|
||||||
|
...editModeOnProps,
|
||||||
|
refreshLimit: 3600,
|
||||||
|
refreshWarning: 'Show warning',
|
||||||
|
};
|
||||||
|
|
||||||
|
render(setup(warningProps));
|
||||||
|
await openRefreshIntervalModal();
|
||||||
|
await displayOptions();
|
||||||
|
|
||||||
|
userEvent.click(screen.getByText(/30 seconds/i));
|
||||||
|
userEvent.click(screen.getByRole('button', { name: /save/i }));
|
||||||
|
|
||||||
|
// screen.debug(screen.getByRole('alert'));
|
||||||
|
expect.anything();
|
||||||
|
});
|
||||||
|
});
|
@ -17,13 +17,14 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { render } from 'spec/helpers/testing-library';
|
||||||
|
|
||||||
import HoverMenu from 'src/dashboard/components/menu/HoverMenu';
|
import HoverMenu from 'src/dashboard/components/menu/HoverMenu';
|
||||||
|
|
||||||
describe('HoverMenu', () => {
|
describe('HoverMenu', () => {
|
||||||
it('should render a div.hover-menu', () => {
|
it('should render a hover menu', () => {
|
||||||
const wrapper = shallow(<HoverMenu />);
|
const rendered = render(<HoverMenu />);
|
||||||
expect(wrapper.find('.hover-menu')).toExist();
|
const hoverMenu = rendered.container.querySelector('.hover-menu');
|
||||||
|
expect(hoverMenu).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -17,20 +17,46 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Resizable } from 're-resizable';
|
import { render } from 'spec/helpers/testing-library';
|
||||||
import { shallow } from 'enzyme';
|
|
||||||
|
|
||||||
import ResizableContainer from 'src/dashboard/components/resizable/ResizableContainer';
|
import ResizableContainer from 'src/dashboard/components/resizable/ResizableContainer';
|
||||||
|
|
||||||
|
interface ResizableContainerProps {
|
||||||
|
id: string;
|
||||||
|
children?: object;
|
||||||
|
adjustableWidth?: boolean;
|
||||||
|
adjustableHeight?: boolean;
|
||||||
|
gutterWidth?: number;
|
||||||
|
widthStep?: number;
|
||||||
|
heightStep?: number;
|
||||||
|
widthMultiple?: number;
|
||||||
|
heightMultiple?: number;
|
||||||
|
minWidthMultiple?: number;
|
||||||
|
maxWidthMultiple?: number;
|
||||||
|
minHeightMultiple?: number;
|
||||||
|
maxHeightMultiple?: number;
|
||||||
|
staticHeight?: number;
|
||||||
|
staticHeightMultiple?: number;
|
||||||
|
staticWidth?: number;
|
||||||
|
staticWidthMultiple?: number;
|
||||||
|
onResizeStop?: () => {};
|
||||||
|
onResize?: () => {};
|
||||||
|
onResizeStart?: () => {};
|
||||||
|
editMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
describe('ResizableContainer', () => {
|
describe('ResizableContainer', () => {
|
||||||
const props = { editMode: false, id: 'id' };
|
const props = { editMode: false, id: 'id' };
|
||||||
|
|
||||||
function setup(propOverrides) {
|
const setup = (overrides?: ResizableContainerProps) => (
|
||||||
return shallow(<ResizableContainer {...props} {...propOverrides} />);
|
<ResizableContainer {...props} {...overrides} />
|
||||||
}
|
);
|
||||||
|
|
||||||
it('should render a Resizable', () => {
|
it('should render a Resizable container', () => {
|
||||||
const wrapper = setup();
|
const rendered = render(setup());
|
||||||
expect(wrapper.find(Resizable)).toExist();
|
const resizableContainer = rendered.container.querySelector(
|
||||||
|
'.resizable-container',
|
||||||
|
);
|
||||||
|
expect(resizableContainer).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -17,29 +17,33 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { render } from 'spec/helpers/testing-library';
|
||||||
|
|
||||||
import ResizableHandle from 'src/dashboard/components/resizable/ResizableHandle';
|
import ResizableHandle from 'src/dashboard/components/resizable/ResizableHandle';
|
||||||
|
|
||||||
/* eslint-disable react/jsx-pascal-case */
|
/* eslint-disable react/jsx-pascal-case */
|
||||||
describe('ResizableHandle', () => {
|
describe('ResizableHandle', () => {
|
||||||
it('should render a right resize handle', () => {
|
it('should render a right resize handle', () => {
|
||||||
const wrapper = shallow(<ResizableHandle.right />);
|
const rendered = render(<ResizableHandle.right />);
|
||||||
expect(wrapper.find('.resize-handle.resize-handle--right')).toExist();
|
expect(
|
||||||
|
rendered.container.querySelector('.resize-handle.resize-handle--right'),
|
||||||
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render a bottom resize handle', () => {
|
it('should render a bottom resize handle', () => {
|
||||||
const wrapper = shallow(<ResizableHandle.bottom />);
|
const rendered = render(<ResizableHandle.bottom />);
|
||||||
expect(wrapper.find('.resize-handle.resize-handle--bottom')).toHaveLength(
|
expect(
|
||||||
1,
|
rendered.container.querySelector('.resize-handle.resize-handle--bottom'),
|
||||||
);
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render a bottomRight resize handle', () => {
|
it('should render a bottomRight resize handle', () => {
|
||||||
const wrapper = shallow(<ResizableHandle.bottomRight />);
|
const rendered = render(<ResizableHandle.bottomRight />);
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('.resize-handle.resize-handle--bottom-right'),
|
rendered.container.querySelector(
|
||||||
).toHaveLength(1);
|
'.resize-handle.resize-handle--bottom-right',
|
||||||
|
),
|
||||||
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
/* eslint-enable react/jsx-pascal-case */
|
/* eslint-enable react/jsx-pascal-case */
|
@ -16,17 +16,16 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
/* eslint-disable no-unused-expressions */
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
|
|
||||||
import AggregateOption from 'src/explore/components/controls/MetricControl/AggregateOption';
|
import AggregateOption from 'src/explore/components/controls/MetricControl/AggregateOption';
|
||||||
|
|
||||||
describe('AggregateOption', () => {
|
describe('AggregateOption', () => {
|
||||||
it('renders the aggregate', () => {
|
it('renders the aggregate', () => {
|
||||||
const wrapper = shallow(
|
render(<AggregateOption aggregate={{ aggregate_name: 'SUM' }} />);
|
||||||
<AggregateOption aggregate={{ aggregate_name: 'SUM' }} />,
|
|
||||||
);
|
const aggregateOption = screen.getByText(/sum/i);
|
||||||
expect(wrapper.text()).toBe('SUM');
|
expect(aggregateOption).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -18,45 +18,40 @@
|
|||||||
*/
|
*/
|
||||||
/* eslint-disable no-unused-expressions */
|
/* eslint-disable no-unused-expressions */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sinon from 'sinon';
|
import { render, screen } from 'spec/helpers/testing-library';
|
||||||
import { mount } from 'enzyme';
|
|
||||||
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
|
||||||
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
|
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
|
||||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
import userEvent from '@testing-library/user-event';
|
||||||
import Checkbox from 'src/components/Checkbox';
|
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
name: 'show_legend',
|
name: 'show_legend',
|
||||||
onChange: sinon.spy(),
|
onChange: jest.fn(),
|
||||||
value: false,
|
value: false,
|
||||||
label: 'checkbox label',
|
label: 'checkbox label',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setup = (overrides = {}) => (
|
||||||
|
<ThemeProvider theme={supersetTheme}>
|
||||||
|
<CheckboxControl {...defaultProps} {...overrides} />;
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
|
||||||
describe('CheckboxControl', () => {
|
describe('CheckboxControl', () => {
|
||||||
let wrapper;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper = mount(
|
|
||||||
<ThemeProvider theme={supersetTheme}>
|
|
||||||
<CheckboxControl {...defaultProps} />
|
|
||||||
</ThemeProvider>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders a Checkbox', () => {
|
it('renders a Checkbox', () => {
|
||||||
const controlHeader = wrapper.childAt(0).find(ControlHeader);
|
render(setup());
|
||||||
expect(controlHeader).toHaveLength(1);
|
|
||||||
expect(controlHeader.find(Checkbox)).toHaveLength(1);
|
const checkbox = screen.getByRole('checkbox');
|
||||||
|
expect(checkbox).toBeVisible();
|
||||||
|
expect(checkbox).not.toBeChecked();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Checks the box when the label is clicked', () => {
|
it('Checks the box when the label is clicked', () => {
|
||||||
const fullComponent = wrapper.childAt(0);
|
render(setup());
|
||||||
const spy = sinon.spy(fullComponent.instance(), 'onChange');
|
const label = screen.getByRole('button', {
|
||||||
|
name: /checkbox label/i,
|
||||||
|
});
|
||||||
|
|
||||||
fullComponent.instance().forceUpdate();
|
userEvent.click(label);
|
||||||
|
expect(defaultProps.onChange).toHaveBeenCalled();
|
||||||
fullComponent.find('label span').last().simulate('click');
|
|
||||||
|
|
||||||
expect(spy.calledOnce).toBe(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,61 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
import React from 'react';
|
|
||||||
import { shallow } from 'enzyme';
|
|
||||||
import { SketchPicker } from 'react-color';
|
|
||||||
import {
|
|
||||||
CategoricalScheme,
|
|
||||||
getCategoricalSchemeRegistry,
|
|
||||||
} from '@superset-ui/core';
|
|
||||||
import Popover from 'src/components/Popover';
|
|
||||||
import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
|
|
||||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
|
||||||
|
|
||||||
const defaultProps = {
|
|
||||||
value: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('ColorPickerControl', () => {
|
|
||||||
let wrapper;
|
|
||||||
let inst;
|
|
||||||
beforeAll(() => {
|
|
||||||
getCategoricalSchemeRegistry()
|
|
||||||
.registerValue(
|
|
||||||
'test',
|
|
||||||
new CategoricalScheme({
|
|
||||||
id: 'test',
|
|
||||||
colors: ['red', 'green', 'blue'],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.setDefaultKey('test');
|
|
||||||
wrapper = shallow(<ColorPickerControl {...defaultProps} />);
|
|
||||||
inst = wrapper.instance();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders a OverlayTrigger', () => {
|
|
||||||
const controlHeader = wrapper.find(ControlHeader);
|
|
||||||
expect(controlHeader).toHaveLength(1);
|
|
||||||
expect(wrapper.find(Popover)).toExist();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders a Popover with a SketchPicker', () => {
|
|
||||||
const popOver = shallow(inst.renderPopover());
|
|
||||||
expect(popOver.find(SketchPicker)).toHaveLength(1);
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* 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 { render } from 'spec/helpers/testing-library';
|
||||||
|
import {
|
||||||
|
CategoricalScheme,
|
||||||
|
getCategoricalSchemeRegistry,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import ColorPickerControl from 'src/explore/components/controls/ColorPickerControl';
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
value: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('ColorPickerControl', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
getCategoricalSchemeRegistry()
|
||||||
|
.registerValue(
|
||||||
|
'test',
|
||||||
|
new CategoricalScheme({
|
||||||
|
id: 'test',
|
||||||
|
colors: ['red', 'green', 'blue'],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.setDefaultKey('test');
|
||||||
|
render(<ColorPickerControl {...defaultProps} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders an OverlayTrigger', () => {
|
||||||
|
const rendered = render(<ColorPickerControl {...defaultProps} />);
|
||||||
|
|
||||||
|
// This is the div wrapping the OverlayTrigger and SketchPicker
|
||||||
|
const controlWrapper = rendered.container.querySelectorAll('div')[1];
|
||||||
|
expect(controlWrapper.childElementCount).toBe(2);
|
||||||
|
|
||||||
|
// This is the div containing the OverlayTrigger
|
||||||
|
const overlayTrigger = rendered.container.querySelectorAll('div')[2];
|
||||||
|
expect(overlayTrigger).toHaveStyle(
|
||||||
|
'position: absolute; width: 50px; height: 20px; top: 0px; left: 0px; right: 0px; bottom: 0px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==) center;',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a Popover with a SketchPicker', () => {
|
||||||
|
const rendered = render(<ColorPickerControl {...defaultProps} />);
|
||||||
|
|
||||||
|
// This is the div wrapping the OverlayTrigger and SketchPicker
|
||||||
|
const controlWrapper = rendered.container.querySelectorAll('div')[1];
|
||||||
|
expect(controlWrapper.childElementCount).toBe(2);
|
||||||
|
|
||||||
|
// This is the div containing the SketchPicker
|
||||||
|
const sketchPicker = rendered.container.querySelectorAll('div')[3];
|
||||||
|
expect(sketchPicker).toHaveStyle(
|
||||||
|
'position: absolute; width: 50px; height: 20px; top: 0px; left: 0px; right: 0px; bottom: 0px; border-radius: 2px;',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -22,13 +22,13 @@ import Label from 'src/components/Label';
|
|||||||
import { Tooltip } from 'src/components/Tooltip';
|
import { Tooltip } from 'src/components/Tooltip';
|
||||||
import { TooltipContent } from './TooltipContent';
|
import { TooltipContent } from './TooltipContent';
|
||||||
|
|
||||||
interface Props {
|
export interface CacheLabelProps {
|
||||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||||
cachedTimestamp?: string;
|
cachedTimestamp?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CacheLabel: React.FC<Props> = ({
|
const CacheLabel: React.FC<CacheLabelProps> = ({
|
||||||
className,
|
className,
|
||||||
onClick,
|
onClick,
|
||||||
cachedTimestamp,
|
cachedTimestamp,
|
||||||
|
@ -60,8 +60,8 @@ export default function UserInfo({ user }: UserInfoProps) {
|
|||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<p>
|
<p>
|
||||||
<i className="fa fa-clock-o" /> {t('joined')}{' '}
|
<i className="fa fa-clock-o" data-test="clock-icon-test" />{' '}
|
||||||
{moment(user.createdOn, 'YYYYMMDD').fromNow()}
|
{t('joined')} {moment(user.createdOn, 'YYYYMMDD').fromNow()}
|
||||||
</p>
|
</p>
|
||||||
<p className="email">
|
<p className="email">
|
||||||
<i className="fa fa-envelope-o" /> {user.email}
|
<i className="fa fa-envelope-o" /> {user.email}
|
||||||
|
Loading…
Reference in New Issue
Block a user