From 7d8b707da572d7f883d1f7340516564d326bb683 Mon Sep 17 00:00:00 2001 From: Joe Li Date: Thu, 9 Oct 2025 14:42:57 -0700 Subject: [PATCH 1/2] test: migrate 3 JSX test files to TypeScript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Merged ListView.test.jsx comprehensive tests into ListView.test.tsx (9 tests total) - Removed DatasourceControl.test.jsx (all tests already in .tsx with 21 tests) - Removed VizTypeControl.test.jsx (all tests already in .tsx with 10 tests) All 40 tests pass. TypeScript migration aligns with frontend modernization goals. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../src/components/ListView/ListView.test.jsx | 271 ------------------ .../src/components/ListView/ListView.test.tsx | 262 ++++++++++++++++- .../DatasourceControl.test.jsx | 170 ----------- .../VizTypeControl/VizTypeControl.test.jsx | 131 --------- 4 files changed, 259 insertions(+), 575 deletions(-) delete mode 100644 superset-frontend/src/components/ListView/ListView.test.jsx delete mode 100644 superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.jsx delete mode 100644 superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.jsx diff --git a/superset-frontend/src/components/ListView/ListView.test.jsx b/superset-frontend/src/components/ListView/ListView.test.jsx deleted file mode 100644 index 4045c4b85cca..000000000000 --- a/superset-frontend/src/components/ListView/ListView.test.jsx +++ /dev/null @@ -1,271 +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 { render, screen, within, waitFor } from 'spec/helpers/testing-library'; -import userEvent from '@testing-library/user-event'; -import { QueryParamProvider } from 'use-query-params'; -import thunk from 'redux-thunk'; -import configureStore from 'redux-mock-store'; -import fetchMock from 'fetch-mock'; - -// Only import components that are directly referenced in tests -import { ListView } from './ListView'; - -const middlewares = [thunk]; -const mockStore = configureStore(middlewares); - -function makeMockLocation(query) { - const queryStr = encodeURIComponent(query); - return { - protocol: 'http:', - host: 'localhost', - pathname: '/', - search: queryStr.length ? `?${queryStr}` : '', - }; -} - -const fetchSelectsMock = jest.fn(() => []); -const mockedProps = { - title: 'Data Table', - columns: [ - { - accessor: 'id', - Header: 'ID', - sortable: true, - id: 'id', - }, - { - accessor: 'age', - Header: 'Age', - id: 'age', - }, - { - accessor: 'name', - Header: 'Name', - id: 'name', - }, - { - accessor: 'time', - Header: 'Time', - id: 'time', - }, - ], - filters: [ - { - Header: 'ID', - id: 'id', - input: 'select', - selects: [{ label: 'foo', value: 'bar' }], - operator: 'eq', - }, - { - Header: 'Name', - id: 'name', - input: 'search', - operator: 'ct', - }, - { - Header: 'Age', - id: 'age', - input: 'select', - fetchSelects: fetchSelectsMock, - paginate: true, - operator: 'eq', - }, - { - Header: 'Time', - id: 'time', - input: 'datetime_range', - operator: 'between', - }, - ], - data: [ - { id: 1, name: 'data 1', age: 10, time: '2020-11-18T07:53:45.354Z' }, - { id: 2, name: 'data 2', age: 1, time: '2020-11-18T07:53:45.354Z' }, - ], - count: 2, - pageSize: 1, - fetchData: jest.fn(() => []), - loading: false, - bulkSelectEnabled: true, - disableBulkSelect: jest.fn(), - bulkActions: [ - { - key: 'something', - name: 'do something', - style: 'danger', - onSelect: jest.fn(), - }, - ], - cardSortSelectOptions: [ - { - desc: false, - id: 'something', - label: 'Alphabetical', - value: 'alphabetical', - }, - ], -}; - -const factory = (props = mockedProps) => - render( - - - , - { store: mockStore() }, - ); - -// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks -describe('ListView', () => { - beforeEach(() => { - fetchMock.reset(); - jest.clearAllMocks(); - factory(); - }); - - afterEach(() => { - fetchMock.reset(); - mockedProps.fetchData.mockClear(); - mockedProps.bulkActions.forEach(ba => { - ba.onSelect.mockClear(); - }); - }); - - // Example of converted test: - test('calls fetchData on mount', () => { - expect(mockedProps.fetchData).toHaveBeenCalledWith({ - filters: [], - pageIndex: 0, - pageSize: 1, - sortBy: [], - }); - }); - - test('calls fetchData on sort', async () => { - const sortHeader = screen.getAllByTestId('sort-header')[1]; - await userEvent.click(sortHeader); - - expect(mockedProps.fetchData).toHaveBeenCalledWith({ - filters: [], - pageIndex: 0, - pageSize: 1, - sortBy: [ - { - desc: false, - id: 'id', - }, - ], - }); - }); - - // Update pagination control tests for Ant Design pagination - test('renders pagination controls', () => { - const paginationList = screen.getByRole('list'); - expect(paginationList).toBeInTheDocument(); - - const pageOneItem = screen.getByRole('listitem', { name: '1' }); - expect(pageOneItem).toBeInTheDocument(); - }); - - test('calls fetchData on page change', async () => { - const pageTwoItem = screen.getByRole('listitem', { name: '2' }); - await userEvent.click(pageTwoItem); - - await waitFor(() => { - const { calls } = mockedProps.fetchData.mock; - const pageChangeCall = calls.find( - call => - call[0].pageIndex === 1 && - call[0].filters.length === 0 && - call[0].pageSize === 1, - ); - expect(pageChangeCall).toBeDefined(); - }); - }); - - test('handles bulk actions on 1 row', async () => { - const checkboxes = screen.getAllByRole('checkbox'); - await userEvent.click(checkboxes[1]); // Index 1 is the first row checkbox - - const bulkActionButton = within( - screen.getByTestId('bulk-select-controls'), - ).getByTestId('bulk-select-action'); - await userEvent.click(bulkActionButton); - - expect(mockedProps.bulkActions[0].onSelect).toHaveBeenCalledWith([ - { - age: 10, - id: 1, - name: 'data 1', - time: '2020-11-18T07:53:45.354Z', - }, - ]); - }); - - // Update UI filters test to use more specific selector - test('renders UI filters', () => { - const filterControls = screen.getAllByRole('combobox'); - expect(filterControls).toHaveLength(2); - }); - - test('calls fetchData on filter', async () => { - // Handle select filter - const selectFilter = screen.getAllByRole('combobox')[0]; - await userEvent.click(selectFilter); - const option = screen.getByText('foo'); - await userEvent.click(option); - - // Handle search filter - const searchFilter = screen.getByPlaceholderText('Type a value'); - await userEvent.type(searchFilter, 'something'); - await userEvent.tab(); - - expect(mockedProps.fetchData).toHaveBeenCalledWith( - expect.objectContaining({ - filters: [ - { - id: 'id', - operator: 'eq', - value: { label: 'foo', value: 'bar' }, - }, - { - id: 'name', - operator: 'ct', - value: 'something', - }, - ], - }), - ); - }); - - test('calls fetchData on card view sort', async () => { - factory({ - ...mockedProps, - renderCard: jest.fn(), - initialSort: [{ id: 'something' }], - }); - - const sortSelect = screen.getByTestId('card-sort-select'); - await userEvent.click(sortSelect); - - const sortOption = screen.getByText('Alphabetical'); - await userEvent.click(sortOption); - - expect(mockedProps.fetchData).toHaveBeenCalled(); - }); -}); diff --git a/superset-frontend/src/components/ListView/ListView.test.tsx b/superset-frontend/src/components/ListView/ListView.test.tsx index f40dc229fbce..7cc8f4e9d972 100644 --- a/superset-frontend/src/components/ListView/ListView.test.tsx +++ b/superset-frontend/src/components/ListView/ListView.test.tsx @@ -16,10 +16,118 @@ * specific language governing permissions and limitations * under the License. */ -import { render, waitFor } from 'spec/helpers/testing-library'; +import { render, screen, within, waitFor } from 'spec/helpers/testing-library'; +import userEvent from '@testing-library/user-event'; +import { QueryParamProvider } from 'use-query-params'; +import thunk from 'redux-thunk'; +import configureStore from 'redux-mock-store'; +import fetchMock from 'fetch-mock'; import { ListView } from './ListView'; -const mockedProps = { +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); + +function makeMockLocation(query?: string) { + const queryStr = query ? encodeURIComponent(query) : ''; + return { + protocol: 'http:', + host: 'localhost', + pathname: '/', + search: queryStr.length ? `?${queryStr}` : '', + } as Location; +} + +const fetchSelectsMock = jest.fn(() => []); +const mockedPropsComprehensive = { + title: 'Data Table', + columns: [ + { + accessor: 'id', + Header: 'ID', + sortable: true, + id: 'id', + }, + { + accessor: 'age', + Header: 'Age', + id: 'age', + }, + { + accessor: 'name', + Header: 'Name', + id: 'name', + }, + { + accessor: 'time', + Header: 'Time', + id: 'time', + }, + ], + filters: [ + { + key: 'id', + Header: 'ID', + id: 'id', + input: 'select', + selects: [{ label: 'foo', value: 'bar' }], + operator: 'eq', + }, + { + key: 'name', + Header: 'Name', + id: 'name', + input: 'search', + operator: 'ct', + }, + { + key: 'age', + Header: 'Age', + id: 'age', + input: 'select', + fetchSelects: fetchSelectsMock, + paginate: true, + operator: 'eq', + }, + { + key: 'time', + Header: 'Time', + id: 'time', + input: 'datetime_range', + operator: 'between', + }, + ], + data: [ + { id: 1, name: 'data 1', age: 10, time: '2020-11-18T07:53:45.354Z' }, + { id: 2, name: 'data 2', age: 1, time: '2020-11-18T07:53:45.354Z' }, + ], + count: 2, + pageSize: 1, + fetchData: jest.fn(() => []), + loading: false, + refreshData: jest.fn(), + addSuccessToast: jest.fn(), + addDangerToast: jest.fn(), + bulkSelectEnabled: true, + disableBulkSelect: jest.fn(), + bulkActions: [ + { + key: 'something', + name: 'do something', + style: 'danger', + onSelect: jest.fn(), + }, + ], + cardSortSelectOptions: [ + { + desc: false, + id: 'something', + label: 'Alphabetical', + value: 'alphabetical', + }, + ], +}; + +const mockedPropsSimple = { title: 'Data Table', columns: [ { @@ -59,7 +167,7 @@ const mockedProps = { test('redirects to first page when page index is invalid', async () => { const fetchData = jest.fn(); window.history.pushState({}, '', '/?pageIndex=9'); - render(, { + render(, { useRouter: true, useQueryParams: true, }); @@ -75,3 +183,151 @@ test('redirects to first page when page index is invalid', async () => { }); fetchData.mockClear(); }); + +// Comprehensive test suite from original JSX file +const factory = (props = mockedPropsComprehensive) => + render( + + + , + { store: mockStore() }, + ); + +// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks +describe('ListView', () => { + beforeEach(() => { + fetchMock.reset(); + jest.clearAllMocks(); + factory(); + }); + + afterEach(() => { + fetchMock.reset(); + mockedPropsComprehensive.fetchData.mockClear(); + mockedPropsComprehensive.bulkActions.forEach(ba => { + ba.onSelect.mockClear(); + }); + }); + + test('calls fetchData on mount', () => { + expect(mockedPropsComprehensive.fetchData).toHaveBeenCalledWith({ + filters: [], + pageIndex: 0, + pageSize: 1, + sortBy: [], + }); + }); + + test('calls fetchData on sort', async () => { + const sortHeader = screen.getAllByTestId('sort-header')[1]; + await userEvent.click(sortHeader); + + expect(mockedPropsComprehensive.fetchData).toHaveBeenCalledWith({ + filters: [], + pageIndex: 0, + pageSize: 1, + sortBy: [ + { + desc: false, + id: 'id', + }, + ], + }); + }); + + test('renders pagination controls', () => { + const paginationList = screen.getByRole('list'); + expect(paginationList).toBeInTheDocument(); + + const pageOneItem = screen.getByRole('listitem', { name: '1' }); + expect(pageOneItem).toBeInTheDocument(); + }); + + test('calls fetchData on page change', async () => { + const pageTwoItem = screen.getByRole('listitem', { name: '2' }); + await userEvent.click(pageTwoItem); + + await waitFor(() => { + const { calls } = mockedPropsComprehensive.fetchData.mock; + const pageChangeCall = calls.find( + (call: any) => + call?.[0]?.pageIndex === 1 && + call?.[0]?.filters?.length === 0 && + call?.[0]?.pageSize === 1, + ); + expect(pageChangeCall).toBeDefined(); + }); + }); + + test('handles bulk actions on 1 row', async () => { + const checkboxes = screen.getAllByRole('checkbox'); + await userEvent.click(checkboxes[1]); // Index 1 is the first row checkbox + + const bulkActionButton = within( + screen.getByTestId('bulk-select-controls'), + ).getByTestId('bulk-select-action'); + await userEvent.click(bulkActionButton); + + expect( + mockedPropsComprehensive.bulkActions[0].onSelect, + ).toHaveBeenCalledWith([ + { + age: 10, + id: 1, + name: 'data 1', + time: '2020-11-18T07:53:45.354Z', + }, + ]); + }); + + test('renders UI filters', () => { + const filterControls = screen.getAllByRole('combobox'); + expect(filterControls).toHaveLength(2); + }); + + test('calls fetchData on filter', async () => { + // Handle select filter + const selectFilter = screen.getAllByRole('combobox')[0]; + await userEvent.click(selectFilter); + const option = screen.getByText('foo'); + await userEvent.click(option); + + // Handle search filter + const searchFilter = screen.getByPlaceholderText('Type a value'); + await userEvent.type(searchFilter, 'something'); + await userEvent.tab(); + + expect(mockedPropsComprehensive.fetchData).toHaveBeenCalledWith( + expect.objectContaining({ + filters: [ + { + id: 'id', + operator: 'eq', + value: { label: 'foo', value: 'bar' }, + }, + { + id: 'name', + operator: 'ct', + value: 'something', + }, + ], + }), + ); + }); + + test('calls fetchData on card view sort', async () => { + factory({ + ...mockedPropsComprehensive, + renderCard: jest.fn(), + initialSort: [{ id: 'something' }], + } as any); + + const sortSelect = screen.getByTestId('card-sort-select'); + await userEvent.click(sortSelect); + + const sortOption = screen.getByText('Alphabetical'); + await userEvent.click(sortOption); + + expect(mockedPropsComprehensive.fetchData).toHaveBeenCalled(); + }); +}); diff --git a/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.jsx b/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.jsx deleted file mode 100644 index 8f6b520cab8d..000000000000 --- a/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.jsx +++ /dev/null @@ -1,170 +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 configureStore from 'redux-mock-store'; -import { DatasourceType } from '@superset-ui/core'; -import { - fireEvent, - render, - screen, - userEvent, - waitFor, -} from 'spec/helpers/testing-library'; -import DatasourceControl, { - getDatasourceTitle, -} from 'src/explore/components/controls/DatasourceControl'; - -const defaultProps = { - name: 'datasource', - label: 'Dataset', - value: '1__table', - datasource: { - name: 'birth_names', - type: 'table', - uid: '1__table', - id: 1, - columns: [], - metrics: [], - owners: [{ username: 'admin', userId: 1 }], - database: { - backend: 'mysql', - name: 'main', - }, - health_check_message: 'Warning message!', - }, - actions: { - setDatasource: jest.fn(), - }, - onChange: jest.fn(), - user: { - createdOn: '2021-04-27T18:12:38.952304', - email: 'admin', - firstName: 'admin', - isActive: true, - lastName: 'admin', - permissions: {}, - roles: { Admin: Array(173) }, - userId: 1, - username: 'admin', - }, -}; - -// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks -describe('DatasourceControl', () => { - const setup = (overrideProps = {}) => { - const mockStore = configureStore([]); - const store = mockStore({}); - const props = { - ...defaultProps, - ...overrideProps, - }; - return { - rendered: render(, { - useRedux: true, - useRouter: true, - store, - }), - store, - props, - }; - }; - - test('should not render Modal', () => { - setup(); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - }); - - test('should not render ChangeDatasourceModal', () => { - setup(); - expect(screen.queryByTestId('Swap dataset-modal')).not.toBeInTheDocument(); - }); - - test('show or hide Edit Datasource option', async () => { - const { - rendered: { container, rerender }, - store, - props, - } = setup(); - expect( - container.querySelector('[data-test="datasource-menu-trigger"]'), - ).toBeInTheDocument(); - userEvent.click(screen.getByLabelText('more')); - await waitFor(() => { - expect(screen.queryAllByRole('menuitem')).toHaveLength(3); - }); - - // Close the menu - userEvent.click(document.body); - await waitFor(() => { - expect(screen.queryAllByRole('menuitem')).toHaveLength(0); - }); - - rerender(, { - useRedux: true, - useRouter: true, - store, - }); - expect( - container.querySelector('[data-test="datasource-menu-trigger"]'), - ).toBeInTheDocument(); - userEvent.click(screen.getByLabelText('more')); - await waitFor(() => { - expect(screen.queryAllByRole('menuitem')).toHaveLength(2); - }); - }); - - test('should render health check message', async () => { - setup(); - const modalTrigger = screen.getByLabelText('warning'); - expect(modalTrigger).toBeInTheDocument(); - - // Hover the modal so healthcheck message can show up - fireEvent.mouseOver(modalTrigger); - await waitFor(() => { - expect( - screen.getByText(defaultProps.datasource.health_check_message), - ).toBeInTheDocument(); - }); - }); - - test('Gets Datasource Title', () => { - const sql = 'This is the sql'; - const name = 'this is a name'; - const emptyResult = ''; - const queryDatasource1 = { type: DatasourceType.Query, sql }; - let displayText = getDatasourceTitle(queryDatasource1); - expect(displayText).toBe(sql); - const queryDatasource2 = { type: DatasourceType.Query, sql: null }; - displayText = getDatasourceTitle(queryDatasource2); - expect(displayText).toBe(null); - const queryDatasource3 = { type: 'random type', name }; - displayText = getDatasourceTitle(queryDatasource3); - expect(displayText).toBe(name); - const queryDatasource4 = { type: 'random type' }; - displayText = getDatasourceTitle(queryDatasource4); - expect(displayText).toBe(emptyResult); - displayText = getDatasourceTitle(); - expect(displayText).toBe(emptyResult); - displayText = getDatasourceTitle(null); - expect(displayText).toBe(emptyResult); - displayText = getDatasourceTitle('I should not be a string'); - expect(displayText).toBe(emptyResult); - displayText = getDatasourceTitle([]); - expect(displayText).toBe(emptyResult); - }); -}); diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.jsx b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.jsx deleted file mode 100644 index 1b4d43eb9c75..000000000000 --- a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.jsx +++ /dev/null @@ -1,131 +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 sinon from 'sinon'; -import { getChartMetadataRegistry, ChartMetadata } from '@superset-ui/core'; -import { - act, - cleanup, - render, - screen, - userEvent, -} from 'spec/helpers/testing-library'; -import VizTypeControl from 'src/explore/components/controls/VizTypeControl'; -import { DynamicPluginProvider } from 'src/components'; - -const defaultProps = { - name: 'viz_type', - label: 'Visualization Type', - value: 'vis1', - onChange: sinon.spy(), - isModalOpenInit: true, -}; - -/** - * AntD and/or the Icon component seems to be doing some kind of async changes, - * so even though the test passes, there is a warning an update to Icon was not - * wrapped in act(). This sufficiently act-ifies whatever side effects are going - * on and prevents those warnings. - */ -const waitForEffects = () => - act(() => new Promise(resolve => setTimeout(resolve, 0))); - -// Increase global timeout -jest.setTimeout(60000); - -// Add cleanup after each test -afterEach(async () => { - cleanup(); - // Wait for any pending effects to complete - await new Promise(resolve => setTimeout(resolve, 0)); -}); - -// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks -describe('VizTypeControl', () => { - const registry = getChartMetadataRegistry(); - registry - .registerValue( - 'vis1', - new ChartMetadata({ - name: 'vis1', - thumbnail: '', - tags: ['Featured'], - }), - ) - .registerValue( - 'vis2', - new ChartMetadata({ - name: 'vis2', - thumbnail: '', - tags: ['foobar'], - }), - ); - - beforeEach(async () => { - render( - - - , - { useRedux: true }, - ); - await waitForEffects(); - }, 30000); // Increase beforeEach timeout - - test('calls onChange when submitted', async () => { - const thumbnail = await screen.findByTestId('viztype-selector-container', { - timeout: 15000, - }); - const submit = await screen.findByText('Select', { timeout: 15000 }); - - await act(async () => { - userEvent.click(thumbnail); - await waitForEffects(); - }); - - expect(defaultProps.onChange.called).toBe(false); - - await act(async () => { - userEvent.click(submit); - await waitForEffects(); - }); - - expect(defaultProps.onChange.called).toBe(true); - }, 30000); - - test('filters images based on text input', async () => { - const thumbnails = await screen.findByTestId('viztype-selector-container', { - timeout: 15000, - }); - expect(thumbnails).toBeInTheDocument(); - - const searchInput = await screen.findByPlaceholderText( - 'Search all charts', - { timeout: 15000 }, - ); - - await act(async () => { - userEvent.type(searchInput, 'foo'); - await waitForEffects(); - }); - - const thumbnail = await screen.findByTestId('viztype-selector-container', { - timeout: 15000, - }); - expect(thumbnail).toBeInTheDocument(); - }, 30000); -}); From 76f1878b637733b20adce1cce8f473d9fb652a09 Mon Sep 17 00:00:00 2001 From: Joe Li Date: Fri, 10 Oct 2025 09:12:46 -0700 Subject: [PATCH 2/2] test: eliminate any types from ListView.test.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added MockedListViewProps type for proper Jest mock typing - Replaced string literal operators with ListViewFilterOperator enum values - Fixed fetchSelectsMock to return proper Promise structure - Properly typed fetchData mock with jest.fn<> generic parameters - Changed bulk action 'style' property to 'type' (correct interface) - Removed 'title' property (not in ListViewProps interface) - Created factory function with explicit overrides pattern All 9 ListView tests pass with full TypeScript type safety. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../src/components/ListView/ListView.test.tsx | 62 ++++++++++++++----- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/superset-frontend/src/components/ListView/ListView.test.tsx b/superset-frontend/src/components/ListView/ListView.test.tsx index 7cc8f4e9d972..9d55ec1ad847 100644 --- a/superset-frontend/src/components/ListView/ListView.test.tsx +++ b/superset-frontend/src/components/ListView/ListView.test.tsx @@ -22,7 +22,32 @@ import { QueryParamProvider } from 'use-query-params'; import thunk from 'redux-thunk'; import configureStore from 'redux-mock-store'; import fetchMock from 'fetch-mock'; -import { ListView } from './ListView'; +import { ReactNode } from 'react'; +import { ListView, type ListViewProps } from './ListView'; +import { ListViewFilterOperator, type ListViewFetchDataConfig } from './types'; + +// Test-specific type that properly represents mocked props +type MockedListViewProps = Omit< + ListViewProps, + | 'fetchData' + | 'refreshData' + | 'addSuccessToast' + | 'addDangerToast' + | 'disableBulkSelect' + | 'bulkActions' +> & { + fetchData: jest.Mock; + refreshData: jest.Mock; + addSuccessToast: jest.Mock; + addDangerToast: jest.Mock; + disableBulkSelect: jest.Mock; + bulkActions: Array<{ + key: string; + name: ReactNode; + onSelect: jest.Mock; + type?: 'primary' | 'secondary' | 'danger'; + }>; +}; const middlewares = [thunk]; const mockStore = configureStore(middlewares); @@ -37,9 +62,12 @@ function makeMockLocation(query?: string) { } as Location; } -const fetchSelectsMock = jest.fn(() => []); -const mockedPropsComprehensive = { - title: 'Data Table', +const fetchSelectsMock = jest.fn(() => + Promise.resolve({ data: [], totalCount: 0 }), +); + +// Create a properly typed mock with all required fields and Jest mock types +const mockedPropsComprehensive: MockedListViewProps = { columns: [ { accessor: 'id', @@ -70,14 +98,14 @@ const mockedPropsComprehensive = { id: 'id', input: 'select', selects: [{ label: 'foo', value: 'bar' }], - operator: 'eq', + operator: ListViewFilterOperator.Equals, }, { key: 'name', Header: 'Name', id: 'name', input: 'search', - operator: 'ct', + operator: ListViewFilterOperator.Contains, }, { key: 'age', @@ -86,14 +114,14 @@ const mockedPropsComprehensive = { input: 'select', fetchSelects: fetchSelectsMock, paginate: true, - operator: 'eq', + operator: ListViewFilterOperator.Equals, }, { key: 'time', Header: 'Time', id: 'time', input: 'datetime_range', - operator: 'between', + operator: ListViewFilterOperator.Between, }, ], data: [ @@ -102,7 +130,7 @@ const mockedPropsComprehensive = { ], count: 2, pageSize: 1, - fetchData: jest.fn(() => []), + fetchData: jest.fn(() => []), loading: false, refreshData: jest.fn(), addSuccessToast: jest.fn(), @@ -113,7 +141,7 @@ const mockedPropsComprehensive = { { key: 'something', name: 'do something', - style: 'danger', + type: 'danger', onSelect: jest.fn(), }, ], @@ -128,7 +156,6 @@ const mockedPropsComprehensive = { }; const mockedPropsSimple = { - title: 'Data Table', columns: [ { accessor: 'id', @@ -185,13 +212,15 @@ test('redirects to first page when page index is invalid', async () => { }); // Comprehensive test suite from original JSX file -const factory = (props = mockedPropsComprehensive) => - render( +const factory = (overrides?: Partial) => { + const props = { ...mockedPropsComprehensive, ...overrides }; + return render( - + , { store: mockStore() }, ); +}; // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks describe('ListView', () => { @@ -250,7 +279,7 @@ describe('ListView', () => { await waitFor(() => { const { calls } = mockedPropsComprehensive.fetchData.mock; const pageChangeCall = calls.find( - (call: any) => + (call: [ListViewFetchDataConfig]) => call?.[0]?.pageIndex === 1 && call?.[0]?.filters?.length === 0 && call?.[0]?.pageSize === 1, @@ -317,10 +346,9 @@ describe('ListView', () => { test('calls fetchData on card view sort', async () => { factory({ - ...mockedPropsComprehensive, renderCard: jest.fn(), initialSort: [{ id: 'something' }], - } as any); + }); const sortSelect = screen.getByTestId('card-sort-select'); await userEvent.click(sortSelect);