@eohjsc/react-native-smart-city 0.3.10 → 0.3.13

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 (39) hide show
  1. package/index.js +2 -0
  2. package/package.json +1 -1
  3. package/src/commons/Action/ItemQuickAction.js +1 -1
  4. package/src/commons/ActionGroup/NumberUpDownActionTemplate.js +2 -0
  5. package/src/commons/ActionGroup/__test__/NumberUpDownTemplate.test.js +1 -1
  6. package/src/commons/ActionGroup/__test__/index.test.js +1 -1
  7. package/src/commons/Device/HistoryChart.js +2 -2
  8. package/src/commons/Device/ItemDevice.js +2 -2
  9. package/src/commons/OneTapTemplate/NumberUpDownActionTemplate.js +5 -1
  10. package/src/commons/SubUnit/Favorites/index.js +6 -2
  11. package/src/configs/API.js +4 -0
  12. package/src/configs/SCConfig.js +1 -1
  13. package/src/context/actionType.ts +5 -2
  14. package/src/context/mockStore.ts +17 -3
  15. package/src/context/reducer.ts +38 -4
  16. package/src/hooks/IoT/__test__/useValueEvaluation.test.js +58 -0
  17. package/src/hooks/IoT/index.js +2 -1
  18. package/src/hooks/IoT/useValueEvaluation.js +45 -0
  19. package/src/hooks/useReceiveNotifications.js +12 -18
  20. package/src/navigations/UnitStack.js +8 -0
  21. package/src/screens/AddNewAction/SelectAction.js +3 -2
  22. package/src/screens/AddNewAction/SetupSensor.js +3 -2
  23. package/src/screens/Device/detail.js +18 -27
  24. package/src/screens/Device/hooks/useEvaluateValue.js +97 -0
  25. package/src/screens/Device/hooks/useFavoriteDevice.js +2 -2
  26. package/src/screens/ScriptDetail/index.js +13 -3
  27. package/src/screens/Unit/Detail.js +10 -5
  28. package/src/screens/Unit/SelectAddress.js +1 -3
  29. package/src/screens/Unit/SelectDevices.js +158 -0
  30. package/src/screens/Unit/SelectDevicesStyles.js +40 -0
  31. package/src/screens/Unit/Summaries.js +1 -1
  32. package/src/screens/Unit/__test__/SelectAddress.test.js +90 -1
  33. package/src/screens/Unit/__test__/SelectDevices.test.js +110 -0
  34. package/src/screens/UnitSummary/components/RunningDevices/index.js +3 -1
  35. package/src/utils/Apis/axios.js +6 -0
  36. package/src/utils/I18n/translations/en.json +2 -0
  37. package/src/utils/I18n/translations/vi.json +2 -0
  38. package/src/utils/Route/index.js +1 -0
  39. package/src/utils/Validation.js +3 -0
@@ -0,0 +1,97 @@
1
+ import { useCallback } from 'react';
2
+ import { useSCContextSelector } from '../../../context';
3
+
4
+ const evaluateRange = (value, configuration) => {
5
+ /*
6
+ configuration: {
7
+ ranges: [
8
+ { start: 0.5, end: 1.5, evaluate: 'On' },
9
+ { start: -0.5, end: 0.49, evaluate: {text: 'Off'} },
10
+ ]
11
+ }
12
+ */
13
+ if (!value) {
14
+ // eslint-disable-next-line no-param-reassign
15
+ value = 0;
16
+ }
17
+
18
+ for (let i = 0; i < configuration?.ranges?.length; i++) {
19
+ const range = configuration.ranges[i];
20
+ if (range.start <= value && value <= range.end) {
21
+ return range.evaluate;
22
+ }
23
+ if (!range.end && range.start <= value) {
24
+ return range.evaluate;
25
+ }
26
+ }
27
+ return { text: value };
28
+ };
29
+
30
+ const evaluateBoolean = (value, configuration) => {
31
+ /*
32
+ configuration: {
33
+ 'on': {
34
+ 'value': 1,
35
+ 'evaluate': {
36
+ 'text': 'On',
37
+ },
38
+ },
39
+ 'off': {
40
+ 'value': 0,
41
+
42
+ 'evaluate': {
43
+ 'text': 'Off',
44
+ },
45
+ },
46
+ }
47
+ */
48
+ if (!value) {
49
+ // eslint-disable-next-line no-param-reassign
50
+ value = 0;
51
+ }
52
+ if (value === configuration.on?.value) {
53
+ return configuration.on.evaluate;
54
+ }
55
+ if (value === configuration.off?.value) {
56
+ return configuration.off.evaluate;
57
+ }
58
+ return { text: value };
59
+ };
60
+
61
+ const valueEvaluationFuncs = {
62
+ range: evaluateRange,
63
+ boolean: evaluateBoolean,
64
+ };
65
+
66
+ export const useEvaluateValue = () => {
67
+ const valueEvaluations = useSCContextSelector((state) => {
68
+ return state.valueEvaluations;
69
+ });
70
+
71
+ const evaluateValue = useCallback(
72
+ (configId, value) => {
73
+ if (value === null || value === undefined) {
74
+ return { text: '--', color: null };
75
+ }
76
+
77
+ const valueEvaluation = valueEvaluations[configId];
78
+ if (!valueEvaluation) {
79
+ return null;
80
+ }
81
+
82
+ const evaluateFunc = valueEvaluationFuncs[valueEvaluation.template];
83
+ if (!evaluateFunc) {
84
+ return null;
85
+ }
86
+
87
+ try {
88
+ return evaluateFunc(value, valueEvaluation.configuration);
89
+ } catch (e) {
90
+ return null;
91
+ }
92
+ },
93
+ [valueEvaluations]
94
+ );
95
+
96
+ return evaluateValue;
97
+ };
@@ -16,14 +16,14 @@ export const useFavoriteDevice = (device) => {
16
16
  const { success } = await axiosPost(
17
17
  API.DEVICE.ADD_TO_FAVOURITES(device?.id)
18
18
  );
19
- success && setAction(Action.ADD_DEVICE_TO_FAVORITES, device.id);
19
+ success && setAction(Action.ADD_DEVICES_TO_FAVORITES, [device.id]);
20
20
  }, [device, setAction]);
21
21
 
22
22
  const removeFromFavorites = useCallback(async () => {
23
23
  const { success } = await axiosPost(
24
24
  API.DEVICE.REMOVE_FROM_FAVOURITES(device?.id)
25
25
  );
26
- success && setAction(Action.REMOVE_DEVICE_FROM_FAVORITES, device.id);
26
+ success && setAction(Action.REMOVE_DEVICES_FROM_FAVORITES, [device.id]);
27
27
  }, [device, setAction]);
28
28
 
29
29
  return {
@@ -34,7 +34,11 @@ import Routes from '../../utils/Route';
34
34
  import { ToastBottomHelper } from '../../utils/Utils';
35
35
  import ItemAutomate from '../../commons/Automate/ItemAutomate';
36
36
  import withPreventDoubleClick from '../../commons/WithPreventDoubleClick';
37
- import { AUTOMATE_SELECT, AUTOMATE_TYPE } from '../../configs/Constants';
37
+ import {
38
+ AUTOMATE_SELECT,
39
+ AUTOMATE_TYPE,
40
+ STATE_VALUE_SENSOR_TYPES,
41
+ } from '../../configs/Constants';
38
42
  import { popAction } from '../../navigations/utils';
39
43
  import { TESTID } from '../../configs/Constants';
40
44
  import useKeyboardAnimated from '../../hooks/Explore/useKeyboardAnimated';
@@ -359,17 +363,23 @@ const ScriptDetail = ({ route }) => {
359
363
  date_repeat,
360
364
  time_repeat,
361
365
  weekday_repeat,
366
+ sensor_type,
362
367
  } = automate;
363
368
  if (type === AUTOMATE_TYPE.VALUE_CHANGE) {
369
+ const stateConditionData = STATE_VALUE_SENSOR_TYPES.find(
370
+ (i) => i.type === sensor_type
371
+ );
372
+ const isNumberValue = !stateConditionData;
373
+
364
374
  let text;
365
375
  if (condition === '>') {
366
376
  text = 'higher_than';
367
377
  } else if (condition === '<') {
368
378
  text = 'lower_than';
369
379
  } else if (condition === '=') {
370
- text = 'equal';
380
+ text = isNumberValue ? 'equal' : stateConditionData?.stateValue[value];
371
381
  }
372
- return `${config_name} ${t(text)} ${value}`;
382
+ return `${config_name} ${t(text)} ${isNumberValue ? value : ''}`;
373
383
  } else if (type === AUTOMATE_TYPE.SCHEDULE) {
374
384
  const time =
375
385
  time_repeat?.length >= 8
@@ -4,8 +4,9 @@ import React, {
4
4
  useState,
5
5
  useRef,
6
6
  useContext,
7
+ useMemo,
7
8
  } from 'react';
8
- import { AppState, RefreshControl, View } from 'react-native';
9
+ import { AppState, RefreshControl, View, Platform } from 'react-native';
9
10
  import { useIsFocused } from '@react-navigation/native';
10
11
 
11
12
  import { useTranslations } from '../../hooks/Common/useTranslations';
@@ -24,6 +25,7 @@ import {
24
25
  } from '../../hooks/Common';
25
26
  import { useFavorites } from './hook/useFavorites';
26
27
  import { useUnitConnectRemoteDevices } from './hook/useUnitConnectRemoteDevices';
28
+ import { useValueEvaluations } from '../../hooks/IoT';
27
29
  import { fetchWithCache, axiosGet } from '../../utils/Apis/axios';
28
30
  import ShortDetailSubUnit from '../../commons/SubUnit/ShortDetail';
29
31
  import NavBar from '../../commons/NavBar';
@@ -52,6 +54,7 @@ import MediaPlayerDetail from '../../commons/MediaPlayerDetail';
52
54
  const UnitDetail = ({ route }) => {
53
55
  const t = useTranslations();
54
56
  const { setAction } = useContext(SCContext);
57
+ const isIOS = useMemo(() => Platform.OS === 'ios', []);
55
58
 
56
59
  const {
57
60
  unitId,
@@ -186,10 +189,12 @@ const UnitDetail = ({ route }) => {
186
189
  });
187
190
 
188
191
  return () => {
189
- subscription.remove();
192
+ subscription?.remove();
190
193
  };
191
194
  }, [fetchDetails]);
192
195
 
196
+ useValueEvaluations(unitId);
197
+
193
198
  useUnitConnectRemoteDevices(unit);
194
199
 
195
200
  useEffect(() => {
@@ -292,13 +297,13 @@ const UnitDetail = ({ route }) => {
292
297
  }, [user, onRefresh]);
293
298
 
294
299
  useEffect(() => {
295
- if (isFirstOpenCamera) {
300
+ if (isFirstOpenCamera && isIOS) {
296
301
  const to = setTimeout(() => {
297
302
  setAction(Action.IS_FIRST_OPEN_CAMERA, false);
298
303
  clearTimeout(to);
299
304
  }, 3000);
300
305
  }
301
- }, [isFirstOpenCamera, setAction]);
306
+ }, [isFirstOpenCamera, isIOS, setAction]);
302
307
 
303
308
  return (
304
309
  <WrapParallaxScrollView
@@ -315,7 +320,7 @@ const UnitDetail = ({ route }) => {
315
320
  onBack={(isSuccessfullyConnected && Dashboard) || (routeName && onBack)}
316
321
  >
317
322
  {/* NOTE: This is a trick to fix camera not full screen on first open app */}
318
- {isFirstOpenCamera && (
323
+ {isFirstOpenCamera && isIOS && (
319
324
  <MediaPlayerDetail
320
325
  uri={Constants.URL_STREAM_CAMERA_DEMO}
321
326
  isPaused={false}
@@ -70,9 +70,7 @@ const SelectAddress = memo(({ route }) => {
70
70
  API.EXTERNAL.GOOGLE_MAP.AUTO_COMPLETE,
71
71
  config
72
72
  );
73
- if (success) {
74
- setSearchData(data.predictions);
75
- }
73
+ success && setSearchData(data.predictions);
76
74
  // eslint-disable-next-line no-empty
77
75
  } catch (error) {}
78
76
  }, []);
@@ -0,0 +1,158 @@
1
+ import React, {
2
+ memo,
3
+ useState,
4
+ useEffect,
5
+ useCallback,
6
+ useMemo,
7
+ useContext,
8
+ } from 'react';
9
+ import { View, ScrollView, TouchableOpacity } from 'react-native';
10
+ import { useNavigation } from '@react-navigation/native';
11
+ import { Icon } from '@ant-design/react-native';
12
+ import { HeaderCustom } from '../../commons/Header';
13
+ import Text from '../../commons/Text';
14
+ import NavBar from '../../commons/NavBar';
15
+ import BottomButtonView from '../../commons/BottomButtonView';
16
+ import { FullLoading } from '../../commons';
17
+ import Device from '../AddNewAction/Device';
18
+ import { useTranslations } from '../../hooks/Common/useTranslations';
19
+ import { SCContext } from '../../context';
20
+ import { Action } from '../../context/actionType';
21
+ import { axiosGet, axiosPost } from '../../utils/Apis/axios';
22
+ import { API, Colors } from '../../configs';
23
+ import styles from './SelectDevicesStyles';
24
+
25
+ const SelectDevices = memo(({ route }) => {
26
+ const t = useTranslations();
27
+ const { goBack } = useNavigation();
28
+ const { unitId } = route.params;
29
+ const { setAction } = useContext(SCContext);
30
+ const [listStation, setListStation] = useState([]);
31
+ const [listMenuItem, setListMenuItem] = useState([]);
32
+ const [indexStation, setIndexStation] = useState(0);
33
+ const [stations, setStations] = useState([]);
34
+ const [selectedIds, setSelectedIds] = useState([]);
35
+ const [loading, setLoading] = useState(false);
36
+
37
+ const fetchData = useCallback(async () => {
38
+ setLoading(true);
39
+ const { success, data } = await axiosGet(API.UNIT.DEVICES(unitId));
40
+ if (success) {
41
+ const newData = data.filter((item) => item.devices.length > 0);
42
+ const listMenu = newData.map((item, index) => ({
43
+ text: item.name,
44
+ station: item,
45
+ index: index,
46
+ }));
47
+ setStations(newData);
48
+ setListMenuItem(listMenu);
49
+ setListStation(listMenu);
50
+ }
51
+ setLoading(false);
52
+ }, [unitId]);
53
+
54
+ const addDevicesToFavorites = useCallback(async () => {
55
+ if (selectedIds.length === 0) {
56
+ return;
57
+ }
58
+ setLoading(true);
59
+ const { success } = await axiosPost(
60
+ API.UNIT.ADD_DEVICES_TO_FAVORITES(unitId),
61
+ {
62
+ devices: selectedIds,
63
+ }
64
+ );
65
+ if (success) {
66
+ setAction(Action.ADD_DEVICES_TO_FAVORITES, selectedIds);
67
+ goBack();
68
+ }
69
+ setLoading(false);
70
+ }, [unitId, selectedIds, setAction, goBack]);
71
+
72
+ useEffect(() => {
73
+ fetchData();
74
+ }, [fetchData]);
75
+
76
+ const onSnapToItem = useCallback(
77
+ (item, index) => {
78
+ setIndexStation(index);
79
+ },
80
+ // eslint-disable-next-line react-hooks/exhaustive-deps
81
+ [unitId, indexStation]
82
+ );
83
+
84
+ const onSelectDevice = useCallback(
85
+ (device) => {
86
+ setSelectedIds((ids) => {
87
+ const index = ids.indexOf(device.id);
88
+ if (index !== -1) {
89
+ return ids.filter((id) => id !== device.id);
90
+ }
91
+ return [...ids, device.id];
92
+ });
93
+ },
94
+ [setSelectedIds]
95
+ );
96
+
97
+ const rightComponent = useMemo(
98
+ () => (
99
+ <TouchableOpacity style={styles.buttonClose} onPress={goBack}>
100
+ <Icon name={'close'} size={24} color={Colors.Black} />
101
+ </TouchableOpacity>
102
+ ),
103
+ // eslint-disable-next-line react-hooks/exhaustive-deps
104
+ []
105
+ );
106
+
107
+ return (
108
+ <View style={styles.wrap}>
109
+ <HeaderCustom rightComponent={rightComponent} />
110
+ <ScrollView
111
+ style={styles.wrap}
112
+ contentContainerStyle={styles.contentContainerStyle}
113
+ scrollIndicatorInsets={{ right: 1 }}
114
+ >
115
+ <Text bold type="H2" style={styles.title}>
116
+ {t('select_device')}
117
+ </Text>
118
+
119
+ {listStation.length ? (
120
+ <NavBar
121
+ listStation={listStation}
122
+ listMenuItem={listMenuItem}
123
+ onSnapToItem={onSnapToItem}
124
+ indexStation={indexStation}
125
+ style={styles.navbar}
126
+ />
127
+ ) : (
128
+ <View style={styles.noneData}>
129
+ <Text center>{t('text_unit_add_to_favorites_no_devices')}</Text>
130
+ </View>
131
+ )}
132
+
133
+ <View style={styles.boxDevices}>
134
+ {stations[indexStation]?.devices &&
135
+ stations[indexStation].devices.map((device) => (
136
+ <Device
137
+ svgMain={device.icon || 'sensor'}
138
+ title={device.name}
139
+ sensor={device}
140
+ isSelectDevice={selectedIds.includes(device.id)}
141
+ onPress={onSelectDevice}
142
+ />
143
+ ))}
144
+ </View>
145
+ </ScrollView>
146
+
147
+ <BottomButtonView
148
+ style={styles.bottomButtonView}
149
+ mainTitle={t('done')}
150
+ onPressMain={addDevicesToFavorites}
151
+ typeMain={selectedIds.length === 0 ? 'disabled' : 'primary'}
152
+ />
153
+ {loading && <FullLoading />}
154
+ </View>
155
+ );
156
+ });
157
+
158
+ export default SelectDevices;
@@ -0,0 +1,40 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { getBottomSpace } from 'react-native-iphone-x-helper';
3
+ import { Colors, Constants } from '../../configs';
4
+
5
+ export default StyleSheet.create({
6
+ wrap: {
7
+ flex: 1,
8
+ backgroundColor: Colors.White,
9
+ },
10
+ contentContainerStyle: {
11
+ paddingBottom: getBottomSpace() + 100,
12
+ },
13
+ navbar: {
14
+ paddingTop: 16,
15
+ },
16
+ title: {
17
+ marginHorizontal: 16,
18
+ },
19
+ boxDevices: {
20
+ flexWrap: 'wrap',
21
+ flexDirection: 'row',
22
+ marginTop: 22,
23
+ justifyContent: 'space-between',
24
+ paddingLeft: 16,
25
+ paddingRight: 16,
26
+ },
27
+
28
+ bottomButtonView: {
29
+ paddingTop: 24,
30
+ paddingBottom: 32,
31
+
32
+ backgroundColor: Colors.White,
33
+ borderColor: Colors.ShadownTransparent,
34
+ borderTopWidth: 1,
35
+ },
36
+ noneData: {
37
+ paddingHorizontal: 16,
38
+ marginTop: Constants.height * 0.3,
39
+ },
40
+ });
@@ -69,7 +69,7 @@ const Summaries = memo(({ unit }) => {
69
69
  });
70
70
 
71
71
  return () => {
72
- subscription.remove();
72
+ subscription?.remove();
73
73
  };
74
74
  }, [fetchUnitSummary]);
75
75
 
@@ -103,6 +103,22 @@ describe('Test SelectAddress', () => {
103
103
  updateLocation: mockUpdateLocation,
104
104
  },
105
105
  };
106
+ mockUpdateLocation.mockClear();
107
+ mockGoBack.mockClear();
108
+ mock.resetHistory();
109
+ });
110
+
111
+ test('test not do anything then click done', async () => {
112
+ await act(async () => {
113
+ tree = await create(wrapComponent(route));
114
+ });
115
+ const instance = tree.root;
116
+ const bottomButton = instance.findByType(BottomButtonView);
117
+ await act(async () => {
118
+ await bottomButton.props.onPressMain();
119
+ });
120
+ expect(mockUpdateLocation).not.toBeCalled();
121
+ expect(mockGoBack).not.toBeCalled();
106
122
  });
107
123
 
108
124
  test('test search location', async () => {
@@ -151,7 +167,6 @@ describe('Test SelectAddress', () => {
151
167
  mock
152
168
  .onGet(API.EXTERNAL.GOOGLE_MAP.GET_LAT_LNG_BY_PLACE_ID)
153
169
  .reply(200, response.data);
154
-
155
170
  await act(async () => {
156
171
  await rowLocations[0].props.onPress({ place_id: 1, description: '1' });
157
172
  });
@@ -163,6 +178,52 @@ describe('Test SelectAddress', () => {
163
178
  expect(mockGoBack).toBeCalled();
164
179
  });
165
180
 
181
+ test('test get lat lng of location failed', async () => {
182
+ await act(async () => {
183
+ tree = await create(wrapComponent(route));
184
+ });
185
+ const instance = tree.root;
186
+ const searchBars = instance.findAllByType(SearchBarLocation);
187
+ expect(searchBars).toHaveLength(1);
188
+
189
+ let response = {
190
+ status: 200,
191
+ data: {
192
+ predictions: [
193
+ { place_id: 1, description: '1' },
194
+ { place_id: 2, description: '2' },
195
+ { place_id: 3, description: '3' },
196
+ ],
197
+ },
198
+ };
199
+ mock.onGet(API.EXTERNAL.GOOGLE_MAP.AUTO_COMPLETE).reply(200, response.data);
200
+ await act(async () => {
201
+ await searchBars[0].props.onTextInput('');
202
+ });
203
+ let rowLocations = instance.findAllByType(RowLocation);
204
+ expect(rowLocations).toHaveLength(0);
205
+
206
+ await act(async () => {
207
+ await searchBars[0].props.onTextInput('input');
208
+ });
209
+ rowLocations = instance.findAllByType(RowLocation);
210
+ expect(rowLocations).toHaveLength(3);
211
+ response = {
212
+ status: 404,
213
+ data: {},
214
+ };
215
+ mock
216
+ .onGet(API.EXTERNAL.GOOGLE_MAP.GET_LAT_LNG_BY_PLACE_ID)
217
+ .reply(404, response.data);
218
+ await act(async () => {
219
+ await rowLocations[0].props.onPress({ place_id: 1, description: '1' });
220
+ });
221
+ const bottomButton = instance.findByType(BottomButtonView);
222
+ await act(async () => {
223
+ await bottomButton.props.onPressMain();
224
+ });
225
+ });
226
+
166
227
  test('test get current location success', async () => {
167
228
  await act(async () => {
168
229
  tree = await create(wrapComponent(route));
@@ -196,6 +257,34 @@ describe('Test SelectAddress', () => {
196
257
  });
197
258
  });
198
259
 
260
+ test('test get current location success get location name failed', async () => {
261
+ await act(async () => {
262
+ tree = await create(wrapComponent(route));
263
+ });
264
+ const instance = tree.root;
265
+ const button = instance.find(
266
+ (el) => el.props.testID === TESTID.BUTTON_YOUR_LOCATION
267
+ );
268
+
269
+ const response = {
270
+ status: 400,
271
+ data: {
272
+ results: [],
273
+ },
274
+ };
275
+ mock
276
+ .onGet(API.EXTERNAL.GOOGLE_MAP.GET_LOCATION_FROM_LAT_LNG)
277
+ .reply(400, response.data);
278
+ await act(async () => {
279
+ await button.props.onPress();
280
+ });
281
+ const bottomButton = instance.findByType(BottomButtonView);
282
+ await act(async () => {
283
+ await bottomButton.props.onPressMain();
284
+ });
285
+ expect(mockGoBack).toBeCalled();
286
+ });
287
+
199
288
  test('test get current location failed permission denied', async () => {
200
289
  await act(async () => {
201
290
  tree = await create(wrapComponent(route));
@@ -0,0 +1,110 @@
1
+ import React from 'react';
2
+ import { act, create } from 'react-test-renderer';
3
+ import MockAdapter from 'axios-mock-adapter';
4
+
5
+ import { SCProvider } from '../../../context';
6
+ import { mockSCStore } from '../../../context/mockStore';
7
+ import SelectDevices from '../SelectDevices';
8
+ import Device from '../../AddNewAction/Device';
9
+ import BottomButtonView from '../../../commons/BottomButtonView';
10
+ import { API } from '../../../configs';
11
+ import api from '../../../utils/Apis/axios';
12
+
13
+ const wrapComponent = (route) => (
14
+ <SCProvider initState={mockSCStore({})}>
15
+ <SelectDevices route={route} />
16
+ </SCProvider>
17
+ );
18
+
19
+ const mock = new MockAdapter(api.axiosInstance);
20
+
21
+ const mockGoBack = jest.fn();
22
+ jest.mock('@react-navigation/native', () => {
23
+ return {
24
+ ...jest.requireActual('@react-navigation/native'),
25
+ useNavigation: () => ({
26
+ goBack: mockGoBack,
27
+ }),
28
+ };
29
+ });
30
+
31
+ jest.mock('react', () => {
32
+ return {
33
+ ...jest.requireActual('react'),
34
+ memo: (x) => x,
35
+ };
36
+ });
37
+
38
+ describe('Test SelectDevices', () => {
39
+ let tree, route;
40
+
41
+ beforeAll(() => {
42
+ mockGoBack.mockClear();
43
+ mock.resetHistory();
44
+ route = {
45
+ params: {
46
+ unitId: 1,
47
+ },
48
+ };
49
+ });
50
+
51
+ test('test select then add devices to favorites', async () => {
52
+ let data = [
53
+ {
54
+ id: 1,
55
+ name: 'station 1',
56
+ devices: [
57
+ {
58
+ id: 1,
59
+ name: 'device 1',
60
+ icon: 'sensor',
61
+ icon_kit: null,
62
+ },
63
+ {
64
+ id: 2,
65
+ name: 'device 2',
66
+ icon: null,
67
+ icon_kit: 'icon',
68
+ },
69
+ ],
70
+ },
71
+ {
72
+ id: 2,
73
+ name: 'station 2',
74
+ devices: [],
75
+ },
76
+ ];
77
+ mock.onGet(API.UNIT.DEVICES(1)).replyOnce(200, data);
78
+ mock.onPost(API.UNIT.ADD_DEVICES_TO_FAVORITES(1)).replyOnce(200);
79
+
80
+ await act(async () => {
81
+ tree = await create(wrapComponent(route));
82
+ });
83
+ const instance = tree.root;
84
+
85
+ const bottomButton = instance.findByType(BottomButtonView);
86
+
87
+ await act(async () => {
88
+ await bottomButton.props.onPressMain();
89
+ });
90
+ expect(mock.history.post).toHaveLength(0);
91
+
92
+ const devices = instance.findAllByType(Device);
93
+ expect(devices).toHaveLength(2);
94
+
95
+ await act(async () => {
96
+ await devices[0].props.onPress({ id: 1 });
97
+ });
98
+ await act(async () => {
99
+ await devices[1].props.onPress({ id: 2 });
100
+ });
101
+ await act(async () => {
102
+ await devices[0].props.onPress({ id: 1 });
103
+ });
104
+ await act(async () => {
105
+ await bottomButton.props.onPressMain();
106
+ });
107
+ expect(mock.history.post).toHaveLength(1);
108
+ expect(mockGoBack).toBeCalled();
109
+ });
110
+ });
@@ -19,7 +19,9 @@ const RunningDevices = memo(({ unit, summaryDetail }) => {
19
19
  await connectGoogleHome(unit.remote_control_options.googlehome))();
20
20
  }
21
21
  }
22
- }, [connectGoogleHome, unit]);
22
+ // eslint-disable-next-line react-hooks/exhaustive-deps
23
+ }, [unit]);
24
+
23
25
  return (
24
26
  <View style={styles.container}>
25
27
  {!!devices &&
@@ -5,6 +5,7 @@ import NetInfo from '@react-native-community/netinfo';
5
5
  import { PROBLEM_CODE } from '../../configs/Constants';
6
6
  import { getTranslate } from '../I18n';
7
7
  import { SCConfig } from '../../configs';
8
+ import { isHTML } from '../Validation';
8
9
 
9
10
  const api = create({
10
11
  headers: {
@@ -46,6 +47,11 @@ const parseErrorResponse = async (error) => {
46
47
  case PROBLEM_CODE.SERVER_ERROR:
47
48
  message = getTranslate(SCConfig.language, 'server_error');
48
49
  break;
50
+ case PROBLEM_CODE.CLIENT_ERROR:
51
+ if (error.status === 404 && isHTML(error.data)) {
52
+ message = getTranslate(SCConfig.language, 'not_found');
53
+ }
54
+ break;
49
55
  }
50
56
  ToastBottomHelper.error(message);
51
57
  }