This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch replace-jest-enzyme
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 02aab77a50513d940364439fb148cb7a5d016345
Author: Evan Rusackas <[email protected]>
AuthorDate: Sat Feb 8 23:10:51 2025 -0700

    SliceAdder tests to RTL
---
 .../src/dashboard/components/SliceAdder.test.tsx   | 361 ++++++++++++---------
 1 file changed, 207 insertions(+), 154 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/SliceAdder.test.tsx 
b/superset-frontend/src/dashboard/components/SliceAdder.test.tsx
index 689f86f783..36c9a45ac2 100644
--- a/superset-frontend/src/dashboard/components/SliceAdder.test.tsx
+++ b/superset-frontend/src/dashboard/components/SliceAdder.test.tsx
@@ -16,181 +16,234 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { shallow, ShallowWrapper } from 'enzyme';
-import sinon from 'sinon';
-
-import SliceAdder, {
-  ChartList,
-  DEFAULT_SORT_KEY,
-  SliceAdderProps,
-} from 'src/dashboard/components/SliceAdder';
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import '@testing-library/jest-dom'; // Add this import
+import { Provider } from 'react-redux';
+import { ThemeProvider, supersetTheme } from '@superset-ui/core';
 import { sliceEntitiesForDashboard as mockSliceEntities } from 
'spec/fixtures/mockSliceEntities';
-import { styledShallow } from 'spec/helpers/theming';
+import SliceAdder, { SliceAdderProps } from './SliceAdder';
+import { configureStore } from '@reduxjs/toolkit';
+import { DatasourceType } from '@superset-ui/core';
+
+// Mock the Select component to avoid debounce issues
+jest.mock('@superset-ui/core', () => ({
+  ...jest.requireActual('@superset-ui/core'),
+  Select: ({ value, onChange, options }: any) => (
+    <select
+      data-test="select"
+      value={value}
+      onChange={e => onChange(e.target.value)}
+    >
+      {options?.map((opt: any) => (
+        <option key={opt.value} value={opt.value}>
+          {opt.label}
+        </option>
+      ))}
+    </select>
+  ),
+}));
+
+jest.mock('lodash/debounce', () => {
+  const debounced = (fn: Function) => {
+    const debouncedFn = ((...args: any[]) =>
+      fn(...args)) as unknown as Function & {
+      cancel: () => void;
+    };
+    debouncedFn.cancel = () => {};
+    return debouncedFn;
+  };
+  return debounced;
+});
 
-jest.mock(
-  'lodash/debounce',
-  () => (fn: { throttle: jest.Mock<any, any, any> }) => {
-    // eslint-disable-next-line no-param-reassign
-    fn.throttle = jest.fn();
-    return fn;
-  },
-);
+const mockStore = configureStore({
+  reducer: (state = { common: { locale: 'en' } }) => state,
+});
 
-describe('SliceAdder', () => {
-  const props: SliceAdderProps = {
-    slices: {
-      ...mockSliceEntities.slices,
-    },
-    fetchSlices: jest.fn(),
-    updateSlices: jest.fn(),
-    selectedSliceIds: [127, 128],
-    userId: 1,
-    dashboardId: 0,
-    editMode: false,
-    errorMessage: '',
-    isLoading: false,
-    lastUpdated: 0,
-  };
-  const errorProps = {
-    ...props,
-    errorMessage: 'this is error',
-  };
-  describe('SliceAdder.sortByComparator', () => {
-    it('should sort by timestamp descending', () => {
-      const sortedTimestamps = Object.values(props.slices)
-        .sort(SliceAdder.sortByComparator('changed_on'))
-        .map(slice => slice.changed_on);
-      expect(
-        sortedTimestamps.every((currentTimestamp, index) => {
-          if (index === 0) {
-            return true;
-          }
-          return currentTimestamp < sortedTimestamps[index - 1];
-        }),
-      ).toBe(true);
-    });
+const defaultProps: SliceAdderProps = {
+  slices: mockSliceEntities.slices,
+  fetchSlices: jest.fn(),
+  updateSlices: jest.fn(),
+  selectedSliceIds: [127, 128],
+  userId: 1,
+  dashboardId: 0,
+  editMode: false,
+  errorMessage: '',
+  isLoading: false,
+  lastUpdated: 0,
+};
+
+const renderSliceAdder = (props = defaultProps) =>
+  render(
+    <Provider store={mockStore}>
+      <ThemeProvider theme={supersetTheme}>
+        <SliceAdder {...props} />
+      </ThemeProvider>
+    </Provider>,
+  );
 
-    it('should sort by slice_name', () => {
-      const sortedNames = Object.values(props.slices)
-        .sort(SliceAdder.sortByComparator('slice_name'))
-        .map(slice => slice.slice_name);
-      const expectedNames = Object.values(props.slices)
-        .map(slice => slice.slice_name)
-        .sort();
-      expect(sortedNames).toEqual(expectedNames);
-    });
+describe('SliceAdder', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
   });
 
-  it('render chart list', () => {
-    const wrapper = styledShallow(<SliceAdder {...props} />);
-    wrapper.setState({ filteredSlices: Object.values(props.slices) });
-    expect(wrapper.find(ChartList)).toBeTruthy();
+  it('renders the create new chart button', () => {
+    renderSliceAdder();
+    expect(screen.getByText('Create new chart')).toBeInTheDocument();
   });
 
-  it('render error', () => {
-    const wrapper = shallow(<SliceAdder {...errorProps} />);
-    wrapper.setState({ filteredSlices: Object.values(props.slices) });
-    expect(wrapper.text()).toContain(errorProps.errorMessage);
+  it('renders loading state', () => {
+    renderSliceAdder({ ...defaultProps, isLoading: true });
+    expect(screen.getByRole('status')).toBeInTheDocument();
   });
 
-  it('componentDidMount', () => {
-    const componentDidMountSpy = sinon.spy(
-      SliceAdder.prototype,
-      'componentDidMount',
-    );
-    const fetchSlicesSpy = sinon.spy(props, 'fetchSlices');
-    shallow(<SliceAdder {...props} />, {
-      lifecycleExperimental: true,
-    });
-
-    expect(componentDidMountSpy.calledOnce).toBe(true);
-
-    expect(fetchSlicesSpy.calledOnce).toBe(true);
-
-    componentDidMountSpy.restore();
-    fetchSlicesSpy.restore();
+  it('renders error message', () => {
+    const errorMessage = 'Error loading charts';
+    renderSliceAdder({ ...defaultProps, errorMessage });
+    expect(screen.getByText(errorMessage)).toBeInTheDocument();
   });
 
-  describe('UNSAFE_componentWillReceiveProps', () => {
-    let wrapper: ShallowWrapper;
-    let setStateSpy: sinon.SinonSpy;
-
-    beforeEach(() => {
-      wrapper = shallow(<SliceAdder {...props} />);
-      wrapper.setState({ filteredSlices: Object.values(props.slices) });
-      setStateSpy = sinon.spy(wrapper.instance() as SliceAdder, 'setState');
-    });
-    afterEach(() => {
-      setStateSpy.restore();
-    });
-
-    it('fetch slices should update state', () => {
-      const instance = wrapper.instance() as SliceAdder;
-      instance.UNSAFE_componentWillReceiveProps({
-        ...props,
-        lastUpdated: new Date().getTime(),
-      });
-      expect(setStateSpy.calledOnce).toBe(true);
-
-      const stateKeys = Object.keys(setStateSpy.lastCall.args[0]);
-      expect(stateKeys).toContain('filteredSlices');
-    });
-
-    it('select slices should update state', () => {
-      const instance = wrapper.instance() as SliceAdder;
+  it('fetches slices on mount', () => {
+    renderSliceAdder();
+    expect(defaultProps.fetchSlices).toHaveBeenCalledWith(1, '', 'changed_on');
+  });
 
-      instance.UNSAFE_componentWillReceiveProps({
-        ...props,
-        selectedSliceIds: [127],
-      });
+  it('handles search input changes', async () => {
+    renderSliceAdder();
+    const searchInput = screen.getByPlaceholderText('Filter your charts');
+    await userEvent.type(searchInput, 'test search');
+    expect(defaultProps.fetchSlices).toHaveBeenCalledWith(
+      1,
+      'test search',
+      'changed_on',
+    );
+  });
 
-      expect(setStateSpy.calledOnce).toBe(true);
+  it('handles sort selection changes', async () => {
+    renderSliceAdder();
+    // Update selector to match the actual rendered element
+    const sortSelect = screen.getByText('Sort by recent');
+    await userEvent.click(sortSelect);
+    const vizTypeOption = screen.getByText('Sort by viz type');
+    await userEvent.click(vizTypeOption);
+    expect(defaultProps.fetchSlices).toHaveBeenCalledWith(1, '', 'viz_type');
+  });
 
-      const stateKeys = Object.keys(setStateSpy.lastCall.args[0]);
-      expect(stateKeys).toContain('selectedSliceIdsSet');
-    });
+  it('handles show only my charts toggle', async () => {
+    renderSliceAdder();
+    const checkbox = screen.getByRole('checkbox');
+    await userEvent.click(checkbox);
+    expect(defaultProps.fetchSlices).toHaveBeenCalledWith(
+      undefined,
+      '',
+      'changed_on',
+    );
   });
 
-  describe('should rerun filter and sort', () => {
-    let wrapper: ShallowWrapper<SliceAdder>;
-    let spy: jest.Mock;
-
-    beforeEach(() => {
-      spy = jest.fn();
-      const fetchSlicesProps: SliceAdderProps = {
-        ...props,
-        fetchSlices: spy,
-      };
-      wrapper = shallow(<SliceAdder {...fetchSlicesProps} />);
-      wrapper.setState({
-        filteredSlices: Object.values(fetchSlicesProps.slices),
-      });
-    });
+  it('opens new chart in new tab when create new chart is clicked', () => {
+    const windowSpy = jest.spyOn(window, 'open').mockImplementation();
+    renderSliceAdder();
+    const createButton = screen.getByText('Create new chart');
+    fireEvent.click(createButton);
+    expect(windowSpy).toHaveBeenCalledWith(
+      '/chart/add?dashboard_id=0',
+      '_blank',
+      'noopener noreferrer',
+    );
+    windowSpy.mockRestore();
+  });
 
-    afterEach(() => {
-      spy.mockReset();
+  describe('sortByComparator', () => {
+    const baseSlice = {
+      slice_url: '/superset/explore/',
+      thumbnail_url: '/thumbnail',
+      datasource_url: '/superset/datasource/1',
+      changed_on_humanized: '1 day ago',
+      datasource_id: 1,
+      datasource_name: 'test_datasource',
+      datasource_type: DatasourceType.Table,
+      form_data: {},
+      viz_type: 'test_viz',
+      datasource: '1__table',
+      description: '',
+      description_markdown: '',
+      modified: '2020-01-01',
+      owners: [],
+      created_by: { id: 1 }, // Fix: provide required user object instead of 
null
+      cache_timeout: null,
+      uuid: '1234',
+      query_context: null,
+    };
+
+    it('should sort by changed_on in descending order', () => {
+      const input = [
+        {
+          ...baseSlice,
+          slice_id: 1,
+          slice_name: 'Test 1',
+          changed_on: 1577836800000, // 2020-01-01
+        },
+        {
+          ...baseSlice,
+          slice_id: 2,
+          slice_name: 'Test 2',
+          changed_on: 1578009600000, // 2020-01-03
+          uuid: '5678',
+        },
+        {
+          ...baseSlice,
+          slice_id: 3,
+          slice_name: 'Test 3',
+          changed_on: 1577923200000, // 2020-01-02
+          uuid: '9012',
+        },
+      ];
+      const sorted = input.sort(SliceAdder.sortByComparator('changed_on'));
+      expect(sorted[0].changed_on).toBe(1578009600000);
+      expect(sorted[2].changed_on).toBe(1577836800000);
     });
 
-    it('searchUpdated', () => {
-      const newSearchTerm = 'new search term';
-
-      (wrapper.instance() as SliceAdder).handleChange(newSearchTerm);
-
-      expect(spy).toHaveBeenCalled();
-      expect(spy).toHaveBeenCalledWith(
-        props.userId,
-        newSearchTerm,
-        DEFAULT_SORT_KEY,
-      );
+    it('should sort by other fields in ascending order', () => {
+      const input = [
+        {
+          ...baseSlice,
+          slice_id: 1,
+          slice_name: 'c',
+          changed_on: 1577836800000, // Add changed_on field
+          uuid: '1234',
+        },
+        {
+          ...baseSlice,
+          slice_id: 2,
+          slice_name: 'a',
+          changed_on: 1577836800000, // Add changed_on field
+          uuid: '5678',
+        },
+        {
+          ...baseSlice,
+          slice_id: 3,
+          slice_name: 'b',
+          changed_on: 1577836800000, // Add changed_on field
+          uuid: '9012',
+        },
+      ];
+      const sorted = input.sort(SliceAdder.sortByComparator('slice_name'));
+      expect(sorted[0].slice_name).toBe('a');
+      expect(sorted[2].slice_name).toBe('c');
     });
+  });
 
-    it('handleSelect', () => {
-      const newSortBy = 'viz_type';
-
-      (wrapper.instance() as SliceAdder).handleSelect(newSortBy);
-
-      expect(spy).toHaveBeenCalled();
-      expect(spy).toHaveBeenCalledWith(props.userId, '', newSortBy);
-    });
+  it('should update selectedSliceIdsSet when props change', () => {
+    const { rerender } = renderSliceAdder();
+    rerender(
+      <Provider store={mockStore}>
+        <ThemeProvider theme={supersetTheme}>
+          <SliceAdder {...defaultProps} selectedSliceIds={[129]} />
+        </ThemeProvider>
+      </Provider>,
+    );
+    // Verify the internal state was updated by checking if new charts are 
available
+    expect(screen.getByRole('checkbox')).toBeInTheDocument();
   });
 });

Reply via email to