rebase from live enviornment
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
export const ACTION_TYPES = {
|
||||
SET_VALIDATION_ERRORS: 'SET_VALIDATION_ERRORS',
|
||||
CLEAR_VALIDATION_ERROR: 'CLEAR_VALIDATION_ERROR',
|
||||
CLEAR_VALIDATION_ERRORS: 'CLEAR_VALIDATION_ERRORS',
|
||||
HIDE_VALIDATION_ERROR: 'HIDE_VALIDATION_ERROR',
|
||||
SHOW_VALIDATION_ERROR: 'SHOW_VALIDATION_ERROR',
|
||||
SHOW_ALL_VALIDATION_ERRORS: 'SHOW_ALL_VALIDATION_ERRORS',
|
||||
} as const;
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import deprecated from '@wordpress/deprecated';
|
||||
import { FieldValidationStatus } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ACTION_TYPES as types } from './action-types';
|
||||
import { ReturnOrGeneratorYieldUnion } from '../mapped-types';
|
||||
|
||||
export const setValidationErrors = (
|
||||
errors: Record< string, FieldValidationStatus >
|
||||
) => ( {
|
||||
type: types.SET_VALIDATION_ERRORS,
|
||||
errors,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Clears validation errors for the given ids.
|
||||
*
|
||||
* @param errors Array of error ids to clear.
|
||||
*/
|
||||
export const clearValidationErrors = ( errors?: string[] | undefined ) => ( {
|
||||
type: types.CLEAR_VALIDATION_ERRORS,
|
||||
errors,
|
||||
} );
|
||||
|
||||
export const clearAllValidationErrors = () => {
|
||||
deprecated( 'clearAllValidationErrors', {
|
||||
version: '9.0.0',
|
||||
alternative: 'clearValidationErrors',
|
||||
plugin: 'WooCommerce Blocks',
|
||||
link: 'https://github.com/woocommerce/woocommerce-blocks/pull/7601',
|
||||
hint: 'Calling `clearValidationErrors` with no arguments will clear all validation errors.',
|
||||
} );
|
||||
|
||||
// Return clearValidationErrors which will clear all errors by defaults if no error ids are passed.
|
||||
return clearValidationErrors();
|
||||
};
|
||||
|
||||
export const clearValidationError = ( error: string ) => ( {
|
||||
type: types.CLEAR_VALIDATION_ERROR,
|
||||
error,
|
||||
} );
|
||||
|
||||
export const hideValidationError = ( error: string ) => ( {
|
||||
type: types.HIDE_VALIDATION_ERROR,
|
||||
error,
|
||||
} );
|
||||
|
||||
export const showValidationError = ( error: string ) => ( {
|
||||
type: types.SHOW_VALIDATION_ERROR,
|
||||
error,
|
||||
} );
|
||||
|
||||
export const showAllValidationErrors = () => ( {
|
||||
type: types.SHOW_ALL_VALIDATION_ERRORS,
|
||||
} );
|
||||
|
||||
export type ValidationAction = ReturnOrGeneratorYieldUnion<
|
||||
| typeof setValidationErrors
|
||||
| typeof clearAllValidationErrors
|
||||
| typeof clearValidationError
|
||||
| typeof clearValidationErrors
|
||||
| typeof hideValidationError
|
||||
| typeof showValidationError
|
||||
| typeof showAllValidationErrors
|
||||
>;
|
||||
@@ -0,0 +1 @@
|
||||
export const STORE_KEY = 'wc/store/validation';
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createReduxStore, register } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import reducer from './reducers';
|
||||
import { STORE_KEY } from './constants';
|
||||
import * as actions from './actions';
|
||||
import * as selectors from './selectors';
|
||||
import { DispatchFromMap, SelectFromMap } from '../mapped-types';
|
||||
|
||||
export const config = {
|
||||
reducer,
|
||||
selectors,
|
||||
actions,
|
||||
};
|
||||
|
||||
const store = createReduxStore( STORE_KEY, config );
|
||||
register( store );
|
||||
|
||||
export const VALIDATION_STORE_KEY = STORE_KEY;
|
||||
|
||||
declare module '@wordpress/data' {
|
||||
function dispatch(
|
||||
key: typeof VALIDATION_STORE_KEY
|
||||
): DispatchFromMap< typeof actions >;
|
||||
function select( key: typeof VALIDATION_STORE_KEY ): SelectFromMap<
|
||||
typeof selectors
|
||||
> & {
|
||||
hasFinishedResolution: ( selector: string ) => boolean;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type { Reducer } from 'redux';
|
||||
import isShallowEqual from '@wordpress/is-shallow-equal';
|
||||
import { isString, FieldValidationStatus } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ValidationAction } from './actions';
|
||||
import { ACTION_TYPES as types } from './action-types';
|
||||
|
||||
const reducer: Reducer< Record< string, FieldValidationStatus > > = (
|
||||
state: Record< string, FieldValidationStatus > = {},
|
||||
action: Partial< ValidationAction >
|
||||
) => {
|
||||
const newState = { ...state };
|
||||
switch ( action.type ) {
|
||||
case types.SET_VALIDATION_ERRORS:
|
||||
if ( ! action.errors ) {
|
||||
return state;
|
||||
}
|
||||
const hasNewError = Object.entries( action.errors ).some(
|
||||
( [ property, error ] ) => {
|
||||
if ( typeof error?.message !== 'string' ) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
state.hasOwnProperty( property ) &&
|
||||
isShallowEqual( state[ property ], error )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
if ( ! hasNewError ) {
|
||||
return state;
|
||||
}
|
||||
return { ...state, ...action.errors };
|
||||
|
||||
case types.CLEAR_VALIDATION_ERROR:
|
||||
if (
|
||||
! isString( action.error ) ||
|
||||
! newState.hasOwnProperty( action.error )
|
||||
) {
|
||||
return newState;
|
||||
}
|
||||
delete newState[ action.error ];
|
||||
return newState;
|
||||
case types.CLEAR_VALIDATION_ERRORS:
|
||||
const { errors } = action;
|
||||
if ( typeof errors === 'undefined' ) {
|
||||
return {};
|
||||
}
|
||||
if ( ! Array.isArray( errors ) ) {
|
||||
return newState;
|
||||
}
|
||||
errors.forEach( ( error ) => {
|
||||
if ( newState.hasOwnProperty( error ) ) {
|
||||
delete newState[ error ];
|
||||
}
|
||||
} );
|
||||
return newState;
|
||||
case types.HIDE_VALIDATION_ERROR:
|
||||
if (
|
||||
! isString( action.error ) ||
|
||||
! newState.hasOwnProperty( action.error )
|
||||
) {
|
||||
return newState;
|
||||
}
|
||||
newState[ action.error ].hidden = true;
|
||||
return newState;
|
||||
case types.SHOW_VALIDATION_ERROR:
|
||||
if (
|
||||
! isString( action.error ) ||
|
||||
! newState.hasOwnProperty( action.error )
|
||||
) {
|
||||
return newState;
|
||||
}
|
||||
newState[ action.error ].hidden = false;
|
||||
return newState;
|
||||
case types.SHOW_ALL_VALIDATION_ERRORS:
|
||||
Object.keys( newState ).forEach( ( property ) => {
|
||||
if ( newState[ property ].hidden ) {
|
||||
newState[ property ].hidden = false;
|
||||
}
|
||||
} );
|
||||
return { ...newState };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export type State = ReturnType< typeof reducer >;
|
||||
export default reducer;
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import type { State } from './reducers';
|
||||
|
||||
/**
|
||||
* Gets a validation error by ID.
|
||||
*
|
||||
* @param { State } state The current state.
|
||||
* @param { string } errorId The error ID.
|
||||
* @return { string } The validation error.
|
||||
*/
|
||||
export const getValidationError = ( state: State, errorId: string ) =>
|
||||
state[ errorId ];
|
||||
|
||||
/**
|
||||
* Gets a validation error ID for use in HTML which can be used as a CSS selector, or to reference an error message.
|
||||
*
|
||||
* @param { State } state The current state.
|
||||
* @param { string } errorId The error ID.
|
||||
* @return { string } The validation error ID.
|
||||
*/
|
||||
export const getValidationErrorId = ( state: State, errorId: string ) => {
|
||||
if ( ! state.hasOwnProperty( errorId ) || state[ errorId ].hidden ) {
|
||||
return;
|
||||
}
|
||||
return `validate-error-${ errorId }`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the store has validation errors.
|
||||
*
|
||||
* @param { State } state The current state.
|
||||
* @return { boolean } Whether the store has validation errors or not.
|
||||
*/
|
||||
export const hasValidationErrors = ( state: State ) => {
|
||||
return Object.keys( state ).length > 0;
|
||||
};
|
||||
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { FieldValidationStatus } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import reducer from '../reducers';
|
||||
import { ACTION_TYPES as types } from '.././action-types';
|
||||
import { ValidationAction } from '../actions';
|
||||
|
||||
describe( 'Validation reducer', () => {
|
||||
it( 'Sets a single validation error', () => {
|
||||
const singleValidationAction: ValidationAction = {
|
||||
type: types.SET_VALIDATION_ERRORS,
|
||||
errors: {
|
||||
singleValidationError: {
|
||||
message: 'This is a single validation error message',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
const nextState = reducer( {}, singleValidationAction );
|
||||
expect( nextState ).toEqual( {
|
||||
singleValidationError: {
|
||||
message: 'This is a single validation error message',
|
||||
hidden: false,
|
||||
},
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'Does not add new errors if the same error already exists in state', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
};
|
||||
const existingErrorValidation: ValidationAction = {
|
||||
type: types.SET_VALIDATION_ERRORS,
|
||||
errors: {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
const nextState = reducer( state, existingErrorValidation );
|
||||
expect( nextState ).toEqual( {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'Does not add new errors if error message is not string, but keeps existing errors', () => {
|
||||
const integerErrorAction: ValidationAction = {
|
||||
type: types.SET_VALIDATION_ERRORS,
|
||||
errors: {
|
||||
integerError: {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore ignoring because we're testing runtime errors with integers.
|
||||
message: 1234,
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
const nextState = reducer( {}, integerErrorAction );
|
||||
expect( nextState ).not.toHaveProperty( 'integerError' );
|
||||
} );
|
||||
|
||||
it( 'Updates existing error if message or hidden property changes', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
existingValidationError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
};
|
||||
const updateExistingErrorAction: ValidationAction = {
|
||||
type: types.SET_VALIDATION_ERRORS,
|
||||
errors: {
|
||||
existingValidationError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const nextState = reducer( state, updateExistingErrorAction );
|
||||
expect( nextState ).toEqual( {
|
||||
existingValidationError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: true,
|
||||
},
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'Appends new errors to list of existing errors', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
};
|
||||
const addNewError: ValidationAction = {
|
||||
type: types.SET_VALIDATION_ERRORS,
|
||||
errors: {
|
||||
newError: {
|
||||
message: 'This is a new error',
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
const nextState = reducer( state, addNewError );
|
||||
expect( nextState ).toEqual( {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
newError: {
|
||||
message: 'This is a new error',
|
||||
hidden: false,
|
||||
},
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'Clears all validation errors', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
};
|
||||
const clearAllErrors: ValidationAction = {
|
||||
type: types.CLEAR_VALIDATION_ERRORS,
|
||||
errors: undefined,
|
||||
};
|
||||
const nextState = reducer( state, clearAllErrors );
|
||||
expect( nextState ).toEqual( {} );
|
||||
} );
|
||||
|
||||
it( 'Clears a single validation error', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
testError: {
|
||||
message: 'This is error should not be removed',
|
||||
hidden: false,
|
||||
},
|
||||
};
|
||||
const clearError: ValidationAction = {
|
||||
type: types.CLEAR_VALIDATION_ERROR,
|
||||
error: 'existingError',
|
||||
};
|
||||
const nextState = reducer( state, clearError );
|
||||
expect( nextState ).not.toHaveProperty( 'existingError' );
|
||||
expect( nextState ).toHaveProperty( 'testError' );
|
||||
} );
|
||||
|
||||
it( 'Clears multiple validation errors', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
testError: {
|
||||
message: 'This is error should also be removed',
|
||||
hidden: false,
|
||||
},
|
||||
};
|
||||
const clearError: ValidationAction = {
|
||||
type: types.CLEAR_VALIDATION_ERRORS,
|
||||
errors: [ 'existingError', 'testError' ],
|
||||
};
|
||||
const nextState = reducer( state, clearError );
|
||||
expect( nextState ).not.toHaveProperty( 'existingError' );
|
||||
expect( nextState ).not.toHaveProperty( 'testError' );
|
||||
} );
|
||||
|
||||
it( 'Hides a single validation error', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
testError: {
|
||||
message: 'This is error should not be removed',
|
||||
hidden: false,
|
||||
},
|
||||
};
|
||||
const testAction: ValidationAction = {
|
||||
type: types.HIDE_VALIDATION_ERROR,
|
||||
error: 'existingError',
|
||||
};
|
||||
const nextState = reducer( state, testAction );
|
||||
expect( nextState ).toEqual( {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: true,
|
||||
},
|
||||
testError: {
|
||||
message: 'This is error should not be removed',
|
||||
hidden: false,
|
||||
},
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'Shows a single validation error', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: true,
|
||||
},
|
||||
testError: {
|
||||
message: 'This is error should not be removed',
|
||||
hidden: true,
|
||||
},
|
||||
visibleError: {
|
||||
message: 'This is error should remain visible',
|
||||
hidden: false,
|
||||
},
|
||||
};
|
||||
const testAction: ValidationAction = {
|
||||
type: types.SHOW_VALIDATION_ERROR,
|
||||
error: 'existingError',
|
||||
};
|
||||
const nextState = reducer( state, testAction );
|
||||
expect( nextState ).toEqual( {
|
||||
existingError: {
|
||||
message: 'This is an existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
testError: {
|
||||
message: 'This is error should not be removed',
|
||||
hidden: true,
|
||||
},
|
||||
visibleError: {
|
||||
message: 'This is error should remain visible',
|
||||
hidden: false,
|
||||
},
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'Shows all validation errors', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
firstExistingError: {
|
||||
message: 'This is first existing error message',
|
||||
hidden: true,
|
||||
},
|
||||
secondExistingError: {
|
||||
message: 'This is the second existing error message',
|
||||
hidden: true,
|
||||
},
|
||||
};
|
||||
const showAllErrors: ValidationAction = {
|
||||
type: types.SHOW_ALL_VALIDATION_ERRORS,
|
||||
};
|
||||
const nextState = reducer( state, showAllErrors );
|
||||
expect( nextState ).toEqual( {
|
||||
firstExistingError: {
|
||||
message: 'This is first existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
secondExistingError: {
|
||||
message: 'This is the second existing error message',
|
||||
hidden: false,
|
||||
},
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { FieldValidationStatus } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
getValidationErrorId,
|
||||
getValidationError,
|
||||
hasValidationErrors,
|
||||
} from '../selectors';
|
||||
|
||||
describe( 'Validation selectors', () => {
|
||||
it( 'Gets the validation error', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
validationError: {
|
||||
message: 'This is a test message',
|
||||
hidden: false,
|
||||
},
|
||||
};
|
||||
const validationError = getValidationError( state, 'validationError' );
|
||||
expect( validationError ).toEqual( {
|
||||
message: 'This is a test message',
|
||||
hidden: false,
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'Gets the generated validation error ID', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
validationError: {
|
||||
message: 'This is a test message',
|
||||
hidden: false,
|
||||
},
|
||||
};
|
||||
const validationErrorID = getValidationErrorId(
|
||||
state,
|
||||
'validationError'
|
||||
);
|
||||
expect( validationErrorID ).toEqual( `validate-error-validationError` );
|
||||
} );
|
||||
|
||||
it( 'Checks if state has any validation errors', () => {
|
||||
const state: Record< string, FieldValidationStatus > = {
|
||||
validationError: {
|
||||
message: 'This is a test message',
|
||||
hidden: false,
|
||||
},
|
||||
};
|
||||
const validationErrors = hasValidationErrors( state );
|
||||
expect( validationErrors ).toEqual( true );
|
||||
const stateWithNoErrors: Record< string, FieldValidationStatus > = {};
|
||||
const stateWithNoErrorsCheckResult =
|
||||
hasValidationErrors( stateWithNoErrors );
|
||||
expect( stateWithNoErrorsCheckResult ).toEqual( false );
|
||||
} );
|
||||
} );
|
||||
Reference in New Issue
Block a user