@aws-amplify/ui-react-native 1.2.20 → 1.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/Authenticator/Authenticator.d.ts +101 -148
  3. package/dist/Authenticator/Authenticator.js +2 -3
  4. package/dist/Authenticator/Defaults/ConfirmResetPassword/ConfirmResetPassword.d.ts +13 -2
  5. package/dist/Authenticator/Defaults/ConfirmResetPassword/ConfirmResetPassword.js +4 -3
  6. package/dist/Authenticator/Defaults/ConfirmSignIn/ConfirmSignIn.d.ts +13 -2
  7. package/dist/Authenticator/Defaults/ConfirmSignIn/ConfirmSignIn.js +4 -3
  8. package/dist/Authenticator/Defaults/ConfirmSignUp/ConfirmSignUp.d.ts +13 -2
  9. package/dist/Authenticator/Defaults/ConfirmSignUp/ConfirmSignUp.js +4 -3
  10. package/dist/Authenticator/Defaults/ConfirmVerifyUser/ConfirmVerifyUser.d.ts +13 -2
  11. package/dist/Authenticator/Defaults/ConfirmVerifyUser/ConfirmVerifyUser.js +4 -3
  12. package/dist/Authenticator/Defaults/ForceNewPassword/ForceNewPassword.d.ts +13 -2
  13. package/dist/Authenticator/Defaults/ForceNewPassword/ForceNewPassword.js +4 -3
  14. package/dist/Authenticator/Defaults/ResetPassword/ResetPassword.d.ts +13 -2
  15. package/dist/Authenticator/Defaults/ResetPassword/ResetPassword.js +4 -3
  16. package/dist/Authenticator/Defaults/SetupTOTP/SetupTOTP.d.ts +13 -2
  17. package/dist/Authenticator/Defaults/SetupTOTP/SetupTOTP.js +4 -3
  18. package/dist/Authenticator/Defaults/SignIn/SignIn.d.ts +13 -2
  19. package/dist/Authenticator/Defaults/SignIn/SignIn.js +4 -3
  20. package/dist/Authenticator/Defaults/SignUp/SignUp.d.ts +13 -2
  21. package/dist/Authenticator/Defaults/SignUp/SignUp.js +4 -3
  22. package/dist/Authenticator/Defaults/VerifyUser/VerifyUser.d.ts +13 -2
  23. package/dist/Authenticator/Defaults/VerifyUser/VerifyUser.js +4 -3
  24. package/dist/Authenticator/Defaults/types.d.ts +21 -20
  25. package/dist/Authenticator/common/DefaultContent/types.d.ts +1 -1
  26. package/dist/Authenticator/common/DefaultFormFields/DefaultRadioFormFields.d.ts +7 -3
  27. package/dist/Authenticator/common/DefaultFormFields/DefaultRadioFormFields.js +4 -3
  28. package/dist/Authenticator/common/DefaultFormFields/DefaultTextFormFields.d.ts +6 -2
  29. package/dist/Authenticator/common/DefaultFormFields/DefaultTextFormFields.js +3 -3
  30. package/dist/Authenticator/common/DefaultFormFields/types.d.ts +12 -3
  31. package/dist/Authenticator/hooks/types.d.ts +3 -2
  32. package/dist/Authenticator/hooks/useFieldValues/types.d.ts +4 -1
  33. package/dist/Authenticator/hooks/useFieldValues/useFieldValues.d.ts +1 -1
  34. package/dist/Authenticator/hooks/useFieldValues/useFieldValues.js +21 -3
  35. package/dist/Authenticator/hooks/useFieldValues/utils.d.ts +10 -1
  36. package/dist/Authenticator/hooks/useFieldValues/utils.js +32 -2
  37. package/dist/primitives/TextField/TextField.js +2 -1
  38. package/dist/primitives/TextField/styles.js +3 -0
  39. package/dist/primitives/TextField/types.d.ts +1 -0
  40. package/dist/theme/createTheme.js +24 -18
  41. package/dist/theme/types.d.ts +1 -1
  42. package/dist/version.d.ts +1 -1
  43. package/dist/version.js +1 -1
  44. package/jest.config.js +1 -0
  45. package/package.json +5 -5
  46. package/src/Authenticator/Authenticator.tsx +2 -6
  47. package/src/Authenticator/Defaults/ConfirmResetPassword/ConfirmResetPassword.tsx +7 -3
  48. package/src/Authenticator/Defaults/ConfirmSignIn/ConfirmSignIn.tsx +7 -3
  49. package/src/Authenticator/Defaults/ConfirmSignIn/__tests__/ConfirmSignIn.spec.tsx +1 -0
  50. package/src/Authenticator/Defaults/ConfirmSignUp/ConfirmSignUp.tsx +7 -3
  51. package/src/Authenticator/Defaults/ConfirmSignUp/__tests__/ConfirmSignUp.spec.tsx +1 -0
  52. package/src/Authenticator/Defaults/ConfirmVerifyUser/ConfirmVerifyUser.tsx +7 -3
  53. package/src/Authenticator/Defaults/ConfirmVerifyUser/__tests__/ConfirmVerifyUser.spec.tsx +1 -0
  54. package/src/Authenticator/Defaults/ForceNewPassword/ForceNewPassword.tsx +7 -3
  55. package/src/Authenticator/Defaults/ForceNewPassword/__tests__/__snapshots__/ForceNewPassword.spec.tsx.snap +1 -1
  56. package/src/Authenticator/Defaults/ResetPassword/ResetPassword.tsx +7 -3
  57. package/src/Authenticator/Defaults/ResetPassword/__tests__/ResetPassword.spec.tsx +1 -0
  58. package/src/Authenticator/Defaults/SetupTOTP/SetupTOTP.tsx +7 -3
  59. package/src/Authenticator/Defaults/SetupTOTP/__tests__/SetupTOTP.spec.tsx +1 -0
  60. package/src/Authenticator/Defaults/SignIn/SignIn.tsx +7 -3
  61. package/src/Authenticator/Defaults/SignIn/__tests__/SignIn.spec.tsx +1 -0
  62. package/src/Authenticator/Defaults/SignUp/SignUp.tsx +7 -3
  63. package/src/Authenticator/Defaults/SignUp/__tests__/__snapshots__/SignUp.spec.tsx.snap +1 -1
  64. package/src/Authenticator/Defaults/VerifyUser/VerifyUser.tsx +7 -3
  65. package/src/Authenticator/Defaults/VerifyUser/__tests__/VerifyUser.spec.tsx +1 -0
  66. package/src/Authenticator/Defaults/types.ts +63 -49
  67. package/src/Authenticator/__tests__/Authenticator.spec.tsx +16 -19
  68. package/src/Authenticator/__tests__/__snapshots__/Authenticator.spec.tsx.snap +1 -9
  69. package/src/Authenticator/__tests__/withAuthenticator.spec.tsx +1 -1
  70. package/src/Authenticator/common/DefaultContent/types.ts +1 -4
  71. package/src/Authenticator/common/DefaultFormFields/DefaultRadioFormFields.tsx +8 -6
  72. package/src/Authenticator/common/DefaultFormFields/DefaultTextFormFields.tsx +10 -7
  73. package/src/Authenticator/common/DefaultFormFields/types.ts +15 -5
  74. package/src/Authenticator/hooks/types.ts +3 -0
  75. package/src/Authenticator/hooks/useFieldValues/__tests__/useFieldValues.spec.ts +75 -2
  76. package/src/Authenticator/hooks/useFieldValues/__tests__/utils.spec.ts +67 -1
  77. package/src/Authenticator/hooks/useFieldValues/types.ts +5 -0
  78. package/src/Authenticator/hooks/useFieldValues/useFieldValues.ts +26 -1
  79. package/src/Authenticator/hooks/useFieldValues/utils.ts +44 -1
  80. package/src/primitives/TextField/TextField.tsx +2 -1
  81. package/src/primitives/TextField/__tests__/TextField.spec.tsx +57 -8
  82. package/src/primitives/TextField/__tests__/__snapshots__/TextField.spec.tsx.snap +31 -31
  83. package/src/primitives/TextField/styles.ts +3 -0
  84. package/src/primitives/TextField/types.ts +1 -0
  85. package/src/theme/__tests__/createTheme.spec.ts +48 -0
  86. package/src/theme/createTheme.ts +44 -21
  87. package/src/theme/types.ts +17 -16
  88. package/src/version.ts +1 -1
@@ -8,7 +8,7 @@ import {
8
8
  DefaultRadioFormFields,
9
9
  } from '../../common';
10
10
  import { useFieldValues } from '../../hooks';
11
- import { DefaultVerifyUserComponent } from '../types';
11
+ import { DefaultVerifyUserProps } from '../types';
12
12
 
13
13
  const COMPONENT_NAME = 'VerifyUser';
14
14
 
@@ -19,17 +19,19 @@ const {
19
19
  getAccountRecoveryInfoText,
20
20
  } = authenticatorTextUtil;
21
21
 
22
- const VerifyUser: DefaultVerifyUserComponent = ({
22
+ const VerifyUser = ({
23
23
  fields,
24
24
  handleBlur,
25
25
  handleChange,
26
26
  handleSubmit,
27
27
  skipVerification,
28
+ validationErrors,
28
29
  ...rest
29
- }) => {
30
+ }: DefaultVerifyUserProps): JSX.Element => {
30
31
  const {
31
32
  disableFormSubmit: disabled,
32
33
  fields: fieldsWithHandlers,
34
+ fieldValidationErrors,
33
35
  handleFormSubmit,
34
36
  } = useFieldValues({
35
37
  componentName: COMPONENT_NAME,
@@ -37,6 +39,7 @@ const VerifyUser: DefaultVerifyUserComponent = ({
37
39
  handleBlur,
38
40
  handleChange,
39
41
  handleSubmit,
42
+ validationErrors,
40
43
  });
41
44
 
42
45
  const headerText = getVerifyContactText();
@@ -59,6 +62,7 @@ const VerifyUser: DefaultVerifyUserComponent = ({
59
62
  buttons={buttons}
60
63
  fields={fieldsWithHandlers}
61
64
  headerText={headerText}
65
+ validationErrors={fieldValidationErrors}
62
66
  />
63
67
  );
64
68
  };
@@ -44,6 +44,7 @@ const props = {
44
44
  handleBlur: jest.fn(),
45
45
  handleChange: jest.fn(),
46
46
  handleSubmit,
47
+ hasValidationErrors: false,
47
48
  Header: VerifyUser.Header,
48
49
  isPending: false,
49
50
  skipVerification: jest.fn(),
@@ -21,55 +21,69 @@ export type DefaultComponents<
21
21
  Props = {}
22
22
  > = AuthenticatorComponentDefaults<FieldType, Props>;
23
23
 
24
- export type DefaultConfirmResetPasswordComponent = DefaultComponents<
25
- TextFieldOptionsType,
26
- { style?: ConfirmResetPasswordStyle }
27
- >['ConfirmResetPassword'];
28
-
29
- export type DefaultConfirmSignInComponent = DefaultComponents<
30
- TextFieldOptionsType,
31
- { style?: ConfirmSignInStyle }
32
- >['ConfirmSignIn'];
33
-
34
- export type DefaultConfirmSignUpComponent = DefaultComponents<
35
- TextFieldOptionsType,
36
- { style?: ConfirmSignUpStyle }
37
- >['ConfirmSignUp'];
38
-
39
- export type DefaultConfirmVerifyUserComponent = DefaultComponents<
40
- TextFieldOptionsType,
41
- { style?: ConfirmVerifyUserStyle }
42
- >['ConfirmVerifyUser'];
43
-
44
- export type DefaultForceNewPasswordComponent = DefaultComponents<
45
- TextFieldOptionsType,
46
- { style?: ForceNewPasswordStyle }
47
- >['ForceNewPassword'];
48
-
49
- export type DefaultResetPasswordComponent = DefaultComponents<
50
- TextFieldOptionsType,
51
- { style?: ResetPasswordStyle }
52
- >['ResetPassword'];
53
-
54
- export type DefaultSetupTOTPComponent = DefaultComponents<
55
- TextFieldOptionsType,
56
- { style?: SetupTOTPStyle }
57
- >['SetupTOTP'];
58
-
59
- export type DefaultSignInComponent = DefaultComponents<
60
- TextFieldOptionsType,
61
- { style?: SignInStyle }
62
- >['SignIn'];
63
-
64
- export type DefaultSignUpComponent = DefaultComponents<
65
- TextFieldOptionsType,
66
- { style?: SignUpStyle }
67
- >['SignUp'];
68
-
69
- export type DefaultVerifyUserComponent = DefaultComponents<
70
- RadioFieldOptions,
71
- { style?: VerifyUserStyle }
72
- >['VerifyUser'];
24
+ export type DefaultConfirmResetPasswordProps = React.ComponentPropsWithoutRef<
25
+ DefaultComponents<
26
+ TextFieldOptionsType,
27
+ { style?: ConfirmResetPasswordStyle }
28
+ >['ConfirmResetPassword']
29
+ >;
30
+
31
+ export type DefaultConfirmSignInProps = React.ComponentPropsWithoutRef<
32
+ DefaultComponents<
33
+ TextFieldOptionsType,
34
+ { style?: ConfirmSignInStyle }
35
+ >['ConfirmSignIn']
36
+ >;
37
+
38
+ export type DefaultConfirmSignUpProps = React.ComponentPropsWithoutRef<
39
+ DefaultComponents<
40
+ TextFieldOptionsType,
41
+ { style?: ConfirmSignUpStyle }
42
+ >['ConfirmSignUp']
43
+ >;
44
+
45
+ export type DefaultConfirmVerifyUserProps = React.ComponentPropsWithoutRef<
46
+ DefaultComponents<
47
+ TextFieldOptionsType,
48
+ { style?: ConfirmVerifyUserStyle }
49
+ >['ConfirmVerifyUser']
50
+ >;
51
+
52
+ export type DefaultForceNewPasswordProps = React.ComponentPropsWithoutRef<
53
+ DefaultComponents<
54
+ TextFieldOptionsType,
55
+ { style?: ForceNewPasswordStyle }
56
+ >['ForceNewPassword']
57
+ >;
58
+
59
+ export type DefaultResetPasswordProps = React.ComponentPropsWithoutRef<
60
+ DefaultComponents<
61
+ TextFieldOptionsType,
62
+ { style?: ResetPasswordStyle }
63
+ >['ResetPassword']
64
+ >;
65
+
66
+ export type DefaultSetupTOTPProps = React.ComponentPropsWithoutRef<
67
+ DefaultComponents<
68
+ TextFieldOptionsType,
69
+ { style?: SetupTOTPStyle }
70
+ >['SetupTOTP']
71
+ >;
72
+
73
+ export type DefaultSignInProps = React.ComponentPropsWithoutRef<
74
+ DefaultComponents<TextFieldOptionsType, { style?: SignInStyle }>['SignIn']
75
+ >;
76
+
77
+ export type DefaultSignUpProps = React.ComponentPropsWithoutRef<
78
+ DefaultComponents<TextFieldOptionsType, { style?: SignUpStyle }>['SignUp']
79
+ >;
80
+
81
+ export type DefaultVerifyUserProps = React.ComponentPropsWithoutRef<
82
+ DefaultComponents<
83
+ RadioFieldOptions,
84
+ { style?: VerifyUserStyle }
85
+ >['VerifyUser']
86
+ >;
73
87
 
74
88
  /**
75
89
  * Custom Authenticator components
@@ -81,30 +81,27 @@ describe('Authenticator', () => {
81
81
  expect(toJSON()).toMatchSnapshot();
82
82
  });
83
83
 
84
- it.each(['authenticated', 'signOut'])(
85
- 'handles the %s route as expected with children',
86
- (route) => {
87
- useAuthenticatorSpy.mockImplementation(
88
- () => ({ route } as unknown as UseAuthenticator)
89
- );
84
+ it('handles `authStatus` of authenticated as expected', () => {
85
+ useAuthenticatorSpy.mockImplementation(
86
+ () => ({ authStatus: 'authenticated' } as unknown as UseAuthenticator)
87
+ );
90
88
 
91
- const { getByTestId, toJSON } = render(
92
- <Authenticator>
93
- <TestChildren />
94
- </Authenticator>
95
- );
89
+ const { getByTestId, toJSON } = render(
90
+ <Authenticator>
91
+ <TestChildren />
92
+ </Authenticator>
93
+ );
96
94
 
97
- const children = getByTestId(CHILD_TEST_ID);
95
+ const children = getByTestId(CHILD_TEST_ID);
98
96
 
99
- expect(children.type).toBe('Text');
100
- expect(children.props.children).toBe(CHILD_CONTENT);
97
+ expect(children.type).toBe('Text');
98
+ expect(children.props.children).toBe(CHILD_CONTENT);
101
99
 
102
- expect(toJSON()).toMatchSnapshot();
103
- }
104
- );
100
+ expect(toJSON()).toMatchSnapshot();
101
+ });
105
102
 
106
- it.each(['authenticated', 'signOut'])(
107
- 'handles the %s route as expected without children',
103
+ it.each(['unauthenticated', 'configuring'])(
104
+ 'handles an authStatus of %s as expected',
108
105
  (route) => {
109
106
  useAuthenticatorSpy.mockImplementation(
110
107
  () => ({ route } as unknown as UseAuthenticator)
@@ -59,15 +59,7 @@ exports[`Authenticator behaves as expected in the happy path 1`] = `
59
59
  </SafeAreaProvider>
60
60
  `;
61
61
 
62
- exports[`Authenticator handles the authenticated route as expected with children 1`] = `
63
- <Text
64
- testID="child-test-id"
65
- >
66
- Test Children
67
- </Text>
68
- `;
69
-
70
- exports[`Authenticator handles the signOut route as expected with children 1`] = `
62
+ exports[`Authenticator handles \`authStatus\` of authenticated as expected 1`] = `
71
63
  <Text
72
64
  testID="child-test-id"
73
65
  >
@@ -61,7 +61,7 @@ describe('withAuthenticator', () => {
61
61
  useAuthenticatorSpy.mockImplementation(
62
62
  () =>
63
63
  ({
64
- route: 'authenticated',
64
+ authStatus: 'authenticated',
65
65
  } as unknown as UIReactCoreModule.UseAuthenticator)
66
66
  );
67
67
 
@@ -60,10 +60,7 @@ export type DefaultContentProps<
60
60
  | TextFieldOptionsType
61
61
  | RadioFieldOptions
62
62
  | unknown = unknown
63
- > = Pick<
64
- DefaultComponentProps<FieldsType>,
65
- 'error' | 'Footer' | 'isPending'
66
- > & {
63
+ > = Pick<DefaultComponentProps<FieldsType>, 'error' | 'isPending'> & {
67
64
  buttons: DefaultButtons;
68
65
  body?: React.ReactNode;
69
66
  fields: FieldsType[];
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { censorAllButFirstAndLast, censorPhoneNumber } from '@aws-amplify/ui';
3
3
 
4
4
  import { Radio, RadioGroup } from '../../../primitives';
5
- import { DefaultRadioFormFieldsComponent } from './types';
5
+ import { DefaultRadioFormFieldsProps } from './types';
6
6
 
7
7
  const censorContactInformation = (name: string, value: string): string => {
8
8
  let censoredVal = value;
@@ -17,16 +17,16 @@ const censorContactInformation = (name: string, value: string): string => {
17
17
  return censoredVal;
18
18
  };
19
19
 
20
- const DefaultFormFields: DefaultRadioFormFieldsComponent = ({
20
+ const DefaultRadioFormFields = ({
21
21
  fields,
22
- isPending,
23
22
  fieldContainerStyle,
24
23
  fieldLabelStyle,
24
+ isPending,
25
25
  style,
26
- }) => {
26
+ }: DefaultRadioFormFieldsProps): JSX.Element => {
27
27
  return (
28
28
  <RadioGroup disabled={isPending} style={style}>
29
- {fields.map(({ name, value, ...props }) => (
29
+ {(fields ?? []).map(({ name, value, ...props }) => (
30
30
  <Radio
31
31
  {...props}
32
32
  key={value}
@@ -42,4 +42,6 @@ const DefaultFormFields: DefaultRadioFormFieldsComponent = ({
42
42
  );
43
43
  };
44
44
 
45
- export default DefaultFormFields;
45
+ DefaultRadioFormFields.displayName = 'FormFields';
46
+
47
+ export default DefaultRadioFormFields;
@@ -5,19 +5,22 @@ import { getErrors } from '@aws-amplify/ui';
5
5
 
6
6
  import Field from './Field';
7
7
  import { FieldErrors } from './FieldErrors';
8
- import { DefaultTextFormFieldsComponent } from './types';
8
+ import {
9
+ // DefaultTextFormFieldsComponent,
10
+ DefaultTextFormFieldsProps,
11
+ } from './types';
9
12
 
10
- const DefaultTextFormFields: DefaultTextFormFieldsComponent = ({
13
+ const DefaultTextFormFields = ({
11
14
  fieldContainerStyle,
12
15
  fieldErrorsContainer,
13
16
  fieldErrorStyle,
14
17
  fieldStyle,
15
- fields = [],
16
- isPending,
18
+ fields,
19
+ isPending = false,
17
20
  style,
18
21
  validationErrors,
19
- }) => {
20
- const formFields = fields.map(({ name, type, ...field }) => {
22
+ }: DefaultTextFormFieldsProps): JSX.Element => {
23
+ const formFields = (fields ?? []).map(({ name, type, ...field }) => {
21
24
  const errors = validationErrors ? getErrors(validationErrors?.[name]) : [];
22
25
 
23
26
  const hasError = errors?.length > 0;
@@ -44,6 +47,6 @@ const DefaultTextFormFields: DefaultTextFormFieldsComponent = ({
44
47
  return <View style={style}>{formFields}</View>;
45
48
  };
46
49
 
47
- DefaultTextFormFields.displayName = 'DefaultTextFormFields';
50
+ DefaultTextFormFields.displayName = 'FormFields';
48
51
 
49
52
  export default DefaultTextFormFields;
@@ -1,5 +1,8 @@
1
1
  import { StyleProp, TextStyle, ViewStyle } from 'react-native';
2
- import { AuthenticatorFormFieldsComponent } from '@aws-amplify/ui-react-core';
2
+ import {
3
+ AuthenticatorFormFieldsComponent,
4
+ UseAuthenticator,
5
+ } from '@aws-amplify/ui-react-core';
3
6
 
4
7
  import { RadioFieldOptions, TextFieldOptionsType } from '../../hooks';
5
8
 
@@ -25,8 +28,15 @@ export interface DefaultFormFieldsStyle {
25
28
  export type DefaultFormFieldsComponent<FieldsType> =
26
29
  AuthenticatorFormFieldsComponent<FieldsType, DefaultFormFieldsStyle>;
27
30
 
28
- export type DefaultTextFormFieldsComponent =
29
- DefaultFormFieldsComponent<TextFieldOptionsType>;
31
+ interface FormFieldsProps extends DefaultFormFieldsStyle {
32
+ isPending?: UseAuthenticator['isPending'];
33
+ validationErrors?: UseAuthenticator['validationErrors'];
34
+ }
35
+
36
+ export interface DefaultTextFormFieldsProps extends FormFieldsProps {
37
+ fields?: TextFieldOptionsType[];
38
+ }
30
39
 
31
- export type DefaultRadioFormFieldsComponent =
32
- DefaultFormFieldsComponent<RadioFieldOptions>;
40
+ export interface DefaultRadioFormFieldsProps extends FormFieldsProps {
41
+ fields?: RadioFieldOptions[];
42
+ }
@@ -7,6 +7,7 @@ import {
7
7
 
8
8
  export type MachineFieldTypeKey = 'password' | 'tel';
9
9
  export type AuthenticatorFieldTypeKey =
10
+ | 'email'
10
11
  | 'password'
11
12
  | 'phone'
12
13
  | 'default'
@@ -23,10 +24,12 @@ type FieldOptions<FieldProps, Type extends AuthenticatorFieldTypeKey> = {
23
24
  type: Type;
24
25
  } & Omit<FieldProps, 'disabled' | 'onBlur'>;
25
26
 
27
+ type EmailFieldOptions = FieldOptions<PhoneNumberFieldProps, 'email'>;
26
28
  type PasswordFieldOptions = FieldOptions<PasswordFieldProps, 'password'>;
27
29
  type PhoneFieldOptions = FieldOptions<PhoneNumberFieldProps, 'phone'>;
28
30
  type DefaultFieldOptions = FieldOptions<TextFieldProps, 'default'>;
29
31
  export type TextFieldOptionsType = (
32
+ | EmailFieldOptions
30
33
  | PasswordFieldOptions
31
34
  | PhoneFieldOptions
32
35
  | DefaultFieldOptions
@@ -2,7 +2,10 @@ import { act, renderHook } from '@testing-library/react-hooks';
2
2
  import { NativeSyntheticEvent, TextInputFocusEventData } from 'react-native';
3
3
 
4
4
  import { Logger } from 'aws-amplify';
5
- import { UnverifiedContactMethodType } from '@aws-amplify/ui';
5
+ import {
6
+ UnverifiedContactMethodType,
7
+ authenticatorTextUtil,
8
+ } from '@aws-amplify/ui';
6
9
  import {
7
10
  RadioFieldOptions,
8
11
  TextFieldOptionsType,
@@ -58,6 +61,7 @@ describe('useFieldValues', () => {
58
61
  value: undefined,
59
62
  },
60
63
  ],
64
+ fieldValidationErrors: {},
61
65
  handleFormSubmit: expect.any(Function),
62
66
  });
63
67
  });
@@ -80,6 +84,7 @@ describe('useFieldValues', () => {
80
84
  value: undefined,
81
85
  },
82
86
  ],
87
+ fieldValidationErrors: {},
83
88
  handleFormSubmit: expect.any(Function),
84
89
  });
85
90
  });
@@ -95,6 +100,7 @@ describe('useFieldValues', () => {
95
100
  expect(result.current).toStrictEqual({
96
101
  disableFormSubmit: true,
97
102
  fields: [{ ...radioField, onChange: expect.any(Function) }],
103
+ fieldValidationErrors: {},
98
104
  handleFormSubmit: expect.any(Function),
99
105
  });
100
106
  });
@@ -105,6 +111,7 @@ describe('useFieldValues', () => {
105
111
  expect(result.current).toStrictEqual({
106
112
  disableFormSubmit: false,
107
113
  fields: mockfields,
114
+ fieldValidationErrors: {},
108
115
  handleFormSubmit: expect.any(Function),
109
116
  });
110
117
  });
@@ -129,7 +136,9 @@ describe('useFieldValues', () => {
129
136
  const mockEvent = {
130
137
  nativeEvent: { target: 1 },
131
138
  } as NativeSyntheticEvent<TextInputFocusEventData>;
132
- result.current.fields[0].onBlur?.(mockEvent);
139
+ act(() => {
140
+ result.current.fields[0].onBlur?.(mockEvent);
141
+ });
133
142
  expect(props.handleBlur).toHaveBeenCalledTimes(1);
134
143
  expect(props.handleBlur).toHaveBeenCalledWith({
135
144
  name: textField.name,
@@ -153,6 +162,68 @@ describe('useFieldValues', () => {
153
162
  });
154
163
  });
155
164
 
165
+ it('runs validations for email fields', () => {
166
+ const emailField = {
167
+ label: 'test',
168
+ type: 'email',
169
+ name: 'invalid_email',
170
+ value: 'test@',
171
+ } as TextFieldOptionsType;
172
+ const phoneTextField = {
173
+ type: 'phone',
174
+ name: 'testPhone',
175
+ } as TextFieldOptionsType;
176
+ const { result } = renderHook(() =>
177
+ useFieldValues({
178
+ ...props,
179
+ fields: [emailField, phoneTextField],
180
+ })
181
+ );
182
+ const mockEvent = {
183
+ nativeEvent: { target: 1 },
184
+ } as NativeSyntheticEvent<TextInputFocusEventData>;
185
+ act(() => {
186
+ result.current.fields[0].onBlur?.(mockEvent);
187
+ });
188
+ expect(props.handleBlur).toHaveBeenCalledTimes(1);
189
+ expect(props.handleBlur).toHaveBeenCalledWith({
190
+ name: emailField.name,
191
+ value: undefined,
192
+ });
193
+ expect(result.current.fieldValidationErrors).toStrictEqual({
194
+ [emailField.name]: [authenticatorTextUtil.getInvalidEmailText()],
195
+ });
196
+ });
197
+
198
+ it('runs validations for required fields', () => {
199
+ const requiredField = {
200
+ label: 'test',
201
+ type: 'password',
202
+ name: 'required',
203
+ required: true,
204
+ } as TextFieldOptionsType;
205
+ const { result } = renderHook(() =>
206
+ useFieldValues({
207
+ ...props,
208
+ fields: [requiredField],
209
+ })
210
+ );
211
+ const mockEvent = {
212
+ nativeEvent: { target: 1 },
213
+ } as NativeSyntheticEvent<TextInputFocusEventData>;
214
+ act(() => {
215
+ result.current.fields[0].onBlur?.(mockEvent);
216
+ });
217
+ expect(props.handleBlur).toHaveBeenCalledTimes(1);
218
+ expect(props.handleBlur).toHaveBeenCalledWith({
219
+ name: requiredField.name,
220
+ value: undefined,
221
+ });
222
+ expect(result.current.fieldValidationErrors).toStrictEqual({
223
+ [requiredField.name]: [authenticatorTextUtil.getRequiredFieldText()],
224
+ });
225
+ });
226
+
156
227
  it('calls expected handlers for radios', () => {
157
228
  const { result } = renderHook(() =>
158
229
  useFieldValues({
@@ -234,6 +305,7 @@ describe('useFieldValues', () => {
234
305
  value: undefined,
235
306
  },
236
307
  ],
308
+ fieldValidationErrors: {},
237
309
  handleFormSubmit: expect.any(Function),
238
310
  });
239
311
  });
@@ -267,6 +339,7 @@ describe('useFieldValues', () => {
267
339
  value: mockValue,
268
340
  },
269
341
  ],
342
+ fieldValidationErrors: {},
270
343
  handleFormSubmit: expect.any(Function),
271
344
  });
272
345
  });
@@ -1,10 +1,12 @@
1
1
  import { Logger } from 'aws-amplify';
2
- import { TypedField } from '../../types';
2
+ import { authenticatorTextUtil } from '@aws-amplify/ui';
3
3
 
4
+ import { TextFieldOptionsType, TypedField } from '../../types';
4
5
  import {
5
6
  getRouteTypedFields,
6
7
  getSanitizedRadioFields,
7
8
  getSanitizedTextFields,
9
+ runFieldValidation,
8
10
  } from '../utils';
9
11
 
10
12
  const warnSpy = jest.spyOn(Logger.prototype, 'warn');
@@ -214,3 +216,67 @@ describe('getRouteTypedFields', () => {
214
216
  expect(fields).toStrictEqual(expected);
215
217
  });
216
218
  });
219
+
220
+ describe('runFieldValidation', () => {
221
+ const { getInvalidEmailText, getRequiredFieldText } = authenticatorTextUtil;
222
+ const field: TextFieldOptionsType = {
223
+ required: true,
224
+ type: 'email',
225
+ name: 'email',
226
+ };
227
+
228
+ it('should return an empty array when no errors are found', () => {
229
+ const value = 'test@example.com';
230
+ const stateValidations = {};
231
+
232
+ const result = runFieldValidation(field, value, stateValidations);
233
+
234
+ expect(result).toEqual([]);
235
+ });
236
+
237
+ it('should return an array with the required field error when value is missing', () => {
238
+ const value = undefined;
239
+ const stateValidations = {};
240
+
241
+ const result = runFieldValidation(field, value, stateValidations);
242
+
243
+ expect(result).toEqual([getRequiredFieldText(), getInvalidEmailText()]);
244
+ });
245
+
246
+ it('should return an array with the invalid email error when email value is invalid', () => {
247
+ const value = 'invalid-email';
248
+ const stateValidations = {};
249
+
250
+ const result = runFieldValidation(field, value, stateValidations);
251
+
252
+ expect(result).toEqual([getInvalidEmailText()]);
253
+ });
254
+
255
+ it('should include state machine validation errors in the result', () => {
256
+ const value = 'test@example.com';
257
+ const errorMessage = 'Email already exists.';
258
+ const stateValidations = {
259
+ email: errorMessage,
260
+ };
261
+
262
+ const result = runFieldValidation(field, value, stateValidations);
263
+
264
+ expect(result).toEqual([errorMessage]);
265
+ });
266
+
267
+ it('should concatenate state machine validation errors with other errors', () => {
268
+ const value = undefined;
269
+ const errorMessage = 'Email already exists.';
270
+ const stateValidations = {
271
+ email: errorMessage,
272
+ };
273
+
274
+ const result = runFieldValidation(field, value, stateValidations);
275
+
276
+ expect(result).toEqual([
277
+ getRequiredFieldText(),
278
+ getInvalidEmailText(),
279
+ errorMessage,
280
+ ]);
281
+ });
282
+ });
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  AuthenticatorComponentDefaultProps,
3
3
  AuthenticatorRouteComponentName,
4
+ AuthenticatorMachineContext,
4
5
  } from '@aws-amplify/ui-react-core';
6
+ import { ValidationError } from '@aws-amplify/ui';
5
7
 
6
8
  import { TypedField } from '../types';
7
9
 
@@ -26,10 +28,13 @@ export interface UseFieldValuesParams<FieldType extends TypedField> {
26
28
  * machine "SUBMIT"" event handler, validates `field` value against machine validation rules
27
29
  */
28
30
  handleSubmit: MachineEventHandlers['handleSubmit'];
31
+
32
+ validationErrors?: AuthenticatorMachineContext['validationErrors'];
29
33
  }
30
34
 
31
35
  export interface UseFieldValues<FieldType extends TypedField> {
32
36
  fields: FieldType[]; // return either radio or text
37
+ fieldValidationErrors: ValidationError | undefined;
33
38
  disableFormSubmit: boolean;
34
39
  handleFormSubmit: () => void;
35
40
  }