@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.
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/commons/Action/ItemQuickAction.js +1 -1
- package/src/commons/ActionGroup/NumberUpDownActionTemplate.js +2 -0
- package/src/commons/ActionGroup/__test__/NumberUpDownTemplate.test.js +1 -1
- package/src/commons/ActionGroup/__test__/index.test.js +1 -1
- package/src/commons/Device/HistoryChart.js +2 -2
- package/src/commons/Device/ItemDevice.js +2 -2
- package/src/commons/OneTapTemplate/NumberUpDownActionTemplate.js +5 -1
- package/src/commons/SubUnit/Favorites/index.js +6 -2
- package/src/configs/API.js +4 -0
- package/src/configs/SCConfig.js +1 -1
- package/src/context/actionType.ts +5 -2
- package/src/context/mockStore.ts +17 -3
- package/src/context/reducer.ts +38 -4
- package/src/hooks/IoT/__test__/useValueEvaluation.test.js +58 -0
- package/src/hooks/IoT/index.js +2 -1
- package/src/hooks/IoT/useValueEvaluation.js +45 -0
- package/src/hooks/useReceiveNotifications.js +12 -18
- package/src/navigations/UnitStack.js +8 -0
- package/src/screens/AddNewAction/SelectAction.js +3 -2
- package/src/screens/AddNewAction/SetupSensor.js +3 -2
- package/src/screens/Device/detail.js +18 -27
- package/src/screens/Device/hooks/useEvaluateValue.js +97 -0
- package/src/screens/Device/hooks/useFavoriteDevice.js +2 -2
- package/src/screens/ScriptDetail/index.js +13 -3
- package/src/screens/Unit/Detail.js +10 -5
- package/src/screens/Unit/SelectAddress.js +1 -3
- package/src/screens/Unit/SelectDevices.js +158 -0
- package/src/screens/Unit/SelectDevicesStyles.js +40 -0
- package/src/screens/Unit/Summaries.js +1 -1
- package/src/screens/Unit/__test__/SelectAddress.test.js +90 -1
- package/src/screens/Unit/__test__/SelectDevices.test.js +110 -0
- package/src/screens/UnitSummary/components/RunningDevices/index.js +3 -1
- package/src/utils/Apis/axios.js +6 -0
- package/src/utils/I18n/translations/en.json +2 -0
- package/src/utils/I18n/translations/vi.json +2 -0
- package/src/utils/Route/index.js +1 -0
- 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.
|
|
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.
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
+
});
|
|
@@ -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
|
-
|
|
22
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
23
|
+
}, [unit]);
|
|
24
|
+
|
|
23
25
|
return (
|
|
24
26
|
<View style={styles.container}>
|
|
25
27
|
{!!devices &&
|
package/src/utils/Apis/axios.js
CHANGED
|
@@ -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
|
}
|