| /** |
| * 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 thunk from 'redux-thunk'; |
| import URI from 'urijs'; |
| import { fireEvent, render, waitFor } from 'spec/helpers/testing-library'; |
| import fetchMock from 'fetch-mock'; |
| import TabbedSqlEditors from 'src/SqlLab/components/TabbedSqlEditors'; |
| import { initialState } from 'src/SqlLab/fixtures'; |
| import { newQueryTabName } from 'src/SqlLab/utils/newQueryTabName'; |
| import { Store } from 'redux'; |
| import { RootState } from 'src/views/store'; |
| import { SET_ACTIVE_QUERY_EDITOR } from 'src/SqlLab/actions/sqlLab'; |
| |
| jest.mock('src/SqlLab/components/SqlEditor', () => () => ( |
| <div data-test="mock-sql-editor" /> |
| )); |
| |
| const middlewares = [thunk]; |
| const mockStore = configureStore(middlewares); |
| const store = mockStore(initialState); |
| |
| const setup = (overridesStore?: Store, initialState?: RootState) => |
| render(<TabbedSqlEditors />, { |
| useRedux: true, |
| initialState, |
| ...(overridesStore && { store: overridesStore }), |
| }); |
| let pathStub = jest.spyOn(URI.prototype, 'path'); |
| |
| beforeEach(() => { |
| fetchMock.get('glob:*/api/v1/database/*', {}); |
| fetchMock.get('glob:*/api/v1/saved_query/*', {}); |
| pathStub = jest.spyOn(URI.prototype, 'path').mockReturnValue(`/sqllab/`); |
| store.clearActions(); |
| }); |
| |
| afterEach(() => { |
| fetchMock.reset(); |
| pathStub.mockReset(); |
| }); |
| |
| // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks |
| describe('componentDidMount', () => { |
| let uriStub = jest.spyOn(URI.prototype, 'search'); |
| let replaceState = jest.spyOn(window.history, 'replaceState'); |
| beforeEach(() => { |
| replaceState = jest.spyOn(window.history, 'replaceState'); |
| uriStub = jest.spyOn(URI.prototype, 'search'); |
| }); |
| afterEach(() => { |
| replaceState.mockReset(); |
| uriStub.mockReset(); |
| }); |
| test('should handle id', async () => { |
| const id = 1; |
| fetchMock.get(`glob:*/api/v1/sqllab/permalink/kv:${id}`, { |
| label: 'test permalink', |
| sql: 'SELECT * FROM test_table', |
| dbId: 1, |
| }); |
| uriStub.mockReturnValue({ id: 1 }); |
| setup(store); |
| expect(replaceState).toHaveBeenCalledWith( |
| expect.anything(), |
| expect.anything(), |
| '/sqllab', |
| ); |
| await waitFor(() => |
| expect( |
| fetchMock.calls(`glob:*/api/v1/sqllab/permalink/kv:${id}`), |
| ).toHaveLength(1), |
| ); |
| fetchMock.reset(); |
| }); |
| test('should handle permalink', async () => { |
| const key = '9sadkfl'; |
| fetchMock.get(`glob:*/api/v1/sqllab/permalink/${key}`, { |
| label: 'test permalink', |
| sql: 'SELECT * FROM test_table', |
| dbId: 1, |
| }); |
| pathStub.mockReturnValue(`/sqllab/p/${key}`); |
| setup(store); |
| await waitFor(() => |
| expect( |
| fetchMock.calls(`glob:*/api/v1/sqllab/permalink/${key}`), |
| ).toHaveLength(1), |
| ); |
| expect(replaceState).toHaveBeenCalledWith( |
| expect.anything(), |
| expect.anything(), |
| '/sqllab', |
| ); |
| fetchMock.reset(); |
| }); |
| test('should handle savedQueryId', () => { |
| uriStub.mockReturnValue({ savedQueryId: 1 }); |
| setup(store); |
| expect(replaceState).toHaveBeenCalledWith( |
| expect.anything(), |
| expect.anything(), |
| '/sqllab', |
| ); |
| }); |
| test('should handle sql', () => { |
| uriStub.mockReturnValue({ sql: 1, dbid: 1 }); |
| setup(store); |
| expect(replaceState).toHaveBeenCalledWith( |
| expect.anything(), |
| expect.anything(), |
| '/sqllab', |
| ); |
| }); |
| test('should handle custom url params', () => { |
| uriStub.mockReturnValue({ |
| sql: 1, |
| dbid: 1, |
| custom_value: 'str', |
| extra_attr1: 'true', |
| }); |
| setup(store); |
| expect(replaceState).toHaveBeenCalledWith( |
| expect.anything(), |
| expect.anything(), |
| '/sqllab?custom_value=str&extra_attr1=true', |
| ); |
| }); |
| }); |
| |
| test('should removeQueryEditor', async () => { |
| const { getByRole, getAllByRole, queryByText } = setup( |
| undefined, |
| initialState, |
| ); |
| const tabCount = getAllByRole('tab').filter( |
| tab => !tab.classList.contains('ant-tabs-tab-remove'), |
| ).length; |
| const tabList = getByRole('tablist'); |
| const closeButton = tabList.getElementsByTagName('button')[0]; |
| expect(closeButton).toBeInTheDocument(); |
| if (closeButton) { |
| fireEvent.click(closeButton); |
| } |
| await waitFor(() => |
| expect( |
| getAllByRole('tab').filter( |
| tab => !tab.classList.contains('ant-tabs-tab-remove'), |
| ).length, |
| ).toEqual(tabCount - 1), |
| ); |
| expect( |
| queryByText(initialState.sqlLab.queryEditors[0].name), |
| ).not.toBeInTheDocument(); |
| }); |
| test('should add new query editor', async () => { |
| const { getAllByLabelText, getAllByRole } = setup(undefined, initialState); |
| const tabCount = getAllByRole('tab').filter( |
| tab => !tab.classList.contains('ant-tabs-tab-remove'), |
| ).length; |
| fireEvent.click(getAllByLabelText('Add tab')[0]); |
| await waitFor(() => |
| expect( |
| getAllByRole('tab').filter( |
| tab => !tab.classList.contains('ant-tabs-tab-remove'), |
| ).length, |
| ).toEqual(tabCount + 1), |
| ); |
| expect( |
| getAllByRole('tab').filter( |
| tab => !tab.classList.contains('ant-tabs-tab-remove'), |
| )[tabCount], |
| ).toHaveTextContent(/Untitled Query (\d+)+/); |
| }); |
| test('should properly increment query tab name', async () => { |
| const { getAllByLabelText, getAllByRole } = setup(undefined, initialState); |
| const tabCount = getAllByRole('tab').filter( |
| tab => !tab.classList.contains('ant-tabs-tab-remove'), |
| ).length; |
| const newTitle = newQueryTabName(initialState.sqlLab.queryEditors); |
| fireEvent.click(getAllByLabelText('Add tab')[0]); |
| await waitFor(() => |
| expect( |
| getAllByRole('tab').filter( |
| tab => !tab.classList.contains('ant-tabs-tab-remove'), |
| ).length, |
| ).toEqual(tabCount + 1), |
| ); |
| expect( |
| getAllByRole('tab').filter( |
| tab => !tab.classList.contains('ant-tabs-tab-remove'), |
| )[tabCount], |
| ).toHaveTextContent(newTitle); |
| }); |
| test('should handle select', async () => { |
| const { getAllByRole } = setup(store); |
| const tabs = getAllByRole('tab').filter( |
| tab => !tab.classList.contains('ant-tabs-tab-remove'), |
| ); |
| fireEvent.click(tabs[1]); |
| await waitFor(() => expect(store.getActions()).toHaveLength(1)); |
| expect(store.getActions()[0]).toEqual( |
| expect.objectContaining({ |
| type: SET_ACTIVE_QUERY_EDITOR, |
| queryEditor: initialState.sqlLab.queryEditors[1], |
| }), |
| ); |
| }); |
| test('should render', () => { |
| const { getAllByRole } = setup(store); |
| const tabs = getAllByRole('tab').filter( |
| tab => !tab.classList.contains('ant-tabs-tab-remove'), |
| ); |
| expect(tabs).toHaveLength(initialState.sqlLab.queryEditors.length); |
| }); |
| test('should disable new tab when offline', () => { |
| const { queryAllByLabelText } = setup(undefined, { |
| ...initialState, |
| sqlLab: { |
| ...initialState.sqlLab, |
| offline: true, |
| }, |
| }); |
| expect(queryAllByLabelText('Add tab').length).toEqual(0); |
| }); |
| test('should have an empty state when query editors is empty', async () => { |
| const { getByText, getByRole } = setup(undefined, { |
| ...initialState, |
| sqlLab: { |
| ...initialState.sqlLab, |
| queryEditors: [], |
| tabHistory: [], |
| }, |
| }); |
| |
| // Clear the new tab applied in componentDidMount and check the state of the empty tab |
| const removeTabButton = getByRole('tab', { name: 'remove' }); |
| fireEvent.click(removeTabButton); |
| |
| await waitFor(() => |
| expect(getByText('Add a new tab to create SQL Query')).toBeInTheDocument(), |
| ); |
| }); |