This is an automated email from the ASF dual-hosted git repository.
msyavuz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 3a565a6c16e fix(tests): update DatasetList tests to new fetch-mock API
(#37623)
3a565a6c16e is described below
commit 3a565a6c16ec23251bf009b1ae64a56e1c2ca13a
Author: Joe Li <[email protected]>
AuthorDate: Tue Feb 3 03:15:58 2026 -0800
fix(tests): update DatasetList tests to new fetch-mock API (#37623)
Co-authored-by: Claude Opus 4.5 <[email protected]>
---
.../DatasetList/DatasetList.behavior.test.tsx | 78 +++--
.../DatasetList/DatasetList.integration.test.tsx | 52 +--
.../DatasetList/DatasetList.listview.test.tsx | 378 +++++++++++++--------
.../DatasetList/DatasetList.permissions.test.tsx | 82 +++--
.../src/pages/DatasetList/DatasetList.test.tsx | 84 +++--
.../pages/DatasetList/DatasetList.testHelpers.tsx | 183 +++++-----
6 files changed, 538 insertions(+), 319 deletions(-)
diff --git
a/superset-frontend/src/pages/DatasetList/DatasetList.behavior.test.tsx
b/superset-frontend/src/pages/DatasetList/DatasetList.behavior.test.tsx
index af73876de11..4aa30c34600 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.behavior.test.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.behavior.test.tsx
@@ -62,8 +62,8 @@ afterEach(async () => {
// Reset browser history state to prevent query params leaking between tests
window.history.replaceState({}, '', '/');
- fetchMock.resetHistory();
- fetchMock.restore();
+ fetchMock.clearHistory();
+ fetchMock.removeRoutes();
jest.restoreAllMocks();
});
@@ -97,7 +97,9 @@ test('typing in search triggers debounced API call with
search filter', async ()
const searchInput = within(searchContainer).getByRole('textbox');
// Record initial API calls
- const initialCallCount = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const initialCallCount = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
// Type search query and submit with Enter to trigger the debounced fetch
await userEvent.type(searchInput, 'sales{enter}');
@@ -105,16 +107,16 @@ test('typing in search triggers debounced API call with
search filter', async ()
// Wait for debounced API call
await waitFor(
() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(initialCallCount);
},
{ timeout: 5000 },
);
// Verify the latest API call includes search filter in URL
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
const latestCall = calls[calls.length - 1];
- const url = latestCall[0] as string;
+ const url = latestCall.url as string;
// URL should contain filters parameter with search term
expect(url).toContain('filters');
@@ -134,13 +136,14 @@ test('typing in search triggers debounced API call with
search filter', async ()
test('500 error triggers danger toast with error message', async () => {
const addDangerToast = jest.fn();
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{
status: 500,
body: { message: 'Internal Server Error' },
},
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
// Pass toast spy directly via props to bypass withToasts HOC
@@ -174,10 +177,11 @@ test('500 error triggers danger toast with error
message', async () => {
test('network timeout triggers danger toast', async () => {
const addDangerToast = jest.fn();
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ throws: new Error('Network timeout') },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
// Pass toast spy directly via props to bypass withToasts HOC
@@ -215,10 +219,14 @@ test('clicking delete opens modal with related objects
count', async () => {
// Set up delete mocks
setupDeleteMocks(datasetToDelete.id);
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [datasetToDelete], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [datasetToDelete],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -256,10 +264,14 @@ test('clicking delete opens modal with related objects
count', async () => {
test('clicking export calls handleResourceExport with dataset ID', async () =>
{
const datasetToExport = mockDatasets[0];
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [datasetToExport], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [datasetToExport],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -290,16 +302,23 @@ test('clicking duplicate opens modal and submits
duplicate request', async () =>
kind: 'virtual',
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [datasetToDuplicate], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [datasetToDuplicate],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
fetchMock.post(
API_ENDPOINTS.DATASET_DUPLICATE,
- { id: 999, table_name: 'Copy of Dataset' },
- { overwriteRoutes: true },
+ {
+ id: 999,
+ table_name: 'Copy of Dataset',
+ },
+ { name: API_ENDPOINTS.DATASET_DUPLICATE },
);
const addSuccessToast = jest.fn();
@@ -314,7 +333,7 @@ test('clicking duplicate opens modal and submits duplicate
request', async () =>
});
// Track initial dataset list API calls BEFORE duplicate action
- const initialDatasetCallCount = fetchMock.calls(
+ const initialDatasetCallCount = fetchMock.callHistory.calls(
API_ENDPOINTS.DATASETS,
).length;
@@ -341,11 +360,11 @@ test('clicking duplicate opens modal and submits
duplicate request', async () =>
// Verify duplicate API was called with correct payload
await waitFor(() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASET_DUPLICATE);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASET_DUPLICATE);
expect(calls.length).toBeGreaterThan(0);
// Verify POST body contains correct dataset info
- const requestBody = JSON.parse(calls[0][1]?.body as string);
+ const requestBody = JSON.parse(calls[0].options?.body as string);
expect(requestBody.base_model_id).toBe(datasetToDuplicate.id);
expect(requestBody.table_name).toBe('Copy of Dataset');
});
@@ -358,7 +377,7 @@ test('clicking duplicate opens modal and submits duplicate
request', async () =>
// Verify refreshData() is called (observable via new dataset list API call)
await waitFor(
() => {
- const datasetCalls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const datasetCalls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(datasetCalls.length).toBeGreaterThan(initialDatasetCallCount);
},
{ timeout: 3000 },
@@ -379,10 +398,14 @@ test('certified dataset shows badge and tooltip with
certification details', asy
}),
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [certifiedDataset], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [certifiedDataset],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -420,10 +443,14 @@ test('dataset with warning shows icon and tooltip with
markdown content', async
}),
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [datasetWithWarning], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [datasetWithWarning],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -455,10 +482,11 @@ test('dataset with warning shows icon and tooltip with
markdown content', async
test('dataset name links to Explore with correct URL and accessible label',
async () => {
const dataset = mockDatasets[0];
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
diff --git
a/superset-frontend/src/pages/DatasetList/DatasetList.integration.test.tsx
b/superset-frontend/src/pages/DatasetList/DatasetList.integration.test.tsx
index 0b228509b2a..80490826077 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.integration.test.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.integration.test.tsx
@@ -62,8 +62,8 @@ afterEach(async () => {
// Reset browser history state to prevent query params leaking between tests
window.history.replaceState({}, '', '/');
- fetchMock.resetHistory();
- fetchMock.restore();
+ fetchMock.clearHistory();
+ fetchMock.removeRoutes();
jest.restoreAllMocks();
});
@@ -72,11 +72,10 @@ test('ListView provider correctly merges filter + sort +
pagination state on ref
// the ListView provider correctly merges them for the API call.
// Component tests verify individual pieces persist; this verifies they
COMBINE correctly.
- fetchMock.get(
- API_ENDPOINTS.DATASETS,
- { result: mockDatasets, count: mockDatasets.length },
- { overwriteRoutes: true },
- );
+ fetchMock.get(API_ENDPOINTS.DATASETS, {
+ result: mockDatasets,
+ count: mockDatasets.length,
+ });
renderDatasetList(mockAdminUser);
@@ -90,30 +89,34 @@ test('ListView provider correctly merges filter + sort +
pagination state on ref
name: /Name/i,
});
- const callsBeforeSort = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const callsBeforeSort = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
await userEvent.click(nameHeader);
// Wait for sort-triggered refetch to complete before applying filter
await waitFor(() => {
- expect(fetchMock.calls(API_ENDPOINTS.DATASETS).length).toBeGreaterThan(
- callsBeforeSort,
- );
+ expect(
+ fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS).length,
+ ).toBeGreaterThan(callsBeforeSort);
});
// 2. Apply a filter using selectOption helper
- const beforeFilterCallCount = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const beforeFilterCallCount = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
await selectOption('Virtual', 'Type');
// Wait for filter API call to complete
await waitFor(() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(beforeFilterCallCount);
});
// 3. Verify the final API call contains ALL three state pieces merged
correctly
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
const latestCall = calls[calls.length - 1];
- const url = latestCall[0] as string;
+ const url = latestCall.url as string;
// Decode the rison payload using URL parser
const risonPayload = new URL(url, 'http://localhost').searchParams.get('q');
@@ -147,11 +150,10 @@ test('bulk action orchestration: selection → action →
cleanup cycle works co
setupBulkDeleteMocks();
- fetchMock.get(
- API_ENDPOINTS.DATASETS,
- { result: mockDatasets, count: mockDatasets.length },
- { overwriteRoutes: true },
- );
+ fetchMock.get(API_ENDPOINTS.DATASETS, {
+ result: mockDatasets,
+ count: mockDatasets.length,
+ });
renderDatasetList(mockAdminUser);
@@ -213,7 +215,7 @@ test('bulk action orchestration: selection → action →
cleanup cycle works co
await userEvent.type(confirmInput, 'DELETE');
// Capture datasets call count before confirming
- const datasetsCallCountBeforeDelete = fetchMock.calls(
+ const datasetsCallCountBeforeDelete = fetchMock.callHistory.calls(
API_ENDPOINTS.DATASETS,
).length;
@@ -224,7 +226,9 @@ test('bulk action orchestration: selection → action →
cleanup cycle works co
// 3. Wait for bulk delete API call to be made
await waitFor(() => {
- const deleteCalls = fetchMock.calls(API_ENDPOINTS.DATASET_BULK_DELETE);
+ const deleteCalls = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASET_BULK_DELETE,
+ );
expect(deleteCalls.length).toBeGreaterThan(0);
});
@@ -235,7 +239,9 @@ test('bulk action orchestration: selection → action →
cleanup cycle works co
// Wait for datasets refetch after delete
await waitFor(() => {
- const datasetsCallCount = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const datasetsCallCount = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
expect(datasetsCallCount).toBeGreaterThan(datasetsCallCountBeforeDelete);
});
diff --git
a/superset-frontend/src/pages/DatasetList/DatasetList.listview.test.tsx
b/superset-frontend/src/pages/DatasetList/DatasetList.listview.test.tsx
index b43f78dc9c4..449d85a7ba5 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.listview.test.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.listview.test.tsx
@@ -33,6 +33,7 @@ import {
mockHandleResourceExport,
assertOnlyExpectedCalls,
API_ENDPOINTS,
+ DELETE_ROUTE_NAME,
} from './DatasetList.testHelpers';
const mockAddDangerToast = jest.fn();
@@ -112,10 +113,11 @@ const setupErrorTestScenario = ({
});
// Configure fetchMock to return single dataset
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
// Render component with toast mocks
@@ -143,8 +145,8 @@ afterEach(async () => {
// QueryParamProvider reads from window.history, which persists across
renders
window.history.replaceState({}, '', '/');
- fetchMock.resetHistory();
- fetchMock.restore();
+ fetchMock.clearHistory();
+ fetchMock.removeRoutes();
jest.restoreAllMocks();
});
@@ -199,10 +201,11 @@ test('renders all required column headers', async () => {
test('displays dataset name in Name column', async () => {
const dataset = mockDatasets[0];
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -216,10 +219,14 @@ test('displays dataset type as Physical or Virtual',
async () => {
const physicalDataset = mockDatasets[0]; // kind: 'physical'
const virtualDataset = mockDatasets[1]; // kind: 'virtual'
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [physicalDataset, virtualDataset], count: 2 },
- { overwriteRoutes: true },
+ {
+ result: [physicalDataset, virtualDataset],
+ count: 2,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -234,10 +241,11 @@ test('displays dataset type as Physical or Virtual',
async () => {
test('displays database name in Database column', async () => {
const dataset = mockDatasets[0];
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -252,10 +260,11 @@ test('displays database name in Database column', async
() => {
test('displays schema name in Schema column', async () => {
const dataset = mockDatasets[0];
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -268,10 +277,11 @@ test('displays schema name in Schema column', async () =>
{
test('displays last modified date in humanized format', async () => {
const dataset = mockDatasets[0];
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -296,21 +306,23 @@ test('sorting by Name column updates API call with sort
parameter', async () =>
});
// Record initial calls
- const initialCalls = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const initialCalls = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
// Click Name header to sort
await userEvent.click(nameHeader);
// Wait for new API call
await waitFor(() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(initialCalls);
});
// Verify latest call includes sort parameter
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
const latestCall = calls[calls.length - 1];
- const url = latestCall[0] as string;
+ const url = latestCall.url as string;
// URL should contain order_column for sorting
expect(url).toMatch(/order_column|sort/);
@@ -328,17 +340,19 @@ test('sorting by Database column updates sort parameter',
async () => {
name: /Database/i,
});
- const initialCalls = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const initialCalls = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
await userEvent.click(databaseHeader);
await waitFor(() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(initialCalls);
});
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
- const url = calls[calls.length - 1][0] as string;
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
+ const url = calls[calls.length - 1].url as string;
expect(url).toMatch(/order_column|sort/);
});
@@ -354,27 +368,30 @@ test('sorting by Last modified column updates sort
parameter', async () => {
name: /Last modified/i,
});
- const initialCalls = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const initialCalls = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
await userEvent.click(modifiedHeader);
await waitFor(() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(initialCalls);
});
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
- const url = calls[calls.length - 1][0] as string;
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
+ const url = calls[calls.length - 1].url as string;
expect(url).toMatch(/order_column|sort/);
});
test('export button triggers handleResourceExport with dataset ID', async ()
=> {
const dataset = mockDatasets[0];
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -403,10 +420,11 @@ test('delete button opens modal with dataset details',
async () => {
setupDeleteMocks(dataset.id);
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -429,10 +447,14 @@ test('delete action successfully deletes dataset and
refreshes list', async () =
const datasetToDelete = mockDatasets[0];
setupDeleteMocks(datasetToDelete.id);
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [datasetToDelete], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [datasetToDelete],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser, {
@@ -455,7 +477,9 @@ test('delete action successfully deletes dataset and
refreshes list', async () =
await userEvent.type(confirmInput, 'DELETE');
// Track API calls before confirm
- const callsBefore = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const callsBefore = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
// Click confirm - find the danger button (last delete button in modal)
const confirmButton = within(modal)
@@ -465,13 +489,8 @@ test('delete action successfully deletes dataset and
refreshes list', async () =
// Wait for delete API call
await waitFor(() => {
- const deleteCalls = fetchMock.calls(
- `glob:*/api/v1/dataset/${datasetToDelete.id}`,
- );
- const hasDelete = deleteCalls.some(
- call => (call[1] as RequestInit)?.method === 'DELETE',
- );
- expect(hasDelete).toBe(true);
+ const deleteCalls = fetchMock.callHistory.calls(DELETE_ROUTE_NAME);
+ expect(deleteCalls.length).toBeGreaterThan(0);
});
// Success toast shown and modal closes
@@ -482,9 +501,9 @@ test('delete action successfully deletes dataset and
refreshes list', async () =
// List refreshes
await waitFor(() => {
- expect(fetchMock.calls(API_ENDPOINTS.DATASETS).length).toBeGreaterThan(
- callsBefore,
- );
+ expect(
+ fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS).length,
+ ).toBeGreaterThan(callsBefore);
});
});
@@ -492,10 +511,11 @@ test('delete action cancel closes modal without
deleting', async () => {
const dataset = mockDatasets[0];
setupDeleteMocks(dataset.id);
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -520,9 +540,11 @@ test('delete action cancel closes modal without deleting',
async () => {
});
// No delete API call made (only related_objects GET was called)
- const deleteCalls = fetchMock.calls(`glob:*/api/v1/dataset/${dataset.id}`);
+ const deleteCalls = fetchMock.callHistory.calls(
+ `glob:*/api/v1/dataset/${dataset.id}`,
+ );
const hasDeleteMethod = deleteCalls.some(
- call => (call[1] as RequestInit)?.method === 'DELETE',
+ call => (call.options as RequestInit)?.method === 'DELETE',
);
expect(hasDeleteMethod).toBe(false);
@@ -534,10 +556,11 @@ test('duplicate action successfully duplicates virtual
dataset', async () => {
const virtualDataset = mockDatasets[1]; // Virtual dataset (kind: 'virtual')
setupDuplicateMocks();
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [virtualDataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser, {
@@ -560,7 +583,9 @@ test('duplicate action successfully duplicates virtual
dataset', async () => {
await userEvent.type(input, 'Copy of Analytics');
// Track API calls before submit
- const callsBefore = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const callsBefore = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
// Submit
const submitButton = within(modal).getByRole('button', {
@@ -570,7 +595,9 @@ test('duplicate action successfully duplicates virtual
dataset', async () => {
// Wait for duplicate API call and modal closes
await waitFor(() => {
- const dupCalls = fetchMock.calls(API_ENDPOINTS.DATASET_DUPLICATE);
+ const dupCalls = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASET_DUPLICATE,
+ );
expect(dupCalls.length).toBeGreaterThan(0);
// Modal closes (duplicate success doesn't show toast, just closes modal)
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
@@ -578,9 +605,9 @@ test('duplicate action successfully duplicates virtual
dataset', async () => {
// List refreshes
await waitFor(() => {
- expect(fetchMock.calls(API_ENDPOINTS.DATASETS).length).toBeGreaterThan(
- callsBefore,
- );
+ expect(
+ fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS).length,
+ ).toBeGreaterThan(callsBefore);
});
});
@@ -588,10 +615,14 @@ test('duplicate button visible only for virtual
datasets', async () => {
const physicalDataset = mockDatasets[0]; // kind: 'physical'
const virtualDataset = mockDatasets[1]; // kind: 'virtual'
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [physicalDataset, virtualDataset], count: 2 },
- { overwriteRoutes: true },
+ {
+ result: [physicalDataset, virtualDataset],
+ count: 2,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -648,10 +679,14 @@ test('bulk select enables checkboxes', async () => {
}, 30000);
test('selecting all datasets shows correct count in toolbar', async () => {
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: mockDatasets, count: mockDatasets.length },
- { overwriteRoutes: true },
+ {
+ result: mockDatasets,
+ count: mockDatasets.length,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -688,10 +723,14 @@ test('selecting all datasets shows correct count in
toolbar', async () => {
}, 30000);
test('bulk export triggers export with selected IDs', async () => {
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [mockDatasets[0]], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [mockDatasets[0]],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -731,10 +770,14 @@ test('bulk export triggers export with selected IDs',
async () => {
test('bulk delete opens confirmation modal', async () => {
setupBulkDeleteMocks();
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [mockDatasets[0]], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [mockDatasets[0]],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -838,10 +881,14 @@ test('certified badge appears for certified datasets',
async () => {
}),
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [certifiedDataset], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [certifiedDataset],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -869,10 +916,14 @@ test('warning icon appears for datasets with warnings',
async () => {
}),
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [datasetWithWarning], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [datasetWithWarning],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -898,10 +949,14 @@ test('info tooltip appears for datasets with
descriptions', async () => {
description: 'Sales data from Q4 2024',
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [datasetWithDescription], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [datasetWithDescription],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -924,10 +979,11 @@ test('info tooltip appears for datasets with
descriptions', async () => {
test('dataset name links to Explore page', async () => {
const dataset = mockDatasets[0];
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -948,10 +1004,14 @@ test('dataset name links to Explore page', async () => {
test('physical dataset shows delete, export, and edit actions (no duplicate)',
async () => {
const physicalDataset = mockDatasets[0]; // kind: 'physical'
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [physicalDataset], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [physicalDataset],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -980,10 +1040,11 @@ test('physical dataset shows delete, export, and edit
actions (no duplicate)', a
test('virtual dataset shows delete, export, edit, and duplicate actions',
async () => {
const virtualDataset = mockDatasets[1]; // kind: 'virtual'
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [virtualDataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -1013,10 +1074,11 @@ test('edit action is enabled for dataset owner', async
() => {
owners: [{ id: mockAdminUser.userId, username: 'admin' }],
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -1040,10 +1102,11 @@ test('edit action is disabled for non-owner', async ()
=> {
owners: [{ id: 999, username: 'other_user' }], // Different user
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
// Use a non-admin user to test ownership check
@@ -1073,10 +1136,11 @@ test('all action buttons are clickable and enabled for
admin user', async () =>
owners: [{ id: mockAdminUser.userId, username: 'admin' }],
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [virtualDataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -1112,10 +1176,14 @@ test('all action buttons are clickable and enabled for
admin user', async () =>
});
test('displays error when initial dataset fetch fails with 500', async () => {
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { status: 500, body: { message: 'Internal Server Error' } },
- { overwriteRoutes: true },
+ {
+ status: 500,
+ body: { message: 'Internal Server Error' },
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser, {
@@ -1134,10 +1202,14 @@ test('displays error when initial dataset fetch fails
with 500', async () => {
});
test('displays error when initial dataset fetch fails with 403 permission
denied', async () => {
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { status: 403, body: { message: 'Access Denied' } },
- { overwriteRoutes: true },
+ {
+ status: 403,
+ body: { message: 'Access Denied' },
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser, {
@@ -1402,20 +1474,22 @@ test('sort order persists after deleting a dataset',
async () => {
});
// Record initial API calls count
- const initialCalls = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const initialCalls = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
// Click Name header to sort
await userEvent.click(nameHeader);
// Wait for new API call with sort parameter
await waitFor(() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(initialCalls);
});
// Record the sort parameter from the API call after sorting
- const callsAfterSort = fetchMock.calls(API_ENDPOINTS.DATASETS);
- const sortedUrl = callsAfterSort[callsAfterSort.length - 1][0] as string;
+ const callsAfterSort = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
+ const sortedUrl = callsAfterSort[callsAfterSort.length - 1].url as string;
expect(sortedUrl).toMatch(/order_column|sort/);
// Delete a dataset - get delete button from first row only
@@ -1433,7 +1507,9 @@ test('sort order persists after deleting a dataset',
async () => {
await userEvent.type(confirmInput, 'DELETE');
// Record call count before delete to track refetch
- const callsBeforeDelete = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const callsBeforeDelete = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
const confirmButton = within(modal)
.getAllByRole('button', { name: /^delete$/i })
@@ -1452,7 +1528,9 @@ test('sort order persists after deleting a dataset',
async () => {
// Wait for list refetch to complete (prevents async cleanup error)
await waitFor(() => {
- const currentCalls = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const currentCalls = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
expect(currentCalls).toBeGreaterThan(callsBeforeDelete);
});
@@ -1476,10 +1554,14 @@ test('sort order persists after deleting a dataset',
async () => {
// test. Component tests here focus on individual behaviors.
test('bulk selection clears when filter changes', async () => {
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: mockDatasets, count: mockDatasets.length },
- { overwriteRoutes: true },
+ {
+ result: mockDatasets,
+ count: mockDatasets.length,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -1528,7 +1610,9 @@ test('bulk selection clears when filter changes', async
() => {
});
// Record API call count before filter
- const beforeFilterCallCount = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const beforeFilterCallCount = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
// Wait for filter combobox to be ready before applying filter
await screen.findByRole('combobox', { name: 'Type' });
@@ -1538,14 +1622,14 @@ test('bulk selection clears when filter changes', async
() => {
// Wait for filter API call to complete
await waitFor(() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(beforeFilterCallCount);
});
// Verify filter was applied by decoding URL payload
- const urlAfterFilter = fetchMock
+ const urlAfterFilter = fetchMock.callHistory
.calls(API_ENDPOINTS.DATASETS)
- .at(-1)?.[0] as string;
+ .at(-1)?.url as string;
const risonAfterFilter = new URL(
urlAfterFilter,
'http://localhost',
@@ -1578,19 +1662,22 @@ test('type filter API call includes correct filter
parameter', async () => {
await screen.findByRole('combobox', { name: 'Type' });
// Snapshot call count before filter
- const callsBeforeFilter = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const callsBeforeFilter = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
// Apply Type filter
await selectOption('Virtual', 'Type');
// Wait for filter API call to complete
await waitFor(() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(callsBeforeFilter);
});
// Verify the latest API call includes the Type filter
- const url = fetchMock.calls(API_ENDPOINTS.DATASETS).at(-1)?.[0] as string;
+ const url = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS).at(-1)
+ ?.url as string;
expect(url).toContain('filters');
// searchParams.get() already URL-decodes, so pass directly to rison.decode
@@ -1622,21 +1709,23 @@ test('type filter persists after duplicating a
dataset', async () => {
await screen.findByRole('combobox', { name: 'Type' });
// Snapshot call count before filter
- const callsBeforeFilter = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const callsBeforeFilter = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
// Apply Type filter
await selectOption('Virtual', 'Type');
// Wait for filter API call to complete
await waitFor(() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(callsBeforeFilter);
});
// Verify filter is present by checking the latest API call
- const urlAfterFilter = fetchMock
+ const urlAfterFilter = fetchMock.callHistory
.calls(API_ENDPOINTS.DATASETS)
- .at(-1)?.[0] as string;
+ .at(-1)?.url as string;
const risonAfterFilter = new URL(
urlAfterFilter,
'http://localhost',
@@ -1654,7 +1743,7 @@ test('type filter persists after duplicating a dataset',
async () => {
);
// Capture datasets API call count BEFORE any duplicate operations
- const datasetsCallCountBeforeDuplicate = fetchMock.calls(
+ const datasetsCallCountBeforeDuplicate = fetchMock.callHistory.calls(
API_ENDPOINTS.DATASETS,
).length;
@@ -1682,20 +1771,24 @@ test('type filter persists after duplicating a
dataset', async () => {
// Wait for duplicate API call to be made
await waitFor(() => {
- const duplicateCalls = fetchMock.calls(API_ENDPOINTS.DATASET_DUPLICATE);
+ const duplicateCalls = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASET_DUPLICATE,
+ );
expect(duplicateCalls.length).toBeGreaterThan(0);
});
// Wait for datasets refetch to occur (proves duplicate triggered a refresh)
await waitFor(() => {
- const datasetsCallCount = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const datasetsCallCount = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
expect(datasetsCallCount).toBeGreaterThan(datasetsCallCountBeforeDuplicate);
});
// Verify Type filter persisted in the NEW datasets API call after
duplication
- const urlAfterDuplicate = fetchMock
+ const urlAfterDuplicate = fetchMock.callHistory
.calls(API_ENDPOINTS.DATASETS)
- .at(-1)?.[0] as string;
+ .at(-1)?.url as string;
const risonAfterDuplicate = new URL(
urlAfterDuplicate,
'http://localhost',
@@ -1729,10 +1822,11 @@ test('edit action shows error toast when dataset fetch
fails', async () => {
],
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [ownedDataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
// Mock SupersetClient.get to fail for the specific dataset endpoint
@@ -1776,10 +1870,14 @@ test('bulk export error shows toast and clears loading
state', async () => {
// Mock handleResourceExport to throw an error
mockHandleResourceExport.mockRejectedValueOnce(new Error('Export failed'));
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [mockDatasets[0]], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [mockDatasets[0]],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser, {
@@ -1836,16 +1934,19 @@ test('bulk export error shows toast and clears loading
state', async () => {
test('bulk delete error shows toast without refreshing list', async () => {
// Mock bulk delete to fail
- fetchMock.delete(
- API_ENDPOINTS.DATASET_BULK_DELETE,
- { status: 500, body: { message: 'Bulk delete failed' } },
- { overwriteRoutes: true },
- );
+ fetchMock.delete(API_ENDPOINTS.DATASET_BULK_DELETE, {
+ status: 500,
+ body: { message: 'Bulk delete failed' },
+ });
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [mockDatasets[0]], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [mockDatasets[0]],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser, {
@@ -1919,10 +2020,14 @@ test('bulk select shows "N Selected (Virtual)" for
virtual-only selection', asyn
// Use only virtual datasets
const virtualDatasets = mockDatasets.filter(d => d.kind === 'virtual');
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: virtualDatasets, count: virtualDatasets.length },
- { overwriteRoutes: true },
+ {
+ result: virtualDatasets,
+ count: virtualDatasets.length,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -1966,10 +2071,14 @@ test('bulk select shows "N Selected (Physical)" for
physical-only selection', as
// Use only physical datasets
const physicalDatasets = mockDatasets.filter(d => d.kind === 'physical');
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: physicalDatasets, count: physicalDatasets.length },
- { overwriteRoutes: true },
+ {
+ result: physicalDatasets,
+ count: physicalDatasets.length,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -2017,10 +2126,14 @@ test('bulk select shows mixed count for virtual and
physical selection', async (
mockDatasets.find(d => d.kind === 'virtual')!,
];
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: mixedDatasets, count: mixedDatasets.length },
- { overwriteRoutes: true },
+ {
+ result: mixedDatasets,
+ count: mixedDatasets.length,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -2081,20 +2194,17 @@ test('delete modal shows affected dashboards with
overflow for >10 items', async
title: `Dashboard ${i + 1}`,
}));
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
- fetchMock.get(
- `glob:*/api/v1/dataset/${dataset.id}/related_objects*`,
- {
- charts: { count: 0, result: [] },
- dashboards: { count: 15, result: manyDashboards },
- },
- { overwriteRoutes: true },
- );
+ fetchMock.get(`glob:*/api/v1/dataset/${dataset.id}/related_objects*`, {
+ charts: { count: 0, result: [] },
+ dashboards: { count: 15, result: manyDashboards },
+ });
renderDatasetList(mockAdminUser);
@@ -2126,20 +2236,17 @@ test('delete modal shows affected dashboards with
overflow for >10 items', async
test('delete modal hides affected dashboards section when count is zero',
async () => {
const dataset = mockDatasets[0];
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
- fetchMock.get(
- `glob:*/api/v1/dataset/${dataset.id}/related_objects*`,
- {
- charts: { count: 2, result: [{ id: 1, slice_name: 'Chart 1' }] },
- dashboards: { count: 0, result: [] },
- },
- { overwriteRoutes: true },
- );
+ fetchMock.get(`glob:*/api/v1/dataset/${dataset.id}/related_objects*`, {
+ charts: { count: 2, result: [{ id: 1, slice_name: 'Chart 1' }] },
+ dashboards: { count: 0, result: [] },
+ });
renderDatasetList(mockAdminUser);
@@ -2172,20 +2279,17 @@ test('delete modal shows affected charts with overflow
for >10 items', async ()
slice_name: `Chart ${i + 1}`,
}));
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
- fetchMock.get(
- `glob:*/api/v1/dataset/${dataset.id}/related_objects*`,
- {
- charts: { count: 12, result: manyCharts },
- dashboards: { count: 0, result: [] },
- },
- { overwriteRoutes: true },
- );
+ fetchMock.get(`glob:*/api/v1/dataset/${dataset.id}/related_objects*`, {
+ charts: { count: 12, result: manyCharts },
+ dashboards: { count: 0, result: [] },
+ });
renderDatasetList(mockAdminUser);
diff --git
a/superset-frontend/src/pages/DatasetList/DatasetList.permissions.test.tsx
b/superset-frontend/src/pages/DatasetList/DatasetList.permissions.test.tsx
index a997ff9a551..5a93913551b 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.permissions.test.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.permissions.test.tsx
@@ -20,7 +20,6 @@ import { act, screen, waitFor, within } from
'@testing-library/react';
import fetchMock from 'fetch-mock';
import {
setupMocks,
- setupApiPermissions,
renderDatasetList,
mockAdminUser,
mockReadOnlyUser,
@@ -35,7 +34,7 @@ import {
jest.setTimeout(30000);
beforeEach(() => {
- setupMocks();
+ // Default setup - tests that need different permissions will call
setupMocks() again
jest.clearAllMocks();
});
@@ -51,14 +50,21 @@ afterEach(async () => {
// Reset browser history to prevent query param leakage
window.history.replaceState({}, '', '/');
- fetchMock.resetHistory();
- fetchMock.restore();
+ fetchMock.clearHistory();
+ fetchMock.removeRoutes();
jest.restoreAllMocks();
});
test('admin users see all UI elements', async () => {
// Setup API with full admin permissions
- setupApiPermissions(['can_read', 'can_write', 'can_export',
'can_duplicate']);
+ setupMocks({
+ [API_ENDPOINTS.DATASETS_INFO]: [
+ 'can_read',
+ 'can_write',
+ 'can_export',
+ 'can_duplicate',
+ ],
+ });
renderDatasetList(mockAdminUser);
@@ -90,7 +96,7 @@ test('admin users see all UI elements', async () => {
test('read-only users cannot see Actions column', async () => {
// Setup API with read-only permissions
- setupApiPermissions(['can_read']);
+ setupMocks({ [API_ENDPOINTS.DATASETS_INFO]: ['can_read'] });
renderDatasetList(mockReadOnlyUser);
@@ -107,7 +113,7 @@ test('read-only users cannot see Actions column', async ()
=> {
test('read-only users cannot see bulk select button', async () => {
// Setup API with read-only permissions
- setupApiPermissions(['can_read']);
+ setupMocks({ [API_ENDPOINTS.DATASETS_INFO]: ['can_read'] });
renderDatasetList(mockReadOnlyUser);
@@ -123,7 +129,7 @@ test('read-only users cannot see bulk select button', async
() => {
test('read-only users cannot see Create/Import buttons', async () => {
// Setup API with read-only permissions
- setupApiPermissions(['can_read']);
+ setupMocks({ [API_ENDPOINTS.DATASETS_INFO]: ['can_read'] });
renderDatasetList(mockReadOnlyUser);
@@ -144,7 +150,9 @@ test('read-only users cannot see Create/Import buttons',
async () => {
test('write users see Actions column', async () => {
// Setup API with write permissions
- setupApiPermissions(['can_read', 'can_write', 'can_export']);
+ setupMocks({
+ [API_ENDPOINTS.DATASETS_INFO]: ['can_read', 'can_write', 'can_export'],
+ });
renderDatasetList(mockWriteUser);
@@ -162,7 +170,9 @@ test('write users see Actions column', async () => {
test('write users see bulk select button', async () => {
// Setup API with write permissions
- setupApiPermissions(['can_read', 'can_write', 'can_export']);
+ setupMocks({
+ [API_ENDPOINTS.DATASETS_INFO]: ['can_read', 'can_write', 'can_export'],
+ });
renderDatasetList(mockWriteUser);
@@ -177,7 +187,9 @@ test('write users see bulk select button', async () => {
test('write users see Create/Import buttons', async () => {
// Setup API with write permissions
- setupApiPermissions(['can_read', 'can_write', 'can_export']);
+ setupMocks({
+ [API_ENDPOINTS.DATASETS_INFO]: ['can_read', 'can_write', 'can_export'],
+ });
renderDatasetList(mockWriteUser);
@@ -198,7 +210,7 @@ test('write users see Create/Import buttons', async () => {
test('export-only users see bulk select (for export only)', async () => {
// Setup API with export-only permissions
- setupApiPermissions(['can_read', 'can_export']);
+ setupMocks({ [API_ENDPOINTS.DATASETS_INFO]: ['can_read', 'can_export'] });
renderDatasetList(mockExportOnlyUser);
@@ -214,7 +226,7 @@ test('export-only users see bulk select (for export only)',
async () => {
test('export-only users cannot see Create/Import buttons', async () => {
// Setup API with export-only permissions
- setupApiPermissions(['can_read', 'can_export']);
+ setupMocks({ [API_ENDPOINTS.DATASETS_INFO]: ['can_read', 'can_export'] });
renderDatasetList(mockExportOnlyUser);
@@ -233,14 +245,23 @@ test('export-only users cannot see Create/Import
buttons', async () => {
test('action buttons respect user permissions', async () => {
// Setup API with full admin permissions
- setupApiPermissions(['can_read', 'can_write', 'can_export',
'can_duplicate']);
+ setupMocks({
+ [API_ENDPOINTS.DATASETS_INFO]: [
+ 'can_read',
+ 'can_write',
+ 'can_export',
+ 'can_duplicate',
+ ],
+ });
const dataset = mockDatasets[0];
+ // Override the default DATASETS route with test-specific data
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -263,14 +284,16 @@ test('action buttons respect user permissions', async ()
=> {
test('read-only user sees no delete or duplicate buttons in row', async () => {
// Setup API with read-only permissions
- setupApiPermissions(['can_read']);
+ setupMocks({ [API_ENDPOINTS.DATASETS_INFO]: ['can_read'] });
const dataset = mockDatasets[0];
+ // Override the default DATASETS route with test-specific data
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockReadOnlyUser);
@@ -299,17 +322,21 @@ test('read-only user sees no delete or duplicate buttons
in row', async () => {
test('write user sees edit, delete, and export actions', async () => {
// Setup API with write permissions (includes delete)
// Note: can_write grants both edit and delete permissions in DatasetList
- setupApiPermissions(['can_read', 'can_write', 'can_export']);
+ setupMocks({
+ [API_ENDPOINTS.DATASETS_INFO]: ['can_read', 'can_write', 'can_export'],
+ });
const dataset = {
...mockDatasets[0],
owners: [{ id: mockWriteUser.userId, username: 'writeuser' }],
};
+ // Override the default DATASETS route with test-specific data
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockWriteUser);
@@ -341,14 +368,16 @@ test('write user sees edit, delete, and export actions',
async () => {
test('export-only user has no Actions column (no write/duplicate
permissions)', async () => {
// Setup API with export-only permissions
// Note: Export action alone doesn't render Actions column - it's in
toolbar/bulk select
- setupApiPermissions(['can_read', 'can_export']);
+ setupMocks({ [API_ENDPOINTS.DATASETS_INFO]: ['can_read', 'can_export'] });
const dataset = mockDatasets[0];
+ // Override the default DATASETS route with test-specific data
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockExportOnlyUser);
@@ -377,15 +406,20 @@ test('export-only user has no Actions column (no
write/duplicate permissions)',
test('user with can_duplicate sees duplicate button only for virtual
datasets', async () => {
// Setup API with duplicate permission
- setupApiPermissions(['can_read', 'can_duplicate']);
+ setupMocks({ [API_ENDPOINTS.DATASETS_INFO]: ['can_read', 'can_duplicate'] });
const physicalDataset = mockDatasets[0]; // kind: 'physical'
const virtualDataset = mockDatasets[1]; // kind: 'virtual'
+ // Override the default DATASETS route with test-specific data
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [physicalDataset, virtualDataset], count: 2 },
- { overwriteRoutes: true },
+ {
+ result: [physicalDataset, virtualDataset],
+ count: 2,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
diff --git a/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
b/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
index 51ad4cbab7a..d77d27aae1f 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
@@ -53,8 +53,8 @@ afterEach(async () => {
// Reset browser history state to prevent query params leaking between tests
window.history.replaceState({}, '', '/');
- fetchMock.resetHistory();
- fetchMock.restore();
+ fetchMock.clearHistory();
+ fetchMock.removeRoutes();
jest.restoreAllMocks();
});
@@ -74,7 +74,6 @@ test('shows loading state during initial data fetch', () => {
new Promise(resolve =>
setTimeout(() => resolve({ result: [], count: 0 }), 10000),
),
- { overwriteRoutes: true },
);
renderDatasetList(mockAdminUser);
@@ -93,7 +92,6 @@ test('maintains component structure during loading', () => {
new Promise(resolve =>
setTimeout(() => resolve({ result: [], count: 0 }), 10000),
),
- { overwriteRoutes: true },
);
renderDatasetList(mockAdminUser);
@@ -215,10 +213,14 @@ test('handles datasets with missing fields and renders
gracefully', async () =>
sql: null,
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [datasetWithMissingFields], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [datasetWithMissingFields],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -242,10 +244,11 @@ test('handles datasets with missing fields and renders
gracefully', async () =>
});
test('handles empty results (shows empty state)', async () => {
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [], count: 0 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -258,7 +261,7 @@ test('makes correct initial API call on load', async () => {
renderDatasetList(mockAdminUser);
await waitFor(() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(0);
});
});
@@ -267,9 +270,9 @@ test('API call includes correct page size', async () => {
renderDatasetList(mockAdminUser);
await waitFor(() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(0);
- const url = calls[0][0] as string;
+ const url = calls[0].url as string;
expect(url).toContain('page_size');
});
});
@@ -281,7 +284,9 @@ test('typing in name filter updates input value and
triggers API with decoded se
const searchInput = within(searchContainer).getByRole('textbox');
// Record initial API calls
- const initialCallCount = fetchMock.calls(API_ENDPOINTS.DATASETS).length;
+ const initialCallCount = fetchMock.callHistory.calls(
+ API_ENDPOINTS.DATASETS,
+ ).length;
// Type in search box and press Enter to trigger search
await userEvent.type(searchInput, 'sales{enter}');
@@ -294,11 +299,11 @@ test('typing in name filter updates input value and
triggers API with decoded se
// Wait for API call after Enter key press
await waitFor(
() => {
- const calls = fetchMock.calls(API_ENDPOINTS.DATASETS);
+ const calls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
expect(calls.length).toBeGreaterThan(initialCallCount);
// Get latest API call
- const url = calls[calls.length - 1][0] as string;
+ const url = calls[calls.length - 1].url as string;
// Verify URL contains search filter
expect(url).toContain('filters');
@@ -351,9 +356,7 @@ test('handles 500 error on initial load without crashing',
async () => {
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ throws: new Error('Internal Server Error') },
- {
- overwriteRoutes: true,
- },
+ {},
);
renderDatasetList(mockAdminUser, {
@@ -369,8 +372,9 @@ test('handles 500 error on initial load without crashing',
async () => {
test('handles 403 error on _info endpoint and disables create actions', async
() => {
const addDangerToast = jest.fn();
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS_INFO);
fetchMock.get(API_ENDPOINTS.DATASETS_INFO, mockApiError403, {
- overwriteRoutes: true,
+ name: API_ENDPOINTS.DATASETS_INFO,
});
renderDatasetList(mockAdminUser, {
@@ -391,10 +395,11 @@ test('handles 403 error on _info endpoint and disables
create actions', async ()
});
test('handles network timeout without crashing', async () => {
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ throws: new Error('Network timeout') },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser, {
@@ -412,7 +417,7 @@ test('component requires explicit mocks for all API
endpoints', async () => {
setupMocks();
// Clear call history to start fresh
- fetchMock.resetHistory();
+ fetchMock.clearHistory();
// Render component with standard setup
renderDatasetList(mockAdminUser);
@@ -421,15 +426,15 @@ test('component requires explicit mocks for all API
endpoints', async () => {
await waitForDatasetsPageReady();
// Verify that critical endpoints were called and had mocks available
- const newDatasetsCalls = fetchMock.calls(API_ENDPOINTS.DATASETS);
- const newInfoCalls = fetchMock.calls(API_ENDPOINTS.DATASETS_INFO);
+ const newDatasetsCalls = fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS);
+ const newInfoCalls =
fetchMock.callHistory.calls(API_ENDPOINTS.DATASETS_INFO);
// These should have been called during render
expect(newDatasetsCalls.length).toBeGreaterThan(0);
expect(newInfoCalls.length).toBeGreaterThan(0);
// Verify no unmatched calls (all endpoints were mocked)
- const unmatchedCalls = fetchMock.calls(false); // false = unmatched only
+ const unmatchedCalls = fetchMock.callHistory.calls('unmatched');
expect(unmatchedCalls.length).toBe(0);
});
@@ -453,10 +458,14 @@ test('renders datasets with certification data', async ()
=> {
}),
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [certifiedDataset], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [certifiedDataset],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -481,10 +490,14 @@ test('displays datasets with warning_markdown', async ()
=> {
}),
};
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [datasetWithWarning], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [datasetWithWarning],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -503,10 +516,14 @@ test('displays datasets with warning_markdown', async ()
=> {
test('displays dataset with multiple owners', async () => {
const datasetWithOwners = mockDatasets[1]; // Has 2 owners: Jane Smith, Bob
Jones
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [datasetWithOwners], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [datasetWithOwners],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -525,10 +542,14 @@ test('displays dataset with multiple owners', async () =>
{
test('displays ModifiedInfo with humanized date', async () => {
const datasetWithModified = mockDatasets[0]; // changed_by_name: 'John Doe',
changed_on: '1 day ago'
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
- { result: [datasetWithModified], count: 1 },
- { overwriteRoutes: true },
+ {
+ result: [datasetWithModified],
+ count: 1,
+ },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
@@ -548,10 +569,11 @@ test('displays ModifiedInfo with humanized date', async
() => {
test('dataset name links to Explore with correct explore_url', async () => {
const dataset = mockDatasets[0]; // explore_url:
'/explore/?datasource=1__table'
+ fetchMock.removeRoute(API_ENDPOINTS.DATASETS);
fetchMock.get(
API_ENDPOINTS.DATASETS,
{ result: [dataset], count: 1 },
- { overwriteRoutes: true },
+ { name: API_ENDPOINTS.DATASETS },
);
renderDatasetList(mockAdminUser);
diff --git
a/superset-frontend/src/pages/DatasetList/DatasetList.testHelpers.tsx
b/superset-frontend/src/pages/DatasetList/DatasetList.testHelpers.tsx
index 844ad8f9163..4c1324edcd4 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.testHelpers.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.testHelpers.tsx
@@ -23,6 +23,7 @@ import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import { configureStore } from '@reduxjs/toolkit';
import { QueryParamProvider } from 'use-query-params';
+import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5';
import DatasetList from 'src/pages/DatasetList';
import handleResourceExport from 'src/utils/export';
@@ -329,14 +330,17 @@ export const API_ENDPOINTS = {
DATASET_RELATED_CHANGED_BY: 'glob:*/api/v1/dataset/related/changed_by*',
};
-// Setup API permissions mock (for permission-based testing)
-export const setupApiPermissions = (permissions: string[]) => {
- fetchMock.get(
- API_ENDPOINTS.DATASETS_INFO,
- { permissions },
- { overwriteRoutes: true },
- );
-};
+// Default permissions for admin users
+const DEFAULT_PERMISSIONS = [
+ 'can_read',
+ 'can_write',
+ 'can_export',
+ 'can_duplicate',
+];
+
+// Type for payload map used in setupMocks
+// Uses Record type to allow API_ENDPOINTS values as keys
+type PayloadMap = Partial<Record<string, string[]>>;
// Store utilities
export const createMockStore = (initialState: Partial<StoreState> = {}) =>
@@ -384,7 +388,7 @@ export const renderDatasetList = (
return render(
<Provider store={store}>
<MemoryRouter>
- <QueryParamProvider>
+ <QueryParamProvider adapter={ReactRouter5Adapter}>
<DatasetList user={user} {...props} />
</QueryParamProvider>
</MemoryRouter>
@@ -400,6 +404,10 @@ export const waitForDatasetsPageReady = async () => {
await screen.findByText('Datasets');
};
+// Route name constants for delete operations (used for call history queries)
+export const DELETE_ROUTE_NAME = 'dataset-delete';
+export const RELATED_OBJECTS_ROUTE_NAME = 'dataset-related-objects';
+
// Helper functions for specific operations
export const setupDeleteMocks = (datasetId: number) => {
fetchMock.get(
@@ -408,30 +416,29 @@ export const setupDeleteMocks = (datasetId: number) => {
charts: mockRelatedCharts,
dashboards: mockRelatedDashboards,
},
- { overwriteRoutes: true },
+ { name: RELATED_OBJECTS_ROUTE_NAME },
);
fetchMock.delete(
`glob:*/api/v1/dataset/${datasetId}`,
- { message: 'Dataset deleted successfully' },
- { overwriteRoutes: true },
+ {
+ message: 'Dataset deleted successfully',
+ },
+ { name: DELETE_ROUTE_NAME },
);
};
export const setupDuplicateMocks = () => {
- fetchMock.post(
- API_ENDPOINTS.DATASET_DUPLICATE,
- { id: 999, table_name: 'Copy of Dataset' },
- { overwriteRoutes: true },
- );
+ fetchMock.post(API_ENDPOINTS.DATASET_DUPLICATE, {
+ id: 999,
+ table_name: 'Copy of Dataset',
+ });
};
export const setupBulkDeleteMocks = () => {
- fetchMock.delete(
- API_ENDPOINTS.DATASET_BULK_DELETE,
- { message: '3 datasets deleted successfully' },
- { overwriteRoutes: true },
- );
+ fetchMock.delete(API_ENDPOINTS.DATASET_BULK_DELETE, {
+ message: '3 datasets deleted successfully',
+ });
};
// Setup error mocks for negative flow testing
@@ -439,25 +446,17 @@ export const setupDeleteErrorMocks = (
datasetId: number,
statusCode: number,
) => {
- fetchMock.get(
- `glob:*/api/v1/dataset/${datasetId}/related_objects*`,
- {
- status: statusCode,
- body: { message: 'Failed to fetch related objects' },
- },
- { overwriteRoutes: true },
- );
+ fetchMock.get(`glob:*/api/v1/dataset/${datasetId}/related_objects*`, {
+ status: statusCode,
+ body: { message: 'Failed to fetch related objects' },
+ });
};
export const setupDuplicateErrorMocks = (statusCode: number) => {
- fetchMock.post(
- API_ENDPOINTS.DATASET_DUPLICATE,
- {
- status: statusCode,
- body: { message: 'Failed to duplicate dataset' },
- },
- { overwriteRoutes: true },
- );
+ fetchMock.post(API_ENDPOINTS.DATASET_DUPLICATE, {
+ status: statusCode,
+ body: { message: 'Failed to duplicate dataset' },
+ });
};
/**
@@ -468,11 +467,10 @@ export const setupDuplicateErrorMocks = (statusCode:
number) => {
* @throws If any unmocked endpoints were called or expected endpoints weren't
called
*/
export const assertOnlyExpectedCalls = (expectedEndpoints: string[]) => {
- const allCalls = fetchMock.calls(true); // Get all calls including unmatched
- const unmatchedCalls = allCalls.filter(call => call.isUnmatched);
+ const unmatchedCalls = fetchMock.callHistory.calls('unmatched');
if (unmatchedCalls.length > 0) {
- const unmatchedUrls = unmatchedCalls.map(call => call[0]);
+ const unmatchedUrls = unmatchedCalls.map(call => call.url);
throw new Error(
`Unmocked endpoints called: ${unmatchedUrls.join(', ')}. ` +
'Add explicit mocks in setupMocks() or test setup.',
@@ -481,7 +479,7 @@ export const assertOnlyExpectedCalls = (expectedEndpoints:
string[]) => {
// Verify expected endpoints were called
expectedEndpoints.forEach(endpoint => {
- const calls = fetchMock.calls(endpoint);
+ const calls = fetchMock.callHistory.calls(endpoint);
if (calls.length === 0) {
throw new Error(
`Expected endpoint not called: ${endpoint}. ` +
@@ -492,48 +490,75 @@ export const assertOnlyExpectedCalls =
(expectedEndpoints: string[]) => {
};
// MSW setup using fetch-mock (following ChartList pattern)
-export const setupMocks = () => {
- fetchMock.reset();
+// payloadMap allows customizing permissions for the _info endpoint
+export const setupMocks = (
+ payloadMap: PayloadMap = {
+ [API_ENDPOINTS.DATASETS_INFO]: DEFAULT_PERMISSIONS,
+ },
+) => {
+ fetchMock.removeRoutes();
- fetchMock.get(API_ENDPOINTS.DATASETS_INFO, {
- permissions: ['can_read', 'can_write', 'can_export', 'can_duplicate'],
- });
+ // Get permissions from payloadMap or use defaults
+ const permissions =
+ payloadMap[API_ENDPOINTS.DATASETS_INFO] || DEFAULT_PERMISSIONS;
- fetchMock.get(API_ENDPOINTS.DATASETS, {
- result: mockDatasets,
- count: mockDatasets.length,
- });
+ fetchMock.get(
+ API_ENDPOINTS.DATASETS_INFO,
+ { permissions },
+ { name: API_ENDPOINTS.DATASETS_INFO },
+ );
- fetchMock.get(API_ENDPOINTS.DATASET_FAVORITE_STATUS, {
- result: [],
- });
+ fetchMock.get(
+ API_ENDPOINTS.DATASETS,
+ {
+ result: mockDatasets,
+ count: mockDatasets.length,
+ },
+ { name: API_ENDPOINTS.DATASETS },
+ );
- fetchMock.get(API_ENDPOINTS.DATASET_RELATED_DATABASE, {
- result: [
- { value: 1, text: 'PostgreSQL' },
- { value: 2, text: 'MySQL' },
- { value: 3, text: 'Redshift' },
- ],
- count: 3,
- });
+ fetchMock.get(
+ API_ENDPOINTS.DATASET_FAVORITE_STATUS,
+ { result: [] },
+ { name: API_ENDPOINTS.DATASET_FAVORITE_STATUS },
+ );
- fetchMock.get(API_ENDPOINTS.DATASET_RELATED_SCHEMA, {
- result: [
- { value: 'public', text: 'public' },
- { value: 'analytics', text: 'analytics' },
- { value: 'metrics', text: 'metrics' },
- { value: 'reports', text: 'reports' },
- ],
- count: 4,
- });
+ fetchMock.get(
+ API_ENDPOINTS.DATASET_RELATED_DATABASE,
+ {
+ result: [
+ { value: 1, text: 'PostgreSQL' },
+ { value: 2, text: 'MySQL' },
+ { value: 3, text: 'Redshift' },
+ ],
+ count: 3,
+ },
+ { name: API_ENDPOINTS.DATASET_RELATED_DATABASE },
+ );
- fetchMock.get(API_ENDPOINTS.DATASET_RELATED_OWNERS, {
- result: [],
- count: 0,
- });
+ fetchMock.get(
+ API_ENDPOINTS.DATASET_RELATED_SCHEMA,
+ {
+ result: [
+ { value: 'public', text: 'public' },
+ { value: 'analytics', text: 'analytics' },
+ { value: 'metrics', text: 'metrics' },
+ { value: 'reports', text: 'reports' },
+ ],
+ count: 4,
+ },
+ { name: API_ENDPOINTS.DATASET_RELATED_SCHEMA },
+ );
- fetchMock.get(API_ENDPOINTS.DATASET_RELATED_CHANGED_BY, {
- result: [],
- count: 0,
- });
+ fetchMock.get(
+ API_ENDPOINTS.DATASET_RELATED_OWNERS,
+ { result: [], count: 0 },
+ { name: API_ENDPOINTS.DATASET_RELATED_OWNERS },
+ );
+
+ fetchMock.get(
+ API_ENDPOINTS.DATASET_RELATED_CHANGED_BY,
+ { result: [], count: 0 },
+ { name: API_ENDPOINTS.DATASET_RELATED_CHANGED_BY },
+ );
};