diff --git a/packages/@adobe/react-spectrum/test/color/ColorField.test.js b/packages/@adobe/react-spectrum/test/color/ColorField.test.js index 9d0d65e6980..d9320808f69 100644 --- a/packages/@adobe/react-spectrum/test/color/ColorField.test.js +++ b/packages/@adobe/react-spectrum/test/color/ColorField.test.js @@ -64,12 +64,16 @@ describe('ColorField', function () { }); it('should allow placeholder and show warning', function () { - using spyWarn = jest.spyOn(console, 'warn').mockImplementation(() => {}); - let {getByPlaceholderText, getByRole} = renderComponent({placeholder: 'Enter a color'}); - expect(getByRole('textbox')).toBe(getByPlaceholderText('Enter a color')); - expect(spyWarn).toHaveBeenCalledWith( - 'Placeholders are deprecated due to accessibility issues. Please use help text instead. See the docs for details: https://react-spectrum.adobe.com/react-spectrum/ColorField.html#help-text' - ); + let spyWarn = jest.spyOn(console, 'warn').mockImplementation(() => {}); + try { + let {getByPlaceholderText, getByRole} = renderComponent({placeholder: 'Enter a color'}); + expect(getByRole('textbox')).toBe(getByPlaceholderText('Enter a color')); + expect(spyWarn).toHaveBeenCalledWith( + 'Placeholders are deprecated due to accessibility issues. Please use help text instead. See the docs for details: https://react-spectrum.adobe.com/react-spectrum/ColorField.html#help-text' + ); + } finally { + spyWarn.mockRestore(); + } }); it('should show valid validation state', function () { @@ -535,7 +539,9 @@ describe('ColorField', function () { expect(input).toHaveValue('0'); let button = getByTestId('submit'); - await user.click(button); + await act(async () => { + await user.click(button); + }); expect(input).toHaveValue('255'); }); } diff --git a/packages/react-aria-components/test/NumberField.test.js b/packages/react-aria-components/test/NumberField.test.js index 0a3363652c6..7b7d9954079 100644 --- a/packages/react-aria-components/test/NumberField.test.js +++ b/packages/react-aria-components/test/NumberField.test.js @@ -256,6 +256,48 @@ describe('NumberField', () => { expect(numberfield).not.toHaveAttribute('data-invalid'); }); + it('should clear validation errors when a controlled value is updated externally', async () => { + function ControlledNumberField() { + let [value, setValue] = useState(1); + + return ( +
+ (v % 2 ? 'Odd values are invalid' : null)}> + + + + + + + + + +
+ ); + } + + let {getByRole, getByTestId} = render(); + let input = getByRole('textbox'); + + act(() => { + getByTestId('form').checkValidity(); + }); + + let describedBy = input.getAttribute('aria-describedby'); + expect(describedBy).toBeTruthy(); + expect(document.getElementById(describedBy)).toHaveTextContent('Odd values are invalid'); + + await user.click(getByRole('button', {name: 'Set to 10'})); + + expect(input).not.toHaveAttribute('aria-describedby'); + expect(input).not.toHaveAttribute('aria-invalid'); + }); + it('supports pasting value in another numbering system', async () => { let {getByRole, rerender} = render(); let input = getByRole('textbox'); diff --git a/packages/react-stately/src/numberfield/useNumberFieldState.ts b/packages/react-stately/src/numberfield/useNumberFieldState.ts index eed17a1d227..6d5824729ae 100644 --- a/packages/react-stately/src/numberfield/useNumberFieldState.ts +++ b/packages/react-stately/src/numberfield/useNumberFieldState.ts @@ -24,7 +24,7 @@ import { } from '@react-types/shared'; import {FormValidationState, useFormValidationState} from '../form/useFormValidationState'; import {NumberFormatter, NumberParser} from '@internationalized/number'; -import {useCallback, useMemo, useState} from 'react'; +import {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {useControlledState} from '../utils/useControlledState'; export interface NumberFieldProps @@ -185,6 +185,14 @@ export function useNumberFieldState(props: NumberFieldStateOptions): NumberField value: numberValue }); + let prevControlledValue = useRef(value); + useEffect(() => { + if (value !== undefined && !Object.is(value, prevControlledValue.current)) { + validation.commitValidation(); + } + prevControlledValue.current = value; + }, [value]); + let clampStep = step !== undefined && !isNaN(step) ? step : 1; if (intlOptions.style === 'percent' && (step === undefined || isNaN(step))) { clampStep = 0.01;