From bcd21cee74bd376298fd58331a7079185198b881 Mon Sep 17 00:00:00 2001 From: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:00:03 +0530 Subject: [PATCH] fix: y axis unit not interactive in the panel edit mode (#9003) --- .../ColumnUnitSelector/ColumnUnitSelector.tsx | 1 - .../RightContainer/YAxisUnitSelector.tsx | 43 +++- .../__tests__/YAxisUnitSelector.test.tsx | 240 ++++++++++++++++++ .../NewWidget/RightContainer/index.tsx | 1 - 4 files changed, 276 insertions(+), 9 deletions(-) create mode 100644 frontend/src/container/NewWidget/RightContainer/__tests__/YAxisUnitSelector.test.tsx diff --git a/frontend/src/container/NewWidget/RightContainer/ColumnUnitSelector/ColumnUnitSelector.tsx b/frontend/src/container/NewWidget/RightContainer/ColumnUnitSelector/ColumnUnitSelector.tsx index 95450ddf49a6..5742815de0da 100644 --- a/frontend/src/container/NewWidget/RightContainer/ColumnUnitSelector/ColumnUnitSelector.tsx +++ b/frontend/src/container/NewWidget/RightContainer/ColumnUnitSelector/ColumnUnitSelector.tsx @@ -73,7 +73,6 @@ export function ColumnUnitSelector( Column Units {aggregationQueries.map(({ value, label }) => ( handleColumnUnitSelect(value, unitValue) diff --git a/frontend/src/container/NewWidget/RightContainer/YAxisUnitSelector.tsx b/frontend/src/container/NewWidget/RightContainer/YAxisUnitSelector.tsx index bfa9600c18ab..264169097145 100644 --- a/frontend/src/container/NewWidget/RightContainer/YAxisUnitSelector.tsx +++ b/frontend/src/container/NewWidget/RightContainer/YAxisUnitSelector.tsx @@ -1,6 +1,6 @@ import { AutoComplete, Input, Typography } from 'antd'; import { find } from 'lodash-es'; -import { Dispatch, SetStateAction } from 'react'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; import { flattenedCategories } from './dataFormatCategories'; @@ -14,25 +14,54 @@ const findCategoryByName = ( find(flattenedCategories, (option) => option.name === searchValue); type OnSelectType = Dispatch> | ((val: string) => void); + function YAxisUnitSelector({ - defaultValue, value, onSelect, fieldLabel, handleClear, }: { - defaultValue: string; value: string; onSelect: OnSelectType; fieldLabel: string; handleClear?: () => void; }): JSX.Element { + const [inputValue, setInputValue] = useState(''); + + // Sync input value with the actual value prop + useEffect(() => { + const category = findCategoryById(value); + setInputValue(category?.name || ''); + }, [value]); + const onSelectHandler = (selectedValue: string): void => { - onSelect(findCategoryByName(selectedValue)?.id || ''); + const category = findCategoryByName(selectedValue); + if (category) { + onSelect(category.id); + setInputValue(selectedValue); + } }; + + const onChangeHandler = (inputValue: string): void => { + setInputValue(inputValue); + // Clear the yAxisUnit if input is empty or doesn't match any option + if (!inputValue) { + onSelect(''); + } + }; + + const onClearHandler = (): void => { + setInputValue(''); + onSelect(''); + if (handleClear) { + handleClear(); + } + }; + const options = flattenedCategories.map((options) => ({ value: options.name, })); + return (
{fieldLabel} @@ -41,9 +70,9 @@ function YAxisUnitSelector({ rootClassName="y-axis-root-popover" options={options} allowClear - defaultValue={findCategoryById(defaultValue)?.name} - value={findCategoryById(value)?.name || ''} - onClear={handleClear} + value={inputValue} + onChange={onChangeHandler} + onClear={onClearHandler} onSelect={onSelectHandler} filterOption={(inputValue, option): boolean => { if (option) { diff --git a/frontend/src/container/NewWidget/RightContainer/__tests__/YAxisUnitSelector.test.tsx b/frontend/src/container/NewWidget/RightContainer/__tests__/YAxisUnitSelector.test.tsx new file mode 100644 index 000000000000..f1cb5e1e5f98 --- /dev/null +++ b/frontend/src/container/NewWidget/RightContainer/__tests__/YAxisUnitSelector.test.tsx @@ -0,0 +1,240 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { act } from 'react-dom/test-utils'; + +import YAxisUnitSelector from '../YAxisUnitSelector'; + +// Mock the dataFormatCategories to have predictable test data +jest.mock('../dataFormatCategories', () => ({ + flattenedCategories: [ + { id: 'seconds', name: 'seconds (s)' }, + { id: 'milliseconds', name: 'milliseconds (ms)' }, + { id: 'hours', name: 'hours (h)' }, + { id: 'minutes', name: 'minutes (m)' }, + ], +})); + +const MOCK_SECONDS = 'seconds'; +const MOCK_MILLISECONDS = 'milliseconds'; + +describe('YAxisUnitSelector', () => { + const defaultProps = { + value: MOCK_SECONDS, + onSelect: jest.fn(), + fieldLabel: 'Y Axis Unit', + handleClear: jest.fn(), + }; + + let user: ReturnType; + + beforeEach(() => { + jest.clearAllMocks(); + user = userEvent.setup(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('Rendering (Read) & (write)', () => { + it('renders with correct field label', () => { + render( + , + ); + expect(screen.getByText('Y Axis Unit')).toBeInTheDocument(); + const input = screen.getByRole('combobox'); + + expect(input).toHaveValue('seconds (s)'); + }); + + it('renders with custom field label', () => { + render( + , + ); + expect(screen.getByText('Custom Unit Label')).toBeInTheDocument(); + }); + + it('displays empty input when value prop is empty', () => { + render( + , + ); + expect(screen.getByDisplayValue('')).toBeInTheDocument(); + }); + + it('shows placeholder text', () => { + render( + , + ); + expect(screen.getByPlaceholderText('Unit')).toBeInTheDocument(); + }); + + it('handles numeric input', async () => { + render( + , + ); + const input = screen.getByRole('combobox'); + + await user.clear(input); + await user.type(input, '12345'); + expect(input).toHaveValue('12345'); + }); + + it('handles mixed content input', async () => { + render( + , + ); + const input = screen.getByRole('combobox'); + + await user.clear(input); + await user.type(input, 'Test123!@#'); + expect(input).toHaveValue('Test123!@#'); + }); + }); + + describe('State Management', () => { + it('syncs input value with value prop changes', async () => { + const { rerender } = render( + , + ); + const input = screen.getByRole('combobox'); + + // Initial value + expect(input).toHaveValue('seconds (s)'); + + // Change value prop + rerender( + , + ); + + await waitFor(() => { + expect(input).toHaveValue('milliseconds (ms)'); + }); + }); + + it('handles empty value prop correctly', async () => { + const { rerender } = render( + , + ); + const input = screen.getByRole('combobox'); + + // Change to empty value + rerender( + , + ); + + await waitFor(() => { + expect(input).toHaveValue(''); + }); + }); + + it('handles invalid value prop gracefully', async () => { + const { rerender } = render( + , + ); + const input = screen.getByRole('combobox'); + + // Change to invalid value + rerender( + , + ); + + await waitFor(() => { + expect(input).toHaveValue(''); + }); + }); + + it('maintains local state during typing', async () => { + render( + , + ); + const input = screen.getByRole('combobox'); + + // first clear then type + await user.clear(input); + await user.type(input, 'test'); + expect(input).toHaveValue('test'); + + // Value prop change should not override local typing + await act(async () => { + // Simulate prop change + render( + , + ); + }); + + // Local typing should be preserved + expect(input).toHaveValue('test'); + }); + }); +}); diff --git a/frontend/src/container/NewWidget/RightContainer/index.tsx b/frontend/src/container/NewWidget/RightContainer/index.tsx index 4317f1c73ac3..64c6e91ced23 100644 --- a/frontend/src/container/NewWidget/RightContainer/index.tsx +++ b/frontend/src/container/NewWidget/RightContainer/index.tsx @@ -337,7 +337,6 @@ function RightContainer({ {allowYAxisUnit && (