@eohjsc/react-native-smart-city 0.7.27 → 0.7.30

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 (67) hide show
  1. package/index.js +2 -0
  2. package/package.json +2 -1
  3. package/src/commons/Dashboard/MyDashboardDevice/__test__/index.test.js +68 -0
  4. package/src/commons/Dashboard/MyDashboardDevice/index.js +46 -11
  5. package/src/commons/Dashboard/MyUnit/__test__/MyUnit.test.js +43 -11
  6. package/src/commons/Dashboard/MyUnit/index.js +40 -32
  7. package/src/commons/ModalAlert/index.js +51 -0
  8. package/src/commons/ModalAlert/styles.js +54 -0
  9. package/src/commons/SubUnit/ShortDetail.js +20 -4
  10. package/src/commons/SubUnit/__test__/ShortDetail.test.js +46 -1
  11. package/src/configs/API.js +6 -0
  12. package/src/configs/AccessibilityLabel.js +1 -0
  13. package/src/configs/Constants.js +7 -0
  14. package/src/configs/SCConfig.js +6 -0
  15. package/src/context/SCContext.tsx +12 -1
  16. package/src/context/SCStore.ts +14 -0
  17. package/src/context/actionType.ts +10 -0
  18. package/src/context/mockStore.ts +30 -1
  19. package/src/context/reducer.ts +35 -0
  20. package/src/hooks/IoT/useRemoteControl.js +4 -1
  21. package/src/hooks/IoT/useWatchSharedChips.js +130 -0
  22. package/src/hooks/Review/__test__/useInAppReview.test.js +99 -0
  23. package/src/hooks/Review/useInAppReview.js +70 -0
  24. package/src/hooks/useMqtt.js +78 -27
  25. package/src/iot/Monitor.js +149 -26
  26. package/src/iot/UpdateStates.js +60 -0
  27. package/src/iot/mqtt.js +177 -22
  28. package/src/navigations/UnitStack.js +16 -0
  29. package/src/screens/ActivityLog/ItemLog.js +1 -0
  30. package/src/screens/AddNewGateway/RenameNewDevices.js +5 -0
  31. package/src/screens/AddNewGateway/__test__/RenameNewDevices.test.js +18 -0
  32. package/src/screens/Automate/AddNewAction/ReceiverSelect.js +208 -0
  33. package/src/screens/Automate/AddNewAction/SetupScriptEmail.js +1 -1
  34. package/src/screens/Automate/AddNewAction/SetupScriptNotify.js +18 -28
  35. package/src/screens/Automate/AddNewAction/SetupScriptReceiverEmail.js +22 -129
  36. package/src/screens/Automate/AddNewAction/SetupScriptReceiverNotify.js +59 -0
  37. package/src/screens/Automate/AddNewAction/SetupScriptReceiverSms.js +22 -129
  38. package/src/screens/Automate/AddNewAction/SetupScriptSms.js +1 -1
  39. package/src/screens/Automate/AddNewAction/Styles/{SetupScriptReceiverEmailStyles.js → ReceiverSelectStyles.js} +18 -1
  40. package/src/screens/Automate/AddNewAction/__test__/SetupScriptNotify.test.js +16 -33
  41. package/src/screens/Automate/AddNewAction/__test__/SetupScriptReceiverEmail.test.js +10 -8
  42. package/src/screens/Automate/AddNewAction/__test__/SetupScriptReceiverNotify.test.js +217 -0
  43. package/src/screens/Automate/AddNewAction/__test__/SetupScriptReceiverSms.test.js +10 -8
  44. package/src/screens/Automate/Components/InputName.js +5 -1
  45. package/src/screens/Automate/OneTap/__test__/AddNewOneTap.test.js +18 -0
  46. package/src/screens/Automate/ScriptDetail/index.js +6 -6
  47. package/src/screens/CreatePassword/__test__/index.test.js +133 -0
  48. package/src/screens/CreatePassword/index.js +134 -0
  49. package/src/screens/CreatePassword/styles.js +45 -0
  50. package/src/screens/Device/__test__/DeviceDetail-3rdparty.test.js +447 -0
  51. package/src/screens/Device/__test__/DeviceDetail-arduino.test.js +344 -0
  52. package/src/screens/Device/__test__/{mqttDetail.test.js → DeviceDetail-modbus.test.js} +287 -320
  53. package/src/screens/Device/__test__/DeviceDetail-zigbee.test.js +451 -0
  54. package/src/screens/Device/__test__/DeviceDetail.test.js +502 -0
  55. package/src/screens/Device/__test__/detail.test.js +61 -3
  56. package/src/screens/Device/__test__/sensorDisplayItem.test.js +28 -3
  57. package/src/screens/Device/detail.js +14 -6
  58. package/src/screens/Device/hooks/useDeviceWatchConfigControl.js +3 -2
  59. package/src/screens/EnterPassword/__test__/EnterPassword.test.js +76 -1
  60. package/src/screens/EnterPassword/index.js +34 -4
  61. package/src/screens/EnterPassword/styles.js +1 -1
  62. package/src/utils/FactoryGateway.js +597 -0
  63. package/src/utils/I18n/translations/en.js +10 -0
  64. package/src/utils/I18n/translations/vi.js +10 -0
  65. package/src/utils/Route/index.js +3 -1
  66. package/src/utils/Validation.js +5 -0
  67. package/src/utils/store.js +5 -0
@@ -0,0 +1,217 @@
1
+ import { useNavigation, useRoute } from '@react-navigation/native';
2
+ import { FlatList } from 'react-native';
3
+ import MockAdapter from 'axios-mock-adapter';
4
+ import React from 'react';
5
+ import renderer, { act } from 'react-test-renderer';
6
+ import BottomButtonView from '../../../../commons/BottomButtonView';
7
+ import API from '../../../../configs/API';
8
+ import { SCProvider } from '../../../../context';
9
+ import { mockSCStore } from '../../../../context/mockStore';
10
+ import { Search } from '../../../../commons/DevMode';
11
+ import api from '../../../../utils/Apis/axios';
12
+ import Routes from '../../../../utils/Route';
13
+ import { getTranslate } from '../../../../utils/I18n';
14
+ import { ToastBottomHelper } from '../../../../utils/Utils';
15
+ import ReceiverSelect from '../ReceiverSelect';
16
+ import SetupScriptReceiverNotify from '../SetupScriptReceiverNotify';
17
+ import CheckBox from '@react-native-community/checkbox';
18
+
19
+ const mock = new MockAdapter(api.axiosInstance);
20
+
21
+ const wrapComponent = (route) => (
22
+ <SCProvider initState={mockSCStore({})}>
23
+ <SetupScriptReceiverNotify route={route} />
24
+ </SCProvider>
25
+ );
26
+
27
+ describe('Test SetupScriptReceiverNotify', () => {
28
+ const mockedNavigate = useNavigation().navigate;
29
+ const spyToastSuccess = jest.spyOn(ToastBottomHelper, 'success');
30
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
31
+
32
+ let tree;
33
+ const route = {
34
+ params: {
35
+ unitId: 1,
36
+ automateId: 1,
37
+ automate: {
38
+ id: 1,
39
+ sensor_id: 1,
40
+ },
41
+ closeScreen: Routes.ScriptDetail,
42
+ formData: { title: 'title', message: 'message' },
43
+ },
44
+ };
45
+
46
+ const listUser = [
47
+ {
48
+ id: 1,
49
+ avatar: 'https://image.com',
50
+ email: 'user1@eoh.io',
51
+ name: 'User 1',
52
+ phone_number: null,
53
+ },
54
+ {
55
+ id: 2,
56
+ avatar: null,
57
+ email: null,
58
+ name: 'User 2',
59
+ phone_number: '090xxx',
60
+ share_id: 3,
61
+ },
62
+ ];
63
+ beforeEach(() => {
64
+ mockedNavigate.mockClear();
65
+ useRoute.mockImplementation(() => route);
66
+ spyToastSuccess.mockClear();
67
+ spyToastError.mockClear();
68
+ });
69
+
70
+ it('test create script notify success', async () => {
71
+ mock.onGet(API.SHARE.UNITS_MEMBERS(1)).reply(200, listUser);
72
+ mock.onPost(API.AUTOMATE.ADD_SCRIPT_NOTIFY(1)).reply(200);
73
+ await act(async () => {
74
+ tree = await renderer.create(wrapComponent(route));
75
+ });
76
+
77
+ const instance = tree.root;
78
+ let checkboxs = instance.findAllByType(CheckBox);
79
+ expect(checkboxs).toHaveLength(3);
80
+ expect(checkboxs[0].props.value).toBeFalsy(); // Select All
81
+ expect(checkboxs[1].props.disabled).toBeFalsy();
82
+ expect(checkboxs[2].props.disabled).toBeFalsy();
83
+
84
+ const receiverSelect = instance.findByType(ReceiverSelect);
85
+ expect(receiverSelect.props.listUser).toEqual([]);
86
+
87
+ await act(async () => {
88
+ checkboxs[1].props.onValueChange(true);
89
+ });
90
+ expect(checkboxs[0].props.value).toBeFalsy();
91
+ expect(receiverSelect.props.listUser).toEqual([1]);
92
+
93
+ checkboxs = instance.findAllByType(CheckBox);
94
+ await act(async () => {
95
+ checkboxs[2].props.onValueChange(true);
96
+ });
97
+ expect(checkboxs[0].props.value).toBeTruthy();
98
+
99
+ checkboxs = instance.findAllByType(CheckBox);
100
+ await act(async () => {
101
+ checkboxs[2].props.onValueChange(false);
102
+ });
103
+ expect(checkboxs[0].props.value).toBeFalsy();
104
+
105
+ const button = instance.findByType(BottomButtonView);
106
+ await act(async () => {
107
+ button.props.onPressMain();
108
+ });
109
+ expect(mock.history.post).toHaveLength(1);
110
+ expect(mock.history.post[0].url).toEqual(API.AUTOMATE.ADD_SCRIPT_NOTIFY(1));
111
+ expect(mock.history.post[0].data).toEqual(
112
+ JSON.stringify({
113
+ title: 'title',
114
+ message: 'message',
115
+ receiver: [1],
116
+ })
117
+ );
118
+ expect(spyToastSuccess).toHaveBeenCalledWith(
119
+ getTranslate('en', 'text_done')
120
+ );
121
+ });
122
+
123
+ it('test filter select all and deselect all', async () => {
124
+ mock.onGet(API.SHARE.UNITS_MEMBERS(1)).reply(200, listUser);
125
+ await act(async () => {
126
+ tree = await renderer.create(wrapComponent(route));
127
+ });
128
+
129
+ const instance = tree.root;
130
+ let checkboxs = instance.findAllByType(CheckBox);
131
+ expect(checkboxs).toHaveLength(3);
132
+ expect(checkboxs[0].props.value).toBeFalsy(); // Select All
133
+ expect(checkboxs[1].props.disabled).toBeFalsy();
134
+ expect(checkboxs[2].props.disabled).toBeFalsy();
135
+
136
+ const flatList = instance.findByType(FlatList);
137
+ expect(flatList.props.data).toEqual([
138
+ {
139
+ ...listUser[0],
140
+ label: null,
141
+ invalidLabel: getTranslate('en', 'no_phone_number'),
142
+ },
143
+ {
144
+ ...listUser[1],
145
+ label: '090xxx',
146
+ invalidLabel: getTranslate('en', 'no_phone_number'),
147
+ },
148
+ ]);
149
+
150
+ const search = instance.findByType(Search);
151
+ await act(async () => {
152
+ search.props.onSearch('User 1');
153
+ });
154
+ expect(flatList.props.data).toEqual([
155
+ {
156
+ ...listUser[0],
157
+ label: null,
158
+ invalidLabel: getTranslate('en', 'no_phone_number'),
159
+ },
160
+ ]);
161
+
162
+ await act(async () => {
163
+ search.props.onSearch('090');
164
+ });
165
+ expect(flatList.props.data).toEqual([
166
+ {
167
+ ...listUser[1],
168
+ label: '090xxx',
169
+ invalidLabel: getTranslate('en', 'no_phone_number'),
170
+ },
171
+ ]);
172
+
173
+ await act(async () => {
174
+ search.props.onSearch('');
175
+ });
176
+ await act(async () => {
177
+ checkboxs[0].props.onValueChange(true); // Select All
178
+ });
179
+ checkboxs = instance.findAllByType(CheckBox);
180
+ expect(checkboxs[0].props.value).toBeTruthy();
181
+ expect(checkboxs[1].props.value).toBeTruthy();
182
+ expect(checkboxs[2].props.value).toBeTruthy();
183
+
184
+ await act(async () => {
185
+ checkboxs[0].props.onValueChange(false); // Deselect All
186
+ });
187
+ checkboxs = instance.findAllByType(CheckBox);
188
+ expect(checkboxs[0].props.value).toBeFalsy();
189
+ expect(checkboxs[1].props.value).toBeFalsy();
190
+ expect(checkboxs[2].props.value).toBeFalsy();
191
+ });
192
+
193
+ it('test create script notify fail', async () => {
194
+ mock.onGet(API.SHARE.UNITS_MEMBERS(1)).reply(200, listUser);
195
+ mock.onPost(API.AUTOMATE.ADD_SCRIPT_NOTIFY(1)).reply(400);
196
+ await act(async () => {
197
+ tree = await renderer.create(wrapComponent(route));
198
+ });
199
+ const instance = tree.root;
200
+ const checkboxs = instance.findAllByType(CheckBox);
201
+ expect(checkboxs).toHaveLength(3);
202
+ expect(checkboxs[0].props.value).toBeFalsy(); // Select All
203
+ expect(checkboxs[1].props.disabled).toBeFalsy();
204
+ expect(checkboxs[2].props.disabled).toBeFalsy();
205
+ await act(async () => {
206
+ checkboxs[1].props.onValueChange(true);
207
+ });
208
+
209
+ const button = instance.findByType(BottomButtonView);
210
+ await act(async () => {
211
+ button.props.onPressMain();
212
+ });
213
+ expect(spyToastError).toHaveBeenCalledWith(
214
+ getTranslate('en', 'error_please_try_later')
215
+ );
216
+ });
217
+ });
@@ -66,11 +66,12 @@ describe('Test SetupScriptReceiverSms', () => {
66
66
  });
67
67
  const instance = tree.root;
68
68
  const checkboxs = instance.findAllByType(CheckBox);
69
- expect(checkboxs).toHaveLength(2);
70
- expect(checkboxs[0].props.disabled).toBeFalsy();
71
- expect(checkboxs[1].props.disabled).toBeTruthy();
69
+ expect(checkboxs).toHaveLength(3);
70
+ expect(checkboxs[0].props.value).toBeFalsy(); // Select All
71
+ expect(checkboxs[1].props.disabled).toBeFalsy();
72
+ expect(checkboxs[2].props.disabled).toBeTruthy();
72
73
  await act(async () => {
73
- checkboxs[0].props.onValueChange(true);
74
+ checkboxs[1].props.onValueChange(true);
74
75
  });
75
76
 
76
77
  const button = instance.findByType(BottomButtonView);
@@ -89,11 +90,12 @@ describe('Test SetupScriptReceiverSms', () => {
89
90
  });
90
91
  const instance = tree.root;
91
92
  const checkboxs = instance.findAllByType(CheckBox);
92
- expect(checkboxs).toHaveLength(2);
93
- expect(checkboxs[0].props.disabled).toBeFalsy();
94
- expect(checkboxs[1].props.disabled).toBeTruthy();
93
+ expect(checkboxs).toHaveLength(3);
94
+ expect(checkboxs[0].props.value).toBeFalsy(); // Select All
95
+ expect(checkboxs[1].props.disabled).toBeFalsy();
96
+ expect(checkboxs[2].props.disabled).toBeTruthy();
95
97
  await act(async () => {
96
- checkboxs[0].props.onValueChange(true);
98
+ checkboxs[1].props.onValueChange(true);
97
99
  });
98
100
 
99
101
  const button = instance.findByType(BottomButtonView);
@@ -6,6 +6,7 @@ import { API } from '../../../configs';
6
6
  import { AccessibilityLabel } from '../../../configs/Constants';
7
7
  import _TextInput from '../../../commons/Form/TextInput';
8
8
  import styles from './InputNameStyles';
9
+ import useInAppReview from '../../../hooks/Review/useInAppReview';
9
10
  import { useTranslations } from '../../../hooks/Common/useTranslations';
10
11
  import { axiosPost } from '../../../utils/Apis/axios';
11
12
  import NewActionWrapper from '../../Automate/AddNewAction/NewActionWrapper';
@@ -18,6 +19,8 @@ const InputName = ({ title, placeholder }) => {
18
19
  const { navigate } = useNavigation();
19
20
  const [name, setName] = useState(automateName);
20
21
  const [processing, setProcessing] = useState(false);
22
+ const { allowInAppReview } = useInAppReview(false);
23
+
21
24
  const handleContinue = useCallback(async () => {
22
25
  if (processing) {
23
26
  /* istanbul ignore next */
@@ -37,6 +40,7 @@ const InputName = ({ title, placeholder }) => {
37
40
  );
38
41
 
39
42
  if (success) {
43
+ allowInAppReview();
40
44
  navigate({
41
45
  name: Routes.ScriptDetail,
42
46
  merge: true,
@@ -47,7 +51,7 @@ const InputName = ({ title, placeholder }) => {
47
51
  });
48
52
  }
49
53
  setProcessing(false);
50
- }, [processing, automate, name, navigate, closeScreen]);
54
+ }, [processing, automate, name, navigate, closeScreen, allowInAppReview]);
51
55
 
52
56
  return (
53
57
  <NewActionWrapper
@@ -12,6 +12,18 @@ import api from '../../../../utils/Apis/axios';
12
12
  import Routes from '../../../../utils/Route';
13
13
  import AddNewOneTap from '../index';
14
14
 
15
+ const mockAllowInAppReview = jest.fn();
16
+ const mockAskReview = jest.fn();
17
+ jest.mock('../../../../hooks/Review/useInAppReview', () => {
18
+ return {
19
+ __esModule: true,
20
+ default: jest.fn(() => ({
21
+ allowInAppReview: mockAllowInAppReview,
22
+ askReview: mockAskReview,
23
+ })),
24
+ };
25
+ });
26
+
15
27
  const wrapComponent = (route) => {
16
28
  useRoute.mockReturnValue(route);
17
29
  return (
@@ -28,6 +40,8 @@ describe('test OneTap', () => {
28
40
  const mockedNavigate = useNavigation().navigate;
29
41
  beforeEach(() => {
30
42
  mockedNavigate.mockClear();
43
+ mockAllowInAppReview.mockClear();
44
+ mockAskReview.mockClear();
31
45
  });
32
46
 
33
47
  it('create OneTap success', async () => {
@@ -91,6 +105,8 @@ describe('test OneTap', () => {
91
105
  },
92
106
  },
93
107
  });
108
+ expect(mockAllowInAppReview).toHaveBeenCalled();
109
+ expect(mockAskReview).not.toHaveBeenCalled();
94
110
  });
95
111
 
96
112
  it('create OneTap fail', async () => {
@@ -116,6 +132,8 @@ describe('test OneTap', () => {
116
132
  await item[0].props.onPress();
117
133
  });
118
134
  expect(global.mockedNavigate).not.toHaveBeenCalled();
135
+ expect(mockAllowInAppReview).not.toHaveBeenCalled();
136
+ expect(mockAskReview).not.toHaveBeenCalled();
119
137
  });
120
138
 
121
139
  it('test onClose have automateId', async () => {
@@ -955,7 +955,7 @@ const Item = ({ item, index, enableScript, t, local_control }) => {
955
955
  </View>
956
956
  );
957
957
  } else if (notify_script) {
958
- const { title, message, unit_name } = notify_script;
958
+ const { title, message, unit_name, str_receivers } = notify_script;
959
959
  return (
960
960
  <View style={styles.wrapItem}>
961
961
  <View style={styles.leftItem}>
@@ -978,7 +978,7 @@ const Item = ({ item, index, enableScript, t, local_control }) => {
978
978
  </View>
979
979
  </View>
980
980
  <Text numberOfLines={1} type="H4" color={color}>
981
- {unit_name}
981
+ {`${unit_name}: ${str_receivers}`}
982
982
  </Text>
983
983
  </View>
984
984
  </View>
@@ -1008,7 +1008,7 @@ const Item = ({ item, index, enableScript, t, local_control }) => {
1008
1008
  </View>
1009
1009
  );
1010
1010
  } else if (email_script) {
1011
- const { title, message, str_emails } = email_script;
1011
+ const { title, message, unit_name, str_emails } = email_script;
1012
1012
  return (
1013
1013
  <View style={styles.wrapItem}>
1014
1014
  <View style={styles.leftItem}>
@@ -1031,13 +1031,13 @@ const Item = ({ item, index, enableScript, t, local_control }) => {
1031
1031
  </View>
1032
1032
  </View>
1033
1033
  <Text numberOfLines={1} type="H4" color={color}>
1034
- {str_emails}
1034
+ {`${unit_name}: ${str_emails}`}
1035
1035
  </Text>
1036
1036
  </View>
1037
1037
  </View>
1038
1038
  );
1039
1039
  } else if (sms_script) {
1040
- const { message, str_phone_numbers } = sms_script;
1040
+ const { message, unit_name, str_phone_numbers } = sms_script;
1041
1041
  color = local_control.is_local_control ? color : Colors.Gray7;
1042
1042
  return (
1043
1043
  <View style={styles.wrapItem}>
@@ -1056,7 +1056,7 @@ const Item = ({ item, index, enableScript, t, local_control }) => {
1056
1056
  {message}
1057
1057
  </Text>
1058
1058
  <Text numberOfLines={1} type="H4" color={color}>
1059
- {str_phone_numbers}
1059
+ {`${unit_name}: ${str_phone_numbers}`}
1060
1060
  </Text>
1061
1061
  </View>
1062
1062
  </View>
@@ -0,0 +1,133 @@
1
+ import MockAdapter from 'axios-mock-adapter';
2
+ import React from 'react';
3
+ import { TouchableOpacity } from 'react-native';
4
+ import { act, create } from 'react-test-renderer';
5
+
6
+ import { useNavigation } from '@react-navigation/native';
7
+ import { API } from '../../../configs';
8
+ import { AccessibilityLabel } from '../../../configs/Constants';
9
+ import _TextInputPassword from '../../../commons/Form/TextInputPassword';
10
+ import { SCProvider } from '../../../context';
11
+ import { mockSCStore } from '../../../context/mockStore';
12
+ import api from '../../../utils/Apis/axios';
13
+ import t from '../../../hooks/Common/useTranslations';
14
+ import { ToastBottomHelper } from '../../../utils/Utils';
15
+ import Routes from '../../../utils/Route';
16
+ import CreatePassword from '..';
17
+
18
+ const mock = new MockAdapter(api.axiosInstance);
19
+
20
+ const wrapComponent = () => (
21
+ <SCProvider initState={mockSCStore({})}>
22
+ <CreatePassword />
23
+ </SCProvider>
24
+ );
25
+
26
+ describe('test CreatePassword', () => {
27
+ let tree;
28
+ const { navigate } = useNavigation();
29
+ const spyToastSuccess = jest.spyOn(ToastBottomHelper, 'success');
30
+
31
+ beforeEach(() => {
32
+ spyToastSuccess.mockClear();
33
+ });
34
+
35
+ const onChangePasswords = async (component, text1, text2) => {
36
+ await act(async () => {
37
+ await component[0].props.onChange(text1);
38
+ });
39
+ await act(async () => {
40
+ await component[1].props.onChange(text2);
41
+ });
42
+ };
43
+
44
+ const buttonDone = (instance) => {
45
+ return instance.find(
46
+ (el) =>
47
+ el.props.accessibilityLabel ===
48
+ AccessibilityLabel.CREATE_PASSWORD_BUTTON_DONE &&
49
+ el.type === TouchableOpacity
50
+ );
51
+ };
52
+
53
+ it('validate and create password success', async () => {
54
+ mock.onPatch(API.AUTH.CREATE_PASSWORD).reply(200);
55
+ await act(async () => {
56
+ tree = await create(wrapComponent());
57
+ });
58
+ const instance = tree.root;
59
+ const button = buttonDone(instance);
60
+ const texts = instance.findAllByType(_TextInputPassword);
61
+
62
+ await onChangePasswords(texts, '', '');
63
+ expect(texts[0].props.errorText).toEqual(t('please_enter_new_password'));
64
+ expect(texts[1].props.errorText).toEqual(
65
+ t('please_enter_your_confirm_password')
66
+ );
67
+ expect(button.props.disabled).toBeTruthy();
68
+
69
+ await onChangePasswords(texts, '123', '123');
70
+ expect(texts[0].props.errorText).toEqual(t('password_must_be_strong'));
71
+ expect(texts[1].props.errorText).toEqual('');
72
+ expect(button.props.disabled).toBeTruthy();
73
+
74
+ await onChangePasswords(texts, 'Password1', 'Password2');
75
+ expect(texts[0].props.errorText).toEqual('');
76
+ expect(texts[1].props.errorText).toEqual(t('confirm_password_not_match'));
77
+ expect(button.props.disabled).toBeTruthy();
78
+
79
+ await act(async () => {
80
+ await texts[1].props.onChange('Password1');
81
+ });
82
+ expect(texts[0].props.errorText).toEqual('');
83
+ expect(texts[1].props.errorText).toEqual('');
84
+ expect(button.props.disabled).toBeFalsy();
85
+
86
+ await act(async () => {
87
+ await texts[0].props.onChange('Password2');
88
+ });
89
+ expect(texts[0].props.errorText).toEqual('');
90
+ expect(texts[1].props.errorText).toEqual(t('confirm_password_not_match'));
91
+ expect(button.props.disabled).toBeTruthy();
92
+
93
+ await act(async () => {
94
+ await texts[0].props.onChange('Password1');
95
+ });
96
+ expect(texts[0].props.errorText).toEqual('');
97
+ expect(texts[1].props.errorText).toEqual('');
98
+ expect(button.props.disabled).toBeFalsy();
99
+
100
+ await act(async () => {
101
+ await button.props.onPress();
102
+ });
103
+
104
+ expect(spyToastSuccess).toHaveBeenCalledWith(
105
+ t('create_password_successfully')
106
+ );
107
+ expect(navigate).toHaveBeenCalledWith(Routes.Dashboard, {
108
+ needLogout: true,
109
+ });
110
+ });
111
+
112
+ it('create password fail', async () => {
113
+ mock.onPatch(API.AUTH.CREATE_PASSWORD).reply(400);
114
+ await act(async () => {
115
+ tree = await create(wrapComponent());
116
+ });
117
+ const instance = tree.root;
118
+ const button = buttonDone(instance);
119
+ const texts = instance.findAllByType(_TextInputPassword);
120
+
121
+ await onChangePasswords(texts, 'Password1', 'Password1');
122
+ expect(texts[0].props.errorText).toEqual('');
123
+ expect(texts[1].props.errorText).toEqual('');
124
+ expect(button.props.disabled).toBeFalsy();
125
+
126
+ await act(async () => {
127
+ await button.props.onPress();
128
+ });
129
+
130
+ expect(spyToastSuccess).not.toHaveBeenCalled();
131
+ expect(navigate).not.toHaveBeenCalled();
132
+ });
133
+ });
@@ -0,0 +1,134 @@
1
+ import React, { useMemo, useState, useCallback } from 'react';
2
+ import { TouchableOpacity, View } from 'react-native';
3
+ import { useNavigation } from '@react-navigation/native';
4
+
5
+ import { Colors, API } from '../../configs';
6
+ import { AccessibilityLabel } from '../../configs/Constants';
7
+ import { HeaderCustom } from '../../commons/Header';
8
+ import _TextInputPassword from '../../commons/Form/TextInputPassword';
9
+ import { Section } from '../../commons/Section';
10
+ import Text from '../../commons/Text';
11
+ import { useTranslations } from '../../hooks/Common/useTranslations';
12
+ import { axiosPatch } from '../../utils/Apis/axios';
13
+ import { isValidPassword } from '../../utils/Validation';
14
+ import { ToastBottomHelper } from '../../utils/Utils';
15
+ import Routes from '../../utils/Route';
16
+
17
+ import styles from './styles';
18
+
19
+ const CreatePassword = ({ route }) => {
20
+ const t = useTranslations();
21
+ const { navigate } = useNavigation();
22
+ const [newPassword, setNewPassword] = useState('');
23
+ const [confirmPassword, setConfirmPassword] = useState('');
24
+ const [newPasswordError, setNewPasswordError] = useState('');
25
+ const [confirmPasswordError, setConfirmPasswordError] = useState('');
26
+
27
+ const onChangeNewPassword = useCallback(
28
+ (value) => {
29
+ setNewPassword(value);
30
+ setNewPasswordError(
31
+ !value
32
+ ? t('please_enter_new_password')
33
+ : !isValidPassword(value)
34
+ ? t('password_must_be_strong')
35
+ : ''
36
+ );
37
+ setConfirmPasswordError((prev) =>
38
+ confirmPassword && value !== confirmPassword
39
+ ? t('confirm_password_not_match')
40
+ : value === confirmPassword
41
+ ? ''
42
+ : prev
43
+ );
44
+ },
45
+ [confirmPassword, t]
46
+ );
47
+
48
+ const onChangeConfirmPassword = useCallback(
49
+ (value) => {
50
+ setConfirmPassword(value);
51
+ setConfirmPasswordError(
52
+ !value
53
+ ? t('please_enter_your_confirm_password')
54
+ : value !== newPassword
55
+ ? t('confirm_password_not_match')
56
+ : ''
57
+ );
58
+ },
59
+ [newPassword, t]
60
+ );
61
+
62
+ const canDone = useMemo(() => {
63
+ return (
64
+ newPassword &&
65
+ confirmPassword &&
66
+ !newPasswordError &&
67
+ !confirmPasswordError
68
+ );
69
+ }, [newPassword, confirmPassword, newPasswordError, confirmPasswordError]);
70
+
71
+ const onCreatePassword = useCallback(async () => {
72
+ const { success } = await axiosPatch(API.AUTH.CREATE_PASSWORD, {
73
+ new_password: newPassword,
74
+ confirm_password: confirmPassword,
75
+ });
76
+
77
+ if (success) {
78
+ ToastBottomHelper.success(t('create_password_successfully'));
79
+ navigate(Routes.Dashboard, { needLogout: true });
80
+ }
81
+ }, [newPassword, confirmPassword, navigate, t]);
82
+
83
+ return (
84
+ <View style={styles.container}>
85
+ <HeaderCustom
86
+ title={t('password')}
87
+ isShowSeparator
88
+ titleStyle={{ color: Colors.Gray9 }}
89
+ />
90
+ <Text style={styles.boardTitle} color={Colors.Black} bold>
91
+ {t('create_password')}
92
+ </Text>
93
+ <Section type={'border'}>
94
+ <_TextInputPassword
95
+ secureTextEntry
96
+ label={t('new_password')}
97
+ labelStyle={styles.labelStyle}
98
+ wrapStyle={styles.wrapStyle}
99
+ onChange={onChangeNewPassword}
100
+ value={newPassword}
101
+ textInputStyle={styles.passwordInput}
102
+ selectionColor={Colors.Primary}
103
+ errorText={newPasswordError}
104
+ />
105
+ <_TextInputPassword
106
+ secureTextEntry
107
+ label={t('confirm_password')}
108
+ labelStyle={styles.labelStyle}
109
+ wrapStyle={styles.wrapStyle}
110
+ onChange={onChangeConfirmPassword}
111
+ value={confirmPassword}
112
+ textInputStyle={styles.passwordInput}
113
+ selectionColor={Colors.Primary}
114
+ errorText={confirmPasswordError}
115
+ />
116
+ </Section>
117
+ <TouchableOpacity
118
+ style={[styles.buttonDone, !canDone && styles.buttonDoneDisable]}
119
+ onPress={onCreatePassword}
120
+ accessibilityLabel={AccessibilityLabel.CREATE_PASSWORD_BUTTON_DONE}
121
+ disabled={!canDone}
122
+ >
123
+ <Text
124
+ type="H4"
125
+ style={canDone ? styles.textDone : styles.textDoneDisable}
126
+ >
127
+ {t('done')}
128
+ </Text>
129
+ </TouchableOpacity>
130
+ </View>
131
+ );
132
+ };
133
+
134
+ export default CreatePassword;
@@ -0,0 +1,45 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { Colors } from '../../configs';
3
+
4
+ export default StyleSheet.create({
5
+ container: {
6
+ flex: 1,
7
+ backgroundColor: Colors.Gray2,
8
+ },
9
+ boardTitle: {
10
+ marginBottom: 8,
11
+ marginTop: 12,
12
+ marginLeft: 16,
13
+ },
14
+ labelStyle: {
15
+ color: Colors.Primary,
16
+ },
17
+ wrapStyle: {
18
+ marginTop: 12,
19
+ width: '100%',
20
+ },
21
+ passwordInput: {
22
+ paddingTop: 8,
23
+ paddingBottom: 8,
24
+ },
25
+ buttonDone: {
26
+ justifyContent: 'center',
27
+ alignItems: 'center',
28
+ paddingVertical: 12,
29
+ borderRadius: 30,
30
+ backgroundColor: Colors.Primary,
31
+ width: '90%',
32
+ alignSelf: 'center',
33
+ },
34
+ buttonDoneDisable: {
35
+ backgroundColor: Colors.White,
36
+ borderWidth: 1,
37
+ borderColor: Colors.Neutral.Neutral3,
38
+ },
39
+ textDone: {
40
+ color: Colors.White,
41
+ },
42
+ textDoneDisable: {
43
+ color: Colors.Gray6,
44
+ },
45
+ });