@eohjsc/react-native-smart-city 0.7.13 → 0.7.15
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/package.json +2 -2
- package/src/commons/Widgets/IFrame/IFrame.js +2 -2
- package/src/commons/Widgets/IFrame/IFrameStyles.js +5 -0
- package/src/configs/API.js +2 -1
- package/src/hooks/Common/useBlockBack.js +36 -21
- package/src/hooks/Common/useDevicesStatus.js +16 -13
- package/src/screens/AddNewGateway/ScanDeviceLocal.js +14 -16
- package/src/screens/AddNewGateway/ScanDeviceLocalStyles.js +6 -1
- package/src/screens/AddNewGateway/__test__/ScanDeviceLocal.test.js +4 -13
- package/src/screens/Automate/ScriptDetail/__test__/index.test.js +59 -4
- package/src/screens/Automate/ScriptDetail/index.js +43 -7
- package/src/screens/SharedUnit/__test__/ShareUnit.test.js +22 -1
- package/src/screens/Sharing/UnitMemberList.js +1 -1
- package/src/screens/Sharing/__test__/UnitMemberList.test.js +10 -0
- package/src/screens/Unit/__test__/Detail.test.js +1 -1
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.7.
|
|
4
|
+
"version": "0.7.15",
|
|
5
5
|
"description": "TODO",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"jetifier": "^1.6.6",
|
|
119
119
|
"lint-staged": "^12.4.1",
|
|
120
120
|
"lodash": "^4.17.19",
|
|
121
|
-
"lottie-react-native": "^
|
|
121
|
+
"lottie-react-native": "^7.1.0",
|
|
122
122
|
"md5": "^2.3.0",
|
|
123
123
|
"metro-react-native-babel-preset": "0.73.9",
|
|
124
124
|
"moment": "^2.27.0",
|
|
@@ -29,13 +29,13 @@ const IFrame = memo(({ item = {} }) => {
|
|
|
29
29
|
}, [id, url]);
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
|
-
<View>
|
|
32
|
+
<View style={styles.viewIframe}>
|
|
33
33
|
<TouchableOpacity style={styles.reloadButton} onClick={reload}>
|
|
34
34
|
<IconComponent size={20} icon="ReloadOutlined" />
|
|
35
35
|
</TouchableOpacity>
|
|
36
36
|
<WebView
|
|
37
37
|
source={{ uri: urlWithEnv }}
|
|
38
|
-
|
|
38
|
+
className={styles.iframe}
|
|
39
39
|
ref={ref}
|
|
40
40
|
title={title}
|
|
41
41
|
javaScriptEnabled
|
package/src/configs/API.js
CHANGED
|
@@ -21,7 +21,8 @@ const API = {
|
|
|
21
21
|
AUTOMATE: (id) => `/property_manager/units/${id}/automate/`,
|
|
22
22
|
DEVICE_CONTROL: (id) => `/property_manager/units/${id}/device_control/`,
|
|
23
23
|
DEVICE_SENSOR: (id) => `/property_manager/units/${id}/device_sensor/`,
|
|
24
|
-
|
|
24
|
+
END_DEVICES_STATUS: (id) =>
|
|
25
|
+
`/property_manager/units/${id}/end_devices_status/`,
|
|
25
26
|
CHANGE_OWNER: (id) => `/property_manager/units/${id}/change_owner/`,
|
|
26
27
|
FAVOURITE_DEVICES: (id) =>
|
|
27
28
|
`/property_manager/units/${id}/favourite_devices/`,
|
|
@@ -1,36 +1,51 @@
|
|
|
1
1
|
import { useFocusEffect, useNavigation } from '@react-navigation/native';
|
|
2
2
|
import { useCallback, useRef } from 'react';
|
|
3
|
-
import { BackHandler } from 'react-native';
|
|
3
|
+
import { BackHandler, Platform } from 'react-native';
|
|
4
4
|
|
|
5
5
|
export const useBlockBack = (actionBack) => {
|
|
6
6
|
const navigation = useNavigation();
|
|
7
|
-
const
|
|
7
|
+
const isListening = useRef(false);
|
|
8
8
|
|
|
9
9
|
const blockBack = useCallback(() => {
|
|
10
10
|
actionBack && actionBack();
|
|
11
11
|
return true;
|
|
12
12
|
}, [actionBack]);
|
|
13
13
|
|
|
14
|
+
const blockBeforeRemove = useCallback(
|
|
15
|
+
(e) => {
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
blockBack();
|
|
18
|
+
},
|
|
19
|
+
[blockBack]
|
|
20
|
+
);
|
|
21
|
+
|
|
14
22
|
useFocusEffect(
|
|
15
23
|
useCallback(() => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
if (isListening.current) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
isListening.current = true;
|
|
28
|
+
|
|
29
|
+
if (Platform.OS === 'ios') {
|
|
30
|
+
const unsubscribe = navigation.addListener(
|
|
31
|
+
'beforeRemove',
|
|
32
|
+
blockBeforeRemove
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return () => {
|
|
36
|
+
unsubscribe();
|
|
37
|
+
isListening.current = false;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (Platform.OS === 'android') {
|
|
42
|
+
BackHandler.addEventListener('hardwareBackPress', blockBack);
|
|
43
|
+
|
|
44
|
+
return () => {
|
|
45
|
+
BackHandler.removeEventListener('hardwareBackPress', blockBack);
|
|
46
|
+
isListening.current = false;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}, [blockBack, blockBeforeRemove, navigation])
|
|
35
50
|
);
|
|
36
51
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useContext, useEffect, useRef } from 'react';
|
|
2
2
|
import { useIsFocused } from '@react-navigation/native';
|
|
3
3
|
import { SCContext, useSCContextSelector } from '../../context';
|
|
4
|
-
import {
|
|
4
|
+
import { axiosPost } from '../../utils/Apis/axios';
|
|
5
5
|
import { API } from '../../configs';
|
|
6
6
|
import { Action } from '../../context/actionType';
|
|
7
7
|
|
|
@@ -13,7 +13,7 @@ const useDevicesStatus = (unit, devices) => {
|
|
|
13
13
|
(state) => state.app.isNetworkConnected
|
|
14
14
|
);
|
|
15
15
|
const isFocused = useIsFocused();
|
|
16
|
-
const hasFetched = useRef(
|
|
16
|
+
const hasFetched = useRef([]); // Track if data has been fetched
|
|
17
17
|
|
|
18
18
|
const getDevicesStatus = useCallback(
|
|
19
19
|
async (_unit, _devices) => {
|
|
@@ -21,24 +21,29 @@ const useDevicesStatus = (unit, devices) => {
|
|
|
21
21
|
clearTimeout(timeoutId);
|
|
22
22
|
timeoutId = null;
|
|
23
23
|
}
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
const end_device_ids = _devices.map((item) => item.id);
|
|
25
|
+
|
|
26
|
+
if (hasFetched.current.toString() === end_device_ids.toString()) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const { success, data } = await axiosPost(
|
|
30
|
+
API.UNIT.END_DEVICES_STATUS(_unit.id),
|
|
30
31
|
{
|
|
31
|
-
|
|
32
|
+
end_device_ids,
|
|
32
33
|
}
|
|
33
34
|
);
|
|
34
|
-
|
|
35
|
+
|
|
36
|
+
if (success) {
|
|
37
|
+
hasFetched.current = end_device_ids;
|
|
38
|
+
setAction(Action.SET_DEVICES_STATUS, data);
|
|
39
|
+
}
|
|
35
40
|
timeoutId = setTimeout(() => getDevicesStatus(_unit, _devices), 10000);
|
|
36
41
|
},
|
|
37
42
|
[setAction]
|
|
38
43
|
);
|
|
39
44
|
|
|
40
45
|
useEffect(() => {
|
|
41
|
-
if (!isFocused || !isNetworkConnected
|
|
46
|
+
if (!isFocused || !isNetworkConnected) {
|
|
42
47
|
return;
|
|
43
48
|
}
|
|
44
49
|
if (!devices?.length) {
|
|
@@ -53,7 +58,6 @@ const useDevicesStatus = (unit, devices) => {
|
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
getDevicesStatus(unit, devices);
|
|
56
|
-
hasFetched.current = true; // Mark as fetched
|
|
57
61
|
return () => {
|
|
58
62
|
if (timeoutId) {
|
|
59
63
|
clearTimeout(timeoutId);
|
|
@@ -65,7 +69,6 @@ const useDevicesStatus = (unit, devices) => {
|
|
|
65
69
|
useEffect(() => {
|
|
66
70
|
// Reset the hasFetched flag when the component is no longer focused
|
|
67
71
|
if (!isFocused) {
|
|
68
|
-
hasFetched.current = false;
|
|
69
72
|
if (timeoutId) {
|
|
70
73
|
clearTimeout(timeoutId); // Clear timeout when losing focus
|
|
71
74
|
timeoutId = null;
|
|
@@ -221,23 +221,21 @@ const ScanDeviceLocal = ({ route }) => {
|
|
|
221
221
|
title={t('device_scaned')}
|
|
222
222
|
isShowSeparator
|
|
223
223
|
/>
|
|
224
|
-
|
|
224
|
+
|
|
225
|
+
<View style={styles.rowContainer}>
|
|
226
|
+
<Text style={styles.subTitle} type="Body">
|
|
227
|
+
{t('select_device_and_connect')}
|
|
228
|
+
</Text>
|
|
225
229
|
<ActivityIndicator style={styles.containerLoading} />
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
renderItem={renderItem}
|
|
236
|
-
extraData={deviceList}
|
|
237
|
-
numColumns={1}
|
|
238
|
-
/>
|
|
239
|
-
</>
|
|
240
|
-
)}
|
|
230
|
+
</View>
|
|
231
|
+
<FlatList
|
|
232
|
+
style={styles.listContainer}
|
|
233
|
+
keyExtractor={(item) => item?.host}
|
|
234
|
+
data={deviceList}
|
|
235
|
+
renderItem={renderItem}
|
|
236
|
+
extraData={deviceList}
|
|
237
|
+
numColumns={1}
|
|
238
|
+
/>
|
|
241
239
|
|
|
242
240
|
<ViewButtonBottom
|
|
243
241
|
leftTitle={t('cancel')}
|
|
@@ -6,6 +6,10 @@ export default StyleSheet.create({
|
|
|
6
6
|
flex: 1,
|
|
7
7
|
backgroundColor: Colors.Gray2,
|
|
8
8
|
},
|
|
9
|
+
rowContainer: {
|
|
10
|
+
flexDirection: 'row',
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
},
|
|
9
13
|
title: {
|
|
10
14
|
marginVertical: 16,
|
|
11
15
|
marginLeft: 16,
|
|
@@ -53,6 +57,7 @@ export default StyleSheet.create({
|
|
|
53
57
|
containerLoading: {
|
|
54
58
|
flex: 1,
|
|
55
59
|
justifyContent: 'center',
|
|
56
|
-
alignItems: '
|
|
60
|
+
alignItems: 'flex-start',
|
|
61
|
+
marginLeft: 16,
|
|
57
62
|
},
|
|
58
63
|
});
|
|
@@ -105,6 +105,9 @@ describe('test ScanDeviceLocal', () => {
|
|
|
105
105
|
);
|
|
106
106
|
expect(zeroconfMock.on).toHaveBeenCalledWith('error', expect.any(Function));
|
|
107
107
|
expect(zeroconfMock.scan).toHaveBeenCalledWith('plugandplay', 'tcp');
|
|
108
|
+
|
|
109
|
+
const activityIndicator = instance.findByType(ActivityIndicator);
|
|
110
|
+
expect(activityIndicator).toBeTruthy();
|
|
108
111
|
};
|
|
109
112
|
|
|
110
113
|
it('test connect gateway and navigate', async () => {
|
|
@@ -382,17 +385,6 @@ describe('test ScanDeviceLocal', () => {
|
|
|
382
385
|
expect(mockedGoBack).toHaveBeenCalled();
|
|
383
386
|
});
|
|
384
387
|
|
|
385
|
-
it('test renders loading indicator when device list is empty', async () => {
|
|
386
|
-
const route = { params: { unit: 1 } };
|
|
387
|
-
await act(async () => {
|
|
388
|
-
tree = await renderer.create(wrapComponent(route));
|
|
389
|
-
});
|
|
390
|
-
const instance = tree.root;
|
|
391
|
-
|
|
392
|
-
const activityIndicator = instance.findByType(ActivityIndicator);
|
|
393
|
-
expect(activityIndicator).toBeTruthy();
|
|
394
|
-
});
|
|
395
|
-
|
|
396
388
|
it('test renders device list when devices are available', async () => {
|
|
397
389
|
const route = { params: { unit: 1 } };
|
|
398
390
|
await act(async () => {
|
|
@@ -451,8 +443,7 @@ describe('test ScanDeviceLocal', () => {
|
|
|
451
443
|
await removeCallback('Device 1');
|
|
452
444
|
});
|
|
453
445
|
|
|
454
|
-
|
|
455
|
-
expect(flatListAfter.length).toBe(0);
|
|
446
|
+
expect(flatList.props.data).toEqual([]);
|
|
456
447
|
|
|
457
448
|
const activityIndicator = instance.findByType(ActivityIndicator);
|
|
458
449
|
expect(activityIndicator).toBeTruthy();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { BackHandler, Platform } from 'react-native';
|
|
2
3
|
import { create, act } from 'react-test-renderer';
|
|
3
4
|
import Toast from 'react-native-toast-message';
|
|
4
5
|
import { useNavigation } from '@react-navigation/native';
|
|
@@ -40,10 +41,12 @@ describe('Test ScriptDetail', () => {
|
|
|
40
41
|
const mockGoBack = useNavigation().goBack;
|
|
41
42
|
const mockAddListener = useNavigation().addListener;
|
|
42
43
|
const mockedNavigate = useNavigation().navigate;
|
|
44
|
+
const mockedDispatch = useNavigation().dispatch;
|
|
43
45
|
beforeEach(() => {
|
|
44
46
|
mockGoBack.mockClear();
|
|
45
47
|
mockAddListener.mockClear();
|
|
46
48
|
mockedNavigate.mockClear();
|
|
49
|
+
mockedDispatch.mockClear();
|
|
47
50
|
global.mockedNavigate.mockClear();
|
|
48
51
|
mock.reset();
|
|
49
52
|
route = {
|
|
@@ -596,11 +599,35 @@ describe('Test ScriptDetail', () => {
|
|
|
596
599
|
menuActionMore.props.onItemClick({ doAction: jest.fn() });
|
|
597
600
|
menuActionMore.props.listMenuItem[2].doAction();
|
|
598
601
|
});
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
+
|
|
603
|
+
const mockState = {
|
|
604
|
+
routes: [{ name: 'Home' }],
|
|
605
|
+
};
|
|
606
|
+
const updatedState = mockedDispatch.mock.calls[0][0](mockState);
|
|
607
|
+
expect(updatedState).toEqual({
|
|
608
|
+
type: 'RESET',
|
|
609
|
+
payload: {
|
|
610
|
+
index: 2,
|
|
611
|
+
routes: [
|
|
612
|
+
{
|
|
613
|
+
name: 'Home',
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
name: Routes.ScriptDetail,
|
|
617
|
+
params: route.params,
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
name: Routes.AddUnknownTypeSmart,
|
|
621
|
+
params: {
|
|
622
|
+
automate: automate,
|
|
623
|
+
closeScreen: undefined,
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
],
|
|
627
|
+
},
|
|
602
628
|
});
|
|
603
|
-
|
|
629
|
+
expect(mockedDispatch).toHaveBeenCalledTimes(1);
|
|
630
|
+
|
|
604
631
|
expect(menuActionMore.props.listMenuItem[0].text).toEqual('Device display');
|
|
605
632
|
await act(async () => {
|
|
606
633
|
menuActionMore.props.listMenuItem[0].doAction();
|
|
@@ -705,6 +732,7 @@ describe('Test ScriptDetail', () => {
|
|
|
705
732
|
});
|
|
706
733
|
|
|
707
734
|
it('test navigate to UnitDetail on event beforeRemove', async () => {
|
|
735
|
+
Platform.OS = 'ios';
|
|
708
736
|
route.params.closeScreen = Routes.UnitDetail;
|
|
709
737
|
route.params.preAutomate.unit = 2;
|
|
710
738
|
|
|
@@ -729,4 +757,31 @@ describe('Test ScriptDetail', () => {
|
|
|
729
757
|
unitId: 2,
|
|
730
758
|
});
|
|
731
759
|
});
|
|
760
|
+
|
|
761
|
+
it('test navigate to UnitDetail on event hardwareBackPress', async () => {
|
|
762
|
+
Platform.OS = 'android';
|
|
763
|
+
jest.spyOn(BackHandler, 'addEventListener');
|
|
764
|
+
route.params.closeScreen = Routes.UnitDetail;
|
|
765
|
+
route.params.preAutomate.unit = 2;
|
|
766
|
+
|
|
767
|
+
await act(async () => {
|
|
768
|
+
await create(wrapComponent(route));
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
expect(BackHandler.addEventListener).toHaveBeenCalledWith(
|
|
772
|
+
'hardwareBackPress',
|
|
773
|
+
expect.any(Function)
|
|
774
|
+
);
|
|
775
|
+
const backPressHandler = BackHandler.addEventListener.mock.calls.find(
|
|
776
|
+
([eventName]) => eventName === 'hardwareBackPress'
|
|
777
|
+
)[1];
|
|
778
|
+
|
|
779
|
+
await act(async () => {
|
|
780
|
+
backPressHandler();
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
expect(mockedNavigate).toHaveBeenCalledWith(Routes.UnitDetail, {
|
|
784
|
+
unitId: 2,
|
|
785
|
+
});
|
|
786
|
+
});
|
|
732
787
|
});
|
|
@@ -10,7 +10,11 @@ import { PopoverMode } from 'react-native-popover-view';
|
|
|
10
10
|
import { IconFill, IconOutline } from '@ant-design/icons-react-native';
|
|
11
11
|
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
CommonActions,
|
|
15
|
+
useIsFocused,
|
|
16
|
+
useNavigation,
|
|
17
|
+
} from '@react-navigation/native';
|
|
14
18
|
import Add from '../../../../assets/images/Add.svg';
|
|
15
19
|
import Delay from '../../../../assets/images/Delay.svg';
|
|
16
20
|
import Notify from '../../../../assets/images/Notify.svg';
|
|
@@ -39,7 +43,7 @@ import ItemConditionScriptDetail from '../../../commons/Automate/ItemConditionSc
|
|
|
39
43
|
const PreventDoubleTouch = withPreventDoubleClick(TouchableOpacity);
|
|
40
44
|
|
|
41
45
|
const ScriptDetail = ({ route }) => {
|
|
42
|
-
const { navigate, goBack } = useNavigation();
|
|
46
|
+
const { dispatch, navigate, goBack } = useNavigation();
|
|
43
47
|
const { params = {} } = route;
|
|
44
48
|
const refMenuAction = useRef();
|
|
45
49
|
const { childRef, showingPopover, showPopoverWithRef, hidePopover } =
|
|
@@ -56,8 +60,9 @@ const ScriptDetail = ({ route }) => {
|
|
|
56
60
|
preAutomate = {}, // pre-loaded automate data
|
|
57
61
|
newActionsList, // updated actions list
|
|
58
62
|
closeScreen,
|
|
63
|
+
automate: currentAutomate, // when edit and close
|
|
59
64
|
} = params;
|
|
60
|
-
const [automate, setAutomate] = useState(preAutomate);
|
|
65
|
+
const [automate, setAutomate] = useState(currentAutomate || preAutomate);
|
|
61
66
|
const isFocused = useIsFocused();
|
|
62
67
|
const [data, setData] = useState([]);
|
|
63
68
|
const [isShowRename, setIsShowRename] = useState(false);
|
|
@@ -103,11 +108,42 @@ const ScriptDetail = ({ route }) => {
|
|
|
103
108
|
ToastBottomHelper.error(t('this_script_has_been_disabled'));
|
|
104
109
|
return;
|
|
105
110
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
|
|
112
|
+
// Reset stack and navigate to AddUnknownTypeSmart
|
|
113
|
+
dispatch((state) => {
|
|
114
|
+
const index = state.routes.findIndex((r) => r.name === closeScreen);
|
|
115
|
+
let routes = [];
|
|
116
|
+
if (!closeScreen) {
|
|
117
|
+
routes = state.routes;
|
|
118
|
+
} else if (index >= 0) {
|
|
119
|
+
routes = state.routes.slice(0, index + 1);
|
|
120
|
+
}
|
|
121
|
+
const hasScriptDetail = routes.some(
|
|
122
|
+
(r) => r.name === Routes.ScriptDetail
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (!hasScriptDetail) {
|
|
126
|
+
routes.push({
|
|
127
|
+
name: Routes.ScriptDetail,
|
|
128
|
+
params: route.params,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
routes.push({
|
|
133
|
+
name: Routes.AddUnknownTypeSmart,
|
|
134
|
+
params: {
|
|
135
|
+
automate,
|
|
136
|
+
closeScreen: route.name,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return CommonActions.reset({
|
|
141
|
+
...state,
|
|
142
|
+
routes: routes,
|
|
143
|
+
index: routes.length - 1,
|
|
144
|
+
});
|
|
109
145
|
});
|
|
110
|
-
}, [automate, can_edit, enableScript,
|
|
146
|
+
}, [automate, can_edit, closeScreen, enableScript, dispatch, route, t]);
|
|
111
147
|
|
|
112
148
|
const listMenuItem = useMemo(
|
|
113
149
|
() => [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { create, act } from 'react-test-renderer';
|
|
3
|
-
import { TouchableOpacity } from 'react-native';
|
|
3
|
+
import { FlatList, RefreshControl, TouchableOpacity } from 'react-native';
|
|
4
4
|
import Modal from 'react-native-modal';
|
|
5
5
|
import MockAdapter from 'axios-mock-adapter';
|
|
6
6
|
|
|
@@ -67,4 +67,25 @@ describe('test SharedUnit', () => {
|
|
|
67
67
|
});
|
|
68
68
|
expect(tabHeader.props.textFilter).toEqual(t('text_latest_date'));
|
|
69
69
|
});
|
|
70
|
+
|
|
71
|
+
it('should trigger refresh when pulling down', async () => {
|
|
72
|
+
mock
|
|
73
|
+
.onGet(API.UNIT.FILTER_SHARED_UNITS())
|
|
74
|
+
.reply(200, [{ id: 1, is_star: false, name: 'Unit 1' }]);
|
|
75
|
+
|
|
76
|
+
await act(async () => {
|
|
77
|
+
tree = await create(wrapComponent());
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const instance = tree.root;
|
|
81
|
+
const flatList = instance.findByType(FlatList);
|
|
82
|
+
|
|
83
|
+
const refreshControl = flatList.findByType(RefreshControl);
|
|
84
|
+
expect(refreshControl.props.refreshing).toBe(false);
|
|
85
|
+
|
|
86
|
+
await act(async () => {
|
|
87
|
+
refreshControl.props.onRefresh();
|
|
88
|
+
});
|
|
89
|
+
expect(mock.history.get.length).toBeGreaterThan(0);
|
|
90
|
+
});
|
|
70
91
|
});
|
|
@@ -46,7 +46,7 @@ const UnitMemberList = ({ route }) => {
|
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
if (permissions?.max_members_per_unit
|
|
49
|
+
if (permissions?.max_members_per_unit < dataMembers.length) {
|
|
50
50
|
ToastBottomHelper.error(
|
|
51
51
|
t('reach_max_members_per_unit', {
|
|
52
52
|
length: permissions?.max_members_per_unit,
|
|
@@ -81,6 +81,16 @@ describe('test MemberList', () => {
|
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
it('add new member but reach limit', async () => {
|
|
84
|
+
const dataMember = [
|
|
85
|
+
{
|
|
86
|
+
id: 1,
|
|
87
|
+
name: 'owner',
|
|
88
|
+
avatar: 'https://image1.jpg',
|
|
89
|
+
phone_number: '0933123456',
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
mockAxios.onGet(API.SHARE.UNITS_MEMBERS(1)).reply(200, dataMember);
|
|
93
|
+
|
|
84
94
|
let tree;
|
|
85
95
|
await act(async () => {
|
|
86
96
|
tree = await create(
|
|
@@ -59,7 +59,7 @@ describe('Test UnitDetail', () => {
|
|
|
59
59
|
|
|
60
60
|
const detailUnitApiUrl = API.UNIT.UNIT_DETAIL(1);
|
|
61
61
|
const summaryUnitApiUrl = API.UNIT.UNIT_SUMMARY(1);
|
|
62
|
-
const sensorStatusApiUrl = API.UNIT.
|
|
62
|
+
const sensorStatusApiUrl = API.UNIT.END_DEVICES_STATUS(1);
|
|
63
63
|
const getAutomates = API.UNIT.AUTOMATE(1);
|
|
64
64
|
|
|
65
65
|
let tree;
|