From 73059551995e3dc2ee5d329d939c60ab3af56e48 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 19 Oct 2023 12:58:03 -0400 Subject: [PATCH] fix: prevent #NaN color picker values --- src/ColorPicker/ColorPicker.test.jsx | 26 +++++++++- src/ColorPicker/index.jsx | 72 +++++++++++++++++++++------- 2 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/ColorPicker/ColorPicker.test.jsx b/src/ColorPicker/ColorPicker.test.jsx index a79a24126e..7f156f51ea 100644 --- a/src/ColorPicker/ColorPicker.test.jsx +++ b/src/ColorPicker/ColorPicker.test.jsx @@ -29,13 +29,35 @@ describe('picker works as expected', () => { const color = 'wassap'; const setColor = jest.fn(); it('validates hex color', async () => { - const { rerender } = render(); + render(); + await act(async () => { await userEvent.click(screen.getByRole('button')); }); + expect(screen.queryByTestId('hex-input').value).toEqual('#wassap'); expect(screen.queryByText('Colors must be in hexadecimal format.')).toBeInTheDocument(); - rerender(); + await act(async () => { + await userEvent.clear(screen.getByTestId('hex-input')); + await userEvent.paste(screen.getByTestId('hex-input'), '32116c'); + }); + expect(screen.queryByTestId('hex-input').value).toEqual('#32116c'); + expect(screen.queryByText('Colors must be in hexadecimal format.')).not.toBeInTheDocument(); + + await act(async () => { + await userEvent.clear(screen.getByTestId('hex-input')); + await userEvent.paste(screen.getByTestId('hex-input'), 'yuk'); + }); + + expect(screen.queryByTestId('hex-input').value).toEqual('#yuk'); + expect(screen.queryByText('Colors must be in hexadecimal format.')).toBeInTheDocument(); + + await act(async () => { + await userEvent.clear(screen.getByTestId('hex-input')); + await userEvent.paste(screen.getByTestId('hex-input'), '#fad'); + }); + + expect(screen.queryByTestId('hex-input').value).toEqual('#fad'); expect(screen.queryByText('Colors must be in hexadecimal format.')).not.toBeInTheDocument(); }); }); diff --git a/src/ColorPicker/index.jsx b/src/ColorPicker/index.jsx index 1cef1c336b..2877ec6f23 100644 --- a/src/ColorPicker/index.jsx +++ b/src/ColorPicker/index.jsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { HexColorPicker } from 'react-colorful'; @@ -15,24 +15,63 @@ function ColorPicker({ }) { const [isOpen, open, close] = useToggle(false); const [target, setTarget] = React.useState(null); - const [hexValid, setHexValid] = React.useState(true); - const validateHex = useCallback((input) => { + const colorIsValid = (colorToValidate) => { const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; - if (input.length > 1 && !input.startsWith('#')) { - setColor(`#${input}`); - } else { - setColor(input); + return hexRegex.test(colorToValidate); + }; + + const formatHexColorString = (colorString) => { + if (!colorString.startsWith('#')) { + return `#${colorString}`.slice(0, 7); + } + + return colorString.slice(0, 7); + }; + + const [hexValid, setHexValid] = React.useState(() => (color === '' || colorIsValid(formatHexColorString(color)))); + + const [hexColorString, setHexColorString] = React.useState(() => { + if (color === '') { + return ''; + } + + return formatHexColorString(color); + }); + const [colorToDisplay, setColorToDisplay] = React.useState(() => { + const formattedColor = formatHexColorString(color); + if (colorIsValid(formattedColor)) { + return formattedColor; } - if (input === '' || hexRegex.test(input) === true) { + + return '#fff'; + }); + + const setValidatedColor = (newColor) => { + if (newColor === '') { setHexValid(true); - } else { - setHexValid(false); + setColor(''); + setHexColorString(''); + setColorToDisplay('#fff'); + return; } - }, [setColor]); - // this is needed for when a user changes the color through the sliders - useEffect(() => validateHex(color), [validateHex, color]); + const formattedColor = formatHexColorString(newColor); + + if (colorIsValid(formattedColor)) { + setHexValid(true); + setColor(formattedColor); + setHexColorString(formattedColor); + setColorToDisplay(formattedColor); + return; + } + + setHexValid(false); + setHexColorString(formattedColor); + + // ensure the picker value stays in sync with the textbox + setColor(formattedColor); + }; return ( <> @@ -65,16 +104,17 @@ function ColorPicker({ className="pgn__color-modal rounded shadow" style={{ textAlign: 'start' }} > - +
Hex validateHex(e.target.value)} + value={hexColorString} + onChange={(e) => setValidatedColor(e.target.value)} data-testid="hex-input" + spellCheck="false" />
{!hexValid && (