HEX
Server: LiteSpeed
System: Linux eko108.isimtescil.net 4.18.0-477.21.1.lve.1.el8.x86_64 #1 SMP Tue Sep 5 23:08:35 UTC 2023 x86_64
User: uyarreklamcomtr (11202)
PHP: 7.4.33
Disabled: opcache_get_status
Upload Files
File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/validated-text-input.tsx.tar
packages/woocommerce-blocks/packages/checkout/components/text-input/validated-text-input.tsx000064400000014051151561455170045022 0ustar00var/www/vhosts/uyarreklam.com.tr/httpdocs/wp-content/plugins/woocommerce/**
 * External dependencies
 */
import {
	useEffect,
	useState,
	useCallback,
	forwardRef,
	useImperativeHandle,
	useRef,
} from '@wordpress/element';
import classnames from 'classnames';
import { isObject } from '@woocommerce/types';
import { useDispatch, useSelect } from '@wordpress/data';
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
import { usePrevious } from '@woocommerce/base-hooks';
import { useInstanceId } from '@wordpress/compose';

/**
 * Internal dependencies
 */
import TextInput from './text-input';
import './style.scss';
import { ValidationInputError } from '../validation-input-error';
import { getValidityMessageForInput } from '../../utils';
import { ValidatedTextInputProps } from './types';

export type ValidatedTextInputHandle = {
	revalidate: () => void;
};

/**
 * A text based input which validates the input value.
 */
const ValidatedTextInput = forwardRef<
	ValidatedTextInputHandle,
	ValidatedTextInputProps
>(
	(
		{
			className,
			id,
			ariaDescribedBy,
			errorId,
			focusOnMount = false,
			onChange,
			showError = true,
			errorMessage: passedErrorMessage = '',
			value = '',
			customValidation = () => true,
			customFormatter = ( newValue: string ) => newValue,
			label,
			validateOnMount = true,
			instanceId: preferredInstanceId,
			...rest
		},
		forwardedRef
	): JSX.Element => {
		// True on mount.
		const [ isPristine, setIsPristine ] = useState( true );

		// Track incoming value.
		const previousValue = usePrevious( value );

		// Ref for the input element.
		const inputRef = useRef< HTMLInputElement >( null );

		const instanceId = useInstanceId(
			ValidatedTextInput,
			'',
			preferredInstanceId
		);
		const textInputId =
			typeof id !== 'undefined' ? id : 'textinput-' + instanceId;
		const errorIdString = errorId !== undefined ? errorId : textInputId;

		const {
			setValidationErrors,
			hideValidationError,
			clearValidationError,
		} = useDispatch( VALIDATION_STORE_KEY );

		const { validationError, validationErrorId } = useSelect(
			( select ) => {
				const store = select( VALIDATION_STORE_KEY );
				return {
					validationError: store.getValidationError( errorIdString ),
					validationErrorId:
						store.getValidationErrorId( errorIdString ),
				};
			}
		);

		const validateInput = useCallback(
			( errorsHidden = true ) => {
				const inputObject = inputRef.current || null;

				if ( inputObject === null ) {
					return;
				}

				// Trim white space before validation.
				inputObject.value = inputObject.value.trim();
				inputObject.setCustomValidity( '' );

				if (
					inputObject.checkValidity() &&
					customValidation( inputObject )
				) {
					clearValidationError( errorIdString );
					return;
				}

				setValidationErrors( {
					[ errorIdString ]: {
						message: label
							? getValidityMessageForInput( label, inputObject )
							: inputObject.validationMessage,
						hidden: errorsHidden,
					},
				} );
			},
			[
				clearValidationError,
				customValidation,
				errorIdString,
				setValidationErrors,
				label,
			]
		);

		// Allows parent to trigger revalidation.
		useImperativeHandle(
			forwardedRef,
			function () {
				return {
					revalidate() {
						validateInput( ! value );
					},
				};
			},
			[ validateInput, value ]
		);

		/**
		 * Handle browser autofill / changes via data store.
		 *
		 * Trigger validation on incoming state change if the current element is not in focus. This is because autofilled
		 * elements do not trigger the blur() event, and so values can be validated in the background if the state changes
		 * elsewhere.
		 *
		 * Errors are immediately visible.
		 */
		useEffect( () => {
			if (
				value !== previousValue &&
				( value || previousValue ) &&
				inputRef &&
				inputRef.current !== null &&
				inputRef.current?.ownerDocument?.activeElement !==
					inputRef.current
			) {
				const formattedValue = customFormatter(
					inputRef.current.value
				);

				if ( formattedValue !== value ) {
					onChange( formattedValue );
				}
			}
		}, [ validateInput, customFormatter, value, previousValue, onChange ] );

		/**
		 * Validation on mount.
		 *
		 * If the input is in pristine state on mount, focus the element (if focusOnMount is enabled), and validate in the
		 * background.
		 *
		 * Errors are hidden until blur.
		 */
		useEffect( () => {
			if ( ! isPristine ) {
				return;
			}
			if ( focusOnMount ) {
				inputRef.current?.focus();
			}

			// if validateOnMount is false, only validate input if focusOnMount is also false
			if ( validateOnMount || ! focusOnMount ) {
				validateInput( true );
			}

			setIsPristine( false );
		}, [
			validateOnMount,
			focusOnMount,
			isPristine,
			setIsPristine,
			validateInput,
		] );

		// Remove validation errors when unmounted.
		useEffect( () => {
			return () => {
				clearValidationError( errorIdString );
			};
		}, [ clearValidationError, errorIdString ] );

		if ( passedErrorMessage !== '' && isObject( validationError ) ) {
			validationError.message = passedErrorMessage;
		}

		const hasError = validationError?.message && ! validationError?.hidden;
		const describedBy =
			showError && hasError && validationErrorId
				? validationErrorId
				: ariaDescribedBy;

		return (
			<TextInput
				className={ classnames( className, {
					'has-error': hasError,
				} ) }
				aria-invalid={ hasError === true }
				id={ textInputId }
				feedback={
					showError && (
						<ValidationInputError
							errorMessage={ passedErrorMessage }
							propertyName={ errorIdString }
						/>
					)
				}
				ref={ inputRef }
				onChange={ ( newValue ) => {
					// Hide errors while typing.
					hideValidationError( errorIdString );

					// Validate the input value.
					validateInput( true );

					// Push the changes up to the parent component.
					const formattedValue = customFormatter( newValue );

					if ( formattedValue !== value ) {
						onChange( formattedValue );
					}
				} }
				onBlur={ () => validateInput( false ) }
				ariaDescribedBy={ describedBy }
				value={ value }
				title=""
				label={ label }
				{ ...rest }
			/>
		);
	}
);

export default ValidatedTextInput;
packages/woocommerce-blocks/packages/checkout/components/text-input/test/validated-text-input.tsx000064400000021571151567044520046005 0ustar00var/www/vhosts/uyarreklam.com.tr/httpdocs/wp-content/plugins/woocommerce/**
 * External dependencies
 */
import { act, render, screen } from '@testing-library/react';
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
import { dispatch, select } from '@wordpress/data';
import userEvent from '@testing-library/user-event';
import { useState } from '@wordpress/element';
import * as wpData from '@wordpress/data';

/**
 * Internal dependencies
 */
import ValidatedTextInput from '../validated-text-input';

jest.mock( '@wordpress/data', () => ( {
	__esModule: true,
	...jest.requireActual( '@wordpress/data' ),
	useDispatch: jest.fn().mockImplementation( ( args ) => {
		return jest.requireActual( '@wordpress/data' ).useDispatch( args );
	} ),
} ) );

describe( 'ValidatedTextInput', () => {
	it( 'Removes related validation error on change', async () => {
		render(
			<ValidatedTextInput
				instanceId={ '0' }
				accept={ 'image/*' }
				onChange={ () => void 0 }
				value={ 'Test' }
				id={ 'test-input' }
				label={ 'Test Input' }
			/>
		);

		await act( () =>
			dispatch( VALIDATION_STORE_KEY ).setValidationErrors( {
				'test-input': {
					message: 'Error message',
					hidden: false,
				},
			} )
		);

		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
		).not.toBe( undefined );
		const textInputElement = await screen.getByLabelText( 'Test Input' );
		await userEvent.type( textInputElement, 'New value' );
		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
		).toBe( undefined );
	} );
	it( 'Hides related validation error on change when id is not specified', async () => {
		render(
			<ValidatedTextInput
				instanceId={ '1' }
				accept={ 'image/*' }
				onChange={ () => void 0 }
				value={ 'Test' }
				label={ 'Test Input' }
			/>
		);

		await act( () =>
			dispatch( VALIDATION_STORE_KEY ).setValidationErrors( {
				'textinput-1': {
					message: 'Error message',
					hidden: false,
				},
			} )
		);
		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'textinput-1' )
		).not.toBe( undefined );
		const textInputElement = await screen.getByLabelText( 'Test Input' );
		await userEvent.type( textInputElement, 'New value' );
		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'textinput-1' )
		).toBe( undefined );
	} );
	it( 'Displays a passed error message', async () => {
		render(
			<ValidatedTextInput
				instanceId={ '2' }
				accept={ 'image/*' }
				onChange={ () => void 0 }
				value={ 'Test' }
				label={ 'Test Input' }
				errorMessage={ 'Custom error message' }
			/>
		);
		await act( () =>
			dispatch( VALIDATION_STORE_KEY ).setValidationErrors( {
				'textinput-2': {
					message: 'Error message in data store',
					hidden: false,
				},
			} )
		);
		const customErrorMessageElement = await screen.getByText(
			'Custom error message'
		);
		expect(
			screen.queryByText( 'Error message in data store' )
		).not.toBeInTheDocument();
		await expect( customErrorMessageElement ).toBeInTheDocument();
	} );
	it( 'Displays an error message from the data store', async () => {
		render(
			<ValidatedTextInput
				instanceId={ '3' }
				accept={ 'image/*' }
				onChange={ () => void 0 }
				value={ 'Test' }
				label={ 'Test Input' }
			/>
		);
		await act( () =>
			dispatch( VALIDATION_STORE_KEY ).setValidationErrors( {
				'textinput-3': {
					message: 'Error message 3',
					hidden: false,
				},
			} )
		);
		const errorMessageElement = await screen.getByText( 'Error message 3' );
		await expect( errorMessageElement ).toBeInTheDocument();
	} );
	it( 'Runs custom validation on the input', async () => {
		const TestComponent = () => {
			const [ inputValue, setInputValue ] = useState( 'Test' );
			return (
				<ValidatedTextInput
					instanceId={ '4' }
					id={ 'test-input' }
					onChange={ ( value ) => setInputValue( value ) }
					value={ inputValue }
					label={ 'Test Input' }
					customValidation={ ( inputObject ) => {
						return inputObject.value === 'Valid Value';
					} }
				/>
			);
		};
		render( <TestComponent /> );

		const textInputElement = await screen.getByLabelText( 'Test Input' );
		await userEvent.type( textInputElement, 'Invalid Value' );
		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
		).not.toBe( undefined );
		await userEvent.type( textInputElement, '{selectall}{del}Valid Value' );
		await expect( textInputElement.value ).toBe( 'Valid Value' );
		await expect(
			select( VALIDATION_STORE_KEY ).getValidationError( 'test-input' )
		).toBe( undefined );
	} );
	it( 'Shows a custom error message for an invalid required input', async () => {
		const TestComponent = () => {
			const [ inputValue, setInputValue ] = useState( '' );
			return (
				<ValidatedTextInput
					instanceId={ '5' }
					id={ 'test-input' }
					onChange={ ( value ) => setInputValue( value ) }
					value={ inputValue }
					label={ 'Test Input' }
					required={ true }
				/>
			);
		};
		render( <TestComponent /> );
		const textInputElement = await screen.getByLabelText( 'Test Input' );
		await userEvent.type( textInputElement, 'test' );
		await userEvent.type( textInputElement, '{selectall}{del}' );
		await textInputElement.blur();
		await expect(
			screen.queryByText( 'Please enter a valid test input' )
		).not.toBeNull();
	} );
	describe( 'correctly validates on mount', () => {
		it( 'validates when focusOnMount is true and validateOnMount is not set', async () => {
			const setValidationErrors = jest.fn();
			wpData.useDispatch.mockImplementation( ( storeName: string ) => {
				if ( storeName === VALIDATION_STORE_KEY ) {
					return {
						...jest
							.requireActual( '@wordpress/data' )
							.useDispatch( storeName ),
						setValidationErrors,
					};
				}
				return jest
					.requireActual( '@wordpress/data' )
					.useDispatch( storeName );
			} );

			const TestComponent = () => {
				const [ inputValue, setInputValue ] = useState( '' );
				return (
					<ValidatedTextInput
						instanceId={ '6' }
						id={ 'test-input' }
						onChange={ ( value ) => setInputValue( value ) }
						value={ inputValue }
						label={ 'Test Input' }
						required={ true }
						focusOnMount={ true }
					/>
				);
			};
			await render( <TestComponent /> );
			const textInputElement = await screen.getByLabelText(
				'Test Input'
			);
			await expect( textInputElement ).toHaveFocus();
			await expect( setValidationErrors ).toHaveBeenCalledWith( {
				'test-input': {
					message: 'Please enter a valid test input',
					hidden: true,
				},
			} );
		} );
		it( 'validates when focusOnMount is false, regardless of validateOnMount value', async () => {
			const setValidationErrors = jest.fn();
			wpData.useDispatch.mockImplementation( ( storeName: string ) => {
				if ( storeName === VALIDATION_STORE_KEY ) {
					return {
						...jest
							.requireActual( '@wordpress/data' )
							.useDispatch( storeName ),
						setValidationErrors,
					};
				}
				return jest
					.requireActual( '@wordpress/data' )
					.useDispatch( storeName );
			} );

			const TestComponent = ( { validateOnMount = false } ) => {
				const [ inputValue, setInputValue ] = useState( '' );
				return (
					<ValidatedTextInput
						instanceId={ '6' }
						id={ 'test-input' }
						onChange={ ( value ) => setInputValue( value ) }
						value={ inputValue }
						label={ 'Test Input' }
						required={ true }
						focusOnMount={ true }
						validateOnMount={ validateOnMount }
					/>
				);
			};
			const { rerender } = await render( <TestComponent /> );
			const textInputElement = await screen.getByLabelText(
				'Test Input'
			);
			await expect( textInputElement ).toHaveFocus();
			await expect( setValidationErrors ).not.toHaveBeenCalled();

			await rerender( <TestComponent validateOnMount={ true } /> );
			await expect( textInputElement ).toHaveFocus();
			await expect( setValidationErrors ).not.toHaveBeenCalled();
		} );
		it( 'does not validate when validateOnMount is false and focusOnMount is true', async () => {
			const setValidationErrors = jest.fn();
			wpData.useDispatch.mockImplementation( ( storeName: string ) => {
				if ( storeName === VALIDATION_STORE_KEY ) {
					return {
						...jest
							.requireActual( '@wordpress/data' )
							.useDispatch( storeName ),
						setValidationErrors,
					};
				}
				return jest
					.requireActual( '@wordpress/data' )
					.useDispatch( storeName );
			} );

			const TestComponent = () => {
				const [ inputValue, setInputValue ] = useState( '' );
				return (
					<ValidatedTextInput
						instanceId={ '6' }
						id={ 'test-input' }
						onChange={ ( value ) => setInputValue( value ) }
						value={ inputValue }
						label={ 'Test Input' }
						required={ true }
						focusOnMount={ true }
						validateOnMount={ false }
					/>
				);
			};
			await render( <TestComponent /> );
			const textInputElement = await screen.getByLabelText(
				'Test Input'
			);
			await expect( textInputElement ).toHaveFocus();
			await expect( setValidationErrors ).not.toHaveBeenCalled();
		} );
	} );
} );