diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx index 17385970c5fd..6db7f63edb96 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx @@ -184,9 +184,6 @@ afterEach(() => { jest.restoreAllMocks(); }); -// Set timeout for all tests in this file to prevent CI timeouts -jest.setTimeout(60000); - function defaultRender(initialState: any = defaultState(), modalProps = props) { return render(, { initialState, @@ -226,9 +223,10 @@ test('renders a value filter type', () => { test('renders a numerical range filter type', async () => { defaultRender(); - userEvent.click(screen.getByText(VALUE_REGEX)); + await userEvent.click(screen.getByText(VALUE_REGEX)); - await waitFor(() => userEvent.click(screen.getByText(NUMERICAL_RANGE_REGEX))); + const numericalRangeOption = await screen.findByText(NUMERICAL_RANGE_REGEX); + await userEvent.click(numericalRangeOption); expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument(); expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument(); @@ -250,9 +248,10 @@ test('renders a numerical range filter type', async () => { test('renders a time range filter type', async () => { defaultRender(); - userEvent.click(screen.getByText(VALUE_REGEX)); + await userEvent.click(screen.getByText(VALUE_REGEX)); - await waitFor(() => userEvent.click(screen.getByText(TIME_RANGE_REGEX))); + const timeRangeOption = await screen.findByText(TIME_RANGE_REGEX); + await userEvent.click(timeRangeOption); expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument(); expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument(); @@ -265,9 +264,10 @@ test('renders a time range filter type', async () => { test('renders a time column filter type', async () => { defaultRender(); - userEvent.click(screen.getByText(VALUE_REGEX)); + await userEvent.click(screen.getByText(VALUE_REGEX)); - await waitFor(() => userEvent.click(screen.getByText(TIME_COLUMN_REGEX))); + const timeColumnOption = await screen.findByText(TIME_COLUMN_REGEX); + await userEvent.click(timeColumnOption); expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument(); expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument(); @@ -280,9 +280,10 @@ test('renders a time column filter type', async () => { test('renders a time grain filter type', async () => { defaultRender(); - userEvent.click(screen.getByText(VALUE_REGEX)); + await userEvent.click(screen.getByText(VALUE_REGEX)); - await waitFor(() => userEvent.click(screen.getByText(TIME_GRAIN_REGEX))); + const timeGrainOption = await screen.findByText(TIME_GRAIN_REGEX); + await userEvent.click(timeGrainOption); expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument(); expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument(); @@ -295,7 +296,7 @@ test('renders a time grain filter type', async () => { test('render time filter types as disabled if there are no temporal columns in the dataset', async () => { defaultRender(noTemporalColumnsState()); - userEvent.click(screen.getByText(VALUE_REGEX)); + await userEvent.click(screen.getByText(VALUE_REGEX)); const timeRange = await screen.findByText(TIME_RANGE_REGEX); const timeGrain = await screen.findByText(TIME_GRAIN_REGEX); @@ -309,7 +310,7 @@ test('render time filter types as disabled if there are no temporal columns in t test('validates the name', async () => { defaultRender(); - userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); + await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); await waitFor( async () => { expect(await screen.findByText(NAME_REQUIRED_REGEX)).toBeInTheDocument(); @@ -320,7 +321,7 @@ test('validates the name', async () => { test('validates the column', async () => { defaultRender(); - userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); + await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); expect(await screen.findByText(COLUMN_REQUIRED_REGEX)).toBeInTheDocument(); }); @@ -328,8 +329,8 @@ test('validates the column', async () => { test.skip('validates the default value', async () => { defaultRender(noTemporalColumnsState()); expect(await screen.findByText('birth_names')).toBeInTheDocument(); - userEvent.type(screen.getByRole('combobox'), `Column A{Enter}`); - userEvent.click(getCheckbox(DEFAULT_VALUE_REGEX)); + await userEvent.type(screen.getByRole('combobox'), `Column A{Enter}`); + await userEvent.click(getCheckbox(DEFAULT_VALUE_REGEX)); await waitFor(() => { expect( screen.queryByText(FILL_REQUIRED_FIELDS_REGEX), @@ -345,21 +346,24 @@ test('validates the pre-filter value', async () => { try { defaultRender(); - userEvent.click(screen.getByText(FILTER_SETTINGS_REGEX)); - userEvent.click(getCheckbox(PRE_FILTER_REGEX)); + await userEvent.click(screen.getByText(FILTER_SETTINGS_REGEX)); + await userEvent.click(getCheckbox(PRE_FILTER_REGEX)); jest.runAllTimers(); + + await waitFor(() => { + const errorMessages = screen.getAllByText(PRE_FILTER_REQUIRED_REGEX); + expect(errorMessages.length).toBeGreaterThan(0); + }); } finally { jest.useRealTimers(); } - jest.runOnlyPendingTimers(); - jest.useRealTimers(); - // Wait for validation to complete after timer switch await waitFor( () => { - expect(screen.getByText(PRE_FILTER_REQUIRED_REGEX)).toBeInTheDocument(); + const errorMessages = screen.queryAllByText(PRE_FILTER_REQUIRED_REGEX); + expect(errorMessages.length).toBeGreaterThan(0); }, { timeout: 15000 }, ); @@ -368,13 +372,13 @@ test('validates the pre-filter value', async () => { // eslint-disable-next-line jest/no-disabled-tests test.skip("doesn't render time range pre-filter if there are no temporal columns in datasource", async () => { defaultRender(noTemporalColumnsState()); - userEvent.click(screen.getByText(DATASET_REGEX)); - await waitFor(() => { + await userEvent.click(screen.getByText(DATASET_REGEX)); + await waitFor(async () => { expect(screen.queryByLabelText('Loading')).not.toBeInTheDocument(); - userEvent.click(screen.getByText('birth_names')); + await userEvent.click(screen.getByText('birth_names')); }); - userEvent.click(screen.getByText(FILTER_SETTINGS_REGEX)); - userEvent.click(getCheckbox(PRE_FILTER_REGEX)); + await userEvent.click(screen.getByText(FILTER_SETTINGS_REGEX)); + await userEvent.click(getCheckbox(PRE_FILTER_REGEX)); await waitFor(() => expect( screen.queryByText(TIME_RANGE_PREFILTER_REGEX), @@ -439,9 +443,9 @@ test('deletes a filter', async () => { const removeButtons = screen.getAllByRole('button', { name: 'delete', }); - userEvent.click(removeButtons[2]); + await userEvent.click(removeButtons[2]); - userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); + await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); await waitFor(() => expect(onSave).toHaveBeenCalledWith( @@ -476,8 +480,8 @@ test('deletes a filter including dependencies', async () => { const removeButtons = screen.getAllByRole('button', { name: 'delete', }); - userEvent.click(removeButtons[1]); - userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); + await userEvent.click(removeButtons[1]); + await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); await waitFor(() => expect(onSave).toHaveBeenCalledWith( expect.objectContaining({ @@ -525,7 +529,7 @@ test('switches the order between two filters', async () => { fireEvent.dragEnd(draggableFilters[0]); - userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); + await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); await waitFor(() => expect(onSave).toHaveBeenCalledWith( @@ -568,14 +572,14 @@ test('rearranges three filters and deletes one of them', async () => { const deleteButtons = screen.getAllByRole('button', { name: 'delete', }); - userEvent.click(deleteButtons[1]); + await userEvent.click(deleteButtons[1]); fireEvent.dragStart(draggableFilters[0]); fireEvent.dragOver(draggableFilters[2]); fireEvent.drop(draggableFilters[2]); fireEvent.dragEnd(draggableFilters[0]); - userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); + await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); await waitFor(() => expect(onSave).toHaveBeenCalledWith( @@ -594,47 +598,51 @@ test('rearranges three filters and deletes one of them', async () => { test('modifies the name of a filter', async () => { jest.useFakeTimers(); - const nativeFilterState = [ - buildNativeFilter('NATIVE_FILTER-1', 'state', []), - buildNativeFilter('NATIVE_FILTER-2', 'country', []), - ]; - - const state = { - ...defaultState(), - dashboardInfo: { - metadata: { native_filter_configuration: nativeFilterState }, - }, - dashboardLayout, - }; + try { + const nativeFilterState = [ + buildNativeFilter('NATIVE_FILTER-1', 'state', []), + buildNativeFilter('NATIVE_FILTER-2', 'country', []), + ]; + + const state = { + ...defaultState(), + dashboardInfo: { + metadata: { native_filter_configuration: nativeFilterState }, + }, + dashboardLayout, + }; - const onSave = jest.fn(); + const onSave = jest.fn(); - defaultRender(state, { - ...props, - createNewOnOpen: false, - onSave, - }); - - const filterNameInput = screen.getByRole('textbox', { - name: FILTER_NAME_REGEX, - }); + defaultRender(state, { + ...props, + createNewOnOpen: false, + onSave, + }); - userEvent.clear(filterNameInput); - userEvent.type(filterNameInput, 'New Filter Name'); + const filterNameInput = screen.getByRole('textbox', { + name: FILTER_NAME_REGEX, + }); - jest.runAllTimers(); + await userEvent.clear(filterNameInput); + await userEvent.type(filterNameInput, 'New Filter Name'); - userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); + jest.runAllTimers(); - await waitFor(() => - expect(onSave).toHaveBeenCalledWith( - expect.objectContaining({ - modified: expect.arrayContaining([ - expect.objectContaining({ name: 'New Filter Name' }), - ]), - }), - ), - ); + await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX })); + + await waitFor(() => + expect(onSave).toHaveBeenCalledWith( + expect.objectContaining({ + modified: expect.arrayContaining([ + expect.objectContaining({ name: 'New Filter Name' }), + ]), + }), + ), + ); + } finally { + jest.useRealTimers(); + } }); test('renders a filter with a chart containing BigInt values', async () => { diff --git a/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx b/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx index 8ee41ea4909d..9abc906c3bfa 100644 --- a/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx +++ b/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx @@ -153,12 +153,6 @@ test('Should render null when show:false', async () => { }); }); -// Add cleanup after each test -afterEach(async () => { - // Wait for any pending effects to complete - await new Promise(resolve => setTimeout(resolve, 0)); -}); - test('Should render when show:true', async () => { const props = createProps(); renderModal(props); @@ -193,7 +187,7 @@ test('"Close" button should call "onHide"', async () => { expect(props.onHide).toHaveBeenCalledTimes(0); }); - userEvent.click(screen.getByRole('button', { name: 'Close' })); + await userEvent.click(screen.getByRole('button', { name: 'Close' })); await waitFor(() => { expect(props.onHide).toHaveBeenCalledTimes(1); @@ -254,7 +248,7 @@ test('"Cancel" button should call "onHide"', async () => { expect(props.onHide).toHaveBeenCalledTimes(0); }); - userEvent.click(screen.getByRole('button', { name: 'Cancel' })); + await userEvent.click(screen.getByRole('button', { name: 'Cancel' })); await waitFor(() => { expect(props.onHide).toHaveBeenCalledTimes(1); @@ -262,7 +256,7 @@ test('"Cancel" button should call "onHide"', async () => { }); }); -test('"Save" button should call only "onSave"', async () => { +test('"Save" button should call "onSave" and "onHide"', async () => { const props = createProps(); renderModal(props); await waitFor(() => { @@ -271,7 +265,7 @@ test('"Save" button should call only "onSave"', async () => { expect(screen.getByRole('button', { name: 'Save' })).toBeEnabled(); }); - userEvent.click(screen.getByRole('button', { name: 'Save' })); + await userEvent.click(screen.getByRole('button', { name: 'Save' })); await waitFor(() => { expect(props.onSave).toHaveBeenCalledTimes(1); @@ -293,7 +287,7 @@ test('Empty "Certified by" should clear "Certification details"', async () => { // Expand the Advanced section first to access certification details const advancedPanel = screen.getByText('Advanced').closest('[role="tab"]'); if (advancedPanel) { - userEvent.click(advancedPanel); + await userEvent.click(advancedPanel); } await waitFor(() => { @@ -312,11 +306,11 @@ test('"Name" should not be empty', async () => { const name = screen.getByRole('textbox', { name: 'Name' }); - userEvent.clear(name); + await userEvent.clear(name); expect(name).toHaveValue(''); - userEvent.click(screen.getByRole('button', { name: 'Save' })); + await userEvent.click(screen.getByRole('button', { name: 'Save' })); await waitFor(() => { expect(props.onSave).toHaveBeenCalledTimes(0); @@ -329,12 +323,12 @@ test('"Name" should not be empty when saved', async () => { const name = screen.getByRole('textbox', { name: 'Name' }); - userEvent.clear(name); - userEvent.type(name, 'Test chart new name'); + await userEvent.clear(name); + await userEvent.type(name, 'Test chart new name'); expect(name).toHaveValue('Test chart new name'); - userEvent.click(screen.getByRole('button', { name: 'Save' })); + await userEvent.click(screen.getByRole('button', { name: 'Save' })); await waitFor(() => { expect(props.onSave).toHaveBeenCalledTimes(1); @@ -351,19 +345,17 @@ test('"Cache timeout" should not be empty when saved', async () => { // Expand the Configuration section first to access cache timeout const configPanel = screen.getByText('Configuration').closest('[role="tab"]'); if (configPanel) { - userEvent.click(configPanel); + await userEvent.click(configPanel); } - await waitFor(() => { - const cacheTimeout = screen.getByRole('textbox', { name: 'Cache timeout' }); - - userEvent.clear(cacheTimeout); - userEvent.type(cacheTimeout, '1000'); - - expect(cacheTimeout).toHaveValue('1000'); + const cacheTimeout = await screen.findByRole('textbox', { + name: 'Cache timeout', }); + await userEvent.clear(cacheTimeout); + await userEvent.type(cacheTimeout, '1000'); + expect(cacheTimeout).toHaveValue('1000'); - userEvent.click(screen.getByRole('button', { name: 'Save' })); + await userEvent.click(screen.getByRole('button', { name: 'Save' })); await waitFor(() => { expect(props.onSave).toHaveBeenCalledTimes(1); @@ -386,12 +378,12 @@ test('"Description" should not be empty when saved', async () => { const textboxes = screen.getAllByRole('textbox'); const description = textboxes[1]; // Description is the textarea - userEvent.clear(description); - userEvent.type(description, 'Test description'); + await userEvent.clear(description); + await userEvent.type(description, 'Test description'); expect(description).toHaveValue('Test description'); - userEvent.click(screen.getByRole('button', { name: 'Save' })); + await userEvent.click(screen.getByRole('button', { name: 'Save' })); await waitFor(() => { expect(props.onSave).toHaveBeenCalledTimes(1); @@ -408,19 +400,17 @@ test('"Certified by" should not be empty when saved', async () => { // Expand the Advanced section first to access certified by const advancedPanel = screen.getByText('Advanced').closest('[role="tab"]'); if (advancedPanel) { - userEvent.click(advancedPanel); + await userEvent.click(advancedPanel); } - await waitFor(() => { - const certifiedBy = screen.getByRole('textbox', { name: 'Certified by' }); - - userEvent.clear(certifiedBy); - userEvent.type(certifiedBy, 'Test certified by'); - - expect(certifiedBy).toHaveValue('Test certified by'); + const certifiedBy = await screen.findByRole('textbox', { + name: 'Certified by', }); + await userEvent.clear(certifiedBy); + await userEvent.type(certifiedBy, 'Test certified by'); + expect(certifiedBy).toHaveValue('Test certified by'); - userEvent.click(screen.getByRole('button', { name: 'Save' })); + await userEvent.click(screen.getByRole('button', { name: 'Save' })); await waitFor(() => { expect(props.onSave).toHaveBeenCalledTimes(1); @@ -437,21 +427,17 @@ test('"Certification details" should not be empty when saved', async () => { // Expand the Advanced section first to access certification details const advancedPanel = screen.getByText('Advanced').closest('[role="tab"]'); if (advancedPanel) { - userEvent.click(advancedPanel); + await userEvent.click(advancedPanel); } - await waitFor(() => { - const certificationDetails = screen.getByRole('textbox', { - name: 'Certification details', - }); - - userEvent.clear(certificationDetails); - userEvent.type(certificationDetails, 'Test certification details'); - - expect(certificationDetails).toHaveValue('Test certification details'); + const certificationDetails = await screen.findByRole('textbox', { + name: 'Certification details', }); + await userEvent.clear(certificationDetails); + await userEvent.type(certificationDetails, 'Test certification details'); + expect(certificationDetails).toHaveValue('Test certification details'); - userEvent.click(screen.getByRole('button', { name: 'Save' })); + await userEvent.click(screen.getByRole('button', { name: 'Save' })); await waitFor(() => { expect(props.onSave).toHaveBeenCalledTimes(1); @@ -474,18 +460,14 @@ test('Should display only custom tags when tagging system is enabled', async () const props = createProps(); renderModal(props); - await waitFor(async () => { - expect(await screen.findByText('Tags')).toBeInTheDocument(); - expect( - await screen.findByRole('combobox', { name: 'Tags' }), - ).toBeInTheDocument(); - }); + expect(await screen.findByText('Tags')).toBeInTheDocument(); + expect( + await screen.findByRole('combobox', { name: 'Tags' }), + ).toBeInTheDocument(); - await waitFor(async () => { - expect(await screen.findByText('my test tag')).toBeInTheDocument(); - expect(screen.queryByText('type:chart')).not.toBeInTheDocument(); - expect(screen.queryByText('owner:1')).not.toBeInTheDocument(); - }); + expect(await screen.findByText('my test tag')).toBeInTheDocument(); + expect(screen.queryByText('type:chart')).not.toBeInTheDocument(); + expect(screen.queryByText('owner:1')).not.toBeInTheDocument(); mockIsFeatureEnabled.mockRestore(); }); diff --git a/superset-frontend/src/pages/ChartList/ChartList.listview.test.tsx b/superset-frontend/src/pages/ChartList/ChartList.listview.test.tsx index 65264947b16c..84e1c4e374f8 100644 --- a/superset-frontend/src/pages/ChartList/ChartList.listview.test.tsx +++ b/superset-frontend/src/pages/ChartList/ChartList.listview.test.tsx @@ -17,12 +17,8 @@ * under the License. */ import fetchMock from 'fetch-mock'; -import { - screen, - waitFor, - fireEvent, - within, -} from 'spec/helpers/testing-library'; +import { screen, waitFor, within } from 'spec/helpers/testing-library'; +import userEvent from '@testing-library/user-event'; import { isFeatureEnabled } from '@superset-ui/core'; import { mockCharts, @@ -64,903 +60,884 @@ const mockUser = { }, }; -// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks -describe('ChartList - List View Tests', () => { - beforeEach(() => { - mockHandleResourceExport.mockClear(); - setupMocks(); - }); - - afterEach(() => { - fetchMock.restore(); - }); - - test('renders ChartList in list view', async () => { - renderChartList(mockUser); - - // Wait for component to load - await waitFor(() => { - expect(screen.getByTestId('chart-list-view')).toBeInTheDocument(); - }); - - // Wait for table to be rendered - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); - - // Verify cards are not rendered in list view - await waitFor(() => { - expect(screen.queryByTestId('styled-card')).not.toBeInTheDocument(); - }); - }); - - test('correctly displays dataset names with and without schema', async () => { - // Create custom mock data with different datasource_name_text formats - const customMockCharts = [ - { - ...mockCharts[0], - id: 100, - slice_name: 'Chart with Schema', - datasource_name_text: 'public.test_dataset', - }, - { - ...mockCharts[0], - id: 101, - slice_name: 'Chart without Schema', - datasource_name_text: 'Jinja 5', // Virtual dataset without schema - }, - { - ...mockCharts[0], - id: 102, - slice_name: 'Chart with Dots in Name', - datasource_name_text: 'schema.table.with.dots', // Name contains dots - }, - ]; - - // Setup mock with custom charts - fetchMock.reset(); - setupMocks(); - fetchMock.get( - 'glob:*/api/v1/chart/?*', - { - result: customMockCharts, - chart_count: customMockCharts.length, - }, - { overwriteRoutes: true }, - ); +beforeEach(() => { + mockHandleResourceExport.mockClear(); + setupMocks(); +}); - renderChartList(mockUser); - - // Wait for table to be rendered - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); - - // Wait for data to load - await waitFor(() => { - expect(screen.getByText('Chart with Schema')).toBeInTheDocument(); - }); - - // Find the specific dataset links by their parent row context - const schemaRow = screen.getByText('Chart with Schema').closest('tr'); - const noSchemaRow = screen.getByText('Chart without Schema').closest('tr'); - const dotsRow = screen.getByText('Chart with Dots in Name').closest('tr'); - - // Check dataset name displays correctly for each case - // For chart with schema (public.test_dataset) - expect(schemaRow).toBeInTheDocument(); - const schemaLink = within(schemaRow!).getByRole('link', { - name: /test_dataset/i, - }); - expect(schemaLink).toBeInTheDocument(); - expect(schemaLink).toHaveTextContent('test_dataset'); - - // For chart without schema (Jinja 5) - expect(noSchemaRow).toBeInTheDocument(); - const noSchemaLink = within(noSchemaRow!).getByRole('link', { - name: /Jinja 5/i, - }); - expect(noSchemaLink).toBeInTheDocument(); - expect(noSchemaLink).toHaveTextContent('Jinja 5'); - - // For chart with dots in name (schema.table.with.dots) - expect(dotsRow).toBeInTheDocument(); - const dotsLink = within(dotsRow!).getByRole('link', { - name: /table\.with\.dots/i, - }); - expect(dotsLink).toBeInTheDocument(); - expect(dotsLink).toHaveTextContent('table.with.dots'); - }); - - test('switches from list view to card view', async () => { - renderChartList(mockUser); - - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); - - // Switch to card view - const cardViewToggle = screen.getByRole('img', { name: 'appstore' }); - fireEvent.click(cardViewToggle); - - // Verify table is no longer rendered - await waitFor(() => { - expect(screen.queryByTestId('listview-table')).not.toBeInTheDocument(); - }); - - // Verify cards are rendered - const cards = screen.getAllByTestId('styled-card'); - expect(cards).toHaveLength(mockCharts.length); - }); - - test('renders all required column headers', async () => { - renderChartList(mockUser); - - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); - - const table = screen.getByTestId('listview-table'); - const columnHeaders = table.querySelectorAll('[role="columnheader"]'); - - // All the table headers with default feature flags on - const expectedHeaders = [ - 'Name', - 'Type', - 'Dataset', - 'On dashboards', - 'Owners', - 'Last modified', - 'Actions', - ]; - - // Add one extra column header for favorite stars - expect(columnHeaders).toHaveLength(expectedHeaders.length + 1); - - // Verify all expected headers are present - expectedHeaders.forEach(headerText => { - expect(within(table).getByTitle(headerText)).toBeInTheDocument(); - }); - }); - - test('sorts table when clicking column headers', async () => { - renderChartList(mockUser); - - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); - - const table = screen.getByTestId('listview-table'); - - const allHeaders = table.querySelectorAll('.ant-table-column-sorters'); - - const sortableHeaders = Array.from(allHeaders).filter( - header => !header.closest('.ant-table-measure-cell-content'), - ); - expect(sortableHeaders).toHaveLength(3); - - const nameHeader = within(table).getByTitle('Name'); - fireEvent.click(nameHeader); - - await waitFor(() => { - const sortCalls = fetchMock - .calls(/chart\/\?q/) - .filter( - call => - call[0].includes('order_column') && call[0].includes('slice_name'), - ); - expect(sortCalls).toHaveLength(1); - }); - - const typeHeader = within(table).getByTitle('Type'); - fireEvent.click(typeHeader); - - await waitFor(() => { - const typeSortCalls = fetchMock - .calls(/chart\/\?q/) - .filter( - call => - call[0].includes('order_column') && call[0].includes('viz_type'), - ); - expect(typeSortCalls).toHaveLength(1); - }); - - const lastModifiedHeader = within(table).getByTitle('Last modified'); - fireEvent.click(lastModifiedHeader); - - await waitFor(() => { - const lastModifiedSortCalls = fetchMock - .calls(/chart\/\?q/) - .filter( - call => - call[0].includes('order_column') && - call[0].includes('last_saved_at'), - ); - expect(lastModifiedSortCalls).toHaveLength(1); - }); - }); - - test('displays chart data correctly', async () => { - /** - * @todo Implement test logic for tagging. - * If TAGGING_SYSTEM is ever deprecated to always be on, - * will need to combine this with the tagging column test. - */ - renderChartList(mockUser); - - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); - - const table = screen.getByTestId('listview-table'); - const testChart = mockCharts[0]; - - await waitFor(() => { - expect(within(table).getByText(testChart.slice_name)).toBeInTheDocument(); - }); - - // Find the specific row for our test chart - const chartNameElement = within(table).getByText(testChart.slice_name); - const chartRow = chartNameElement.closest( - '[data-test="table-row"]', - ) as HTMLElement; - expect(chartRow).toBeInTheDocument(); - - // Check for favorite star column within the specific row - const favoriteButton = within(chartRow).getByTestId('fave-unfave-icon'); - expect(favoriteButton).toBeInTheDocument(); - expect(favoriteButton).toHaveAttribute('role', 'button'); - - // Check chart name link within the specific row - const chartLink = within(chartRow).getByTestId( - `${testChart.slice_name}-list-chart-title`, - ); - expect(chartLink).toBeInTheDocument(); - expect(chartLink).toHaveAttribute('href', testChart.url); +afterEach(() => { + fetchMock.restore(); + mockIsFeatureEnabled.mockReset(); +}); - // Check viz type within the specific row - expect(within(chartRow).getByText(testChart.viz_type)).toBeInTheDocument(); +test('ChartList list view renders correctly', async () => { + renderChartList(mockUser); - // Check dataset name and link within the specific row - const datasetName = testChart.datasource_name_text?.split('.').pop() || ''; - expect(within(chartRow).getByText(datasetName)).toBeInTheDocument(); + // Wait for component to load + await waitFor(() => { + expect(screen.getByTestId('chart-list-view')).toBeInTheDocument(); + }); - const datasetLink = within(chartRow).getByTestId('internal-link'); - expect(datasetLink).toBeInTheDocument(); - expect(datasetLink).toHaveAttribute('href', testChart.datasource_url); + // Wait for table to be rendered + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - // Check dashboard display within the specific row - expect( - within(chartRow).getByText(testChart.dashboards[0].dashboard_title), - ).toBeInTheDocument(); + // Verify cards are not rendered in list view + await waitFor(() => { + expect(screen.queryByTestId('styled-card')).not.toBeInTheDocument(); + }); +}); - // Check owners display - find avatar group within the row - const avatarGroup = chartRow.querySelector( - '.ant-avatar-group', - ) as HTMLElement; - expect(avatarGroup).toBeInTheDocument(); +test('ChartList list view correctly displays dataset names with and without schema', async () => { + // Create custom mock data with different datasource_name_text formats + const customMockCharts = [ + { + ...mockCharts[0], + id: 100, + slice_name: 'Chart with Schema', + datasource_name_text: 'public.test_dataset', + }, + { + ...mockCharts[0], + id: 101, + slice_name: 'Chart without Schema', + datasource_name_text: 'Jinja 5', // Virtual dataset without schema + }, + { + ...mockCharts[0], + id: 102, + slice_name: 'Chart with Dots in Name', + datasource_name_text: 'schema.table.with.dots', // Name contains dots + }, + ]; + + // Setup mock with custom charts + fetchMock.reset(); + setupMocks(); + fetchMock.get( + 'glob:*/api/v1/chart/?*', + { + result: customMockCharts, + chart_count: customMockCharts.length, + }, + { overwriteRoutes: true }, + ); + + renderChartList(mockUser); + + // Wait for table to be rendered + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - // Test owner initials for mockCharts[0] (we know it has owners) - const ownerInitials = `${testChart.owners[0].first_name[0]}${testChart.owners[0].last_name[0]}`; - expect(within(avatarGroup).getByText(ownerInitials)).toBeInTheDocument(); + // Wait for data to load + await waitFor(() => { + expect(screen.getByText('Chart with Schema')).toBeInTheDocument(); + }); - // Check last modified time within the specific row - expect( - within(chartRow).getByText(testChart.changed_on_delta_humanized), - ).toBeInTheDocument(); + // Find the specific dataset links by their parent row context + const schemaRow = screen.getByText('Chart with Schema').closest('tr'); + const noSchemaRow = screen.getByText('Chart without Schema').closest('tr'); + const dotsRow = screen.getByText('Chart with Dots in Name').closest('tr'); - // Check actions column within the specific row - const actionsContainer = chartRow.querySelector('.actions'); - expect(actionsContainer).toBeInTheDocument(); + // Check dataset name displays correctly for each case + // For chart with schema (public.test_dataset) + expect(schemaRow).toBeInTheDocument(); + const schemaLink = within(schemaRow!).getByRole('link', { + name: /test_dataset/i, + }); + expect(schemaLink).toBeInTheDocument(); + expect(schemaLink).toHaveTextContent('test_dataset'); - // Verify action buttons exist within the specific row - expect(within(chartRow).getByTestId('delete')).toBeInTheDocument(); - expect(within(chartRow).getByTestId('upload')).toBeInTheDocument(); - expect(within(chartRow).getByTestId('edit-alt')).toBeInTheDocument(); + // For chart without schema (Jinja 5) + expect(noSchemaRow).toBeInTheDocument(); + const noSchemaLink = within(noSchemaRow!).getByRole('link', { + name: /Jinja 5/i, }); + expect(noSchemaLink).toBeInTheDocument(); + expect(noSchemaLink).toHaveTextContent('Jinja 5'); - test('export chart api called when export button is clicked', async () => { - renderChartList(mockUser); + // For chart with dots in name (schema.table.with.dots) + expect(dotsRow).toBeInTheDocument(); + const dotsLink = within(dotsRow!).getByRole('link', { + name: /table\.with\.dots/i, + }); + expect(dotsLink).toBeInTheDocument(); + expect(dotsLink).toHaveTextContent('table.with.dots'); +}); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); +test('ChartList list view switches from list view to card view', async () => { + renderChartList(mockUser); - await waitFor(() => { - expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); - expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - // Click first export button - const table = screen.getByTestId('listview-table'); - const exportButtons = within(table).getAllByTestId('upload'); - fireEvent.click(exportButtons[0]); + // Switch to card view + const cardViewToggle = screen.getByRole('img', { name: 'appstore' }); + await userEvent.click(cardViewToggle); - // Verify export functionality is triggered - check if handleResourceExport was called - await waitFor(() => { - expect(mockHandleResourceExport).toHaveBeenCalledWith( - 'chart', - [mockCharts[0].id], - expect.any(Function), - ); - }); + // Verify table is no longer rendered + await waitFor(() => { + expect(screen.queryByTestId('listview-table')).not.toBeInTheDocument(); }); - test('opens edit properties modal when edit button is clicked', async () => { - renderChartList(mockUser); + // Verify cards are rendered + const cards = screen.getAllByTestId('styled-card'); + expect(cards).toHaveLength(mockCharts.length); +}); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); +test('ChartList list view renders all required column headers', async () => { + renderChartList(mockUser); - await waitFor(() => { - expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); - expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - const table = screen.getByTestId('listview-table'); - const editButtons = within(table).getAllByTestId('edit-alt'); - fireEvent.click(editButtons[0]); + const table = screen.getByTestId('listview-table'); + const columnHeaders = table.querySelectorAll('[role="columnheader"]'); + + // All the table headers with default feature flags on + const expectedHeaders = [ + 'Name', + 'Type', + 'Dataset', + 'On dashboards', + 'Owners', + 'Last modified', + 'Actions', + ]; + + // Add one extra column header for favorite stars + expect(columnHeaders).toHaveLength(expectedHeaders.length + 1); + + // Verify all expected headers are present + expectedHeaders.forEach(headerText => { + expect(within(table).getByTitle(headerText)).toBeInTheDocument(); + }); +}); - // Verify edit modal opens - await waitFor(() => { - const editModal = screen.getByRole('dialog'); - expect(editModal).toBeInTheDocument(); - expect(editModal).toHaveTextContent(/properties/i); - }); +test('ChartList list view sorts table when clicking column headers', async () => { + renderChartList(mockUser); + + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); }); - test('opens delete confirmation when delete button is clicked', async () => { - renderChartList(mockUser); + const table = screen.getByTestId('listview-table'); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); + const allHeaders = table.querySelectorAll('.ant-table-column-sorters'); - await waitFor(() => { - expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); - expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); - }); + const sortableHeaders = Array.from(allHeaders).filter( + header => !header.closest('.ant-table-measure-cell-content'), + ); + expect(sortableHeaders).toHaveLength(3); - const table = screen.getByTestId('listview-table'); - const deleteButtons = within(table).getAllByTestId('delete'); - fireEvent.click(deleteButtons[0]); + const nameHeader = within(table).getByTitle('Name'); + await userEvent.click(nameHeader); - // Verify delete confirmation modal opens - await waitFor(() => { - const deleteModal = screen.getByRole('dialog'); - expect(deleteModal).toBeInTheDocument(); - expect(deleteModal).toHaveTextContent(/delete/i); - }); + await waitFor(() => { + const sortCalls = fetchMock + .calls(/chart\/\?q/) + .filter( + call => + call[0].includes('order_column') && call[0].includes('slice_name'), + ); + expect(sortCalls).toHaveLength(1); }); - test('displays certified badge only for certified charts', async () => { - // Test certified chart (mockCharts[1] has certification) - const certifiedChart = mockCharts[1]; - // Test uncertified chart (mockCharts[0] has no certification) - const uncertifiedChart = mockCharts[0]; + const typeHeader = within(table).getByTitle('Type'); + await userEvent.click(typeHeader); - renderChartList(mockUser); + await waitFor(() => { + const typeSortCalls = fetchMock + .calls(/chart\/\?q/) + .filter( + call => + call[0].includes('order_column') && call[0].includes('viz_type'), + ); + expect(typeSortCalls).toHaveLength(1); + }); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); + const lastModifiedHeader = within(table).getByTitle('Last modified'); + await userEvent.click(lastModifiedHeader); - await waitFor(() => { - expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); - expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); - }); + await waitFor(() => { + const lastModifiedSortCalls = fetchMock + .calls(/chart\/\?q/) + .filter( + call => + call[0].includes('order_column') && call[0].includes('last_saved_at'), + ); + expect(lastModifiedSortCalls).toHaveLength(1); + }); +}); - const table = screen.getByTestId('listview-table'); +test('ChartList list view displays chart data correctly', async () => { + /** + * @todo Implement test logic for tagging. + * If TAGGING_SYSTEM is ever deprecated to always be on, + * will need to combine this with the tagging column test. + */ + renderChartList(mockUser); - const certifiedChartElement = within(table).getByText( - certifiedChart.slice_name, - ); - const certifiedChartRow = certifiedChartElement.closest( - '[data-test="table-row"]', - ) as HTMLElement; - const certifiedBadge = - within(certifiedChartRow).getByLabelText('certified'); - expect(certifiedBadge).toBeInTheDocument(); - - const uncertifiedChartElement = within(table).getByText( - uncertifiedChart.slice_name, - ); - const uncertifiedChartRow = uncertifiedChartElement.closest( - '[data-test="table-row"]', - ) as HTMLElement; - expect( - within(uncertifiedChartRow).queryByLabelText('certified'), - ).not.toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); }); - test('displays info icon only for charts with descriptions', async () => { - // Test chart with description (mockCharts[0] has description) - const chartWithDesc = mockCharts[0]; - // Test chart without description (mockCharts[2] has description: null) - const chartNoDesc = mockCharts[2]; + const table = screen.getByTestId('listview-table'); + const testChart = mockCharts[0]; - renderChartList(mockUser); + await waitFor(() => { + expect(within(table).getByText(testChart.slice_name)).toBeInTheDocument(); + }); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); + // Find the specific row for our test chart + const chartNameElement = within(table).getByText(testChart.slice_name); + const chartRow = chartNameElement.closest( + '[data-test="table-row"]', + ) as HTMLElement; + expect(chartRow).toBeInTheDocument(); + + // Check for favorite star column within the specific row + const favoriteButton = within(chartRow).getByTestId('fave-unfave-icon'); + expect(favoriteButton).toBeInTheDocument(); + expect(favoriteButton).toHaveAttribute('role', 'button'); + + // Check chart name link within the specific row + const chartLink = within(chartRow).getByTestId( + `${testChart.slice_name}-list-chart-title`, + ); + expect(chartLink).toBeInTheDocument(); + expect(chartLink).toHaveAttribute('href', testChart.url); + + // Check viz type within the specific row + expect(within(chartRow).getByText(testChart.viz_type)).toBeInTheDocument(); + + // Check dataset name and link within the specific row + const datasetName = testChart.datasource_name_text?.split('.').pop() || ''; + expect(within(chartRow).getByText(datasetName)).toBeInTheDocument(); + + const datasetLink = within(chartRow).getByTestId('internal-link'); + expect(datasetLink).toBeInTheDocument(); + expect(datasetLink).toHaveAttribute('href', testChart.datasource_url); + + // Check dashboard display within the specific row + expect( + within(chartRow).getByText(testChart.dashboards[0].dashboard_title), + ).toBeInTheDocument(); + + // Check owners display - find avatar group within the row + const avatarGroup = chartRow.querySelector( + '.ant-avatar-group', + ) as HTMLElement; + expect(avatarGroup).toBeInTheDocument(); + + // Test owner initials for mockCharts[0] (we know it has owners) + const ownerInitials = `${testChart.owners[0].first_name[0]}${testChart.owners[0].last_name[0]}`; + expect(within(avatarGroup).getByText(ownerInitials)).toBeInTheDocument(); + + // Check last modified time within the specific row + expect( + within(chartRow).getByText(testChart.changed_on_delta_humanized), + ).toBeInTheDocument(); + + // Check actions column within the specific row + const actionsContainer = chartRow.querySelector('.actions'); + expect(actionsContainer).toBeInTheDocument(); + + // Verify action buttons exist within the specific row + expect(within(chartRow).getByTestId('delete')).toBeInTheDocument(); + expect(within(chartRow).getByTestId('upload')).toBeInTheDocument(); + expect(within(chartRow).getByTestId('edit-alt')).toBeInTheDocument(); +}); - await waitFor(() => { - expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); - expect(screen.getByText(mockCharts[2].slice_name)).toBeInTheDocument(); - }); +test('ChartList list view export chart api called when export button is clicked', async () => { + renderChartList(mockUser); - const table = screen.getByTestId('listview-table'); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - const chartWithDescElement = within(table).getByText( - chartWithDesc.slice_name, + await waitFor(() => { + expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); + expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); + }); + + // Click first export button + const table = screen.getByTestId('listview-table'); + const exportButtons = within(table).getAllByTestId('upload'); + await userEvent.click(exportButtons[0]); + + // Verify export functionality is triggered - check if handleResourceExport was called + await waitFor(() => { + expect(mockHandleResourceExport).toHaveBeenCalledWith( + 'chart', + [mockCharts[0].id], + expect.any(Function), ); - const chartWithDescRow = chartWithDescElement.closest( - '[data-test="table-row"]', - ) as HTMLElement; - const infoTooltip = - within(chartWithDescRow).getByLabelText('Show info tooltip'); - expect(infoTooltip).toBeInTheDocument(); - - const chartNoDescElement = within(table).getByText(chartNoDesc.slice_name); - const chartNoDescRow = chartNoDescElement.closest( - '[data-test="table-row"]', - ) as HTMLElement; - expect( - within(chartNoDescRow).queryByLabelText('Show info tooltip'), - ).not.toBeInTheDocument(); }); +}); - test('displays chart with empty dataset column', async () => { - renderChartList(mockUser); +test('ChartList list view opens edit properties modal when edit button is clicked', async () => { + renderChartList(mockUser); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - await waitFor(() => { - expect(screen.getByText(mockCharts[2].slice_name)).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); + expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); + }); - const table = screen.getByTestId('listview-table'); - const chartNameElement = within(table).getByText(mockCharts[2].slice_name); - const chartRow = chartNameElement.closest( - '[data-test="table-row"]', - ) as HTMLElement; + const table = screen.getByTestId('listview-table'); + const editButtons = within(table).getAllByTestId('edit-alt'); + await userEvent.click(editButtons[0]); - // Chart name should be visible - expect( - within(chartRow).getByText(mockCharts[2].slice_name), - ).toBeInTheDocument(); + // Verify edit modal opens + await waitFor(() => { + const editModal = screen.getByRole('dialog'); + expect(editModal).toBeInTheDocument(); + expect(editModal).toHaveTextContent(/properties/i); + }); +}); - // Find dataset column index by header - const headers = within(table).getAllByRole('columnheader'); - const datasetHeaderIndex = headers.findIndex(header => - header.textContent?.includes('Dataset'), - ); - expect(datasetHeaderIndex).toBeGreaterThan(-1); // Ensure column exists +test('ChartList list view opens delete confirmation when delete button is clicked', async () => { + renderChartList(mockUser); - // Since mockCharts[2] has datasource_name_text: null, verify dataset cell is empty - const datasetCell = - within(chartRow).getAllByRole('cell')[datasetHeaderIndex]; - expect(datasetCell).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - // Verify dataset cell is empty for charts with no dataset - expect(datasetCell).toHaveTextContent(''); - // There's a link element but with empty href - const datasetLink = within(datasetCell).getByRole('link'); - expect(datasetLink).toHaveAttribute('href', ''); + await waitFor(() => { + expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); + expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); }); - test('displays chart with empty on dashboards column', async () => { - renderChartList(mockUser); + const table = screen.getByTestId('listview-table'); + const deleteButtons = within(table).getAllByTestId('delete'); + await userEvent.click(deleteButtons[0]); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); + // Verify delete confirmation modal opens + await waitFor(() => { + const deleteModal = screen.getByRole('dialog'); + expect(deleteModal).toBeInTheDocument(); + expect(deleteModal).toHaveTextContent(/delete/i); + }); +}); - await waitFor(() => { - expect(screen.getByText(mockCharts[2].slice_name)).toBeInTheDocument(); - }); +test('ChartList list view displays certified badge only for certified charts', async () => { + // Test certified chart (mockCharts[1] has certification) + const certifiedChart = mockCharts[1]; + // Test uncertified chart (mockCharts[0] has no certification) + const uncertifiedChart = mockCharts[0]; - // Test mockCharts[2] which has dashboards: [] - const table = screen.getByTestId('listview-table'); - const chartNameElement = within(table).getByText(mockCharts[2].slice_name); - const chartRow = chartNameElement.closest( - '[data-test="table-row"]', - ) as HTMLElement; + renderChartList(mockUser); - // Chart should still render - chart name should be visible - expect( - within(chartRow).getByText(mockCharts[2].slice_name), - ).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - // Find dashboard column index by header - const headers = within(table).getAllByRole('columnheader'); - const dashboardHeaderIndex = headers.findIndex(header => - header.textContent?.includes('On dashboards'), - ); - expect(dashboardHeaderIndex).toBeGreaterThan(-1); // Ensure column exists + await waitFor(() => { + expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); + expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); + }); + + const table = screen.getByTestId('listview-table'); + + const certifiedChartElement = within(table).getByText( + certifiedChart.slice_name, + ); + const certifiedChartRow = certifiedChartElement.closest( + '[data-test="table-row"]', + ) as HTMLElement; + const certifiedBadge = within(certifiedChartRow).getByLabelText('certified'); + expect(certifiedBadge).toBeInTheDocument(); + + const uncertifiedChartElement = within(table).getByText( + uncertifiedChart.slice_name, + ); + const uncertifiedChartRow = uncertifiedChartElement.closest( + '[data-test="table-row"]', + ) as HTMLElement; + expect( + within(uncertifiedChartRow).queryByLabelText('certified'), + ).not.toBeInTheDocument(); +}); - // Since mockCharts[2] has dashboards: [], verify dashboard cell is empty - const dashboardCell = - within(chartRow).getAllByRole('cell')[dashboardHeaderIndex]; - expect(dashboardCell).toBeInTheDocument(); +test('ChartList list view displays info icon only for charts with descriptions', async () => { + // Test chart with description (mockCharts[0] has description) + const chartWithDesc = mockCharts[0]; + // Test chart without description (mockCharts[2] has description: null) + const chartNoDesc = mockCharts[2]; - // Verify no dashboard links are present in this cell - expect(within(dashboardCell).queryByRole('link')).not.toBeInTheDocument(); + renderChartList(mockUser); + + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); }); - test('shows tag info when TAGGING_SYSTEM is enabled', async () => { - // Enable tagging system feature flag - mockIsFeatureEnabled.mockImplementation( - feature => feature === 'TAGGING_SYSTEM', - ); + await waitFor(() => { + expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); + expect(screen.getByText(mockCharts[2].slice_name)).toBeInTheDocument(); + }); - renderChartList(mockUser); + const table = screen.getByTestId('listview-table'); + + const chartWithDescElement = within(table).getByText( + chartWithDesc.slice_name, + ); + const chartWithDescRow = chartWithDescElement.closest( + '[data-test="table-row"]', + ) as HTMLElement; + const infoTooltip = + within(chartWithDescRow).getByLabelText('Show info tooltip'); + expect(infoTooltip).toBeInTheDocument(); + + const chartNoDescElement = within(table).getByText(chartNoDesc.slice_name); + const chartNoDescRow = chartNoDescElement.closest( + '[data-test="table-row"]', + ) as HTMLElement; + expect( + within(chartNoDescRow).queryByLabelText('Show info tooltip'), + ).not.toBeInTheDocument(); +}); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); +test('ChartList list view displays chart with empty dataset column', async () => { + renderChartList(mockUser); - const testChart = mockCharts[0]; - const table = screen.getByTestId('listview-table'); - expect(within(table).getByTitle('Tags')).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - await waitFor(() => { - expect(within(table).getByText(testChart.slice_name)).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByText(mockCharts[2].slice_name)).toBeInTheDocument(); + }); - const chartNameElement = within(table).getByText(testChart.slice_name); - const chartRow = chartNameElement.closest( - '[data-test="table-row"]', - ) as HTMLElement; - expect(chartRow).toBeInTheDocument(); + const table = screen.getByTestId('listview-table'); + const chartNameElement = within(table).getByText(mockCharts[2].slice_name); + const chartRow = chartNameElement.closest( + '[data-test="table-row"]', + ) as HTMLElement; + + // Chart name should be visible + expect( + within(chartRow).getByText(mockCharts[2].slice_name), + ).toBeInTheDocument(); + + // Find dataset column index by header + const headers = within(table).getAllByRole('columnheader'); + const datasetHeaderIndex = headers.findIndex(header => + header.textContent?.includes('Dataset'), + ); + expect(datasetHeaderIndex).toBeGreaterThan(-1); // Ensure column exists + + // Since mockCharts[2] has datasource_name_text: null, verify dataset cell is empty + const datasetCell = within(chartRow).getAllByRole('cell')[datasetHeaderIndex]; + expect(datasetCell).toBeInTheDocument(); + + // Verify dataset cell is empty for charts with no dataset + expect(datasetCell).toHaveTextContent(''); + // There's a link element but with empty href + const datasetLink = within(datasetCell).getByRole('link'); + expect(datasetLink).toHaveAttribute('href', ''); +}); - const tagList = chartRow.querySelector('.tag-list') as HTMLElement; - expect(tagList).toBeInTheDocument(); +test('ChartList list view displays chart with empty on dashboards column', async () => { + renderChartList(mockUser); - // Find the tag in the row - const tag = within(tagList).getByTestId('tag'); - expect(tag).toBeInTheDocument(); - expect(tag).toHaveTextContent('basic'); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - // Tag should be a link to all_entities page - const tagLink = within(tag).getByRole('link'); - expect(tagLink).toHaveAttribute('href', '/superset/all_entities/?id=1'); - expect(tagLink).toHaveAttribute('target', '_blank'); + await waitFor(() => { + expect(screen.getByText(mockCharts[2].slice_name)).toBeInTheDocument(); }); - test('can bulk select and deselect all charts', async () => { - renderChartList(mockUser); + // Test mockCharts[2] which has dashboards: [] + const table = screen.getByTestId('listview-table'); + const chartNameElement = within(table).getByText(mockCharts[2].slice_name); + const chartRow = chartNameElement.closest( + '[data-test="table-row"]', + ) as HTMLElement; + + // Chart should still render - chart name should be visible + expect( + within(chartRow).getByText(mockCharts[2].slice_name), + ).toBeInTheDocument(); + + // Find dashboard column index by header + const headers = within(table).getAllByRole('columnheader'); + const dashboardHeaderIndex = headers.findIndex(header => + header.textContent?.includes('On dashboards'), + ); + expect(dashboardHeaderIndex).toBeGreaterThan(-1); // Ensure column exists + + // Since mockCharts[2] has dashboards: [], verify dashboard cell is empty + const dashboardCell = + within(chartRow).getAllByRole('cell')[dashboardHeaderIndex]; + expect(dashboardCell).toBeInTheDocument(); + + // Verify no dashboard links are present in this cell + expect(within(dashboardCell).queryByRole('link')).not.toBeInTheDocument(); +}); + +test('ChartList list view shows tag info when TAGGING_SYSTEM is enabled', async () => { + // Enable tagging system feature flag + mockIsFeatureEnabled.mockImplementation( + feature => feature === 'TAGGING_SYSTEM', + ); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); + renderChartList(mockUser); - await waitFor(() => { - expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); - expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - const bulkSelectButton = screen.getByTestId('bulk-select'); - fireEvent.click(bulkSelectButton); + const testChart = mockCharts[0]; + const table = screen.getByTestId('listview-table'); + expect(within(table).getByTitle('Tags')).toBeInTheDocument(); - await waitFor(() => { - // Expect header checkbox + one checkbox per chart - expect(screen.getAllByRole('checkbox')).toHaveLength( - mockCharts.length + 1, - ); - }); + await waitFor(() => { + expect(within(table).getByText(testChart.slice_name)).toBeInTheDocument(); + }); - // Use the header checkbox to select all - const selectAllCheckbox = screen.getAllByLabelText('Select all')[0]; - expect(selectAllCheckbox).not.toBeChecked(); + const chartNameElement = within(table).getByText(testChart.slice_name); + const chartRow = chartNameElement.closest( + '[data-test="table-row"]', + ) as HTMLElement; + expect(chartRow).toBeInTheDocument(); - fireEvent.click(selectAllCheckbox); + const tagList = chartRow.querySelector('.tag-list') as HTMLElement; + expect(tagList).toBeInTheDocument(); - await waitFor(() => { - // All checkboxes should be checked - const checkboxes = screen.getAllByRole('checkbox'); - checkboxes.forEach(checkbox => { - expect(checkbox).toBeChecked(); - }); + // Find the tag in the row + const tag = within(tagList).getByTestId('tag'); + expect(tag).toBeInTheDocument(); + expect(tag).toHaveTextContent('basic'); - // Should show all charts selected - expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( - `${mockCharts.length} Selected`, - ); - }); - - // Use the deselect all link to deselect all - const deselectAllButton = screen.getByTestId('bulk-select-deselect-all'); - fireEvent.click(deselectAllButton); - - await waitFor(() => { - // All checkboxes should be unchecked - const checkboxes = screen.getAllByRole('checkbox'); - checkboxes.forEach(checkbox => { - expect(checkbox).not.toBeChecked(); - }); - - // Should show 0 selected - expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( - '0 Selected', - ); + // Tag should be a link to all_entities page + const tagLink = within(tag).getByRole('link'); + expect(tagLink).toHaveAttribute('href', '/superset/all_entities/?id=1'); + expect(tagLink).toHaveAttribute('target', '_blank'); +}); + +test('ChartList list view can bulk select and deselect all charts', async () => { + renderChartList(mockUser); - // Bulk action buttons should disappear - expect( - screen.queryByTestId('bulk-select-action'), - ).not.toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); }); - test('can bulk export selected charts', async () => { - renderChartList(mockUser); + await waitFor(() => { + expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); + expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); + }); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); + const bulkSelectButton = screen.getByTestId('bulk-select'); + await userEvent.click(bulkSelectButton); - await waitFor(() => { - expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); - }); + await waitFor(() => { + // Expect header checkbox + one checkbox per chart + expect(screen.getAllByRole('checkbox')).toHaveLength(mockCharts.length + 1); + }); - const bulkSelectButton = screen.getByTestId('bulk-select'); - fireEvent.click(bulkSelectButton); + // Use the header checkbox to select all + const selectAllCheckbox = screen.getAllByLabelText('Select all')[0]; + expect(selectAllCheckbox).not.toBeChecked(); - await waitFor(() => { - // Expect header checkbox + one checkbox per chart - expect(screen.getAllByRole('checkbox')).toHaveLength( - mockCharts.length + 1, - ); - }); + await userEvent.click(selectAllCheckbox); - // Use select all to select multiple charts - const selectAllCheckbox = screen.getAllByLabelText('Select all')[0]; - fireEvent.click(selectAllCheckbox); + await waitFor(() => { + // Should show all charts selected + expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( + `${mockCharts.length} Selected`, + ); + }); - await waitFor(() => { - expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( - `${mockCharts.length} Selected`, - ); - }); + // All checkboxes should be checked + let checkboxes = screen.getAllByRole('checkbox'); + checkboxes.forEach(checkbox => { + expect(checkbox).toBeChecked(); + }); - // Click bulk export button - const bulkActions = screen.getAllByTestId('bulk-select-action'); - const exportButton = bulkActions.find(btn => btn.textContent === 'Export'); - expect(exportButton).toBeInTheDocument(); + // Use the deselect all link to deselect all + const deselectAllButton = screen.getByTestId('bulk-select-deselect-all'); + await userEvent.click(deselectAllButton); - fireEvent.click(exportButton!); + await waitFor(() => { + // Should show 0 selected + expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( + '0 Selected', + ); + }); - // Verify export function was called with all chart IDs - await waitFor(() => { - expect(mockHandleResourceExport).toHaveBeenCalledWith( - 'chart', - mockCharts.map(chart => chart.id), - expect.any(Function), - ); - }); + // All checkboxes should be unchecked + checkboxes = screen.getAllByRole('checkbox'); + checkboxes.forEach(checkbox => { + expect(checkbox).not.toBeChecked(); }); - test('can bulk delete selected charts', async () => { - renderChartList(mockUser); + // Bulk action buttons should disappear + expect(screen.queryByTestId('bulk-select-action')).not.toBeInTheDocument(); +}); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); +test('ChartList list view can bulk export selected charts', async () => { + renderChartList(mockUser); - await waitFor(() => { - expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); - expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - const bulkSelectButton = screen.getByTestId('bulk-select'); - fireEvent.click(bulkSelectButton); + await waitFor(() => { + expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); + }); - await waitFor(() => { - // Expect header checkbox + one checkbox per chart - expect(screen.getAllByRole('checkbox')).toHaveLength( - mockCharts.length + 1, - ); - }); + const bulkSelectButton = screen.getByTestId('bulk-select'); + await userEvent.click(bulkSelectButton); - // Use select all to select multiple charts - const selectAllCheckbox = screen.getAllByLabelText('Select all')[0]; - fireEvent.click(selectAllCheckbox); + await waitFor(() => { + // Expect header checkbox + one checkbox per chart + expect(screen.getAllByRole('checkbox')).toHaveLength(mockCharts.length + 1); + }); - await waitFor(() => { - expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( - `${mockCharts.length} Selected`, - ); - }); + // Use select all to select multiple charts + const selectAllCheckbox = screen.getAllByLabelText('Select all')[0]; + await userEvent.click(selectAllCheckbox); - // Click bulk delete button - const bulkActions = screen.getAllByTestId('bulk-select-action'); - const deleteButton = bulkActions.find(btn => btn.textContent === 'Delete'); - expect(deleteButton).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( + `${mockCharts.length} Selected`, + ); + }); - fireEvent.click(deleteButton!); + // Click bulk export button + const bulkActions = screen.getAllByTestId('bulk-select-action'); + const exportButton = bulkActions.find(btn => btn.textContent === 'Export'); + expect(exportButton).toBeInTheDocument(); - // Should open delete confirmation modal - await waitFor(() => { - const deleteModal = screen.getByRole('dialog'); - expect(deleteModal).toBeInTheDocument(); - expect(deleteModal).toHaveTextContent(/delete/i); - expect(deleteModal).toHaveTextContent(/selected charts/i); - }); - }); + await userEvent.click(exportButton!); - test('can bulk add tags to selected charts', async () => { - // Enable tagging system feature flag - mockIsFeatureEnabled.mockImplementation( - feature => feature === 'TAGGING_SYSTEM', + // Verify export function was called with all chart IDs + await waitFor(() => { + expect(mockHandleResourceExport).toHaveBeenCalledWith( + 'chart', + mockCharts.map(chart => chart.id), + expect.any(Function), ); + }); +}); - renderChartList(mockUser); +test('ChartList list view can bulk delete selected charts', async () => { + renderChartList(mockUser); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - // Wait for chart data to load - await waitFor(() => { - expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); - expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); + expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); + }); - // Activate bulk select and select charts - const bulkSelectButton = screen.getByTestId('bulk-select'); - fireEvent.click(bulkSelectButton); + const bulkSelectButton = screen.getByTestId('bulk-select'); + await userEvent.click(bulkSelectButton); - await waitFor(() => { - // Expect header checkbox + one checkbox per chart - expect(screen.getAllByRole('checkbox')).toHaveLength( - mockCharts.length + 1, - ); - }); - - // Select first chart - const table = screen.getByTestId('listview-table'); - // Target first data row specifically (not header row) - const dataRows = within(table).getAllByTestId('table-row'); - const firstRowCheckbox = within(dataRows[0]).getByRole('checkbox'); - fireEvent.click(firstRowCheckbox); - - await waitFor(() => { - expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( - '1 Selected', - ); - }); + await waitFor(() => { + // Expect header checkbox + one checkbox per chart + expect(screen.getAllByRole('checkbox')).toHaveLength(mockCharts.length + 1); + }); - const addTagButton = screen.queryByText('Add Tag') as HTMLButtonElement; - expect(addTagButton).toBeInTheDocument(); - fireEvent.click(addTagButton); + // Use select all to select multiple charts + const selectAllCheckbox = screen.getAllByLabelText('Select all')[0]; + await userEvent.click(selectAllCheckbox); - await waitFor(() => { - const tagModal = screen.getByRole('dialog'); - expect(tagModal).toBeInTheDocument(); - expect(tagModal).toHaveTextContent(/tag/i); - }); + await waitFor(() => { + expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( + `${mockCharts.length} Selected`, + ); }); - test('exit bulk select by hitting x on bulk select bar', async () => { - renderChartList(mockUser); + // Click bulk delete button + const bulkActions = screen.getAllByTestId('bulk-select-action'); + const deleteButton = bulkActions.find(btn => btn.textContent === 'Delete'); + expect(deleteButton).toBeInTheDocument(); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); + await userEvent.click(deleteButton!); - await waitFor(() => { - expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); - expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); - }); + // Should open delete confirmation modal + await waitFor(() => { + const deleteModal = screen.getByRole('dialog'); + expect(deleteModal).toBeInTheDocument(); + expect(deleteModal).toHaveTextContent(/delete/i); + expect(deleteModal).toHaveTextContent(/selected charts/i); + }); +}); - const bulkSelectButton = screen.getByTestId('bulk-select'); - fireEvent.click(bulkSelectButton); +test('ChartList list view can bulk add tags to selected charts', async () => { + // Enable tagging system feature flag + mockIsFeatureEnabled.mockImplementation( + feature => feature === 'TAGGING_SYSTEM', + ); - await waitFor(() => { - // Expect header checkbox + one checkbox per chart - expect(screen.getAllByRole('checkbox')).toHaveLength( - mockCharts.length + 1, - ); - }); + renderChartList(mockUser); - const table = screen.getByTestId('listview-table'); - // Target first data row specifically (not header row) - const dataRows = within(table).getAllByTestId('table-row'); - const firstRowCheckbox = within(dataRows[0]).getByRole('checkbox'); - fireEvent.click(firstRowCheckbox); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - await waitFor(() => { - expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( - '1 Selected', - ); - }); + // Wait for chart data to load + await waitFor(() => { + expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); + expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); + }); - // Find and click the close button (x) on the bulk select bar - const closeIcon = document.querySelector( - '.ant-alert-close-icon', - ) as HTMLButtonElement; - fireEvent.click(closeIcon); + // Activate bulk select and select charts + const bulkSelectButton = screen.getByTestId('bulk-select'); + await userEvent.click(bulkSelectButton); - await waitFor(() => { - expect(screen.queryAllByRole('checkbox')).toHaveLength(0); - expect(screen.queryByTestId('bulk-select-copy')).not.toBeInTheDocument(); - }); + await waitFor(() => { + // Expect header checkbox + one checkbox per chart + expect(screen.getAllByRole('checkbox')).toHaveLength(mockCharts.length + 1); }); - test('exit bulk select by clicking bulk select button again', async () => { - renderChartList(mockUser); + // Select first chart + const table = screen.getByTestId('listview-table'); + // Target first data row specifically (not header row) + const dataRows = within(table).getAllByTestId('table-row'); + const firstRowCheckbox = within(dataRows[0]).getByRole('checkbox'); + await userEvent.click(firstRowCheckbox); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( + '1 Selected', + ); + }); - await waitFor(() => { - expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); - expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); - }); + const addTagButton = screen.queryByText('Add Tag') as HTMLButtonElement; + expect(addTagButton).toBeInTheDocument(); + await userEvent.click(addTagButton); - const bulkSelectButton = screen.getByTestId('bulk-select'); - fireEvent.click(bulkSelectButton); + await waitFor(() => { + const tagModal = screen.getByRole('dialog'); + expect(tagModal).toBeInTheDocument(); + expect(tagModal).toHaveTextContent(/tag/i); + }); +}); - await waitFor(() => { - // Expect header checkbox + one checkbox per chart - expect(screen.getAllByRole('checkbox')).toHaveLength( - mockCharts.length + 1, - ); - }); +test('ChartList list view exit bulk select by hitting x on bulk select bar', async () => { + renderChartList(mockUser); - const table = screen.getByTestId('listview-table'); - // Target first data row specifically (not header row) - const dataRows = within(table).getAllByTestId('table-row'); - const firstRowCheckbox = within(dataRows[0]).getByRole('checkbox'); - fireEvent.click(firstRowCheckbox); + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); - await waitFor(() => { - expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( - '1 Selected', - ); - }); + await waitFor(() => { + expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); + expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); + }); - fireEvent.click(bulkSelectButton); + const bulkSelectButton = screen.getByTestId('bulk-select'); + await userEvent.click(bulkSelectButton); - await waitFor(() => { - expect(screen.queryAllByRole('checkbox')).toHaveLength(0); - expect(screen.queryByTestId('bulk-select-copy')).not.toBeInTheDocument(); - }); + await waitFor(() => { + // Expect header checkbox + one checkbox per chart + expect(screen.getAllByRole('checkbox')).toHaveLength(mockCharts.length + 1); }); - test('displays dataset name without schema prefix correctly', async () => { - // Test just name case - should display the full name when no schema prefix - renderChartList(mockUser); + const table = screen.getByTestId('listview-table'); + // Target first data row specifically (not header row) + const dataRows = within(table).getAllByTestId('table-row'); + const firstRowCheckbox = within(dataRows[0]).getByRole('checkbox'); + await userEvent.click(firstRowCheckbox); - await waitFor(() => { - expect(screen.getByTestId('listview-table')).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( + '1 Selected', + ); + }); - const table = screen.getByTestId('listview-table'); + // Find and click the close button (x) on the bulk select bar + const closeIcon = document.querySelector( + '.ant-alert-close-icon', + ) as HTMLButtonElement; + await userEvent.click(closeIcon); - // Wait for chart with simple dataset name to load - await waitFor(() => { - expect( - within(table).getByText(mockCharts[1].slice_name), - ).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.queryAllByRole('checkbox')).toHaveLength(0); + expect(screen.queryByTestId('bulk-select-copy')).not.toBeInTheDocument(); + }); +}); - // Test mockCharts[1] which has 'sales_data' (no schema prefix) - const chart1Row = within(table) - .getByText(mockCharts[1].slice_name) - .closest('[data-test="table-row"]') as HTMLElement; - const chart1DatasetLink = within(chart1Row).getByTestId('internal-link'); +test('ChartList list view exit bulk select by clicking bulk select button again', async () => { + renderChartList(mockUser); + + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); + + await waitFor(() => { + expect(screen.getByText(mockCharts[0].slice_name)).toBeInTheDocument(); + expect(screen.getByText(mockCharts[1].slice_name)).toBeInTheDocument(); + }); - // Should display the full name when there's no schema prefix - expect(chart1DatasetLink).toHaveTextContent('sales_data'); - expect(chart1DatasetLink).toHaveAttribute( - 'href', - mockCharts[1].datasource_url, + const bulkSelectButton = screen.getByTestId('bulk-select'); + await userEvent.click(bulkSelectButton); + + await waitFor(() => { + // Expect header checkbox + one checkbox per chart + expect(screen.getAllByRole('checkbox')).toHaveLength(mockCharts.length + 1); + }); + + const table = screen.getByTestId('listview-table'); + // Target first data row specifically (not header row) + const dataRows = within(table).getAllByTestId('table-row'); + const firstRowCheckbox = within(dataRows[0]).getByRole('checkbox'); + await userEvent.click(firstRowCheckbox); + + await waitFor(() => { + expect(screen.getByTestId('bulk-select-copy')).toHaveTextContent( + '1 Selected', ); }); + + await userEvent.click(bulkSelectButton); + + await waitFor(() => { + expect(screen.queryAllByRole('checkbox')).toHaveLength(0); + expect(screen.queryByTestId('bulk-select-copy')).not.toBeInTheDocument(); + }); +}); + +test('ChartList list view displays dataset name without schema prefix correctly', async () => { + // Test just name case - should display the full name when no schema prefix + renderChartList(mockUser); + + await waitFor(() => { + expect(screen.getByTestId('listview-table')).toBeInTheDocument(); + }); + + const table = screen.getByTestId('listview-table'); + + // Wait for chart with simple dataset name to load + await waitFor(() => { + expect( + within(table).getByText(mockCharts[1].slice_name), + ).toBeInTheDocument(); + }); + + // Test mockCharts[1] which has 'sales_data' (no schema prefix) + const chart1Row = within(table) + .getByText(mockCharts[1].slice_name) + .closest('[data-test="table-row"]') as HTMLElement; + const chart1DatasetLink = within(chart1Row).getByTestId('internal-link'); + + // Should display the full name when there's no schema prefix + expect(chart1DatasetLink).toHaveTextContent('sales_data'); + expect(chart1DatasetLink).toHaveAttribute( + 'href', + mockCharts[1].datasource_url, + ); });