diff --git a/superset-frontend/spec/javascripts/components/CachedLabel_spec.jsx b/superset-frontend/spec/javascripts/components/CachedLabel_spec.tsx
similarity index 71%
rename from superset-frontend/spec/javascripts/components/CachedLabel_spec.jsx
rename to superset-frontend/spec/javascripts/components/CachedLabel_spec.tsx
index 7e1ecf7738c71..b13c31b7b1ff7 100644
--- a/superset-frontend/spec/javascripts/components/CachedLabel_spec.jsx
+++ b/superset-frontend/spec/javascripts/components/CachedLabel_spec.tsx
@@ -17,22 +17,26 @@
* under the License.
*/
import React from 'react';
-import { shallow } from 'enzyme';
+import { render, screen } from 'spec/helpers/testing-library';
-import Label from 'src/components/Label';
-import CachedLabel from 'src/components/CachedLabel';
+import CachedLabel, { CacheLabelProps } from 'src/components/CachedLabel';
-describe('CachedLabel', () => {
- const defaultProps = {
- onClick: () => {},
- cachedTimestamp: '2017-01-01',
- };
+const defaultProps = {
+ onClick: () => {},
+ cachedTimestamp: '2017-01-01',
+};
+
+const setup = (props: CacheLabelProps) => ;
+describe('CachedLabel', () => {
it('is valid', () => {
expect(React.isValidElement()).toBe(true);
});
+
it('renders', () => {
- const wrapper = shallow();
- expect(wrapper.find(Label)).toExist();
+ render(setup(defaultProps));
+
+ const label = screen.getByText(/cached/i);
+ expect(label).toBeVisible();
});
});
diff --git a/superset-frontend/spec/javascripts/components/ColumnTypeLabel_spec.jsx b/superset-frontend/spec/javascripts/components/ColumnTypeLabel_spec.tsx
similarity index 50%
rename from superset-frontend/spec/javascripts/components/ColumnTypeLabel_spec.jsx
rename to superset-frontend/spec/javascripts/components/ColumnTypeLabel_spec.tsx
index 0bc5a26f90b20..42aab98090406 100644
--- a/superset-frontend/spec/javascripts/components/ColumnTypeLabel_spec.jsx
+++ b/superset-frontend/spec/javascripts/components/ColumnTypeLabel_spec.tsx
@@ -17,61 +17,71 @@
* under the License.
*/
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';
-describe('ColumnOption', () => {
- const defaultProps = {
- type: GenericDataType.STRING,
- };
+const defaultProps = {
+ type: GenericDataType.STRING,
+};
- const props = { ...defaultProps };
-
- function getWrapper(overrides) {
- const wrapper = shallow();
- return wrapper;
- }
+const setup = (overrides?: ColumnTypeLabelProps) => (
+
+
+
+);
+describe('ColumnOption RTL', () => {
+ afterEach(cleanup);
it('is a valid element', () => {
expect(React.isValidElement()).toBe(
true,
);
});
+
it('string type shows ABC icon', () => {
- const lbl = getWrapper({}).find('.type-label');
- expect(lbl).toHaveLength(1);
- expect(lbl.first().text()).toBe('ABC');
+ render(setup(defaultProps));
+
+ const labelIcon = screen.getByText(/abc/i);
+ expect(labelIcon.innerHTML).toMatch(/abc/i);
});
+
it('int type shows # icon', () => {
- const lbl = getWrapper({
- type: GenericDataType.NUMERIC,
- }).find('.type-label');
- expect(lbl).toHaveLength(1);
- expect(lbl.first().text()).toBe('#');
+ render(setup({ type: GenericDataType.NUMERIC }));
+
+ const labelIcon = screen.getByText(/#/i);
+ expect(labelIcon.innerHTML).toMatch(/#/i);
});
+
it('bool type shows T/F icon', () => {
- const lbl = getWrapper({
- type: GenericDataType.BOOLEAN,
- }).find('.type-label');
- expect(lbl).toHaveLength(1);
- expect(lbl.first().text()).toBe('T/F');
+ render(setup({ type: GenericDataType.BOOLEAN }));
+
+ const labelIcon = screen.getByText(/t\/f/i);
+ expect(labelIcon.innerHTML).toMatch(/t\/f/i);
});
+
it('expression type shows function icon', () => {
- const lbl = getWrapper({ type: 'expression' }).find('.type-label');
- expect(lbl).toHaveLength(1);
- expect(lbl.first().text()).toBe('ƒ');
+ render(setup({ type: 'expression' }));
+
+ const labelIcon = screen.getByText('ƒ');
+ expect(labelIcon.innerHTML).toMatch('ƒ');
});
+
it('unknown type shows question mark', () => {
- const lbl = getWrapper({ type: 'unknown' }).find('.type-label');
- expect(lbl).toHaveLength(1);
- expect(lbl.first().text()).toBe('?');
+ render(setup({ type: undefined }));
+
+ const labelIcon = screen.getByText('?');
+ expect(labelIcon.innerHTML).toMatch('?');
});
+
it('datetime type displays', () => {
- const lbl = getWrapper({
- type: GenericDataType.TEMPORAL,
- }).find('.fa-clock-o');
- expect(lbl).toHaveLength(1);
+ const rendered = render(setup({ type: GenericDataType.TEMPORAL }));
+
+ const clockIcon = rendered.container.querySelector('.fa-clock-o');
+ expect(clockIcon).toBeVisible();
});
});
diff --git a/superset-frontend/spec/javascripts/dashboard/components/MissingChart_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/MissingChart_spec.tsx
similarity index 56%
rename from superset-frontend/spec/javascripts/dashboard/components/MissingChart_spec.jsx
rename to superset-frontend/spec/javascripts/dashboard/components/MissingChart_spec.tsx
index f2505400fad67..9c3e78252ceb1 100644
--- a/superset-frontend/spec/javascripts/dashboard/components/MissingChart_spec.jsx
+++ b/superset-frontend/spec/javascripts/dashboard/components/MissingChart_spec.tsx
@@ -17,23 +17,38 @@
* under the License.
*/
import React from 'react';
-import { shallow } from 'enzyme';
+import { render } from 'spec/helpers/testing-library';
import MissingChart from 'src/dashboard/components/MissingChart';
-describe('MissingChart', () => {
- function setup(overrideProps) {
- const wrapper = shallow();
- return wrapper;
- }
+type MissingChartProps = {
+ height: number;
+};
+
+const setup = (overrides?: MissingChartProps) => (
+
+);
+describe('MissingChart', () => {
it('renders a .missing-chart-container', () => {
- const wrapper = setup();
- expect(wrapper.find('.missing-chart-container')).toExist();
+ const rendered = render(setup());
+
+ const missingChartContainer = rendered.container.querySelector(
+ '.missing-chart-container',
+ );
+ expect(missingChartContainer).toBeVisible();
});
it('renders a .missing-chart-body', () => {
- const wrapper = setup();
- expect(wrapper.find('.missing-chart-body')).toExist();
+ const rendered = render(setup());
+
+ const missingChartBody = rendered.container.querySelector(
+ '.missing-chart-body',
+ );
+ const bodyText =
+ 'There is no chart definition associated with this component, could it have been deleted?
Delete this container and save to remove this message.';
+
+ expect(missingChartBody).toBeVisible();
+ expect(missingChartBody?.innerHTML).toMatch(bodyText);
});
});
diff --git a/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx
deleted file mode 100644
index 711dc45f120f5..0000000000000
--- a/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.jsx
+++ /dev/null
@@ -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(, {
- wrappingComponent: ThemeProvider,
- wrappingComponentProps: {
- theme: supersetTheme,
- },
- });
-
-describe('RefreshIntervalModal', () => {
- const mockedProps = {
- triggerNode: ,
- refreshFrequency: 10,
- onChange: jest.fn(),
- editMode: true,
- };
- it('is valid', () => {
- expect(
- React.isValidElement(),
- ).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();
- });
-});
diff --git a/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.tsx b/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.tsx
new file mode 100644
index 0000000000000..cb4d4808e257a
--- /dev/null
+++ b/superset-frontend/spec/javascripts/dashboard/components/RefreshIntervalModal_spec.tsx
@@ -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(, {
+ wrappingComponent: ThemeProvider,
+ wrappingComponentProps: {
+ theme: supersetTheme,
+ },
+ });
+ const mockedProps = {
+ triggerNode: ,
+ 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) => (
+
+
+
+);
+
+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: ,
+ refreshFrequency: 0,
+ onChange: jest.fn(),
+ editMode: true,
+};
+
+describe('RefreshIntervalModal - RTL', () => {
+ it('is valid', () => {
+ expect(
+ React.isValidElement(
+ ,
+ ),
+ ).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();
+ });
+});
diff --git a/superset-frontend/spec/javascripts/dashboard/components/menu/HoverMenu_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/menu/HoverMenu_spec.tsx
similarity index 79%
rename from superset-frontend/spec/javascripts/dashboard/components/menu/HoverMenu_spec.jsx
rename to superset-frontend/spec/javascripts/dashboard/components/menu/HoverMenu_spec.tsx
index 24106f1641db5..220952bf8dc56 100644
--- a/superset-frontend/spec/javascripts/dashboard/components/menu/HoverMenu_spec.jsx
+++ b/superset-frontend/spec/javascripts/dashboard/components/menu/HoverMenu_spec.tsx
@@ -17,13 +17,14 @@
* under the License.
*/
import React from 'react';
-import { shallow } from 'enzyme';
+import { render } from 'spec/helpers/testing-library';
import HoverMenu from 'src/dashboard/components/menu/HoverMenu';
describe('HoverMenu', () => {
- it('should render a div.hover-menu', () => {
- const wrapper = shallow();
- expect(wrapper.find('.hover-menu')).toExist();
+ it('should render a hover menu', () => {
+ const rendered = render();
+ const hoverMenu = rendered.container.querySelector('.hover-menu');
+ expect(hoverMenu).toBeVisible();
});
});
diff --git a/superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.tsx
similarity index 50%
rename from superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.jsx
rename to superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.tsx
index 68c873db3db78..be3f2320c52bf 100644
--- a/superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.jsx
+++ b/superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableContainer_spec.tsx
@@ -17,20 +17,46 @@
* under the License.
*/
import React from 'react';
-import { Resizable } from 're-resizable';
-import { shallow } from 'enzyme';
+import { render } from 'spec/helpers/testing-library';
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', () => {
const props = { editMode: false, id: 'id' };
- function setup(propOverrides) {
- return shallow();
- }
+ const setup = (overrides?: ResizableContainerProps) => (
+
+ );
- it('should render a Resizable', () => {
- const wrapper = setup();
- expect(wrapper.find(Resizable)).toExist();
+ it('should render a Resizable container', () => {
+ const rendered = render(setup());
+ const resizableContainer = rendered.container.querySelector(
+ '.resizable-container',
+ );
+ expect(resizableContainer).toBeVisible();
});
});
diff --git a/superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.tsx
similarity index 68%
rename from superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.jsx
rename to superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.tsx
index bc8059c4a9548..60371d41ae5e9 100644
--- a/superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.jsx
+++ b/superset-frontend/spec/javascripts/dashboard/components/resizable/ResizableHandle_spec.tsx
@@ -17,29 +17,33 @@
* under the License.
*/
import React from 'react';
-import { shallow } from 'enzyme';
+import { render } from 'spec/helpers/testing-library';
import ResizableHandle from 'src/dashboard/components/resizable/ResizableHandle';
/* eslint-disable react/jsx-pascal-case */
describe('ResizableHandle', () => {
it('should render a right resize handle', () => {
- const wrapper = shallow();
- expect(wrapper.find('.resize-handle.resize-handle--right')).toExist();
+ const rendered = render();
+ expect(
+ rendered.container.querySelector('.resize-handle.resize-handle--right'),
+ ).toBeVisible();
});
it('should render a bottom resize handle', () => {
- const wrapper = shallow();
- expect(wrapper.find('.resize-handle.resize-handle--bottom')).toHaveLength(
- 1,
- );
+ const rendered = render();
+ expect(
+ rendered.container.querySelector('.resize-handle.resize-handle--bottom'),
+ ).toBeVisible();
});
it('should render a bottomRight resize handle', () => {
- const wrapper = shallow();
+ const rendered = render();
expect(
- wrapper.find('.resize-handle.resize-handle--bottom-right'),
- ).toHaveLength(1);
+ rendered.container.querySelector(
+ '.resize-handle.resize-handle--bottom-right',
+ ),
+ ).toBeVisible();
});
});
/* eslint-enable react/jsx-pascal-case */
diff --git a/superset-frontend/spec/javascripts/explore/components/AggregateOption_spec.jsx b/superset-frontend/spec/javascripts/explore/components/AggregateOption_spec.tsx
similarity index 81%
rename from superset-frontend/spec/javascripts/explore/components/AggregateOption_spec.jsx
rename to superset-frontend/spec/javascripts/explore/components/AggregateOption_spec.tsx
index 504b790afab9e..9c4364e4e1c63 100644
--- a/superset-frontend/spec/javascripts/explore/components/AggregateOption_spec.jsx
+++ b/superset-frontend/spec/javascripts/explore/components/AggregateOption_spec.tsx
@@ -16,17 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
-/* eslint-disable no-unused-expressions */
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';
describe('AggregateOption', () => {
it('renders the aggregate', () => {
- const wrapper = shallow(
- ,
- );
- expect(wrapper.text()).toBe('SUM');
+ render();
+
+ const aggregateOption = screen.getByText(/sum/i);
+ expect(aggregateOption).toBeVisible();
});
});
diff --git a/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.tsx
similarity index 61%
rename from superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.jsx
rename to superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.tsx
index 6eec246029fa4..0a92830c62c6c 100644
--- a/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.jsx
+++ b/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.tsx
@@ -18,45 +18,40 @@
*/
/* eslint-disable no-unused-expressions */
import React from 'react';
-import sinon from 'sinon';
-import { mount } from 'enzyme';
+import { render, screen } from 'spec/helpers/testing-library';
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
import CheckboxControl from 'src/explore/components/controls/CheckboxControl';
-import ControlHeader from 'src/explore/components/ControlHeader';
-import Checkbox from 'src/components/Checkbox';
+import userEvent from '@testing-library/user-event';
const defaultProps = {
name: 'show_legend',
- onChange: sinon.spy(),
+ onChange: jest.fn(),
value: false,
label: 'checkbox label',
};
-describe('CheckboxControl', () => {
- let wrapper;
-
- beforeEach(() => {
- wrapper = mount(
-
-
- ,
- );
- });
+const setup = (overrides = {}) => (
+
+ ;
+
+);
+describe('CheckboxControl', () => {
it('renders a Checkbox', () => {
- const controlHeader = wrapper.childAt(0).find(ControlHeader);
- expect(controlHeader).toHaveLength(1);
- expect(controlHeader.find(Checkbox)).toHaveLength(1);
+ render(setup());
+
+ const checkbox = screen.getByRole('checkbox');
+ expect(checkbox).toBeVisible();
+ expect(checkbox).not.toBeChecked();
});
it('Checks the box when the label is clicked', () => {
- const fullComponent = wrapper.childAt(0);
- const spy = sinon.spy(fullComponent.instance(), 'onChange');
-
- fullComponent.instance().forceUpdate();
-
- fullComponent.find('label span').last().simulate('click');
+ render(setup());
+ const label = screen.getByRole('button', {
+ name: /checkbox label/i,
+ });
- expect(spy.calledOnce).toBe(true);
+ userEvent.click(label);
+ expect(defaultProps.onChange).toHaveBeenCalled();
});
});
diff --git a/superset-frontend/spec/javascripts/explore/components/ColorPickerControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/ColorPickerControl_spec.jsx
deleted file mode 100644
index 9fda1017e1104..0000000000000
--- a/superset-frontend/spec/javascripts/explore/components/ColorPickerControl_spec.jsx
+++ /dev/null
@@ -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();
- 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);
- });
-});
diff --git a/superset-frontend/spec/javascripts/explore/components/ColorPickerControl_spec.tsx b/superset-frontend/spec/javascripts/explore/components/ColorPickerControl_spec.tsx
new file mode 100644
index 0000000000000..99de2dcb71751
--- /dev/null
+++ b/superset-frontend/spec/javascripts/explore/components/ColorPickerControl_spec.tsx
@@ -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();
+ });
+
+ it('renders an OverlayTrigger', () => {
+ const rendered = render();
+
+ // 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();
+
+ // 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;',
+ );
+ });
+});
diff --git a/superset-frontend/src/components/CachedLabel/index.tsx b/superset-frontend/src/components/CachedLabel/index.tsx
index 5a42949f5237c..a401ab1e0e57c 100644
--- a/superset-frontend/src/components/CachedLabel/index.tsx
+++ b/superset-frontend/src/components/CachedLabel/index.tsx
@@ -22,13 +22,13 @@ import Label from 'src/components/Label';
import { Tooltip } from 'src/components/Tooltip';
import { TooltipContent } from './TooltipContent';
-interface Props {
+export interface CacheLabelProps {
onClick?: React.MouseEventHandler;
cachedTimestamp?: string;
className?: string;
}
-const CacheLabel: React.FC = ({
+const CacheLabel: React.FC = ({
className,
onClick,
cachedTimestamp,
diff --git a/superset-frontend/src/profile/components/UserInfo.tsx b/superset-frontend/src/profile/components/UserInfo.tsx
index b6dcac022f0bb..887b4ec50c136 100644
--- a/superset-frontend/src/profile/components/UserInfo.tsx
+++ b/superset-frontend/src/profile/components/UserInfo.tsx
@@ -60,8 +60,8 @@ export default function UserInfo({ user }: UserInfoProps) {
- {t('joined')}{' '}
- {moment(user.createdOn, 'YYYYMMDD').fromNow()}
+ {' '}
+ {t('joined')} {moment(user.createdOn, 'YYYYMMDD').fromNow()}
{user.email}