Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1ad9ecb
test: add comprehensive React Testing Library and integration tests f…
sadpandajoe Nov 15, 2025
d4378ca
fix(tests): improve DatasetList test stability and cleanup patterns
sadpandajoe Dec 17, 2025
859c50d
refactor(tests): simplify DatasetList listview tests and remove dupli…
sadpandajoe Dec 17, 2025
1aa17e4
fix(tests): improve DatasetList test reliability and add coverage
sadpandajoe Dec 18, 2025
fcee625
refactor(tests): improve DatasetList test determinism
sadpandajoe Dec 18, 2025
e808600
fix(tests): remove conditional filter setup for test isolation
sadpandajoe Dec 18, 2025
1c02b5e
fix(tests): address code review feedback for DatasetList tests
sadpandajoe Dec 18, 2025
6c919eb
test(DatasetList): add 403 permission denied error test
sadpandajoe Dec 18, 2025
e45e80f
fix(tests): assert 403 toast contains permission-denied message
sadpandajoe Dec 19, 2025
da769ef
fix(tests): resolve test isolation issues in DatasetList listview tests
sadpandajoe Dec 19, 2025
d92167d
fix(tests): address code review feedback for test isolation
sadpandajoe Dec 19, 2025
51fc573
fix(tests): improve CI stability for bulk select tests
sadpandajoe Dec 24, 2025
285cb4f
fix(tests): address code review feedback for test reliability
sadpandajoe Jan 6, 2026
501eb84
fix(tests): address code review feedback for test patterns
sadpandajoe Jan 7, 2026
cb1cc0b
fix(tests): address code review feedback for test robustness
sadpandajoe Jan 7, 2026
1606d2c
fix(tests): address code review feedback for test reliability
sadpandajoe Jan 8, 2026
f62fc28
test(DatasetList): add tests for error paths, bulk select copy, and d…
sadpandajoe Jan 9, 2026
acd5ba2
fix(tests): address code review feedback for test reliability
sadpandajoe Jan 9, 2026
4180b80
fix(tests): improve async sequencing in DatasetList tests
sadpandajoe Jan 9, 2026
0d86288
fix(tests): use two-phase wait pattern for bulk select assertions
sadpandajoe Jan 27, 2026
6bb6684
fix(tests): add 30s timeouts to bulk select tests for CI stability
sadpandajoe Jan 27, 2026
660c330
fix(tests): add missing timeout and increase filter test timeout
sadpandajoe Jan 28, 2026
aa4719f
fix(tests): add async cleanup and increase integration test timeout
sadpandajoe Jan 28, 2026
0d3c6b4
fix(tests): add 30s timeout to bulk select checkbox test
sadpandajoe Jan 28, 2026
9958049
fix(tests): address code review feedback for test robustness
sadpandajoe Jan 28, 2026
d2765b9
fix(tests): improve test robustness with row-scoped queries and findA…
sadpandajoe Jan 28, 2026
d76edb3
fix(tests): use table-scoped async queries and stable modal anchors
sadpandajoe Jan 28, 2026
c9471ef
fix(tests): remove double URL-decoding in rison parameter extraction
sadpandajoe Jan 28, 2026
f109619
fix(tests): add explicit timeouts for CI-slow bulk delete and admin U…
sadpandajoe Jan 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
282 changes: 282 additions & 0 deletions superset-frontend/src/features/datasets/DuplicateDatasetModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
/**
* 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 } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { ThemeProvider, supersetTheme } from '@apache-superset/core';
import DuplicateDatasetModal from './DuplicateDatasetModal';

// Test-only fixture type that includes all fields from API responses
// Matches VirtualDataset structure from DatasetList but defined locally for tests
interface VirtualDatasetFixture {
id: number;
table_name: string;
kind: string;
schema: string;
database: {
id: string;
database_name: string;
};
owners: Array<{ first_name: string; last_name: string; id: number }>;
changed_by_name: string;
changed_by: string;
changed_on_delta_humanized: string;
explore_url: string;
extra: string;
sql: string | null;
}

// Test fixture with extra/sql fields that exist in actual API responses
const mockDataset: VirtualDatasetFixture = {
id: 1,
table_name: 'original_dataset',
kind: 'virtual',
schema: 'public',
database: {
id: '1',
database_name: 'PostgreSQL',
},
owners: [],
changed_by_name: 'Admin',
changed_by: 'Admin User',
changed_on_delta_humanized: '1 day ago',
explore_url: '/explore/?datasource=1__table',
extra: '{}',
sql: 'SELECT * FROM table',
};

const Wrapper = ({
dataset,
onHide,
onDuplicate,
}: {
dataset: VirtualDatasetFixture | null;
onHide: jest.Mock;
onDuplicate: jest.Mock;
}) => (
<ThemeProvider theme={supersetTheme}>
<DuplicateDatasetModal
dataset={dataset}
onHide={onHide}
onDuplicate={onDuplicate}
/>
</ThemeProvider>
);

const renderModal = (
dataset: VirtualDatasetFixture | null,
onHide: jest.Mock,
onDuplicate: jest.Mock,
) =>
render(
<Wrapper dataset={dataset} onHide={onHide} onDuplicate={onDuplicate} />,
);

test('modal opens when dataset is provided', async () => {
const onHide = jest.fn();
const onDuplicate = jest.fn();

renderModal(mockDataset, onHide, onDuplicate);

// Modal should be visible
expect(await screen.findByText('Duplicate dataset')).toBeInTheDocument();

// Input field should be present
expect(screen.getByTestId('duplicate-modal-input')).toBeInTheDocument();

// Duplicate button should be present
expect(
screen.getByRole('button', { name: /duplicate/i }),
).toBeInTheDocument();
});

test('modal does not open when dataset is null', () => {
const onHide = jest.fn();
const onDuplicate = jest.fn();

renderModal(null, onHide, onDuplicate);

// Modal should not be visible
expect(screen.queryByText('Duplicate dataset')).not.toBeInTheDocument();
});

test('duplicate button disabled after clearing input', async () => {
const onHide = jest.fn();
const onDuplicate = jest.fn();

renderModal(mockDataset, onHide, onDuplicate);

const input = await screen.findByTestId('duplicate-modal-input');

// Type some text first
await userEvent.type(input, 'test');

// Then clear it
await userEvent.clear(input);

// Duplicate button should now be disabled (empty input)
const duplicateButton = screen.getByRole('button', { name: /duplicate/i });
expect(duplicateButton).toBeDisabled();
});

test('duplicate button enabled when name is entered', async () => {
const onHide = jest.fn();
const onDuplicate = jest.fn();

renderModal(mockDataset, onHide, onDuplicate);

const input = await screen.findByTestId('duplicate-modal-input');

// Type a new name
await userEvent.type(input, 'new_dataset_copy');

// Duplicate button should now be enabled
const duplicateButton = await screen.findByRole('button', {
name: /duplicate/i,
});
expect(duplicateButton).toBeEnabled();
});

test('clicking Duplicate calls onDuplicate with new name', async () => {
const onHide = jest.fn();
const onDuplicate = jest.fn();

renderModal(mockDataset, onHide, onDuplicate);

const input = await screen.findByTestId('duplicate-modal-input');

// Type a new name
await userEvent.type(input, 'new_dataset_copy');

// Click Duplicate button
const duplicateButton = await screen.findByRole('button', {
name: /duplicate/i,
});
await userEvent.click(duplicateButton);

// onDuplicate should be called with the new name
await waitFor(() => {
expect(onDuplicate).toHaveBeenCalledWith('new_dataset_copy');
});
});

test('pressing Enter key triggers duplicate action', async () => {
const onHide = jest.fn();
const onDuplicate = jest.fn();

renderModal(mockDataset, onHide, onDuplicate);

const input = await screen.findByTestId('duplicate-modal-input');

// Clear any existing value and type new name with Enter at end
await userEvent.clear(input);
await userEvent.type(input, 'new_dataset_copy{enter}');

// onDuplicate should be called by onPressEnter handler
await waitFor(() => {
expect(onDuplicate).toHaveBeenCalledWith('new_dataset_copy');
});
});

test('modal closes when onHide is called', async () => {
const onHide = jest.fn();
const onDuplicate = jest.fn();

const { rerender } = renderModal(mockDataset, onHide, onDuplicate);

expect(await screen.findByText('Duplicate dataset')).toBeInTheDocument();

// Simulate closing the modal by setting dataset to null
rerender(
<Wrapper dataset={null} onHide={onHide} onDuplicate={onDuplicate} />,
);

// Modal should no longer be visible (Ant Design keeps it in DOM but hides it)
await waitFor(() => {
expect(screen.queryByText('Duplicate dataset')).not.toBeVisible();
});
});

test('cancel button clears input and closes modal', async () => {
const onHide = jest.fn();
const onDuplicate = jest.fn();

const { rerender } = renderModal(mockDataset, onHide, onDuplicate);

const input = await screen.findByTestId('duplicate-modal-input');

// Type some text
await userEvent.type(input, 'test_name');

expect(input).toHaveValue('test_name');

// Click cancel button
const cancelButton = await screen.findByRole('button', { name: /cancel/i });
await userEvent.click(cancelButton);

// onHide should be called
expect(onHide).toHaveBeenCalled();

// Simulate closing the modal (parent sets dataset to null)
rerender(
<Wrapper dataset={null} onHide={onHide} onDuplicate={onDuplicate} />,
);

// Modal should be hidden
await waitFor(() => {
expect(screen.queryByText('Duplicate dataset')).not.toBeVisible();
});

// Reopen with same dataset - input should be cleared
rerender(
<Wrapper dataset={mockDataset} onHide={onHide} onDuplicate={onDuplicate} />,
);

const reopenedInput = await screen.findByTestId('duplicate-modal-input');
expect(reopenedInput).toHaveValue('');
});

test('input field clears when new dataset is provided', async () => {
const onHide = jest.fn();
const onDuplicate = jest.fn();

const { rerender } = renderModal(mockDataset, onHide, onDuplicate);

const input = await screen.findByTestId('duplicate-modal-input');

// Type a name
await userEvent.type(input, 'old_name');

expect(input).toHaveValue('old_name');

// Switch to different dataset
const newDataset: VirtualDatasetFixture = {
...mockDataset,
id: 2,
table_name: 'different_dataset',
};

rerender(
<Wrapper dataset={newDataset} onHide={onHide} onDuplicate={onDuplicate} />,
);

// Input should be cleared
await waitFor(() => {
expect(input).toHaveValue('');
});
});
Loading
Loading