AutoSizeInput: Fix controlled value being ignored when it's empty string (#96212)

This commit is contained in:
Josh Hunt
2024-11-11 12:35:30 +00:00
committed by GitHub
parent a459f648b5
commit 436f8db3e3
2 changed files with 55 additions and 29 deletions
@@ -1,5 +1,4 @@
import { screen, render, fireEvent, waitFor } from '@testing-library/react';
import { useEffect, useState } from 'react';
import { measureText } from '../../utils/measureText';
@@ -130,28 +129,58 @@ describe('AutoSizeInput', () => {
});
it('should update the input value if the value prop changes', () => {
// Wrapper component to control the `value` prop
const Wrapper = () => {
const [value, setValue] = useState('Initial');
// Simulate prop change after render
useEffect(() => {
setTimeout(() => setValue('Updated'), 100); // Update `value` after 100ms
}, []);
return <AutoSizeInput value={value} />;
};
render(<Wrapper />);
const { rerender } = render(<AutoSizeInput value="Initial" />);
// Get a handle on the original input element (to catch if it's unmounted)
// And check the initial value
const input: HTMLInputElement = screen.getByTestId('autosize-input');
// Check initial value
expect(input.value).toBe('Initial');
// Wait for the value to update
return waitFor(() => {
expect(input.value).toBe('Updated');
});
// Rerender and make sure it clears the input
rerender(<AutoSizeInput value="Updated" />);
expect(input.value).toBe('Updated');
});
it('should clear the input when the value is changed to an empty string', () => {
const { rerender } = render(<AutoSizeInput value="Initial" />);
// Get a handle on the original input element (to catch if it's unmounted)
// And check the initial value
const input: HTMLInputElement = screen.getByTestId('autosize-input');
expect(input.value).toBe('Initial');
// Rerender and make sure it clears the input
rerender(<AutoSizeInput value="" />);
expect(input.value).toBe('');
});
it('should render string values as expected', () => {
render(<AutoSizeInput value="foo" />);
const input: HTMLInputElement = screen.getByTestId('autosize-input');
expect(input.value).toBe('foo');
});
it('should render undefined values as expected', () => {
render(<AutoSizeInput value={undefined} />);
const input: HTMLInputElement = screen.getByTestId('autosize-input');
expect(input.value).toBe('');
});
it('should render null values as expected', () => {
// @ts-expect-error - look - the types forbid this, but we previously fixed an issue if the value is null
// so lets test it just in case https://github.com/grafana/grafana/pull/94078
render(<AutoSizeInput value={null} />);
const input: HTMLInputElement = screen.getByTestId('autosize-input');
expect(input.value).toBe('');
});
it('should render array values as expected', () => {
render(<AutoSizeInput value={['hello', 'world']} />);
const input: HTMLInputElement = screen.getByTestId('autosize-input');
expect(input.value).toBe('hello,world');
});
});
@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';
import * as React from 'react';
import { measureText } from '../../utils/measureText';
@@ -26,21 +26,18 @@ export const AutoSizeInput = React.forwardRef<HTMLInputElement, Props>((props, r
value: controlledValue,
...restProps
} = props;
// Initialize internal state
const [value, setValue] = React.useState(controlledValue ?? defaultValue);
const [inputWidth, setInputWidth] = React.useState(minWidth);
// Update internal state when controlled `value` prop changes
useEffect(() => {
if (controlledValue) {
setValue(controlledValue);
}
}, [controlledValue]);
setValue(controlledValue ?? defaultValue);
}, [controlledValue, defaultValue]);
// Update input width when `value`, `minWidth`, or `maxWidth` change
useEffect(() => {
setInputWidth(getWidthFor(value.toString(), minWidth, maxWidth));
const inputWidth = useMemo(() => {
const valueString = typeof value === 'string' ? value : value.toString();
return getWidthFor(valueString, minWidth, maxWidth);
}, [value, minWidth, maxWidth]);
return (