mirror of
https://github.com/apache/superset.git
synced 2026-06-12 19:19:20 +00:00
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Evan Rusackas <evan@preset.io>
312 lines
9.9 KiB
TypeScript
312 lines
9.9 KiB
TypeScript
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import {
|
|
render,
|
|
screen,
|
|
waitFor,
|
|
fireEvent,
|
|
cleanup,
|
|
defaultStore as store,
|
|
} from 'spec/helpers/testing-library';
|
|
import fetchMock from 'fetch-mock';
|
|
import { SupersetClient } from '@superset-ui/core';
|
|
import mockDatasource from 'spec/fixtures/mockDatasource';
|
|
import React from 'react';
|
|
import DatasourceModalComponent from '.';
|
|
|
|
// Cast to accept partial mock props in tests
|
|
const DatasourceModal = DatasourceModalComponent as unknown as React.FC<
|
|
Record<string, any>
|
|
>;
|
|
|
|
// Define your constants here
|
|
const SAVE_ENDPOINT = 'glob:*/api/v1/dataset/7';
|
|
const SAVE_PAYLOAD = { new: 'data' };
|
|
const SAVE_DATASOURCE_ENDPOINT = 'glob:*/api/v1/dataset/7?override_columns=*';
|
|
const GET_DATASOURCE_ENDPOINT = 'glob:*/api/v1/dataset/7';
|
|
const GET_DATABASE_ENDPOINT = 'glob:*/api/v1/database/?q=*';
|
|
|
|
const mockedProps = {
|
|
datasource: mockDatasource['7__table'],
|
|
addSuccessToast: () => {},
|
|
addDangerToast: () => {},
|
|
onChange: () => {},
|
|
onHide: () => {},
|
|
show: true,
|
|
onDatasourceSave: jest.fn(),
|
|
};
|
|
|
|
let container: HTMLElement;
|
|
const routeProps = {
|
|
history: {},
|
|
location: {},
|
|
match: {},
|
|
};
|
|
async function renderAndWait(props = mockedProps) {
|
|
const { container: renderedContainer } = render(
|
|
<DatasourceModal {...props} {...routeProps} />,
|
|
{ store, useRouter: true },
|
|
);
|
|
|
|
container = renderedContainer;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
fetchMock.clearHistory().removeRoutes();
|
|
cleanup();
|
|
renderAndWait();
|
|
fetchMock.post(SAVE_ENDPOINT, SAVE_PAYLOAD);
|
|
fetchMock.put(SAVE_DATASOURCE_ENDPOINT, {});
|
|
fetchMock.get(GET_DATASOURCE_ENDPOINT, { result: {} });
|
|
fetchMock.get(GET_DATABASE_ENDPOINT, { result: [] });
|
|
});
|
|
|
|
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
|
describe('DatasourceModal', () => {
|
|
test('renders', async () => {
|
|
expect(container).toBeDefined();
|
|
});
|
|
|
|
test('renders the component', () => {
|
|
expect(screen.getByText('Edit Dataset')).toBeInTheDocument();
|
|
});
|
|
|
|
test('renders a Modal', async () => {
|
|
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
|
});
|
|
|
|
test('renders a DatasourceEditor', async () => {
|
|
expect(screen.getByTestId('datasource-editor')).toBeInTheDocument();
|
|
});
|
|
|
|
test('disables the save button when the datasource is managed externally', () => {
|
|
// the render is currently in a before operation, so it needs to be cleaned up
|
|
// we could alternatively move all the renders back into the tests or find a better
|
|
// way to automatically render but still allow to pass in props with the tests
|
|
cleanup();
|
|
|
|
renderAndWait({
|
|
...mockedProps,
|
|
datasource: { ...mockedProps.datasource, is_managed_externally: true },
|
|
});
|
|
const saveButton = screen.getByTestId('datasource-modal-save');
|
|
expect(saveButton).toBeDisabled();
|
|
});
|
|
|
|
test('calls the onDatasourceSave function when the save button is clicked', async () => {
|
|
cleanup();
|
|
const onDatasourceSave = jest.fn();
|
|
|
|
renderAndWait({
|
|
...mockedProps,
|
|
onDatasourceSave:
|
|
onDatasourceSave as unknown as typeof mockedProps.onDatasourceSave,
|
|
});
|
|
const saveButton = screen.getByTestId('datasource-modal-save');
|
|
fireEvent.click(saveButton);
|
|
const okButton = await screen.findByRole('button', { name: 'OK' });
|
|
fireEvent.click(okButton);
|
|
await waitFor(() => {
|
|
expect(onDatasourceSave).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
test('should render error dialog', async () => {
|
|
const putSpy = jest
|
|
.spyOn(SupersetClient, 'put')
|
|
.mockRejectedValue(new Error('Something went wrong'));
|
|
|
|
const saveButton = screen.getByTestId('datasource-modal-save');
|
|
fireEvent.click(saveButton);
|
|
const okButton = await screen.findByRole('button', { name: 'OK' });
|
|
fireEvent.click(okButton);
|
|
|
|
const errorElements = await screen.findAllByText('Error saving dataset');
|
|
const errorDiv = errorElements.find(el => el.closest('div'));
|
|
expect(errorDiv).toBeInTheDocument();
|
|
putSpy.mockRestore();
|
|
});
|
|
|
|
test('shows sync columns checkbox when SQL changes', async () => {
|
|
cleanup();
|
|
const datasourceWithSQL = {
|
|
...mockedProps.datasource,
|
|
sql: 'SELECT * FROM original_table',
|
|
};
|
|
const modifiedDatasource = {
|
|
...datasourceWithSQL,
|
|
sql: 'SELECT * FROM new_table', // Different SQL to trigger checkbox
|
|
};
|
|
|
|
const { rerender } = render(
|
|
<DatasourceModal {...mockedProps} datasource={datasourceWithSQL} />,
|
|
{ store, useRouter: true },
|
|
);
|
|
|
|
// Update with modified SQL
|
|
rerender(
|
|
<DatasourceModal {...mockedProps} datasource={modifiedDatasource} />,
|
|
);
|
|
|
|
const saveButton = screen.getByTestId('datasource-modal-save');
|
|
fireEvent.click(saveButton);
|
|
|
|
// Wait for confirmation modal to appear
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Confirm save')).toBeInTheDocument();
|
|
});
|
|
|
|
// Checkbox should be present and checked by default when SQL changes
|
|
const checkbox = await screen.findByRole('checkbox');
|
|
expect(checkbox).toBeInTheDocument();
|
|
expect(checkbox).toBeChecked();
|
|
|
|
// Should show the sync columns message
|
|
expect(screen.getByText('Automatically sync columns')).toBeInTheDocument();
|
|
});
|
|
|
|
test('syncs columns when checkbox is checked and submits with override_columns=true', async () => {
|
|
const datasourceWithSQL = {
|
|
...mockedProps.datasource,
|
|
sql: 'SELECT * FROM original_table',
|
|
};
|
|
const modifiedDatasource = {
|
|
...datasourceWithSQL,
|
|
sql: 'SELECT * FROM new_table',
|
|
};
|
|
|
|
// Render with the initial datasource
|
|
cleanup();
|
|
fetchMock.clearHistory().removeRoutes();
|
|
fetchMock.post(SAVE_ENDPOINT, SAVE_PAYLOAD);
|
|
fetchMock.put(SAVE_DATASOURCE_ENDPOINT, {});
|
|
fetchMock.get(GET_DATASOURCE_ENDPOINT, { result: {} });
|
|
fetchMock.get(GET_DATABASE_ENDPOINT, { result: [] });
|
|
|
|
const { rerender } = render(
|
|
<DatasourceModal {...mockedProps} datasource={datasourceWithSQL} />,
|
|
{ store, useRouter: true },
|
|
);
|
|
|
|
// Update with modified SQL to trigger checkbox
|
|
rerender(
|
|
<DatasourceModal {...mockedProps} datasource={modifiedDatasource} />,
|
|
);
|
|
|
|
const saveButton = screen.getByTestId('datasource-modal-save');
|
|
fireEvent.click(saveButton);
|
|
|
|
// Wait for confirmation modal to appear
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Confirm save')).toBeInTheDocument();
|
|
});
|
|
|
|
// Checkbox should be present and checked by default when SQL changes
|
|
const checkbox = await screen.findByRole('checkbox');
|
|
expect(checkbox).toBeChecked();
|
|
|
|
// Click OK to submit
|
|
const okButton = screen.getByRole('button', { name: 'OK' });
|
|
fireEvent.click(okButton);
|
|
|
|
// Verify the PUT request was made with override_columns=true
|
|
await waitFor(() => {
|
|
const putCalls = fetchMock.callHistory
|
|
.calls()
|
|
.filter(
|
|
call =>
|
|
call.url.includes('/api/v1/dataset/7') &&
|
|
call.url.includes('override_columns') &&
|
|
call.options?.method === 'put',
|
|
);
|
|
expect(putCalls.length).toBeGreaterThan(0);
|
|
expect(putCalls[putCalls.length - 1].url).toContain(
|
|
'override_columns=true',
|
|
);
|
|
});
|
|
});
|
|
|
|
test('does not sync columns when checkbox is unchecked and submits with override_columns=false', async () => {
|
|
const datasourceWithSQL = {
|
|
...mockedProps.datasource,
|
|
sql: 'SELECT * FROM original_table',
|
|
};
|
|
const modifiedDatasource = {
|
|
...datasourceWithSQL,
|
|
sql: 'SELECT * FROM new_table',
|
|
};
|
|
|
|
// Render with the initial datasource
|
|
cleanup();
|
|
fetchMock.clearHistory().removeRoutes();
|
|
fetchMock.post(SAVE_ENDPOINT, SAVE_PAYLOAD);
|
|
fetchMock.put(SAVE_DATASOURCE_ENDPOINT, {});
|
|
fetchMock.get(GET_DATASOURCE_ENDPOINT, { result: {} });
|
|
fetchMock.get(GET_DATABASE_ENDPOINT, { result: [] });
|
|
|
|
const { rerender } = render(
|
|
<DatasourceModal {...mockedProps} datasource={datasourceWithSQL} />,
|
|
{ store, useRouter: true },
|
|
);
|
|
|
|
// Update with modified SQL to trigger checkbox
|
|
rerender(
|
|
<DatasourceModal {...mockedProps} datasource={modifiedDatasource} />,
|
|
);
|
|
|
|
const saveButton = screen.getByTestId('datasource-modal-save');
|
|
fireEvent.click(saveButton);
|
|
|
|
// Wait for confirmation modal to appear
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Confirm save')).toBeInTheDocument();
|
|
});
|
|
|
|
// Checkbox should be present and checked by default when SQL changes
|
|
const checkbox = await screen.findByRole('checkbox');
|
|
expect(checkbox).toBeChecked();
|
|
|
|
// Uncheck the checkbox
|
|
fireEvent.click(checkbox);
|
|
|
|
// Verify checkbox is now unchecked
|
|
expect(checkbox).not.toBeChecked();
|
|
|
|
// Click OK to submit
|
|
const okButton = screen.getByRole('button', { name: 'OK' });
|
|
fireEvent.click(okButton);
|
|
|
|
// Verify the PUT request was made with override_columns=false
|
|
await waitFor(() => {
|
|
const putCalls = fetchMock.callHistory
|
|
.calls()
|
|
.filter(
|
|
call =>
|
|
call.url.includes('/api/v1/dataset/7') &&
|
|
call.url.includes('override_columns') &&
|
|
call.options?.method === 'put',
|
|
);
|
|
expect(putCalls.length).toBeGreaterThan(0);
|
|
expect(putCalls[putCalls.length - 1].url).toContain(
|
|
'override_columns=false',
|
|
);
|
|
});
|
|
});
|
|
});
|