| /** |
| * 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 { |
| act, |
| cleanup, |
| render, |
| screen, |
| userEvent, |
| waitFor, |
| within, |
| defaultStore as store, |
| } from 'spec/helpers/testing-library'; |
| import { api } from 'src/hooks/apiResources/queryApi'; |
| import fetchMock from 'fetch-mock'; |
| import TableSelector, { TableSelectorMultiple, TableOption } from '.'; |
| |
| const createProps = (props = {}) => ({ |
| database: { |
| id: 1, |
| database_name: 'main', |
| backend: 'sqlite', |
| }, |
| schema: 'test_schema', |
| handleError: jest.fn(), |
| ...props, |
| }); |
| |
| const getTableMockFunction = () => |
| ({ |
| count: 4, |
| result: [ |
| { label: 'table_a', value: 'table_a' }, |
| { label: 'table_b', value: 'table_b' }, |
| { label: 'table_c', value: 'table_c' }, |
| { label: 'table_d', value: 'table_d' }, |
| ], |
| }) as any; |
| |
| const databaseApiRoute = 'glob:*/api/v1/database/?*'; |
| const catalogApiRoute = 'glob:*/api/v1/database/*/catalogs/?*'; |
| const schemaApiRoute = 'glob:*/api/v1/database/*/schemas/?*'; |
| const tablesApiRoute = 'glob:*/api/v1/database/*/tables/*'; |
| |
| const getSelectItemContainer = (select: HTMLElement) => |
| select.parentElement?.parentElement?.getElementsByClassName( |
| 'ant-select-selection-item', |
| ); |
| |
| // Add cleanup and increase timeout |
| beforeAll(() => { |
| jest.setTimeout(30000); |
| }); |
| |
| beforeEach(() => { |
| fetchMock.get(databaseApiRoute, { result: [] }); |
| }); |
| |
| afterEach(async () => { |
| cleanup(); |
| act(() => { |
| store.dispatch(api.util.resetApiState()); |
| }); |
| fetchMock.reset(); |
| // Wait for any pending effects to complete |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| }); |
| |
| test('renders with default props', async () => { |
| fetchMock.get(catalogApiRoute, { result: [] }); |
| fetchMock.get(schemaApiRoute, { result: [] }); |
| fetchMock.get(tablesApiRoute, getTableMockFunction()); |
| |
| const props = createProps(); |
| render(<TableSelector {...props} />, { useRedux: true, store }); |
| const databaseSelect = screen.getByRole('combobox', { |
| name: 'Select database or type to search databases', |
| }); |
| const schemaSelect = screen.getByRole('combobox', { |
| name: 'Select schema or type to search schemas: test_schema', |
| }); |
| const tableSelect = screen.getByRole('combobox', { |
| name: 'Select table or type to search tables', |
| }); |
| await waitFor(() => { |
| expect(databaseSelect).toBeInTheDocument(); |
| expect(schemaSelect).toBeInTheDocument(); |
| expect(tableSelect).toBeInTheDocument(); |
| }); |
| }); |
| |
| test('skips select all options', async () => { |
| fetchMock.get(catalogApiRoute, { result: [] }); |
| fetchMock.get(schemaApiRoute, { result: ['test_schema'] }); |
| fetchMock.get(tablesApiRoute, getTableMockFunction()); |
| |
| const props = createProps(); |
| render(<TableSelector {...props} tableSelectMode="multiple" />, { |
| useRedux: true, |
| store, |
| }); |
| const tableSelect = screen.getByRole('combobox', { |
| name: 'Select table or type to search tables', |
| }); |
| await userEvent.click(tableSelect); |
| expect(await screen.findByText('table_a')).toBeInTheDocument(); |
| expect( |
| screen.queryByRole('option', { name: /Select All/i }), |
| ).not.toBeInTheDocument(); |
| }); |
| |
| test('renders table options without Select All option', async () => { |
| fetchMock.get(catalogApiRoute, { result: [] }); |
| fetchMock.get(schemaApiRoute, { result: ['test_schema'] }); |
| fetchMock.get(tablesApiRoute, getTableMockFunction()); |
| |
| const props = createProps(); |
| render(<TableSelector {...props} />, { useRedux: true, store }); |
| |
| const tableSelect = screen.getByRole('combobox', { |
| name: 'Select table or type to search tables', |
| }); |
| |
| await act(async () => { |
| await userEvent.click(tableSelect); |
| }); |
| |
| await waitFor( |
| () => { |
| expect(screen.getByText('table_a')).toBeInTheDocument(); |
| expect(screen.getByText('table_b')).toBeInTheDocument(); |
| }, |
| { timeout: 10000 }, |
| ); |
| }, 15000); |
| |
| test('table select retain value if not in SQL Lab mode', async () => { |
| fetchMock.get(catalogApiRoute, { result: [] }); |
| fetchMock.get(schemaApiRoute, { result: ['test_schema'] }); |
| fetchMock.get(tablesApiRoute, getTableMockFunction()); |
| |
| const callback = jest.fn(); |
| const props = createProps({ |
| onTableSelectChange: callback, |
| sqlLabMode: false, |
| }); |
| |
| render(<TableSelector {...props} />, { useRedux: true, store }); |
| |
| const tableSelect = screen.getByRole('combobox', { |
| name: 'Select table or type to search tables', |
| }); |
| |
| expect(screen.queryByText('table_a')).not.toBeInTheDocument(); |
| expect(getSelectItemContainer(tableSelect)).toHaveLength(0); |
| |
| await act(async () => { |
| await userEvent.click(tableSelect); |
| }); |
| |
| await waitFor( |
| () => { |
| expect(screen.getByText('table_a')).toBeInTheDocument(); |
| }, |
| { timeout: 10000 }, |
| ); |
| |
| await act(async () => { |
| await userEvent.click(screen.getByText('table_a')); |
| }); |
| |
| await waitFor( |
| () => { |
| expect(callback).toHaveBeenCalled(); |
| }, |
| { timeout: 10000 }, |
| ); |
| |
| const selectedValueContainer = getSelectItemContainer(tableSelect); |
| expect(selectedValueContainer).toHaveLength(1); |
| |
| await waitFor( |
| () => { |
| expect( |
| within(selectedValueContainer?.[0] as HTMLElement).getByText('table_a'), |
| ).toBeInTheDocument(); |
| }, |
| { timeout: 10000 }, |
| ); |
| }, 15000); |
| |
| test('renders disabled without schema', async () => { |
| fetchMock.get(catalogApiRoute, { result: [] }); |
| fetchMock.get(schemaApiRoute, { result: [] }); |
| fetchMock.get(tablesApiRoute, getTableMockFunction()); |
| |
| const props = createProps(); |
| render(<TableSelector {...props} schema={undefined} />, { |
| useRedux: true, |
| store, |
| }); |
| const tableSelect = screen.getByRole('combobox', { |
| name: 'Select table or type to search tables', |
| }); |
| await waitFor(() => { |
| expect(tableSelect).toBeDisabled(); |
| }); |
| }); |
| |
| test('table multi select retain all the values selected', async () => { |
| fetchMock.get(catalogApiRoute, { result: [] }); |
| fetchMock.get(schemaApiRoute, { result: ['test_schema'] }); |
| fetchMock.get(tablesApiRoute, getTableMockFunction()); |
| |
| const callback = jest.fn(); |
| const props = createProps({ |
| onTableSelectChange: callback, |
| }); |
| |
| render(<TableSelectorMultiple {...props} />, { useRedux: true, store }); |
| |
| const tableSelect = screen.getByRole('combobox', { |
| name: 'Select table or type to search tables', |
| }); |
| |
| expect(screen.queryByText('table_a')).not.toBeInTheDocument(); |
| expect(getSelectItemContainer(tableSelect)).toHaveLength(0); |
| |
| await userEvent.click(tableSelect); |
| |
| await waitFor(async () => { |
| const item = await screen.findAllByText('table_b'); |
| await userEvent.click(item[item.length - 1]); |
| }); |
| |
| await waitFor(async () => { |
| const item = await screen.findAllByText('table_c'); |
| await userEvent.click(item[item.length - 1]); |
| }); |
| |
| const selections = await screen.findAllByRole('option', { selected: true }); |
| expect(selections).toHaveLength(2); |
| expect(selections[0]).toHaveTextContent('table_b'); |
| expect(selections[1]).toHaveTextContent('table_c'); |
| }); |
| |
| test('TableOption renders correct icons for different table types', () => { |
| // Test regular table |
| const tableTable = { |
| value: 'test_table', |
| type: 'table', |
| label: 'test_table', |
| }; |
| const { container: tableContainer } = render( |
| <TableOption table={tableTable} />, |
| ); |
| expect(tableContainer.querySelector('.anticon')).toBeInTheDocument(); |
| |
| // Test view |
| const viewTable = { value: 'test_view', type: 'view', label: 'test_view' }; |
| const { container: viewContainer } = render( |
| <TableOption table={viewTable} />, |
| ); |
| expect(viewContainer.querySelector('.anticon')).toBeInTheDocument(); |
| |
| // Test materialized view |
| const materializedViewTable = { |
| value: 'test_materialized_view', |
| type: 'materialized_view', |
| label: 'test_materialized_view', |
| }; |
| const { container: mvContainer } = render( |
| <TableOption table={materializedViewTable} />, |
| ); |
| expect(mvContainer.querySelector('.anticon')).toBeInTheDocument(); |
| }); |