-
Notifications
You must be signed in to change notification settings - Fork 902
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core/presentation): Extract useValidationData hook
This hook encapsulates logic for handling a validationMessage. - Extract and expose the message category - Determine visible/hidden status This hook accounts for message strings (with or without embedded category) and raw JSX.Elements
- Loading branch information
1 parent
aad69b0
commit 8518286
Showing
3 changed files
with
210 additions
and
0 deletions.
There are no files selected for viewing
1 change: 1 addition & 0 deletions
1
app/scripts/modules/core/src/presentation/forms/validation/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
app/scripts/modules/core/src/presentation/forms/validation/useValidationData.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import { | ||
asyncMessage, | ||
errorMessage, | ||
infoMessage, | ||
messageMessage, | ||
successMessage, | ||
warningMessage, | ||
} from 'core/presentation/forms/validation/categories'; | ||
import * as React from 'react'; | ||
|
||
import { mount } from 'enzyme'; | ||
import { IValidationData, useValidationData } from './useValidationData'; | ||
|
||
interface IComponentProps { | ||
validationMessage: React.ReactNode; | ||
touched: boolean; | ||
callback: Function; | ||
} | ||
|
||
describe('useValidationData hook', () => { | ||
function Component({ validationMessage, touched, callback }: IComponentProps) { | ||
callback(useValidationData(validationMessage, touched)); | ||
return <></>; | ||
} | ||
|
||
function runUseValidationDataHook(validationMessage?: React.ReactNode, touched = true): IValidationData { | ||
let ran = false; | ||
let data = null; | ||
const callback = (result: IValidationData) => { | ||
if (ran) { | ||
throw new Error('already ran'); | ||
} | ||
|
||
data = result; | ||
ran = true; | ||
}; | ||
|
||
mount(<Component validationMessage={validationMessage} touched={touched} callback={callback} />); | ||
|
||
if (!ran) { | ||
throw new Error('Callback never ran'); | ||
} | ||
|
||
return data; | ||
} | ||
|
||
it('should default to error category', () => { | ||
const data = runUseValidationDataHook('Test'); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('error'); | ||
}); | ||
|
||
it('should pass non-string ReactNodes through (such as JSX.Element) with no validation category', () => { | ||
const element = <div />; | ||
const data = runUseValidationDataHook(element); | ||
expect(data.messageNode).toBe(element); | ||
expect(data.category).toBeNull(); | ||
}); | ||
|
||
it('should return async category when asyncMessage is provided', () => { | ||
const data = runUseValidationDataHook(asyncMessage('Test')); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('async'); | ||
}); | ||
|
||
it('should return error category when errorMessage is provided', () => { | ||
const data = runUseValidationDataHook(errorMessage('Test')); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('error'); | ||
}); | ||
|
||
it('should return info category when infoMessage is provided', () => { | ||
const data = runUseValidationDataHook(infoMessage('Test')); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('info'); | ||
}); | ||
|
||
it('should return message category when messageMessage is provided', () => { | ||
const data = runUseValidationDataHook(messageMessage('Test')); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('message'); | ||
}); | ||
|
||
it('should return success category when successMessage is provided', () => { | ||
const data = runUseValidationDataHook(successMessage('Test')); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('success'); | ||
}); | ||
|
||
it('should return warning category when warningMessage is provided', () => { | ||
const data = runUseValidationDataHook(warningMessage('Test')); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('warning'); | ||
}); | ||
|
||
it('should return hidden: true when a message is provided but category == error and touched == false', () => { | ||
const data = runUseValidationDataHook(errorMessage('Test'), false); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('error'); | ||
expect(data.hidden).toBe(true); | ||
}); | ||
|
||
it('should return hidden: true when a message is provided but category == warning and touched == false', () => { | ||
const data = runUseValidationDataHook(warningMessage('Test'), false); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('warning'); | ||
expect(data.hidden).toBe(true); | ||
}); | ||
|
||
it('should return hidden: false when a message is provided and category is not (warning || error) even when touched == false', () => { | ||
let data = runUseValidationDataHook(asyncMessage('Test'), false); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('async'); | ||
expect(data.hidden).toBe(false); | ||
|
||
data = runUseValidationDataHook(infoMessage('Test'), false); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('info'); | ||
expect(data.hidden).toBe(false); | ||
|
||
data = runUseValidationDataHook(messageMessage('Test'), false); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('message'); | ||
expect(data.hidden).toBe(false); | ||
|
||
data = runUseValidationDataHook(successMessage('Test'), false); | ||
expect(data.messageNode).toBe('Test'); | ||
expect(data.category).toBe('success'); | ||
expect(data.hidden).toBe(false); | ||
}); | ||
|
||
it('should return hidden: true when no message is provided regardless of the value of touched', () => { | ||
let data = runUseValidationDataHook(null, false); | ||
expect(data.messageNode).toBeNull(); | ||
expect(data.category).toBeNull(); | ||
expect(data.hidden).toBe(true); | ||
|
||
data = runUseValidationDataHook(null, true); | ||
expect(data.messageNode).toBeNull(); | ||
expect(data.category).toBeNull(); | ||
expect(data.hidden).toBe(true); | ||
|
||
data = runUseValidationDataHook(undefined, false); | ||
expect(data.messageNode).toBeNull(); | ||
expect(data.category).toBeNull(); | ||
expect(data.hidden).toBe(true); | ||
|
||
data = runUseValidationDataHook(undefined, true); | ||
expect(data.messageNode).toBeNull(); | ||
expect(data.category).toBeNull(); | ||
expect(data.hidden).toBe(true); | ||
|
||
data = runUseValidationDataHook('', false); | ||
expect(data.messageNode).toBeNull(); | ||
expect(data.category).toBeNull(); | ||
expect(data.hidden).toBe(true); | ||
|
||
data = runUseValidationDataHook('', true); | ||
expect(data.messageNode).toBeNull(); | ||
expect(data.category).toBeNull(); | ||
expect(data.hidden).toBe(true); | ||
|
||
data = runUseValidationDataHook(errorMessage(''), false); | ||
expect(data.messageNode).toBeNull(); | ||
expect(data.category).toBeNull(); | ||
expect(data.hidden).toBe(true); | ||
|
||
data = runUseValidationDataHook(errorMessage(''), true); | ||
expect(data.messageNode).toBeNull(); | ||
expect(data.category).toBeNull(); | ||
expect(data.hidden).toBe(true); | ||
}); | ||
}); |
36 changes: 36 additions & 0 deletions
36
app/scripts/modules/core/src/presentation/forms/validation/useValidationData.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import * as React from 'react'; | ||
import { isString } from 'lodash'; | ||
import { categorizeValidationMessage, IValidationCategory } from './categories'; | ||
|
||
export interface IValidationData { | ||
category: IValidationCategory | undefined; | ||
messageNode: React.ReactNode; | ||
hidden: boolean; | ||
} | ||
|
||
/** | ||
* Encapsulates processing of a validationMessage for use within a form field. | ||
* | ||
* 1) Extracts the validation message and category from a validationMessage (see categories.ts) | ||
* 2) Determines the visiblity of the validation message (errors and warnings are visible when the field is touched) | ||
* | ||
* @returns { category, messageNode, hidden } | ||
*/ | ||
export function useValidationData(validationMessage: React.ReactNode, touched: boolean): IValidationData { | ||
return React.useMemo(() => { | ||
if (!validationMessage) { | ||
return { category: null, messageNode: null, hidden: true }; | ||
} | ||
|
||
if (!isString(validationMessage)) { | ||
return { category: null, messageNode: validationMessage, hidden: false }; | ||
} | ||
|
||
const [category, messageNode] = categorizeValidationMessage(validationMessage); | ||
|
||
const isErrorOrWarning = category === 'error' || category === 'warning'; | ||
const hidden = !touched && isErrorOrWarning; | ||
|
||
return { category, messageNode, hidden }; | ||
}, [validationMessage, touched]); | ||
} |