@eohjsc/react-native-smart-city 0.4.78 → 0.4.79

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 (25) hide show
  1. package/package.json +1 -1
  2. package/src/commons/ActionTemplate/SwitchButtonAction.js +55 -0
  3. package/src/commons/ActionTemplate/__test__/SwitchButtonAction.test.js +55 -0
  4. package/src/commons/ActionTemplate/__test__/index.test.js +24 -0
  5. package/src/commons/ActionTemplate/index.js +3 -0
  6. package/src/commons/Auth/AccountItem.js +5 -4
  7. package/src/commons/ChartAggregationOption/index.js +1 -2
  8. package/src/commons/Sharing/MemberList.js +1 -0
  9. package/src/commons/Sharing/RowMember.js +13 -14
  10. package/src/navigations/AddMemberStack.js +2 -2
  11. package/src/screens/Automate/AddNewAction/ChooseAction.js +12 -3
  12. package/src/screens/Automate/AddNewAction/SetupConfigCondition.js +0 -2
  13. package/src/screens/Automate/AddNewAction/Styles/SetupSensorStyles.js +1 -0
  14. package/src/screens/Automate/AddNewAction/__test__/ChooseAction.test.js +188 -0
  15. package/src/screens/Device/components/VisualChart.js +10 -3
  16. package/src/screens/HanetCamera/styles/memberInfoStyles.js +1 -1
  17. package/src/screens/Notification/styles/indexStyles.js +2 -1
  18. package/src/screens/Sharing/SelectUser.js +9 -6
  19. package/src/screens/Sharing/{SelectPermission.js → SharingSelectPermission.js} +4 -2
  20. package/src/screens/Sharing/Styles/SelectPermissionStyles.js +4 -0
  21. package/src/screens/Sharing/Styles/inforMemberUnitStyles.js +1 -1
  22. package/src/screens/Sharing/__test__/SelectUser.test.js +46 -4
  23. package/src/screens/Sharing/__test__/{SelectPermission.test.js → SharingSelectPermission.test.js} +4 -5
  24. package/src/utils/I18n/translations/en.js +2 -0
  25. package/src/utils/I18n/translations/vi.js +3 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eohjsc/react-native-smart-city",
3
3
  "title": "React Native Smart Home",
4
- "version": "0.4.78",
4
+ "version": "0.4.79",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -0,0 +1,55 @@
1
+ import React, { useCallback } from 'react';
2
+ import { TouchableOpacity } from 'react-native';
3
+ import AccessibilityLabel from '../../configs/AccessibilityLabel';
4
+ import Text from '../Text';
5
+ import styles from './OnOffButtonActionStyles';
6
+
7
+ const SwitchButtonAction = ({ title, configuration, onPress, template }) => {
8
+ const { text_on, text_off, action_on, action_off } = configuration;
9
+ const onPressActionOn = useCallback(() => {
10
+ onPress &&
11
+ onPress({
12
+ ...configuration,
13
+ name: text_on,
14
+ action: action_on,
15
+ action_off: null,
16
+ template,
17
+ });
18
+ }, [onPress, configuration, text_on, action_on, template]);
19
+
20
+ const onPressActionOff = useCallback(() => {
21
+ onPress &&
22
+ onPress({
23
+ ...configuration,
24
+ name: text_off,
25
+ action: action_off,
26
+ action_on: null,
27
+ template,
28
+ });
29
+ }, [onPress, configuration, text_off, action_off, template]);
30
+
31
+ return (
32
+ <>
33
+ <TouchableOpacity onPress={onPressActionOn}>
34
+ <Text
35
+ type="H4"
36
+ style={styles.textWithLine}
37
+ accessibilityLabel={AccessibilityLabel.ON_OFF_BUTTON_ACTION_TITLE}
38
+ >
39
+ {title} {text_on}
40
+ </Text>
41
+ </TouchableOpacity>
42
+ <TouchableOpacity onPress={onPressActionOff}>
43
+ <Text
44
+ type="H4"
45
+ style={styles.textWithLine}
46
+ accessibilityLabel={AccessibilityLabel.ON_OFF_BUTTON_ACTION_TITLE}
47
+ >
48
+ {title} {text_off}
49
+ </Text>
50
+ </TouchableOpacity>
51
+ </>
52
+ );
53
+ };
54
+
55
+ export default SwitchButtonAction;
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import renderer, { act } from 'react-test-renderer';
3
+ import { TouchableOpacity } from 'react-native';
4
+
5
+ import { SCProvider } from '../../../context';
6
+ import { mockSCStore } from '../../../context/mockStore';
7
+ import Text from '../../Text';
8
+ import AccessibilityLabel from '../../../configs/AccessibilityLabel';
9
+ import SwitchButtonAction from '../SwitchButtonAction';
10
+
11
+ const wrapComponent = (configuration, onPress) => (
12
+ <SCProvider initState={mockSCStore({})}>
13
+ <SwitchButtonAction
14
+ configuration={configuration}
15
+ onPress={onPress}
16
+ title={'Cong tac 1'}
17
+ template={'switch_button_action_template'}
18
+ />
19
+ </SCProvider>
20
+ );
21
+
22
+ describe('Test SwitchButtonAction', () => {
23
+ let tree;
24
+
25
+ it('test onPress', async () => {
26
+ const configuration = {
27
+ text_on: 'On',
28
+ text_off: 'Off',
29
+ action_off: 'f49e1577-493d-4bae-a9ab-3b0f96a4ec52',
30
+ action_on: '94600cbe-2035-47d5-bd43-89ace5d59796',
31
+ };
32
+ const mockFuntion = jest.fn();
33
+ await act(async () => {
34
+ tree = await renderer.create(wrapComponent(configuration, mockFuntion));
35
+ });
36
+ const instance = tree.root;
37
+ const touchOpacity = instance.findAllByType(TouchableOpacity);
38
+
39
+ expect(touchOpacity).toHaveLength(2);
40
+ await act(async () => {
41
+ touchOpacity[0].props.onPress();
42
+ touchOpacity[1].props.onPress();
43
+ });
44
+ expect(mockFuntion).toHaveBeenCalled();
45
+
46
+ const textTitle = instance.findAll(
47
+ (el) =>
48
+ el.props.accessibilityLabel ===
49
+ AccessibilityLabel.ON_OFF_BUTTON_ACTION_TITLE && el.type === Text
50
+ );
51
+ expect(textTitle).toHaveLength(2);
52
+ expect(textTitle[0].props.children).toEqual(['Cong tac 1', ' ', 'On']);
53
+ expect(textTitle[1].props.children).toEqual(['Cong tac 1', ' ', 'Off']);
54
+ });
55
+ });
@@ -7,6 +7,8 @@ import { mockSCStore } from '../../../context/mockStore';
7
7
  import SelectActionCard from '../../SelectActionCard';
8
8
  import Modal from 'react-native-modal';
9
9
  import ThreeButtonAction from '../ThreeButtonAction';
10
+ import AccessibilityLabel from '../../../configs/AccessibilityLabel';
11
+ import Text from '../../Text';
10
12
 
11
13
  const mockOnSelectAction = jest.fn();
12
14
 
@@ -35,6 +37,16 @@ describe('Test ActionTemplate', () => {
35
37
  is_display_lock: true,
36
38
  },
37
39
  };
40
+ let switchTemplate = {
41
+ title: '',
42
+ template: 'switch_button_action_template',
43
+ configuration: {
44
+ text_on: 'On',
45
+ text_off: 'Off',
46
+ action_off: 'f49e1577-493d-4bae-a9ab-3b0f96a4ec52',
47
+ action_on: '94600cbe-2035-47d5-bd43-89ace5d59796',
48
+ },
49
+ };
38
50
 
39
51
  it('test onPress SelectActionCard', async () => {
40
52
  await act(async () => {
@@ -75,4 +87,16 @@ describe('Test ActionTemplate', () => {
75
87
  template: 'three_button_action_template',
76
88
  });
77
89
  });
90
+ it('test render SwitchButtonAction', async () => {
91
+ await act(async () => {
92
+ tree = await renderer.create(wrapComponent(switchTemplate));
93
+ });
94
+ const instance = tree.root;
95
+ const textTitle = instance.findAll(
96
+ (el) =>
97
+ el.props.accessibilityLabel ===
98
+ AccessibilityLabel.ON_OFF_BUTTON_ACTION_TITLE && el.type === Text
99
+ );
100
+ expect(textTitle).toHaveLength(2);
101
+ });
78
102
  });
@@ -12,6 +12,7 @@ import { ModalCustom } from '../Modal';
12
12
  import CurtainAction from './CurtainAction';
13
13
  import OnOffSmartLockAction from './OnOffSmartLockAction';
14
14
  import { DEVICE_TYPE } from '../../configs/Constants';
15
+ import SwitchButtonAction from './SwitchButtonAction';
15
16
 
16
17
  const ActionTemplate = memo(({ device, item, onSelectAction }) => {
17
18
  const t = useTranslations();
@@ -61,6 +62,8 @@ const ActionTemplate = memo(({ device, item, onSelectAction }) => {
61
62
  return <CurtainAction {...item} onPress={onPressSelectAction} />;
62
63
  case 'OnOffSmartLockActionTemplate':
63
64
  return <OnOffSmartLockAction {...item} onPress={onPressSelectAction} />;
65
+ case 'switch_button_action_template':
66
+ return <SwitchButtonAction {...item} onPress={onPressSelectAction} />;
64
67
  default:
65
68
  return null;
66
69
  }
@@ -7,6 +7,7 @@ import Avatar from '../../../assets/images/avatar.svg';
7
7
  import AccessibilityLabel from '../../configs/AccessibilityLabel';
8
8
 
9
9
  const AccountItem = ({ account }) => {
10
+ const { name, email, phone_number } = account;
10
11
  return (
11
12
  <View style={styles.container}>
12
13
  <View styles={styles.wrap}>
@@ -14,18 +15,18 @@ const AccountItem = ({ account }) => {
14
15
  </View>
15
16
  <View style={styles.space} />
16
17
  <View style={styles.wrap}>
17
- {(account?.name || account?.email) && (
18
+ {(name || email) && (
18
19
  <Text style={{ color: Colors.Gray9 }}>
19
- {account?.name || shortEmailName(account?.email)}
20
+ {name || shortEmailName(email)}
20
21
  </Text>
21
22
  )}
22
23
 
23
- {account?.phone_number && (
24
+ {!!phone_number /* Can't write test for this case, use !! to avoid app crash when phone_number is '' */ && (
24
25
  <Text
25
26
  style={{ color: Colors.Gray8 }}
26
27
  accessibilityLabel={AccessibilityLabel.TEXT_PHONE_NUMBER_ITEM}
27
28
  >
28
- {account?.phone_number}
29
+ {phone_number}
29
30
  </Text>
30
31
  )}
31
32
  </View>
@@ -9,7 +9,6 @@ const defaultOptions = [
9
9
  title: 'D',
10
10
  data: 'date',
11
11
  },
12
-
13
12
  {
14
13
  title: 'W',
15
14
  data: 'week',
@@ -33,7 +32,7 @@ const ItemButton = ({ title, onPress, isSelected }) => {
33
32
  style={[styles.button, isSelected && styles.selectedButton]}
34
33
  >
35
34
  <View>
36
- <Text bold color={isSelected && Colors.Primary}>
35
+ <Text bold color={isSelected ? Colors.Primary : Colors.Gray9}>
37
36
  {title}
38
37
  </Text>
39
38
  </View>
@@ -38,6 +38,7 @@ export default memo(MemberList);
38
38
  const styles = StyleSheet.create({
39
39
  box: {
40
40
  backgroundColor: Colors.White,
41
+ paddingTop: 20,
41
42
  },
42
43
  viewEmpty: {
43
44
  justifyContent: 'center',
@@ -31,21 +31,22 @@ const RowMember = memo(
31
31
  }) => {
32
32
  const t = useTranslations();
33
33
  const { navigate } = useNavigation();
34
+ const { id, name, phone_number, email, share_id, avatar } = member;
34
35
  const [role, roleColor] = useMemo(
35
36
  () =>
36
- member?.id === ownerId
37
+ id === ownerId
37
38
  ? [t('owner'), Colors.Primary]
38
- : member?.id === currentUserId
39
+ : id === currentUserId
39
40
  ? [t('me'), Colors.Primary]
40
41
  : [t('member'), Colors.Gray6],
41
- [currentUserId, member?.id, ownerId, t]
42
+ [currentUserId, id, ownerId, t]
42
43
  );
43
44
  const firstWordsInName = useMemo(() => {
44
- const wordTemp = member?.name || shortEmailName(member?.email) || '';
45
+ const wordTemp = name || shortEmailName(email) || '';
45
46
  return wordTemp?.charAt();
46
- }, [member?.email, member?.name]);
47
+ }, [email, name]);
47
48
 
48
- if (member?.id === ownerId && member?.share_id) {
49
+ if (id === ownerId && share_id) {
49
50
  return null;
50
51
  }
51
52
  const circleColorTypes = {
@@ -74,11 +75,8 @@ const RowMember = memo(
74
75
  <View style={styles.Border}>
75
76
  {!!leftIcon && (
76
77
  <View style={styles.paddingLeft16}>
77
- {member?.avatar ? (
78
- <Image
79
- source={{ uri: member?.avatar }}
80
- style={styles.avatar}
81
- />
78
+ {avatar ? (
79
+ <Image source={{ uri: avatar }} style={styles.avatar} />
82
80
  ) : (
83
81
  <CircleView size={40} backgroundColor={circleColor} center>
84
82
  <Text color={Colors.White}>{firstWordsInName}</Text>
@@ -88,16 +86,17 @@ const RowMember = memo(
88
86
  )}
89
87
  <View style={styles.columnFlex}>
90
88
  <Text style={styles.titleName}>
91
- {member?.name || shortEmailName(member?.email) || ''}
89
+ {name || shortEmailName(email) || ''}
92
90
  </Text>
93
- {member?.phone_number && (
91
+ {/* Can't write test for this case, use !! to avoid app crash when phone_number is '' */}
92
+ {!!phone_number && (
94
93
  <Text
95
94
  style={styles.status}
96
95
  accessibilityLabel={
97
96
  AccessibilityLabel.TEXT_PHONE_NUMBER_UNIT_MEMBER
98
97
  }
99
98
  >
100
- {member?.phone_number}
99
+ {phone_number}
101
100
  </Text>
102
101
  )}
103
102
  </View>
@@ -2,10 +2,10 @@ import { createStackNavigator } from '@react-navigation/stack';
2
2
  import React, { memo } from 'react';
3
3
 
4
4
  import AddCommonSelectUnit from '../screens/AddCommon/SelectUnit';
5
- import SharingSelectPermission from '../screens/Sharing/SelectPermission';
6
- import SharingInviteMembers from '../screens/Sharing/SelectUser';
7
5
  import Route from '../utils/Route';
8
6
  import { screenOptions } from './utils';
7
+ import SharingInviteMembers from '../screens/Sharing/SelectUser';
8
+ import SharingSelectPermission from '../screens/Sharing/SharingSelectPermission';
9
9
 
10
10
  const Stack = createStackNavigator();
11
11
 
@@ -14,7 +14,7 @@ import NewActionWrapper from './NewActionWrapper';
14
14
  import moment from 'moment';
15
15
  import { ToastBottomHelper } from '../../../utils/Utils';
16
16
 
17
- const RenderActionItem = ({ device, item, handleOnSelectAction, index }) => {
17
+ const RenderActionItem = ({ device, item, handleOnSelectAction, index, t }) => {
18
18
  item.index = index;
19
19
  switch (item.template) {
20
20
  case 'on_off_button_action_template':
@@ -23,6 +23,7 @@ const RenderActionItem = ({ device, item, handleOnSelectAction, index }) => {
23
23
  case 'OnOffSimpleActionTemplate':
24
24
  case 'curtain_action_template':
25
25
  case 'OnOffSmartLockActionTemplate':
26
+ case 'switch_button_action_template':
26
27
  return (
27
28
  <ActionTemplate
28
29
  device={device}
@@ -57,14 +58,20 @@ const RenderActionItem = ({ device, item, handleOnSelectAction, index }) => {
57
58
  onSelectAction={handleOnSelectAction}
58
59
  />
59
60
  );
61
+ default:
62
+ ToastBottomHelper.error(
63
+ t('template_not_supported', { template: item.template }),
64
+ '',
65
+ 3000
66
+ );
67
+ return null;
60
68
  }
61
69
  };
62
70
 
63
71
  const ChooseAction = ({ route }) => {
64
72
  const t = useTranslations();
65
73
  const { navigate } = useNavigation();
66
- const { unitId, device, automateId, numberActionCanAdd } =
67
- route?.params || {};
74
+ const { unitId, device, automateId, numberActionCanAdd } = route?.params;
68
75
  const [data, setData] = useState([]);
69
76
  const [actions, setActions] = useState([]);
70
77
 
@@ -141,10 +148,12 @@ const ChooseAction = ({ route }) => {
141
148
  {!!data?.length &&
142
149
  data.map((item, index) => (
143
150
  <RenderActionItem
151
+ key={`action_item_${index}`}
144
152
  device={device}
145
153
  item={item}
146
154
  index={index}
147
155
  handleOnSelectAction={handleOnSelectAction}
156
+ t={t}
148
157
  />
149
158
  ))}
150
159
  </View>
@@ -43,11 +43,9 @@ const valueEvaluationToOptions = (valueEvaluation) => {
43
43
 
44
44
  const SetupConfigCondition = () => {
45
45
  const t = useTranslations();
46
- //
47
46
  const { navigate } = useNavigation();
48
47
  const { params = {} } = useRoute();
49
48
  const { config, automate, closeScreen } = params;
50
- //
51
49
  const [selectedCondition, setSelectedCondition] = useState(undefined);
52
50
  const [customCondition, setCustomCondition] = useState(undefined);
53
51
  const [isShowModal, setIsShowModal] = useState(false);
@@ -10,6 +10,7 @@ export default StyleSheet.create({
10
10
  color: Colors.Black,
11
11
  },
12
12
  numberInput: {
13
+ borderRadius: 5,
13
14
  borderWidth: 1,
14
15
  borderColor: Colors.Black,
15
16
  height: 38,
@@ -13,6 +13,7 @@ import { TouchableOpacity } from 'react-native';
13
13
  import SelectActionCard from '../../../../commons/SelectActionCard';
14
14
  import { useNavigation } from '@react-navigation/native';
15
15
  import { ToastBottomHelper } from '../../../../utils/Utils';
16
+ import { getTranslate } from '../../../../utils/I18n';
16
17
 
17
18
  const mock = new MockAdapter(api.axiosInstance);
18
19
 
@@ -370,6 +371,35 @@ describe('Test ChooseAction', () => {
370
371
  );
371
372
  });
372
373
 
374
+ test('test press template wrong', async () => {
375
+ const config1 = 1;
376
+ const response = [
377
+ {
378
+ id: 1,
379
+ title: '',
380
+ template: 'TeamplateWorng',
381
+ configuration: {
382
+ allow_config_store_value: true,
383
+ config: config1,
384
+ action_on: '94ae262d-46e3-42ff-9d10-516831ecc830',
385
+ action_off: '94ae262d-46e3-42ff-9d10-516831ecc830',
386
+ },
387
+ },
388
+ ];
389
+ mock.onGet(API.DEVICE.DISPLAY_ACTIONS(1)).reply(200, response);
390
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
391
+ await act(async () => {
392
+ tree = await renderer.create(wrapComponent(route));
393
+ });
394
+ expect(spyToastError).toBeCalledWith(
395
+ getTranslate('en', 'template_not_supported', {
396
+ template: response[0].template,
397
+ }),
398
+ '',
399
+ 3000
400
+ );
401
+ });
402
+
373
403
  test('test reach max can add', async () => {
374
404
  const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
375
405
 
@@ -435,4 +465,162 @@ describe('Test ChooseAction', () => {
435
465
  3000
436
466
  );
437
467
  });
468
+ test('reach max can add actions', async () => {
469
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
470
+
471
+ const config1 = 1,
472
+ config2 = 2,
473
+ config3 = 3,
474
+ config4 = 4;
475
+ const response = [
476
+ {
477
+ id: 1,
478
+ title: '',
479
+ template: 'OnOffSimpleActionTemplate',
480
+ configuration: {
481
+ allow_config_store_value: true,
482
+ config: config1,
483
+ action_on: '94ae262d-46e3-42ff-9d10-516831ecc830',
484
+ action_off: '94ae262d-46e3-42ff-9d10-516831ecc830',
485
+ },
486
+ },
487
+ {
488
+ id: 2,
489
+ title: 'Fan Speed',
490
+ template: 'OptionsDropdownActionTemplate',
491
+ configuration: {
492
+ config: config2,
493
+ action: '05195362-75de-4db5-9e5e-98fef9d4910c',
494
+ allow_config_store_value: true,
495
+ options: [
496
+ {
497
+ text: 'Auto',
498
+ value_int: 1,
499
+ value_text: 'auto',
500
+ },
501
+ {
502
+ text: 'Level1',
503
+ value_int: 2,
504
+ value_text: 'level1',
505
+ },
506
+ ],
507
+ icon: 'up',
508
+ icon_kit: 43,
509
+ },
510
+ },
511
+ {
512
+ id: 3,
513
+ title: '',
514
+ template: 'NumberUpDownActionTemplate',
515
+ configuration: {
516
+ keep_track_config: true,
517
+ config: config3,
518
+ allow_config_store_value: true,
519
+ action: 'b498234c-6c1a-452d-a1d1-87a314c20528',
520
+ min_value: 16,
521
+ max_value: 30,
522
+ text_format: '{number} \u00b0C',
523
+ },
524
+ },
525
+ {
526
+ id: 4,
527
+ title: '',
528
+ template: 'NumberUpDownActionTemplate',
529
+ configuration: {
530
+ config: config4,
531
+ allow_config_store_value: true,
532
+ action: '11111111-6c1a-452d-a1d1-87a314c20528',
533
+ min_value: 12,
534
+ max_value: 20,
535
+ text_format: '{number} \u00b0C',
536
+ },
537
+ },
538
+ ];
539
+ mock.onGet(API.DEVICE.DISPLAY_ACTIONS(1)).reply(200, response);
540
+ route.params.numberActionCanAdd = 2;
541
+ await act(async () => {
542
+ tree = await renderer.create(wrapComponent(route));
543
+ });
544
+ const instance = tree.root;
545
+
546
+ const cards = instance.findAllByType(SelectActionCard);
547
+
548
+ await act(async () => {
549
+ cards.map((card) => card.props.onPress());
550
+ });
551
+
552
+ const simpleActionOn = instance.find(
553
+ (el) =>
554
+ el.props.accessibilityLabel ===
555
+ AccessibilityLabel.ON_OFF_SIMPLE_ACTION_ON &&
556
+ el.type === TouchableOpacity
557
+ );
558
+ await act(async () => {
559
+ await simpleActionOn.props.onPress();
560
+ });
561
+
562
+ const simpleActionOnOff = instance.find(
563
+ (el) =>
564
+ el.props.accessibilityLabel ===
565
+ AccessibilityLabel.ON_OFF_SIMPLE_ACTION_OFF &&
566
+ el.type === TouchableOpacity
567
+ );
568
+ await act(async () => {
569
+ await simpleActionOnOff.props.onPress();
570
+ });
571
+ const selectOptions = instance.findAll(
572
+ (item) =>
573
+ item.props.accessibilityLabel ===
574
+ AccessibilityLabel.OPTIONS_DROPDOWN_ACTION_CHOOSING_ITEM &&
575
+ item.type === TouchableOpacity
576
+ );
577
+ const touchOpacity = instance.find(
578
+ (item) =>
579
+ item.props.accessibilityLabel ===
580
+ AccessibilityLabel.OPTIONS_DROPDOWN_ACTION_DONE &&
581
+ item.type === TouchableOpacity
582
+ );
583
+
584
+ await act(async () => {
585
+ await selectOptions[0].props.onPress(0);
586
+ await touchOpacity.props.onPress();
587
+ });
588
+
589
+ const buttonNumberUp = instance.findAll(
590
+ (item) =>
591
+ item.props.accessibilityLabel ===
592
+ AccessibilityLabel.NUMBER_UP_DOWN_ACTION_UP &&
593
+ item.type === TouchableOpacity
594
+ );
595
+ expect(buttonNumberUp).toHaveLength(2);
596
+
597
+ const buttonSaveNumberAction = instance.findAll(
598
+ (item) =>
599
+ item.props.accessibilityLabel ===
600
+ AccessibilityLabel.NUMBER_UP_DOWN_ACTION_DONE &&
601
+ item.type === TouchableOpacity
602
+ );
603
+ expect(buttonSaveNumberAction).toHaveLength(2);
604
+
605
+ await act(async () => {
606
+ await buttonNumberUp[0].props.onPress();
607
+ await buttonNumberUp[1].props.onPress();
608
+ await buttonSaveNumberAction[0].props.onPress();
609
+ await buttonSaveNumberAction[1].props.onPress();
610
+ });
611
+ const buttonSave = instance.find(
612
+ (el) =>
613
+ el.props.accessibilityLabel === AccessibilityLabel.BOTTOM_VIEW_MAIN &&
614
+ el.type === TouchableOpacity
615
+ );
616
+ await act(async () => {
617
+ await buttonSave.props.onPress();
618
+ });
619
+
620
+ expect(spyToastError).toBeCalledWith(
621
+ 'You can only add more 2 actions',
622
+ null,
623
+ 3000
624
+ );
625
+ });
438
626
  });
@@ -123,6 +123,11 @@ const styles = StyleSheet.create({
123
123
  width: 300,
124
124
  height: 250,
125
125
  },
126
+ titleView: {
127
+ flexDirection: 'row',
128
+ flex: 1,
129
+ lineHeight: 24,
130
+ },
126
131
  });
127
132
 
128
133
  const chartOptions = {
@@ -286,9 +291,11 @@ const VisualChart = ({ item, isDemo = false, isWidgetOrder }) => {
286
291
  return (
287
292
  <View style={styles.container}>
288
293
  <View style={styles.titleHistory}>
289
- <Text size={20} semibold color={Colors.Gray9}>
290
- {item.label}
291
- </Text>
294
+ <View style={styles.titleView}>
295
+ <Text size={20} semibold color={Colors.Gray9}>
296
+ {item.label}
297
+ </Text>
298
+ </View>
292
299
  {canChooseGroup && (
293
300
  <ChartAggregationOption
294
301
  groupBy={groupBy}
@@ -47,7 +47,7 @@ export default StyleSheet.create({
47
47
  buttonRemove: {
48
48
  position: 'absolute',
49
49
  alignItems: 'center',
50
- padding: 32,
50
+ paddingBottom: 92,
51
51
  bottom: 0,
52
52
  left: 0,
53
53
  right: 0,
@@ -1,6 +1,7 @@
1
1
  import { StyleSheet } from 'react-native';
2
2
 
3
3
  import { Colors } from '../../../configs';
4
+ import { getStatusBarHeight } from '../../../configs/Constants';
4
5
 
5
6
  export default StyleSheet.create({
6
7
  wrap: {
@@ -17,6 +18,6 @@ export default StyleSheet.create({
17
18
  marginRight: 22,
18
19
  },
19
20
  styleScrollView: {
20
- marginTop: -10,
21
+ marginTop: getStatusBarHeight(),
21
22
  },
22
23
  });
@@ -20,15 +20,15 @@ import {
20
20
  import { axiosPost } from '../../utils/Apis/axios';
21
21
  import { AccessibilityLabel } from '../../configs/Constants';
22
22
  import Text from '../../commons/Text';
23
+ import { ToastBottomHelper } from '../../utils/Utils';
23
24
 
24
- const SelectUser = ({ route }) => {
25
+ const SharingInviteMembers = ({ route }) => {
25
26
  const t = useTranslations();
26
27
  const navigation = useNavigation();
27
28
  const { unit, permissions } = route?.params || {};
28
29
  const [errorText, setErrorText] = useState('');
29
30
  const [content, setContent] = useState('');
30
31
  const [users, setUsers] = useState([]);
31
-
32
32
  const sharePermissions = useCallback(
33
33
  async (phone, email) => {
34
34
  Keyboard.dismiss();
@@ -44,9 +44,12 @@ const SelectUser = ({ route }) => {
44
44
  unit: unit.id,
45
45
  permissions,
46
46
  });
47
- success && setUsers([...users, data.user]);
47
+ if (success) {
48
+ ToastBottomHelper.success(t('invited_user', { user: phone || email }));
49
+ setUsers([...users, data.user]);
50
+ }
48
51
  },
49
- [users, unit.id, permissions]
52
+ [users, unit.id, permissions, t]
50
53
  );
51
54
 
52
55
  const validate = useCallback(() => {
@@ -145,7 +148,7 @@ const SelectUser = ({ route }) => {
145
148
  </View>
146
149
  </TouchableWithoutFeedback>
147
150
  <ViewButtonBottom
148
- leftTitle={t('cancel')}
151
+ leftTitle={t('back')}
149
152
  onLeftClick={() => navigation.goBack()}
150
153
  rightTitle={t('done')}
151
154
  rightDisabled={false}
@@ -217,4 +220,4 @@ const styles = StyleSheet.create({
217
220
  },
218
221
  });
219
222
 
220
- export default SelectUser;
223
+ export default SharingInviteMembers;
@@ -15,7 +15,7 @@ import { AccessibilityLabel } from '../../configs/Constants';
15
15
 
16
16
  let dataStationTemp = [];
17
17
 
18
- const SelectPermission = ({ route }) => {
18
+ const SharingSelectPermission = ({ route }) => {
19
19
  const t = useTranslations();
20
20
  const { unit, type = '', member } = route?.params || {};
21
21
  const navigation = useNavigation();
@@ -389,6 +389,8 @@ const SelectPermission = ({ route }) => {
389
389
  {t('no_data')}
390
390
  </Text>
391
391
  )}
392
+ </View>
393
+ <View style={styles.wrapViewButtonStyle}>
392
394
  <ViewButtonBottom
393
395
  accessibilityLabelPrefix={
394
396
  AccessibilityLabel.PREFIX.SHARING_SELECT_PERMISSION
@@ -404,4 +406,4 @@ const SelectPermission = ({ route }) => {
404
406
  );
405
407
  };
406
408
 
407
- export default memo(SelectPermission);
409
+ export default memo(SharingSelectPermission);
@@ -63,4 +63,8 @@ export default StyleSheet.create({
63
63
  fontFamily: FONT_PREFIX + '-' + 'Semibold',
64
64
  fontStyle: 'normal',
65
65
  },
66
+ wrapViewButtonStyle: {
67
+ paddingTop: 90,
68
+ marginBottom: 30,
69
+ },
66
70
  });
@@ -71,7 +71,7 @@ export default StyleSheet.create({
71
71
  bottom: 0,
72
72
  borderWidth: 0,
73
73
  alignSelf: 'center',
74
- paddingBottom: 16 + getBottomSpace(),
74
+ paddingBottom: 36 + getBottomSpace(),
75
75
  },
76
76
  removeBorderBottom: {
77
77
  borderBottomWidth: 1,
@@ -2,7 +2,6 @@ import React from 'react';
2
2
  import { create, act } from 'react-test-renderer';
3
3
  import MockAdapter from 'axios-mock-adapter';
4
4
 
5
- import SelectUser from '../SelectUser';
6
5
  import { AccessibilityLabel } from '../../../configs/Constants';
7
6
  import { ViewButtonBottom, Button } from '../../../commons';
8
7
  import _TextInput from '../../../commons/Form/TextInput';
@@ -12,10 +11,12 @@ import { getTranslate } from '../../../utils/I18n';
12
11
  import { SCProvider } from '../../../context';
13
12
  import { mockSCStore } from '../../../context/mockStore';
14
13
  import api from '../../../utils/Apis/axios';
14
+ import { Text } from 'react-native';
15
+ import SharingInviteMembers from '../SelectUser';
15
16
 
16
17
  const wrapComponent = (route) => (
17
18
  <SCProvider initState={mockSCStore({})}>
18
- <SelectUser route={route} />
19
+ <SharingInviteMembers route={route} />
19
20
  </SCProvider>
20
21
  );
21
22
 
@@ -41,7 +42,7 @@ jest.mock('@react-navigation/native', () => {
41
42
  };
42
43
  });
43
44
 
44
- describe('test SelectUser container', () => {
45
+ describe('test SharingInviteMembers container', () => {
45
46
  let tree;
46
47
  let route;
47
48
 
@@ -91,7 +92,7 @@ describe('test SelectUser container', () => {
91
92
  const instance = tree.root;
92
93
  const viewButtonBottom = instance.findByType(ViewButtonBottom);
93
94
  expect(viewButtonBottom.props.leftTitle).toEqual(
94
- getTranslate('en', 'cancel')
95
+ getTranslate('en', 'back')
95
96
  );
96
97
  expect(viewButtonBottom.props.rightTitle).toEqual(
97
98
  getTranslate('en', 'done')
@@ -174,5 +175,46 @@ describe('test SelectUser container', () => {
174
175
  await button.props.onPress();
175
176
  });
176
177
  expect(accountList[0].props.accounts).toHaveLength(1);
178
+
179
+ await act(async () => {
180
+ await textInput.props.onChange('0909123456'); // In case you have invited and then intentionally invite again
181
+ });
182
+ mock.onPost(API.SHARE.SHARE()).reply(400);
183
+ await act(async () => {
184
+ await button.props.onPress();
185
+ });
186
+ expect(accountList[0].props.accounts).toHaveLength(1);
187
+ });
188
+
189
+ it('_TextInput onChange email, validated and call api sharedPermission', async () => {
190
+ await act(async () => {
191
+ tree = await create(wrapComponent(route));
192
+ });
193
+ const instance = tree.root;
194
+ const textInput = instance.findByType(_TextInput);
195
+ const button = instance.findByType(Button);
196
+ let accountList = instance.findAllByType(AccountList);
197
+ expect(accountList).toHaveLength(0);
198
+ await act(async () => {
199
+ await textInput.props.onChange('test@eoh.io');
200
+ });
201
+ mock.onPost(API.SHARE.SHARE()).reply(200, {
202
+ user: {
203
+ id: 3,
204
+ name: 'user add',
205
+ phone_number: '',
206
+ email: 'test@eoh.io',
207
+ },
208
+ });
209
+ await act(async () => {
210
+ await button.props.onPress();
211
+ });
212
+
213
+ const textPhone = instance.findAll(
214
+ (el) =>
215
+ el.props.accessibilityLabel ===
216
+ AccessibilityLabel.TEXT_PHONE_NUMBER_ITEM && el.type === Text
217
+ );
218
+ expect(textPhone).toHaveLength(0);
177
219
  });
178
220
  });
@@ -3,14 +3,13 @@ import React, { useState } from 'react';
3
3
  import { FlatList, Text, Platform } from 'react-native';
4
4
  import { create } from 'react-test-renderer';
5
5
  import MockAdapter from 'axios-mock-adapter';
6
-
7
- import SelectPermission from '../SelectPermission';
8
6
  import { DeviceItem, TitleCheckBox } from '../Components';
9
7
  import { ViewButtonBottom } from '../../../commons';
10
8
  import { SCProvider } from '../../../context';
11
9
  import { mockSCStore } from '../../../context/mockStore';
12
10
  import API from '../../../configs/API';
13
11
  import api from '../../../utils/Apis/axios';
12
+ import SharingSelectPermission from '../SharingSelectPermission';
14
13
 
15
14
  const mock = new MockAdapter(api.axiosInstance);
16
15
 
@@ -37,11 +36,11 @@ jest.mock('@react-navigation/native', () => {
37
36
 
38
37
  const wrapComponent = (route) => (
39
38
  <SCProvider initState={mockSCStore({})}>
40
- <SelectPermission route={route} />
39
+ <SharingSelectPermission route={route} />
41
40
  </SCProvider>
42
41
  );
43
42
 
44
- describe('Test SelectPermission', () => {
43
+ describe('Test SharingSelectPermission', () => {
45
44
  let tree;
46
45
  let route = { params: { unit: { id: 1, name: 'unit 1' } } };
47
46
  let listDevices = [
@@ -242,7 +241,7 @@ describe('Test SelectPermission', () => {
242
241
  expect(mockGoBack).toBeCalled();
243
242
  });
244
243
 
245
- it('test selectPermission type share_device', async () => {
244
+ it('test SharingSelectPermission type share_device', async () => {
246
245
  mocSetdata();
247
246
  useState.mockImplementationOnce((init) => [true, mockSetState]);
248
247
  const response = {
@@ -1448,4 +1448,6 @@ export default {
1448
1448
  'customize...': 'Customize...',
1449
1449
  uri_invalid: 'URI invalid',
1450
1450
  when_value_is: 'When value is',
1451
+ template_not_supported: '"{template}" not yet supported',
1452
+ invited_user: 'Invited user {user}',
1451
1453
  };
@@ -95,6 +95,7 @@ export default {
95
95
  text_new_unit: 'Địa điểm',
96
96
  text_sub_units: 'Khu vực',
97
97
  is_below: 'dưới',
98
+ is: 'bằng',
98
99
  is_above: 'trên',
99
100
  edit_actions_list: 'Chỉnh sửa danh sách hành động',
100
101
  des_edit_actions_list:
@@ -1458,4 +1459,6 @@ export default {
1458
1459
  'customize...': 'Tùy chỉnh...',
1459
1460
  uri_invalid: 'URI không hợp lệ',
1460
1461
  when_value_is: 'Khi giá trị là',
1462
+ template_not_supported: '"{template}" chưa được hỗ trợ',
1463
+ invited_user: 'Đã mời người dùng {user}',
1461
1464
  };