fix(tests): fix flakey tests with PropertiesModal.test.tsx, FiltersConfigModal.test.tsx and ChartList.listview.test.tsx (#36037)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Joe Li
2025-11-10 14:31:48 -08:00
committed by GitHub
parent 64ca080bb8
commit 0a5144fc1d
3 changed files with 937 additions and 970 deletions

View File

@@ -184,9 +184,6 @@ afterEach(() => {
jest.restoreAllMocks();
});
// Set timeout for all tests in this file to prevent CI timeouts
jest.setTimeout(60000);
function defaultRender(initialState: any = defaultState(), modalProps = props) {
return render(<FiltersConfigModal {...modalProps} />, {
initialState,
@@ -226,9 +223,10 @@ test('renders a value filter type', () => {
test('renders a numerical range filter type', async () => {
defaultRender();
userEvent.click(screen.getByText(VALUE_REGEX));
await userEvent.click(screen.getByText(VALUE_REGEX));
await waitFor(() => userEvent.click(screen.getByText(NUMERICAL_RANGE_REGEX)));
const numericalRangeOption = await screen.findByText(NUMERICAL_RANGE_REGEX);
await userEvent.click(numericalRangeOption);
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument();
@@ -250,9 +248,10 @@ test('renders a numerical range filter type', async () => {
test('renders a time range filter type', async () => {
defaultRender();
userEvent.click(screen.getByText(VALUE_REGEX));
await userEvent.click(screen.getByText(VALUE_REGEX));
await waitFor(() => userEvent.click(screen.getByText(TIME_RANGE_REGEX)));
const timeRangeOption = await screen.findByText(TIME_RANGE_REGEX);
await userEvent.click(timeRangeOption);
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument();
@@ -265,9 +264,10 @@ test('renders a time range filter type', async () => {
test('renders a time column filter type', async () => {
defaultRender();
userEvent.click(screen.getByText(VALUE_REGEX));
await userEvent.click(screen.getByText(VALUE_REGEX));
await waitFor(() => userEvent.click(screen.getByText(TIME_COLUMN_REGEX)));
const timeColumnOption = await screen.findByText(TIME_COLUMN_REGEX);
await userEvent.click(timeColumnOption);
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument();
@@ -280,9 +280,10 @@ test('renders a time column filter type', async () => {
test('renders a time grain filter type', async () => {
defaultRender();
userEvent.click(screen.getByText(VALUE_REGEX));
await userEvent.click(screen.getByText(VALUE_REGEX));
await waitFor(() => userEvent.click(screen.getByText(TIME_GRAIN_REGEX)));
const timeGrainOption = await screen.findByText(TIME_GRAIN_REGEX);
await userEvent.click(timeGrainOption);
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument();
@@ -295,7 +296,7 @@ test('renders a time grain filter type', async () => {
test('render time filter types as disabled if there are no temporal columns in the dataset', async () => {
defaultRender(noTemporalColumnsState());
userEvent.click(screen.getByText(VALUE_REGEX));
await userEvent.click(screen.getByText(VALUE_REGEX));
const timeRange = await screen.findByText(TIME_RANGE_REGEX);
const timeGrain = await screen.findByText(TIME_GRAIN_REGEX);
@@ -309,7 +310,7 @@ test('render time filter types as disabled if there are no temporal columns in t
test('validates the name', async () => {
defaultRender();
userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await waitFor(
async () => {
expect(await screen.findByText(NAME_REQUIRED_REGEX)).toBeInTheDocument();
@@ -320,7 +321,7 @@ test('validates the name', async () => {
test('validates the column', async () => {
defaultRender();
userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
expect(await screen.findByText(COLUMN_REQUIRED_REGEX)).toBeInTheDocument();
});
@@ -328,8 +329,8 @@ test('validates the column', async () => {
test.skip('validates the default value', async () => {
defaultRender(noTemporalColumnsState());
expect(await screen.findByText('birth_names')).toBeInTheDocument();
userEvent.type(screen.getByRole('combobox'), `Column A{Enter}`);
userEvent.click(getCheckbox(DEFAULT_VALUE_REGEX));
await userEvent.type(screen.getByRole('combobox'), `Column A{Enter}`);
await userEvent.click(getCheckbox(DEFAULT_VALUE_REGEX));
await waitFor(() => {
expect(
screen.queryByText(FILL_REQUIRED_FIELDS_REGEX),
@@ -345,21 +346,24 @@ test('validates the pre-filter value', async () => {
try {
defaultRender();
userEvent.click(screen.getByText(FILTER_SETTINGS_REGEX));
userEvent.click(getCheckbox(PRE_FILTER_REGEX));
await userEvent.click(screen.getByText(FILTER_SETTINGS_REGEX));
await userEvent.click(getCheckbox(PRE_FILTER_REGEX));
jest.runAllTimers();
await waitFor(() => {
const errorMessages = screen.getAllByText(PRE_FILTER_REQUIRED_REGEX);
expect(errorMessages.length).toBeGreaterThan(0);
});
} finally {
jest.useRealTimers();
}
jest.runOnlyPendingTimers();
jest.useRealTimers();
// Wait for validation to complete after timer switch
await waitFor(
() => {
expect(screen.getByText(PRE_FILTER_REQUIRED_REGEX)).toBeInTheDocument();
const errorMessages = screen.queryAllByText(PRE_FILTER_REQUIRED_REGEX);
expect(errorMessages.length).toBeGreaterThan(0);
},
{ timeout: 15000 },
);
@@ -368,13 +372,13 @@ test('validates the pre-filter value', async () => {
// eslint-disable-next-line jest/no-disabled-tests
test.skip("doesn't render time range pre-filter if there are no temporal columns in datasource", async () => {
defaultRender(noTemporalColumnsState());
userEvent.click(screen.getByText(DATASET_REGEX));
await waitFor(() => {
await userEvent.click(screen.getByText(DATASET_REGEX));
await waitFor(async () => {
expect(screen.queryByLabelText('Loading')).not.toBeInTheDocument();
userEvent.click(screen.getByText('birth_names'));
await userEvent.click(screen.getByText('birth_names'));
});
userEvent.click(screen.getByText(FILTER_SETTINGS_REGEX));
userEvent.click(getCheckbox(PRE_FILTER_REGEX));
await userEvent.click(screen.getByText(FILTER_SETTINGS_REGEX));
await userEvent.click(getCheckbox(PRE_FILTER_REGEX));
await waitFor(() =>
expect(
screen.queryByText(TIME_RANGE_PREFILTER_REGEX),
@@ -439,9 +443,9 @@ test('deletes a filter', async () => {
const removeButtons = screen.getAllByRole('button', {
name: 'delete',
});
userEvent.click(removeButtons[2]);
await userEvent.click(removeButtons[2]);
userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await waitFor(() =>
expect(onSave).toHaveBeenCalledWith(
@@ -476,8 +480,8 @@ test('deletes a filter including dependencies', async () => {
const removeButtons = screen.getAllByRole('button', {
name: 'delete',
});
userEvent.click(removeButtons[1]);
userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await userEvent.click(removeButtons[1]);
await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await waitFor(() =>
expect(onSave).toHaveBeenCalledWith(
expect.objectContaining({
@@ -525,7 +529,7 @@ test('switches the order between two filters', async () => {
fireEvent.dragEnd(draggableFilters[0]);
userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await waitFor(() =>
expect(onSave).toHaveBeenCalledWith(
@@ -568,14 +572,14 @@ test('rearranges three filters and deletes one of them', async () => {
const deleteButtons = screen.getAllByRole('button', {
name: 'delete',
});
userEvent.click(deleteButtons[1]);
await userEvent.click(deleteButtons[1]);
fireEvent.dragStart(draggableFilters[0]);
fireEvent.dragOver(draggableFilters[2]);
fireEvent.drop(draggableFilters[2]);
fireEvent.dragEnd(draggableFilters[0]);
userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await waitFor(() =>
expect(onSave).toHaveBeenCalledWith(
@@ -594,47 +598,51 @@ test('rearranges three filters and deletes one of them', async () => {
test('modifies the name of a filter', async () => {
jest.useFakeTimers();
const nativeFilterState = [
buildNativeFilter('NATIVE_FILTER-1', 'state', []),
buildNativeFilter('NATIVE_FILTER-2', 'country', []),
];
try {
const nativeFilterState = [
buildNativeFilter('NATIVE_FILTER-1', 'state', []),
buildNativeFilter('NATIVE_FILTER-2', 'country', []),
];
const state = {
...defaultState(),
dashboardInfo: {
metadata: { native_filter_configuration: nativeFilterState },
},
dashboardLayout,
};
const state = {
...defaultState(),
dashboardInfo: {
metadata: { native_filter_configuration: nativeFilterState },
},
dashboardLayout,
};
const onSave = jest.fn();
const onSave = jest.fn();
defaultRender(state, {
...props,
createNewOnOpen: false,
onSave,
});
defaultRender(state, {
...props,
createNewOnOpen: false,
onSave,
});
const filterNameInput = screen.getByRole('textbox', {
name: FILTER_NAME_REGEX,
});
const filterNameInput = screen.getByRole('textbox', {
name: FILTER_NAME_REGEX,
});
userEvent.clear(filterNameInput);
userEvent.type(filterNameInput, 'New Filter Name');
await userEvent.clear(filterNameInput);
await userEvent.type(filterNameInput, 'New Filter Name');
jest.runAllTimers();
jest.runAllTimers();
userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
await waitFor(() =>
expect(onSave).toHaveBeenCalledWith(
expect.objectContaining({
modified: expect.arrayContaining([
expect.objectContaining({ name: 'New Filter Name' }),
]),
}),
),
);
await waitFor(() =>
expect(onSave).toHaveBeenCalledWith(
expect.objectContaining({
modified: expect.arrayContaining([
expect.objectContaining({ name: 'New Filter Name' }),
]),
}),
),
);
} finally {
jest.useRealTimers();
}
});
test('renders a filter with a chart containing BigInt values', async () => {

View File

@@ -153,12 +153,6 @@ test('Should render null when show:false', async () => {
});
});
// Add cleanup after each test
afterEach(async () => {
// Wait for any pending effects to complete
await new Promise(resolve => setTimeout(resolve, 0));
});
test('Should render when show:true', async () => {
const props = createProps();
renderModal(props);
@@ -193,7 +187,7 @@ test('"Close" button should call "onHide"', async () => {
expect(props.onHide).toHaveBeenCalledTimes(0);
});
userEvent.click(screen.getByRole('button', { name: 'Close' }));
await userEvent.click(screen.getByRole('button', { name: 'Close' }));
await waitFor(() => {
expect(props.onHide).toHaveBeenCalledTimes(1);
@@ -254,7 +248,7 @@ test('"Cancel" button should call "onHide"', async () => {
expect(props.onHide).toHaveBeenCalledTimes(0);
});
userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
await waitFor(() => {
expect(props.onHide).toHaveBeenCalledTimes(1);
@@ -262,7 +256,7 @@ test('"Cancel" button should call "onHide"', async () => {
});
});
test('"Save" button should call only "onSave"', async () => {
test('"Save" button should call "onSave" and "onHide"', async () => {
const props = createProps();
renderModal(props);
await waitFor(() => {
@@ -271,7 +265,7 @@ test('"Save" button should call only "onSave"', async () => {
expect(screen.getByRole('button', { name: 'Save' })).toBeEnabled();
});
userEvent.click(screen.getByRole('button', { name: 'Save' }));
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
await waitFor(() => {
expect(props.onSave).toHaveBeenCalledTimes(1);
@@ -293,7 +287,7 @@ test('Empty "Certified by" should clear "Certification details"', async () => {
// Expand the Advanced section first to access certification details
const advancedPanel = screen.getByText('Advanced').closest('[role="tab"]');
if (advancedPanel) {
userEvent.click(advancedPanel);
await userEvent.click(advancedPanel);
}
await waitFor(() => {
@@ -312,11 +306,11 @@ test('"Name" should not be empty', async () => {
const name = screen.getByRole('textbox', { name: 'Name' });
userEvent.clear(name);
await userEvent.clear(name);
expect(name).toHaveValue('');
userEvent.click(screen.getByRole('button', { name: 'Save' }));
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
await waitFor(() => {
expect(props.onSave).toHaveBeenCalledTimes(0);
@@ -329,12 +323,12 @@ test('"Name" should not be empty when saved', async () => {
const name = screen.getByRole('textbox', { name: 'Name' });
userEvent.clear(name);
userEvent.type(name, 'Test chart new name');
await userEvent.clear(name);
await userEvent.type(name, 'Test chart new name');
expect(name).toHaveValue('Test chart new name');
userEvent.click(screen.getByRole('button', { name: 'Save' }));
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
await waitFor(() => {
expect(props.onSave).toHaveBeenCalledTimes(1);
@@ -351,19 +345,17 @@ test('"Cache timeout" should not be empty when saved', async () => {
// Expand the Configuration section first to access cache timeout
const configPanel = screen.getByText('Configuration').closest('[role="tab"]');
if (configPanel) {
userEvent.click(configPanel);
await userEvent.click(configPanel);
}
await waitFor(() => {
const cacheTimeout = screen.getByRole('textbox', { name: 'Cache timeout' });
userEvent.clear(cacheTimeout);
userEvent.type(cacheTimeout, '1000');
expect(cacheTimeout).toHaveValue('1000');
const cacheTimeout = await screen.findByRole('textbox', {
name: 'Cache timeout',
});
await userEvent.clear(cacheTimeout);
await userEvent.type(cacheTimeout, '1000');
expect(cacheTimeout).toHaveValue('1000');
userEvent.click(screen.getByRole('button', { name: 'Save' }));
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
await waitFor(() => {
expect(props.onSave).toHaveBeenCalledTimes(1);
@@ -386,12 +378,12 @@ test('"Description" should not be empty when saved', async () => {
const textboxes = screen.getAllByRole('textbox');
const description = textboxes[1]; // Description is the textarea
userEvent.clear(description);
userEvent.type(description, 'Test description');
await userEvent.clear(description);
await userEvent.type(description, 'Test description');
expect(description).toHaveValue('Test description');
userEvent.click(screen.getByRole('button', { name: 'Save' }));
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
await waitFor(() => {
expect(props.onSave).toHaveBeenCalledTimes(1);
@@ -408,19 +400,17 @@ test('"Certified by" should not be empty when saved', async () => {
// Expand the Advanced section first to access certified by
const advancedPanel = screen.getByText('Advanced').closest('[role="tab"]');
if (advancedPanel) {
userEvent.click(advancedPanel);
await userEvent.click(advancedPanel);
}
await waitFor(() => {
const certifiedBy = screen.getByRole('textbox', { name: 'Certified by' });
userEvent.clear(certifiedBy);
userEvent.type(certifiedBy, 'Test certified by');
expect(certifiedBy).toHaveValue('Test certified by');
const certifiedBy = await screen.findByRole('textbox', {
name: 'Certified by',
});
await userEvent.clear(certifiedBy);
await userEvent.type(certifiedBy, 'Test certified by');
expect(certifiedBy).toHaveValue('Test certified by');
userEvent.click(screen.getByRole('button', { name: 'Save' }));
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
await waitFor(() => {
expect(props.onSave).toHaveBeenCalledTimes(1);
@@ -437,21 +427,17 @@ test('"Certification details" should not be empty when saved', async () => {
// Expand the Advanced section first to access certification details
const advancedPanel = screen.getByText('Advanced').closest('[role="tab"]');
if (advancedPanel) {
userEvent.click(advancedPanel);
await userEvent.click(advancedPanel);
}
await waitFor(() => {
const certificationDetails = screen.getByRole('textbox', {
name: 'Certification details',
});
userEvent.clear(certificationDetails);
userEvent.type(certificationDetails, 'Test certification details');
expect(certificationDetails).toHaveValue('Test certification details');
const certificationDetails = await screen.findByRole('textbox', {
name: 'Certification details',
});
await userEvent.clear(certificationDetails);
await userEvent.type(certificationDetails, 'Test certification details');
expect(certificationDetails).toHaveValue('Test certification details');
userEvent.click(screen.getByRole('button', { name: 'Save' }));
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
await waitFor(() => {
expect(props.onSave).toHaveBeenCalledTimes(1);
@@ -474,18 +460,14 @@ test('Should display only custom tags when tagging system is enabled', async ()
const props = createProps();
renderModal(props);
await waitFor(async () => {
expect(await screen.findByText('Tags')).toBeInTheDocument();
expect(
await screen.findByRole('combobox', { name: 'Tags' }),
).toBeInTheDocument();
});
expect(await screen.findByText('Tags')).toBeInTheDocument();
expect(
await screen.findByRole('combobox', { name: 'Tags' }),
).toBeInTheDocument();
await waitFor(async () => {
expect(await screen.findByText('my test tag')).toBeInTheDocument();
expect(screen.queryByText('type:chart')).not.toBeInTheDocument();
expect(screen.queryByText('owner:1')).not.toBeInTheDocument();
});
expect(await screen.findByText('my test tag')).toBeInTheDocument();
expect(screen.queryByText('type:chart')).not.toBeInTheDocument();
expect(screen.queryByText('owner:1')).not.toBeInTheDocument();
mockIsFeatureEnabled.mockRestore();
});