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}