diff --git a/components/src/FormatControls/UnitSelector.test.tsx b/components/src/FormatControls/UnitSelector.test.tsx new file mode 100644 index 00000000..a5be17a0 --- /dev/null +++ b/components/src/FormatControls/UnitSelector.test.tsx @@ -0,0 +1,125 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { FormatOptions } from '../model'; +import { UnitSelector } from './UnitSelector'; + +function renderUnitSelector(value: FormatOptions | undefined = undefined, onChange = vi.fn()): void { + render(); +} + +function getCombobox(): HTMLElement { + return screen.getByRole('combobox'); +} + +function getClearButton(): HTMLElement { + return screen.getByRole('button', { name: 'clear unit' }); +} + +describe('UnitSelector', () => { + describe('initial display', () => { + it('shows "Decimal" when no value is provided', () => { + renderUnitSelector(undefined); + expect(getCombobox()).toHaveValue('Decimal'); + }); + + it('shows the label of the currently selected unit', () => { + renderUnitSelector({ unit: 'bytes' }); + expect(getCombobox()).toHaveValue('Bytes (IEC)'); + }); + + it('shows "Decimal" when value has no unit', () => { + renderUnitSelector({} as FormatOptions); + expect(getCombobox()).toHaveValue('Decimal'); + }); + }); + + describe('selecting a unit', () => { + it('calls onChange with the selected unit when clicking an option', () => { + const onChange = vi.fn(); + render(); + + userEvent.click(getCombobox()); + userEvent.click(screen.getByRole('option', { name: 'Percent (0-100)' })); + + expect(onChange).toHaveBeenCalledWith({ unit: 'percent' }); + }); + + it('calls onChange with the typed and selected unit', () => { + const onChange = vi.fn(); + render(); + + userEvent.click(getCombobox()); + userEvent.keyboard('years'); + userEvent.click(screen.getByRole('option', { name: 'Years' })); + + expect(onChange).toHaveBeenCalledWith({ unit: 'years' }); + }); + + it('calls onChange with only the unit key, dropping other format fields', () => { + const onChange = vi.fn(); + render(); + + userEvent.click(getCombobox()); + userEvent.click(screen.getByRole('option', { name: 'Percent (0-100)' })); + + expect(onChange).toHaveBeenCalledWith({ unit: 'percent' }); + }); + }); + + describe('clear button', () => { + it('renders a clear button', () => { + renderUnitSelector({ unit: 'bytes' }); + expect(getClearButton()).toBeInTheDocument(); + }); + + it('calls onChange with undefined when clear is clicked', () => { + const onChange = vi.fn(); + render(); + + userEvent.click(getClearButton()); + + expect(onChange).toHaveBeenCalledWith(undefined); + expect(onChange).toHaveBeenCalledTimes(1); + }); + + it('calls onChange with undefined when clear is clicked with no value set', () => { + const onChange = vi.fn(); + render(); + + userEvent.click(getClearButton()); + + expect(onChange).toHaveBeenCalledWith(undefined); + }); + }); + + describe('disabled state', () => { + it('is enabled by default', () => { + renderUnitSelector({ unit: 'decimal' }); + expect(getCombobox()).toBeEnabled(); + }); + + it('disables the combobox when disabled prop is true', () => { + render(); + expect(getCombobox()).toBeDisabled(); + }); + + it('disables the clear button when disabled prop is true', () => { + render(); + expect(getClearButton()).toBeDisabled(); + }); + }); +}); diff --git a/components/src/FormatControls/UnitSelector.tsx b/components/src/FormatControls/UnitSelector.tsx index ee038a70..9c33c559 100644 --- a/components/src/FormatControls/UnitSelector.tsx +++ b/components/src/FormatControls/UnitSelector.tsx @@ -11,6 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { IconButton, InputAdornment, TextField } from '@mui/material'; +import ClearIcon from 'mdi-material-ui/Close'; import { ReactElement } from 'react'; import { FormatOptions, UNIT_CONFIG, UnitConfig } from '../model'; @@ -48,12 +50,39 @@ export function UnitSelector({ value, onChange, disabled = false, ...otherProps }; return ( - - value={value ? { id: value.unit || 'decimal', ...unitConfig } : null} + + value={{ id: value?.unit || 'decimal', ...unitConfig }} options={KIND_OPTIONS} groupBy={(option) => option.group ?? 'Decimal'} onChange={handleChange} disabled={disabled} + disableClearable + renderInput={({ InputProps, ...params }) => ( + + {InputProps.endAdornment} + + onChange(undefined)} + disabled={disabled} + tabIndex={-1} + > + + + + + ), + }, + }} + /> + )} {...otherProps} /> );