@eohjsc/react-native-smart-city 0.7.42 → 0.7.43

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 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.42",
4
+ "version": "0.7.43",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -1,6 +1,6 @@
1
1
  import moment from 'moment';
2
2
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
- import { StyleSheet, View } from 'react-native';
3
+ import { ActivityIndicator, StyleSheet, View } from 'react-native';
4
4
  import { useTranslations } from '../../hooks/Common/useTranslations';
5
5
 
6
6
  import { API, Colors } from '../../configs';
@@ -19,6 +19,7 @@ const PowerConsumptionChart = ({
19
19
  powerConfigId,
20
20
  defaultPeriod = 'date',
21
21
  color = Colors.Primary,
22
+ isWidgetOrder = false,
22
23
  style,
23
24
  }) => {
24
25
  const t = useTranslations();
@@ -30,9 +31,11 @@ const PowerConsumptionChart = ({
30
31
  price: '',
31
32
  });
32
33
  const [value, setValue] = useState([moment().subtract(6, 'days'), moment()]);
34
+ const [isFetching, setIsFetching] = useState(false);
33
35
 
34
36
  const fetchData = useCallback(
35
37
  async (startDate, endDate) => {
38
+ setIsFetching(true);
36
39
  let params = new URLSearchParams();
37
40
  params.append('config', powerConfigId);
38
41
  params.append('group_by', groupBy);
@@ -50,6 +53,7 @@ const PowerConsumptionChart = ({
50
53
  if (success) {
51
54
  setDatas(data);
52
55
  }
56
+ setIsFetching(false);
53
57
  },
54
58
  [groupBy, powerConfigId]
55
59
  );
@@ -83,13 +87,20 @@ const PowerConsumptionChart = ({
83
87
  }, [datas, chartConfig]);
84
88
 
85
89
  const renderChart = useMemo(() => {
90
+ if (isFetching) {
91
+ return (
92
+ <View style={styles.loadingWrapper}>
93
+ <ActivityIndicator size="large" color={Colors.Primary} />
94
+ </View>
95
+ );
96
+ }
86
97
  if (!datas || datas.length === 0) {
87
98
  return null;
88
99
  }
89
100
  return (
90
101
  <HorizontalBarChart datas={datas} config={chartConfig} color={color} />
91
102
  );
92
- }, [chartConfig, datas, color]);
103
+ }, [chartConfig, datas, color, isFetching]);
93
104
 
94
105
  const selectStart = (date) => {
95
106
  setValue((state) => [date, state[1]]);
@@ -99,16 +110,23 @@ const PowerConsumptionChart = ({
99
110
  };
100
111
 
101
112
  return (
102
- <View style={style}>
113
+ <View style={[styles.wrapper, style]}>
103
114
  <View style={styles.historyView}>
104
115
  <View style={styles.titleHistory}>
105
- <Text style={styles.label} bold color={Colors.Gray9}>
116
+ <Text
117
+ style={styles.label}
118
+ bold
119
+ color={Colors.Gray9}
120
+ numberOfLines={1}
121
+ >
106
122
  {label || t('power_consumption_chart')}
107
123
  </Text>
108
124
 
109
- <ChartAggregationOption groupBy={groupBy} setGroupBy={setGroupBy} />
125
+ {!isWidgetOrder && (
126
+ <ChartAggregationOption groupBy={groupBy} setGroupBy={setGroupBy} />
127
+ )}
110
128
  </View>
111
- {groupBy === 'date' && (
129
+ {!isWidgetOrder && groupBy === 'date' && (
112
130
  <DateTimeRangeChange
113
131
  startTime={value[0]}
114
132
  endTime={value[1]}
@@ -119,34 +137,36 @@ const PowerConsumptionChart = ({
119
137
  )}
120
138
  </View>
121
139
 
122
- <View style={styles.calculateCostCard}>
123
- <Text style={styles.title}>
124
- {t('input_price_to_calculate_electricity_cost')}
125
- </Text>
126
- <View style={styles.inputRow}>
127
- <View style={styles.inputWrapper}>
128
- <CurrencyInput
129
- value={price}
130
- onChange={setPrice}
131
- minValue={0}
132
- maxValue={100000000}
133
- placeholder={'0'}
134
- onSubmitEditing={onCalculateCost}
135
- currency="đ/kWh"
136
- wrapperStyle={styles.currencyInputNoBorder}
140
+ {!isWidgetOrder && (
141
+ <View style={styles.calculateCostCard}>
142
+ <Text style={styles.title}>
143
+ {t('input_price_to_calculate_electricity_cost')}
144
+ </Text>
145
+ <View style={styles.inputRow}>
146
+ <View style={styles.inputWrapper}>
147
+ <CurrencyInput
148
+ value={price}
149
+ onChange={setPrice}
150
+ minValue={0}
151
+ maxValue={100000000}
152
+ placeholder={'0'}
153
+ onSubmitEditing={onCalculateCost}
154
+ currency="đ/kWh"
155
+ wrapperStyle={styles.currencyInputNoBorder}
156
+ />
157
+ </View>
158
+ <Button
159
+ type="primary"
160
+ title={t('calculate_cost')}
161
+ height={42}
162
+ onPress={onCalculateCost}
163
+ style={styles.buttonCalculate}
164
+ textSemiBold={true}
165
+ accessibilityLabel={AccessibilityLabel.BUTTON_CALCULATE_COST}
137
166
  />
138
167
  </View>
139
- <Button
140
- type="primary"
141
- title={t('calculate_cost')}
142
- height={42}
143
- onPress={onCalculateCost}
144
- style={styles.buttonCalculate}
145
- textSemiBold={true}
146
- accessibilityLabel={AccessibilityLabel.BUTTON_CALCULATE_COST}
147
- />
148
168
  </View>
149
- </View>
169
+ )}
150
170
 
151
171
  <View style={styles.chartCard}>
152
172
  {renderChart}
@@ -169,6 +189,10 @@ const PowerConsumptionChart = ({
169
189
  export default React.memo(PowerConsumptionChart);
170
190
 
171
191
  const styles = StyleSheet.create({
192
+ wrapper: {
193
+ flex: 1,
194
+ marginBottom: 8,
195
+ },
172
196
  historyView: {
173
197
  paddingTop: 0,
174
198
  },
@@ -181,6 +205,7 @@ const styles = StyleSheet.create({
181
205
  justifyContent: 'flex-end',
182
206
  marginTop: 12,
183
207
  marginLeft: 6,
208
+ flex: 1,
184
209
  },
185
210
  calculateCostCard: {
186
211
  backgroundColor: Colors.LightPrimary,
@@ -247,5 +272,12 @@ const styles = StyleSheet.create({
247
272
  label: {
248
273
  fontSize: 18,
249
274
  fontWeight: '700',
275
+ flex: 1,
276
+ marginRight: 8,
277
+ },
278
+ loadingWrapper: {
279
+ height: 150,
280
+ justifyContent: 'center',
281
+ alignItems: 'center',
250
282
  },
251
283
  });
@@ -138,6 +138,7 @@ export default {
138
138
  BOTTOM_BUTTON_RIGHT: 'BOTTOM_BUTTON_RIGHT',
139
139
  VIEW_BUTTON_BOTTOM_RIGHT_BUTTON: 'VIEW_BUTTON_BOTTOM_RIGHT_BUTTON',
140
140
  VIEW_BUTTON_BOTTOM_LEFT_BUTTON: 'VIEW_BUTTON_BOTTOM_LEFT_BUTTON',
141
+ TOUCHABLE_OPACITY: 'TOUCHABLE_OPACITY',
141
142
 
142
143
  // Smart parking drawer
143
144
  ROW_EXIT_SMARTPARKING_DRAWER: 'ROW_EXIT_SMARTPARKING_DRAWER',
package/src/iot/mqtt.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { Buffer } from 'buffer';
1
2
  import { getObject, storeObject } from '../utils/Storage';
2
3
  import { getLastUpdated, updateGlobalValue } from './UpdateStates';
3
- import { Buffer } from 'buffer';
4
4
 
5
5
  const struct = require('python-struct');
6
6
 
@@ -322,7 +322,9 @@ const get_offset_sample_value = async (value, config) => {
322
322
 
323
323
  const getValueConverter = (value, config) => {
324
324
  const converter = mappingValueConverter[config?.value_converter];
325
- return converter ? converter(value) : value;
325
+ return converter
326
+ ? Number(get_decimal_value(converter(value), config))
327
+ : value;
326
328
  };
327
329
 
328
330
  const check_filter = (value, config) => {
@@ -11,7 +11,7 @@ import { SCContext } from '../context';
11
11
  import { Action } from '../context/actionType';
12
12
  import { useTranslations } from '../hooks/Common/useTranslations';
13
13
  import { unwatchAllConfigs } from '../iot/Monitor';
14
- import ActivityLogScreen from '../screens/ActivityLog';
14
+ import ActivityLog from '../screens/ActivityLog';
15
15
  import Route from '../utils/Route';
16
16
  import { screenOptions } from './utils';
17
17
 
@@ -350,7 +350,7 @@ export const UnitStack = memo((props) => {
350
350
  />
351
351
  <Stack.Screen
352
352
  name={Route.ActivityLog}
353
- component={ActivityLogScreen}
353
+ component={ActivityLog}
354
354
  options={{
355
355
  headerShown: false,
356
356
  }}
@@ -1,10 +1,14 @@
1
- import React from 'react';
2
- import { View } from 'react-native';
1
+ import React, { useState, useCallback } from 'react';
2
+ import { View, TouchableOpacity, Modal } from 'react-native';
3
3
  import moment from 'moment';
4
4
  import Text from '../../commons/Text';
5
5
  import t from '../../hooks/Common/useTranslations';
6
6
  import styles from './styles/itemLogStyles';
7
- import { AUTOMATE_TYPE, ACTIVITY_LOG_TYPES } from '../../configs/Constants';
7
+ import {
8
+ AUTOMATE_TYPE,
9
+ ACTIVITY_LOG_TYPES,
10
+ AccessibilityLabel,
11
+ } from '../../configs/Constants';
8
12
 
9
13
  const DetailEmergencyEventLog = ({ item }) => (
10
14
  <Text style={styles.text}>
@@ -20,34 +24,83 @@ const DetailEmergencyEventLog = ({ item }) => (
20
24
  );
21
25
 
22
26
  const DetailLog = ({ item }) => {
23
- switch (item.content_code) {
27
+ const [showModal, setShowModal] = useState(false);
28
+ const { name, username, created_at, message, content_code, action_name } =
29
+ item;
30
+
31
+ const handlePress = useCallback(() => {
32
+ if (message) {
33
+ setShowModal(true);
34
+ }
35
+ }, [message]);
36
+
37
+ const handleClose = useCallback(() => {
38
+ setShowModal(false);
39
+ }, []);
40
+
41
+ switch (content_code) {
24
42
  case ACTIVITY_LOG_TYPES.AUTO_ACTIVATED:
25
43
  return <Text style={styles.text}>{t('auto_activated')}</Text>;
26
44
  case ACTIVITY_LOG_TYPES.ACTIVATED_BY:
27
45
  return (
28
46
  <Text style={styles.text}>
29
- {item.action_name
30
- ? `${item.action_name} ${t('by')} `
31
- : `${t('activated_by')} `}
32
- <Text style={styles.name}>{item.name || item.username}</Text>
47
+ {action_name ? `${action_name} ${t('by')} ` : `${t('activated_by')} `}
48
+ <Text style={styles.name}>{name || username}</Text>
33
49
  </Text>
34
50
  );
35
51
  case ACTIVITY_LOG_TYPES.SCRIPT_UPDATED_BY:
36
52
  return (
37
53
  <Text style={styles.text}>
38
54
  {`${t('script_updated_by')} `}
39
- <Text style={styles.name}>{item.name || item.username}</Text>
55
+ <Text style={styles.name}>{name || username}</Text>
40
56
  </Text>
41
57
  );
42
58
  default:
43
59
  return (
44
- <Text style={styles.text}>
45
- {item.action_name
46
- ? `${item.action_name} ${t('by')} `
47
- : `${t('activated_by')} `}
48
- <Text style={styles.name}>{item.name || item.username}</Text>
49
- <Text style={styles.text}>{'\n' + item.message}</Text>
50
- </Text>
60
+ <>
61
+ <TouchableOpacity onPress={handlePress} activeOpacity={0.7}>
62
+ <Text style={styles.text}>
63
+ {action_name
64
+ ? `${action_name} ${t('by')} `
65
+ : `${t('activated_by')} `}
66
+ <Text style={styles.name}>{name || username}</Text>
67
+ </Text>
68
+ </TouchableOpacity>
69
+ <Modal
70
+ visible={showModal}
71
+ transparent
72
+ animationType="fade"
73
+ onRequestClose={handleClose}
74
+ accessibilityLabel={AccessibilityLabel.MODAL_BUTTON_POPUP}
75
+ >
76
+ <TouchableOpacity
77
+ style={styles.modalOverlay}
78
+ activeOpacity={1}
79
+ onPress={handleClose}
80
+ accessibilityLabel={AccessibilityLabel.TOUCHABLE_OPACITY}
81
+ >
82
+ <View style={styles.modalContent}>
83
+ <Text style={styles.modalTitle}>{t('detail')}</Text>
84
+ <Text style={styles.modalMessage}>
85
+ {action_name
86
+ ? `${action_name} ${t('by')} `
87
+ : `${t('activated_by')} `}
88
+ <Text style={styles.name}>{name || username}</Text>
89
+ </Text>
90
+ <Text style={styles.modalMessage}>{message}</Text>
91
+ <Text style={styles.modalMessage}>
92
+ {moment(created_at).format('DD/MM/YYYY HH:mm:ss')}
93
+ </Text>
94
+ <TouchableOpacity
95
+ style={styles.modalCloseButton}
96
+ onPress={handleClose}
97
+ >
98
+ <Text style={styles.modalCloseText}>{t('close')}</Text>
99
+ </TouchableOpacity>
100
+ </View>
101
+ </TouchableOpacity>
102
+ </Modal>
103
+ </>
51
104
  );
52
105
  }
53
106
  };
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
- import { Text } from 'react-native';
2
+ import { Text, TouchableOpacity, Modal } from 'react-native';
3
3
  import { act, create } from 'react-test-renderer';
4
4
  import ItemLog from '../ItemLog';
5
5
  import { SCProvider } from '../../../context';
6
6
  import { mockSCStore } from '../../../context/mockStore';
7
- import { AUTOMATE_TYPE } from '../../../configs/Constants';
7
+ import { AUTOMATE_TYPE, AccessibilityLabel } from '../../../configs/Constants';
8
8
 
9
9
  const wrapComponent = (props) => (
10
10
  <SCProvider initState={mockSCStore({})}>
@@ -137,3 +137,170 @@ describe('test ItemLog emergency event', () => {
137
137
  await _test(props.item.user.email);
138
138
  });
139
139
  });
140
+
141
+ describe('test ItemLog Modal', () => {
142
+ const wrapComponentWithMessage = (message) => (
143
+ <SCProvider initState={mockSCStore({})}>
144
+ <ItemLog
145
+ item={{
146
+ username: 'testuser',
147
+ message: message,
148
+ created_at: '2021-07-02T15:48:24.917932Z',
149
+ }}
150
+ type="action"
151
+ length={2}
152
+ index={0}
153
+ />
154
+ </SCProvider>
155
+ );
156
+
157
+ const getItemLogModal = (tree) => {
158
+ const modals = tree.root.findAllByType(Modal);
159
+ return modals.find(
160
+ (m) =>
161
+ m.props.accessibilityLabel === AccessibilityLabel.MODAL_BUTTON_POPUP
162
+ );
163
+ };
164
+
165
+ const getOverlayTouchable = (tree) => {
166
+ const touchables = tree.root.findAllByType(TouchableOpacity);
167
+ return touchables.find(
168
+ (t) => t.props.accessibilityLabel === AccessibilityLabel.TOUCHABLE_OPACITY
169
+ );
170
+ };
171
+
172
+ it('should open modal when clicking item with message', async () => {
173
+ let tree;
174
+ await act(async () => {
175
+ tree = await create(wrapComponentWithMessage('Test message content'));
176
+ });
177
+
178
+ const modal = getItemLogModal(tree);
179
+ expect(modal.props.visible).toBe(false);
180
+
181
+ const touchables = tree.root.findAllByType(TouchableOpacity);
182
+ const itemTouchable = touchables[0];
183
+
184
+ await act(async () => {
185
+ itemTouchable.props.onPress();
186
+ });
187
+
188
+ expect(modal.props.visible).toBe(true);
189
+ });
190
+
191
+ it('should display correct message in modal', async () => {
192
+ let tree;
193
+ const testMessage = 'Test message content';
194
+ await act(async () => {
195
+ tree = await create(wrapComponentWithMessage(testMessage));
196
+ });
197
+
198
+ const touchables = tree.root.findAllByType(TouchableOpacity);
199
+ await act(async () => {
200
+ touchables[0].props.onPress();
201
+ });
202
+
203
+ const texts = tree.root.findAllByType(Text);
204
+ const messageText = texts.find((t) => t.props.children === testMessage);
205
+ expect(messageText).toBeTruthy();
206
+ });
207
+
208
+ it('should close modal when pressing close button', async () => {
209
+ let tree;
210
+ await act(async () => {
211
+ tree = await create(wrapComponentWithMessage('Test message'));
212
+ });
213
+
214
+ const modal = getItemLogModal(tree);
215
+ const touchables = tree.root.findAllByType(TouchableOpacity);
216
+
217
+ await act(async () => {
218
+ touchables[0].props.onPress();
219
+ });
220
+ expect(modal.props.visible).toBe(true);
221
+
222
+ // Re-find touchables after modal is open and find close button by style
223
+ const updatedTouchables = tree.root.findAllByType(TouchableOpacity);
224
+ const closeButton = updatedTouchables.find(
225
+ (t) => t.props.style?.alignItems === 'center'
226
+ );
227
+ await act(async () => {
228
+ closeButton.props.onPress();
229
+ });
230
+ expect(modal.props.visible).toBe(false);
231
+ });
232
+
233
+ it('should close modal when pressing overlay', async () => {
234
+ let tree;
235
+ await act(async () => {
236
+ tree = await create(wrapComponentWithMessage('Test message'));
237
+ });
238
+
239
+ const modal = getItemLogModal(tree);
240
+ const touchables = tree.root.findAllByType(TouchableOpacity);
241
+
242
+ await act(async () => {
243
+ touchables[0].props.onPress();
244
+ });
245
+ expect(modal.props.visible).toBe(true);
246
+
247
+ // Find overlay by accessibilityLabel
248
+ const overlayTouchable = getOverlayTouchable(tree);
249
+ await act(async () => {
250
+ overlayTouchable.props.onPress();
251
+ });
252
+ expect(modal.props.visible).toBe(false);
253
+ });
254
+
255
+ it('should close modal on requestClose (Android back button)', async () => {
256
+ let tree;
257
+ await act(async () => {
258
+ tree = await create(wrapComponentWithMessage('Test message'));
259
+ });
260
+
261
+ const modal = getItemLogModal(tree);
262
+ const touchables = tree.root.findAllByType(TouchableOpacity);
263
+
264
+ await act(async () => {
265
+ touchables[0].props.onPress();
266
+ });
267
+ expect(modal.props.visible).toBe(true);
268
+
269
+ await act(async () => {
270
+ modal.props.onRequestClose();
271
+ });
272
+ expect(modal.props.visible).toBe(false);
273
+ });
274
+
275
+ it('should not open modal when item has no message', async () => {
276
+ let tree;
277
+ await act(async () => {
278
+ tree = await create(wrapComponentWithMessage(null));
279
+ });
280
+
281
+ const modal = getItemLogModal(tree);
282
+ const touchables = tree.root.findAllByType(TouchableOpacity);
283
+
284
+ await act(async () => {
285
+ touchables[0].props.onPress();
286
+ });
287
+
288
+ expect(modal.props.visible).toBe(false);
289
+ });
290
+
291
+ it('should not open modal when message is empty string', async () => {
292
+ let tree;
293
+ await act(async () => {
294
+ tree = await create(wrapComponentWithMessage(''));
295
+ });
296
+
297
+ const modal = getItemLogModal(tree);
298
+ const touchables = tree.root.findAllByType(TouchableOpacity);
299
+
300
+ await act(async () => {
301
+ touchables[0].props.onPress();
302
+ });
303
+
304
+ expect(modal.props.visible).toBe(false);
305
+ });
306
+ });
@@ -20,7 +20,7 @@ import { AccessibilityLabel } from '../../configs/Constants';
20
20
 
21
21
  const keyExtractor = (item) => item.id;
22
22
 
23
- const ActivityLogScreen = ({ route }) => {
23
+ const ActivityLog = ({ route }) => {
24
24
  const t = useTranslations();
25
25
  const { id, type, share, filterEnabled } = route?.params || {};
26
26
  const {
@@ -131,4 +131,4 @@ const ActivityLogScreen = ({ route }) => {
131
131
  );
132
132
  };
133
133
 
134
- export default ActivityLogScreen;
134
+ export default ActivityLog;
@@ -1,5 +1,6 @@
1
1
  import { Colors } from '../../../configs';
2
2
  import { StyleSheet } from 'react-native';
3
+ import { colorOpacity } from '../../../utils/Converter/color';
3
4
 
4
5
  export default StyleSheet.create({
5
6
  wrapItem: {
@@ -41,4 +42,38 @@ export default StyleSheet.create({
41
42
  lineHeight: 22,
42
43
  flexShrink: 1,
43
44
  },
45
+ modalOverlay: {
46
+ flex: 1,
47
+ backgroundColor: colorOpacity(Colors.Black, 0.5),
48
+ justifyContent: 'center',
49
+ alignItems: 'center',
50
+ },
51
+ modalContent: {
52
+ backgroundColor: Colors.White,
53
+ borderRadius: 12,
54
+ padding: 20,
55
+ marginHorizontal: 20,
56
+ maxWidth: '90%',
57
+ minWidth: '80%',
58
+ },
59
+ modalTitle: {
60
+ fontSize: 18,
61
+ fontWeight: 'bold',
62
+ color: Colors.Gray9,
63
+ marginBottom: 12,
64
+ },
65
+ modalMessage: {
66
+ marginBottom: 15,
67
+ },
68
+ modalCloseButton: {
69
+ backgroundColor: Colors.Primary,
70
+ borderRadius: 8,
71
+ paddingVertical: 12,
72
+ alignItems: 'center',
73
+ },
74
+ modalCloseText: {
75
+ color: Colors.White,
76
+ fontSize: 16,
77
+ fontWeight: '600',
78
+ },
44
79
  });
@@ -333,6 +333,7 @@ export const SensorDisplayItem = ({
333
333
  defaultPeriod={configuration?.aggregation_period}
334
334
  color={configuration?.configs[0]?.color}
335
335
  style={styles.powerConsumption}
336
+ isWidgetOrder={isWidgetOrder}
336
337
  />
337
338
  )}
338
339
  </View>
@@ -112,6 +112,6 @@ export default StyleSheet.create({
112
112
  },
113
113
  powerConsumption: {
114
114
  marginHorizontal: 16,
115
- marginBottom: 8,
115
+ marginBottom: 20,
116
116
  },
117
117
  });
@@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
2
2
  import renderer, { act, create } from 'react-test-renderer';
3
3
 
4
4
  import moment from 'moment';
5
+ import { ActivityIndicator } from 'react-native';
5
6
  import { Today } from '../../../../../commons';
6
7
  import DateTimeRangeChange from '../../../../../commons/DateTimeRangeChange';
7
8
  import PowerConsumptionChart from '../../../../../commons/Device/PowerConsumptionChart';
@@ -308,4 +309,35 @@ describe('Test PowerConsumption', () => {
308
309
  powerConsumptionChart.findByType(DateTimeRangeChange).props.endTime
309
310
  ).toEqual(newEndDate);
310
311
  });
312
+
313
+ it('should show ActivityIndicator when isFetching is true', async () => {
314
+ const summaryDetail = {
315
+ listConfigs: {
316
+ total_power: 207,
317
+ },
318
+ };
319
+
320
+ let resolvePromise;
321
+ const pendingPromise = new Promise((resolve) => {
322
+ resolvePromise = resolve;
323
+ });
324
+
325
+ mock.onGet(API.VALUE_CONSUME.DISPLAY_HISTORY()).reply(async () => {
326
+ await pendingPromise;
327
+ return [200, []];
328
+ });
329
+
330
+ await act(async () => {
331
+ tree = await create(wrapComponent(summaryDetail));
332
+ });
333
+
334
+ const instance = tree.root;
335
+ const activityIndicator = instance.findByType(ActivityIndicator);
336
+ expect(activityIndicator).toBeDefined();
337
+ expect(activityIndicator.props.size).toEqual('large');
338
+
339
+ await act(async () => {
340
+ resolvePromise();
341
+ });
342
+ });
311
343
  });
@@ -1348,6 +1348,7 @@ export default {
1348
1348
  not_activated: 'Not activated',
1349
1349
  open: 'Open',
1350
1350
  close: 'Close',
1351
+ detail: 'Detail',
1351
1352
  create_contact_success: 'Create contact success!',
1352
1353
  can_not_login_to_current_ssid: "Can't login to current SSID",
1353
1354
  confirm_password_not_match: 'Confirm password does not match',
@@ -1346,6 +1346,7 @@ export default {
1346
1346
  not_activated: 'Không kích hoạt',
1347
1347
  open: 'Mở',
1348
1348
  close: 'Đóng',
1349
+ detail: 'Chi tiết',
1349
1350
  create_contact_success: 'Tạo liên hệ thành công!',
1350
1351
  can_not_login_to_current_ssid: 'Không thể đăng nhập vào SSID hiện tại',
1351
1352
  confirm_password_not_match: 'Mật khẩu xác nhận không trùng khớp',