@eohjsc/react-native-smart-city 0.3.79 → 0.3.81

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.3.79",
4
+ "version": "0.3.81",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -80,4 +80,16 @@ describe('Test SetupGeneratePasscode', () => {
80
80
  const wheelDateTimePicker = instance.findAllByType(WheelDateTimePicker);
81
81
  expect(wheelDateTimePicker).toHaveLength(3);
82
82
  });
83
+
84
+ it('Test onTimePicked', async () => {
85
+ await act(async () => {
86
+ tree = await create(wrapComponent());
87
+ });
88
+ const instance = tree.root;
89
+ const wheelDateTimePickers = instance.findAllByType(WheelDateTimePicker);
90
+ await act(async () => {
91
+ wheelDateTimePickers[0].props.onPicked(new Date(2022, 10, 10));
92
+ });
93
+ expect(wheelDateTimePickers[0].props.defaultValue).toBe(1668038400000);
94
+ });
83
95
  });
@@ -0,0 +1,63 @@
1
+ import React, { memo, useCallback, useState } from 'react';
2
+ import { Switch, View } from 'react-native';
3
+
4
+ import Text from '../../Text';
5
+ import { Colors } from '../../../configs';
6
+ import styles from './OnOffButtonTemplateStyle';
7
+ import AccessibilityLabel from '../../../configs/AccessibilityLabel';
8
+
9
+ const SwitchButtonTemplate = memo(
10
+ ({ isOn, triggerAction, actionGroup = {}, isLight = false, doAction }) => {
11
+ const [_isOn, setIsOn] = useState(isOn);
12
+
13
+ const { configuration = {}, id } = actionGroup;
14
+ const {
15
+ action_off_data,
16
+ action_on_data,
17
+ text_on,
18
+ text_off,
19
+ color_on,
20
+ color_off,
21
+ } = configuration;
22
+
23
+ const onChangeSwitch = useCallback(() => {
24
+ if (_isOn) {
25
+ setIsOn(false);
26
+ doAction(action_off_data, null);
27
+ return;
28
+ }
29
+ doAction(action_on_data, null);
30
+ setIsOn(true);
31
+ }, [action_off_data, action_on_data, doAction, _isOn]);
32
+
33
+ return (
34
+ <>
35
+ <View style={styles.barrierControlContainer}>
36
+ <Switch
37
+ trackColor={{ false: color_off, true: color_on }}
38
+ ios_backgroundColor={_isOn ? color_on : color_off}
39
+ onValueChange={onChangeSwitch}
40
+ value={_isOn}
41
+ />
42
+ <Text
43
+ style={[
44
+ styles.textBig,
45
+ { color: _isOn ? Colors.Gray8 : Colors.Gray6 },
46
+ ]}
47
+ accessibilityLabel={`${AccessibilityLabel.SENSOR_STATUS}-${id}`}
48
+ >
49
+ {_isOn ? text_on : text_off}
50
+ </Text>
51
+ </View>
52
+
53
+ {!!actionGroup.title && !isLight && (
54
+ <Text type="H3" semibold center>
55
+ {actionGroup.title}
56
+ </Text>
57
+ )}
58
+ </>
59
+ );
60
+ }
61
+ );
62
+
63
+ export default SwitchButtonTemplate;
@@ -17,10 +17,10 @@ import SvgCurrentState from '../../../assets/images/Device/current-state.svg';
17
17
  import SvgWindStrength from '../../../assets/images/Device/wind-strength.svg';
18
18
 
19
19
  function getOptionValue(option, allow_config_store_value) {
20
- if (option.value_text && !allow_config_store_value) {
21
- return option.value_text;
20
+ if (option?.value_text && !allow_config_store_value) {
21
+ return option?.value_text;
22
22
  }
23
- return option.value_int;
23
+ return option?.value_int;
24
24
  }
25
25
 
26
26
  const OptionsDropdownActionTemplate = ({
@@ -29,7 +29,7 @@ const OptionsDropdownActionTemplate = ({
29
29
  sensor = {},
30
30
  }) => {
31
31
  const t = useTranslations();
32
- const { configuration = {}, title } = actionGroup;
32
+ const { configuration = {}, title = '' } = actionGroup;
33
33
  const {
34
34
  action_data,
35
35
  options = [],
@@ -155,7 +155,7 @@ const OptionsDropdownActionTemplate = ({
155
155
  <AlertAction
156
156
  visible={stateAlert.visible}
157
157
  hideModal={hideAlertAction}
158
- title={stateAlert.title}
158
+ title={title}
159
159
  message={stateAlert.message}
160
160
  leftButtonTitle={stateAlert.leftButton}
161
161
  leftButtonClick={hideAlertAction}
@@ -30,19 +30,14 @@ const SliderRangeTemplate = memo(({ actionGroup = {}, doAction, sensor }) => {
30
30
  const [valueBrightness, setValueBrightness] = useState(getPercent());
31
31
  const [isFirstTime, setIsFirstTime] = useState(true);
32
32
 
33
- // TODO Thinh: remove action_brightness_data
34
33
  const onChangeBrightness = useCallback(
35
34
  async (value) => {
36
35
  await doAction(
37
- configuration?.action_brightness_data || configuration?.action_data,
36
+ configuration?.action_data,
38
37
  JSON.stringify({ value_brness: value, value: value })
39
38
  );
40
39
  },
41
- [
42
- configuration?.action_brightness_data,
43
- configuration?.action_data,
44
- doAction,
45
- ]
40
+ [configuration?.action_data, doAction]
46
41
  );
47
42
 
48
43
  useEffect(() => {
@@ -250,7 +250,7 @@ describe('Test NumberUpDownActionTemplate', () => {
250
250
  });
251
251
 
252
252
  const text = instance.findByType(Text);
253
- expect(text.props.children).toEqual('24 *C'); // TODO should 24, configValues make changes again
253
+ expect(text.props.children).toEqual('24 *C');
254
254
  });
255
255
 
256
256
  it('action up with allow_config_store_value false', async () => {
@@ -276,7 +276,7 @@ describe('Test NumberUpDownActionTemplate', () => {
276
276
  });
277
277
 
278
278
  const text = instance.findByType(Text);
279
- expect(text.props.children).toEqual('26 *C'); // TODO should 26, configValues make changes again
279
+ expect(text.props.children).toEqual('26 *C');
280
280
  });
281
281
 
282
282
  it('action down limit ', async () => {
@@ -66,26 +66,6 @@ describe('Test SliderRangeTemplate', () => {
66
66
  );
67
67
  });
68
68
 
69
- it('render template SliderRangeTemplate with data backward compatible', async () => {
70
- actionGroup.configuration.action_data = undefined;
71
- actionGroup.configuration.action_brightness_data = action_data;
72
- useConfigGlobalState.mockImplementation(() => [{}, jest.fn()]);
73
- const sensor = { is_managed_by_backend: true, name: 'Sensor' };
74
- await act(async () => {
75
- wrapper = await create(wrapComponent(actionGroup, mockDoAction, sensor));
76
- });
77
- const instance = wrapper.root;
78
- const silderRange = instance.findAllByType(SliderRange);
79
- expect(silderRange).toHaveLength(1);
80
- await act(async () => {
81
- await silderRange[0].props.onSlidingComplete(50);
82
- });
83
- expect(mockDoAction).toHaveBeenCalledWith(
84
- action_data,
85
- JSON.stringify({ value_brness: 50, value: 50 })
86
- );
87
- });
88
-
89
69
  it('render template SliderRangeTemplate with config value not null', async () => {
90
70
  useConfigGlobalState.mockImplementation(() => [
91
71
  {
@@ -0,0 +1,93 @@
1
+ import React from 'react';
2
+ import { act, create } from 'react-test-renderer';
3
+
4
+ import { mockSCStore } from '../../../context/mockStore';
5
+ import { SCProvider } from '../../../context';
6
+ import SwitchButtonTemplate from '../OnOffTemplate/SwitchButtonTemplate';
7
+
8
+ const wrapComponent = (actionGroup, mockDoAction) => (
9
+ <SCProvider initState={mockSCStore({})}>
10
+ <SwitchButtonTemplate actionGroup={actionGroup} doAction={mockDoAction} />
11
+ </SCProvider>
12
+ );
13
+
14
+ jest.mock('@react-navigation/native', () => {
15
+ return {
16
+ ...jest.requireActual('@react-navigation/native'),
17
+ useFocusEffect: jest.fn(),
18
+ };
19
+ });
20
+
21
+ describe('Test SwitchButtonTemplate', () => {
22
+ const action_on_data = {
23
+ color: '#00979D',
24
+ command_prefer_over_bluetooth: true,
25
+ command_prefer_over_googlehome: false,
26
+ command_prefer_over_internet: false,
27
+ googlehome_actions: [],
28
+ icon: 'caret-up',
29
+ id: 20,
30
+ key: '5ed1d4dc-a905-47cd-b0c9-f979644bd21a',
31
+ };
32
+
33
+ const action_off_data = {
34
+ color: '#00979D',
35
+ command_prefer_over_bluetooth: true,
36
+ command_prefer_over_googlehome: false,
37
+ command_prefer_over_internet: false,
38
+ googlehome_actions: [],
39
+ icon: 'caret-up',
40
+ id: 20,
41
+ key: '5ed1d4dc-a905-47cd-b0c9-f979644bd21a',
42
+ };
43
+ const actionGroup = {
44
+ configuration: {
45
+ action_on: '5ed1d4dc-a905-47cd-b0c9-f979644bd21a',
46
+ action_on_data: action_on_data,
47
+ icon_on: 'caret-up',
48
+ text_on: 'ON',
49
+ action_off: '5ed1d4dc-a905-47cd-b0c9-f979644bd21b',
50
+ action_off_data: action_off_data,
51
+ icon_off: 'caret-up',
52
+ text_off: 'OFF',
53
+ },
54
+ };
55
+ let wrapper;
56
+
57
+ const assertRender = async () => {
58
+ const mockDoAction = jest.fn();
59
+
60
+ await act(async () => {
61
+ wrapper = create(wrapComponent(actionGroup, mockDoAction));
62
+ });
63
+
64
+ const renderJson = wrapper.toJSON();
65
+ expect(renderJson[1].props?.visible).toEqual(false);
66
+ expect(renderJson[1]?.type).toEqual('Modal');
67
+ };
68
+
69
+ it('render state on', async () => {
70
+ await assertRender(true, 'ON');
71
+ });
72
+
73
+ it('render state off', async () => {
74
+ await assertRender(false, 'OFF');
75
+ });
76
+
77
+ const assertActionCall = async (state, action_data) => {
78
+ const mockDoAction = jest.fn();
79
+ await act(async () => {
80
+ wrapper = create(wrapComponent(actionGroup, mockDoAction));
81
+ });
82
+
83
+ expect(mockDoAction).not.toHaveBeenCalled();
84
+ };
85
+
86
+ it('action state on', async () => {
87
+ await assertActionCall(true, action_off_data);
88
+ });
89
+
90
+ it('action state off', async () => {
91
+ await assertActionCall(false, action_on_data);
92
+ });
93
+ });
@@ -5,7 +5,6 @@ export const useDropdownAction = () => {
5
5
  const t = useTranslations();
6
6
  const [stateAlert, setStateAlert] = useState({
7
7
  visible: false,
8
- title: 'Fan Speed',
9
8
  message: '',
10
9
  leftButton: t('cancel'),
11
10
  rightButton: t('done'),
@@ -13,6 +13,7 @@ import ColorPickerTemplate from './ColorPickerTemplate';
13
13
  import SliderRangeTemplate from './SliderRangeTemplate';
14
14
  import OnOffSmartLock from './OnOffSmartLock/OnOffSmartLock';
15
15
  import TwoButtonTemplate from './TwoButtonTemplate';
16
+ import SwitchButtonTemplate from './OnOffTemplate/SwitchButtonTemplate';
16
17
 
17
18
  export const getActionComponent = (template) => {
18
19
  switch (template) {
@@ -46,6 +47,8 @@ export const getActionComponent = (template) => {
46
47
  return SliderRangeTemplate;
47
48
  case 'two_button_action_template':
48
49
  return TwoButtonTemplate;
50
+ case 'switch_button_action_template':
51
+ return SwitchButtonTemplate;
49
52
  default:
50
53
  return null;
51
54
  }
@@ -122,7 +122,7 @@ const SelectGateway = ({
122
122
  onRightClick={onRightClickNext}
123
123
  accessibilityLabelPrefix={AccessibilityLabel.PREFIX.SELECT_UNIT}
124
124
  />
125
- <ModalCustom // todo Huy - make reused component
125
+ <ModalCustom // todo Nhat - make reused component
126
126
  onBackButtonPress={goBack}
127
127
  isVisible={showPopupGuide}
128
128
  style={styles.modal}
@@ -260,6 +260,8 @@ export const NOTIFICATION_TYPES = {
260
260
  NOTIFY_REMOVE_SUB_UNIT: 'NOTIFY_REMOVE_SUB_UNIT',
261
261
  NOTIFY_REMOVE_DEVICE: 'NOTIFY_REMOVE_DEVICE',
262
262
  NOTIFY_EMERGENCY: 'NOTIFY_EMERGENCY',
263
+ NOTIFY_CHANGE_UNIT_OLD_OWNER: 'NOTIFY_CHANGE_UNIT_OLD_OWNER',
264
+ NOTIFY_CHANGE_UNIT_NEW_OWNER: 'NOTIFY_CHANGE_UNIT_NEW_OWNER',
263
265
  AIR_QUALITY: 'AIR_QUALITY',
264
266
  TURBIDITY: 'TURBIDITY',
265
267
  PH: 'PH',
@@ -283,3 +283,36 @@ export const STM32_ANALOG_WRITE = [
283
283
  { key: '73', value: 'PB14' },
284
284
  { key: '74', value: 'PB4' },
285
285
  ];
286
+
287
+ export const ESP8266_DIGITAL = [
288
+ { key: '0', value: 'GPIO 0 - D3' },
289
+ { key: '1', value: 'GPIO 1 - D10 - UART 0 TX' },
290
+ { key: '2', value: 'GPIO 2 - D4 - UART 0 TX2' },
291
+ { key: '3', value: 'GPIO 3 - D9 - UART 0 RX' },
292
+ { key: '4', value: 'GPIO 4 - D2' },
293
+ { key: '5', value: 'GPIO 5 - D1' },
294
+ { key: '12', value: 'GPIO 12 - D6' },
295
+ { key: '13', value: 'GPIO 13 - D7 - UART 1 RX' },
296
+ { key: '14', value: 'GPIO 14 - D5' },
297
+ { key: '15', value: 'GPIO 15 - D8 - UART 1 TX' },
298
+ { key: '16', value: 'GPIO 16 - D0' },
299
+ ];
300
+
301
+ export const ESP8266_ANALOG_READ = [{ key: '17', value: 'GPIO 17 - A0' }];
302
+
303
+ export const ESP8266_ANALOG_WRITE = [
304
+ { key: '0', value: 'GPIO 0 - D3' },
305
+ { key: '1', value: 'GPIO 1 - D10 - UART 0 TX' },
306
+ { key: '2', value: 'GPIO 2 - D4 - UART 0 TX2' },
307
+ { key: '3', value: 'GPIO 3 - D9 - UART 0 RX' },
308
+ { key: '4', value: 'GPIO 4 - D2' },
309
+ { key: '5', value: 'GPIO 5 - D1' },
310
+ { key: '12', value: 'GPIO 12 - D6' },
311
+ { key: '13', value: 'GPIO 13 - D7 - UART 1 RX' },
312
+ { key: '14', value: 'GPIO 14 - D5' },
313
+ { key: '15', value: 'GPIO 15 - D8 - UART 1 TX' },
314
+ ];
315
+
316
+ export const LANGUAGE = {
317
+ DEFAULT: 'vi',
318
+ };
@@ -441,7 +441,7 @@ const SelectAction = memo(({ route }) => {
441
441
  onSelectAction={handleOnSelectAction}
442
442
  accessibilityLabel={AccessibilityLabel.ACTION_ITEM}
443
443
  />
444
- {/* TODO: Huy - refactor/combine with value_change case */}
444
+ {/* TODO: Nhat - refactor/combine with value_change case */}
445
445
  {!!sensorData &&
446
446
  sensorData.map((item) => {
447
447
  const isHasValue = !!item?.value;
@@ -1,11 +1,11 @@
1
1
  export const getConfigControlFromDeviceDisplay = (deviceDisplay) => {
2
2
  const configIds = [];
3
- deviceDisplay?.items.map((item) => {
3
+ (deviceDisplay?.items || []).map((item) => {
4
4
  if (item.type !== 'action') {
5
5
  return;
6
6
  }
7
- const actionGroup = item.configuration;
8
- const { configuration } = actionGroup;
7
+ const actionGroup = item?.configuration || {};
8
+ const { configuration = {} } = actionGroup;
9
9
 
10
10
  switch (actionGroup.template) {
11
11
  case 'on_off_button_action_template':
@@ -21,7 +21,7 @@ export const getConfigControlFromDeviceDisplay = (deviceDisplay) => {
21
21
  }
22
22
  break;
23
23
  case 'StatesGridActionTemplate':
24
- configuration.options.forEach((option) => {
24
+ (configuration?.options || []).forEach((option) => {
25
25
  configIds.push(option.config);
26
26
  });
27
27
  break;
@@ -54,6 +54,7 @@ describe('test NotificationItem', () => {
54
54
  NOTIFICATION_TYPES.NOTIFY_MEMBER_LEAVE_UNIT,
55
55
  NOTIFICATION_TYPES.NOTIFY_REMOVE_SUB_UNIT,
56
56
  NOTIFICATION_TYPES.NOTIFY_REMOVE_DEVICE,
57
+ NOTIFICATION_TYPES.NOTIFY_CHANGE_UNIT_OLD_OWNER,
57
58
  ];
58
59
 
59
60
  for (const content_code of listCase2) {
@@ -76,6 +77,7 @@ describe('test NotificationItem', () => {
76
77
  NOTIFICATION_TYPES.NOTIFY_UPDATE_ADDRESS,
77
78
  NOTIFICATION_TYPES.NOTIFY_RENAME_SUB_UNIT,
78
79
  NOTIFICATION_TYPES.NOTIFY_INVITE_MEMBER,
80
+ NOTIFICATION_TYPES.NOTIFY_CHANGE_UNIT_NEW_OWNER,
79
81
  ];
80
82
 
81
83
  for (const content_code of listCaseUnitDetail) {
@@ -355,6 +355,29 @@ const NotificationItem = memo(({ item }) => {
355
355
  default:
356
356
  return null;
357
357
  }
358
+ case NOTIFICATION_TYPES.NOTIFY_CHANGE_UNIT_OLD_OWNER:
359
+ return {
360
+ content: customColorText(
361
+ t('text_notification_content_change_unit_for_old_owner')
362
+ ),
363
+ redirect: () => null,
364
+ iconContent: <Image source={Images.logo} style={styles.logo} />,
365
+ };
366
+ case NOTIFICATION_TYPES.NOTIFY_CHANGE_UNIT_NEW_OWNER:
367
+ return {
368
+ content: customColorText(
369
+ t('text_notification_content_change_unit_for_new_owner')
370
+ ),
371
+ redirect: () => {
372
+ navigation.navigate(Routes.UnitStack, {
373
+ screen: Routes.UnitDetail,
374
+ params: {
375
+ unitId,
376
+ },
377
+ });
378
+ },
379
+ iconContent: <Image source={Images.logo} style={styles.logo} />,
380
+ };
358
381
  default:
359
382
  return null;
360
383
  }
@@ -35,7 +35,11 @@ jest.mock('@react-navigation/native', () => {
35
35
 
36
36
  describe('Test Detail', () => {
37
37
  useRoute.mockReturnValue({
38
- params: {},
38
+ params: {
39
+ item: {
40
+ id: 1,
41
+ },
42
+ },
39
43
  });
40
44
  let tree;
41
45
 
@@ -43,9 +47,9 @@ describe('Test Detail', () => {
43
47
  mock.onGet(API.DEV_MODE.DASHBOARD.GET_WIDGETS(1)).reply(200, [
44
48
  {
45
49
  id: 1,
46
- type: 'camera',
50
+ type: 'value',
47
51
  configuration: {
48
- uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
52
+ configs: [{ id: 1 }],
49
53
  },
50
54
  },
51
55
  ]);
@@ -81,8 +85,21 @@ describe('Test Detail', () => {
81
85
  await TouchableOpacities[1].props.onPress();
82
86
  });
83
87
  const menuActionMore = instance.findByType(MenuActionMore);
88
+ const mockDoAction = jest.fn();
89
+ const item = {
90
+ text: 'text',
91
+ doAction: mockDoAction,
92
+ };
93
+ await act(async () => {
94
+ await menuActionMore.props.onItemClick(item);
95
+ });
96
+ expect(mockDoAction).toBeCalled();
84
97
  const modalPopupCT = instance.findByType(ModalPopupCT);
85
98
  mock.onGet(API.DEV_MODE.DASHBOARD.DETAIL(1)).reply(200);
99
+ await act(async () => {
100
+ await menuActionMore.props.listMenuItem[0].doAction();
101
+ });
102
+ expect(mockNavigate).toHaveBeenCalled();
86
103
  await act(async () => {
87
104
  await menuActionMore.props.listMenuItem[3].doAction();
88
105
  });
@@ -690,6 +690,8 @@
690
690
  "text_notification_content_emergency": "The Emergency button has been activated at **unit_name**: **sub_unit_name**. Please check now!",
691
691
  "text_notification_content_emergency_resolve": "Emergency situation activated at **unit_name**: **sub_unit_name** has resolved.",
692
692
  "text_notification_content_invite_member": "**unit_owner_name** invited you to join **unit_name**.",
693
+ "text_notification_content_change_unit_for_old_owner": "Your ownership of **unit_name** has been assigned to **new_owner_name**.",
694
+ "text_notification_content_change_unit_for_new_owner": "You are now the owner of **unit_name**.",
693
695
  "this_spot_does_not_exsit": "This spot does not exist",
694
696
  "please_scan_again_or_contact_the_parking_manager": "Please scan again or contact the parking manager",
695
697
  "this_spot_does_not_support_to_scan": "This spot does not support to scan",
@@ -704,6 +704,8 @@
704
704
  "text_notification_content_emergency": "Một tình huống khẩn cấp vừa được kích hoạt tại **unit_name**: **sub_unit_name**. Vui lòng kiểm tra ngay!",
705
705
  "text_notification_content_emergency_resolve": "Tình huống khẩn cấp được kích hoạt tại **unit_name**: **sub_unit_name** đã được giải quyết.",
706
706
  "text_notification_content_invite_member": "**unit_owner_name** mời bạn đến địa điểm **unit_name**.",
707
+ "text_notification_content_change_unit_for_old_owner": "Quyền sở hữu **unit_name** của bạn đã được chỉ định cho **new_owner_name**.",
708
+ "text_notification_content_change_unit_for_new_owner": "Bạn hiện là chủ sở hữu của **unit_name**.",
707
709
  "this_spot_does_not_exsit": "Vị trí đỗ này không tồn tại",
708
710
  "please_scan_again_or_contact_the_parking_manager": "Vui lòng quét lại hoặc liên hệ với người quản lý bãi đậu xe",
709
711
  "this_spot_does_not_support_to_scan": "Vị trí đỗ này không hỗ trợ quét",
@@ -18,6 +18,9 @@ import {
18
18
  WRITE_DIGITAL_PIN_MODE,
19
19
  WRITE_ANALOG_PIN_MODE,
20
20
  LANGUAGE,
21
+ ESP8266_DIGITAL,
22
+ ESP8266_ANALOG_READ,
23
+ ESP8266_ANALOG_WRITE,
21
24
  } from '../configs/IOPinConstants';
22
25
 
23
26
  export const setAxiosDefaultAuthToken = (token) => {
@@ -150,8 +153,12 @@ export const notImplemented = (t) => {
150
153
  };
151
154
 
152
155
  export const roundNumber = (value, round = 2) => {
153
- if (!isNaN) {
154
- return Math.round((value || 0) * 10 ** round) / 10 ** round;
156
+ if (typeof value === 'string') {
157
+ const temp = value.split(' ');
158
+ return (
159
+ Math.round((isNaN(temp[0]) ? 0 : temp[0]) * 10 ** round) / 10 ** round +
160
+ ` ${temp[1] || ''}`
161
+ );
155
162
  }
156
163
  return value;
157
164
  };
@@ -168,6 +175,16 @@ export const PIN_MODE_MAPPING = {
168
175
  };
169
176
 
170
177
  export const PIN_MAPPING = {
178
+ esp8266: {
179
+ read: {
180
+ boolean: ESP8266_DIGITAL,
181
+ integer: ESP8266_ANALOG_READ,
182
+ },
183
+ write: {
184
+ boolean: ESP8266_DIGITAL,
185
+ integer: ESP8266_ANALOG_WRITE,
186
+ },
187
+ },
171
188
  esp32: {
172
189
  read: {
173
190
  boolean: ESP32_DIGITAL,
@@ -19,6 +19,7 @@ import {
19
19
  ToastBottomHelper,
20
20
  objectIds,
21
21
  notImplemented,
22
+ roundNumber,
22
23
  } from '../Utils';
23
24
 
24
25
  describe('Test utils', () => {
@@ -50,6 +51,9 @@ describe('Test utils', () => {
50
51
  it('test setAxiosDefaultLanguage', () => {
51
52
  setAxiosDefaultLanguage('en');
52
53
  expect(api.headers['Accept-Language']).toBe('en');
54
+
55
+ setAxiosDefaultLanguage();
56
+ expect(api.headers['Accept-Language']).toBe('vi');
53
57
  });
54
58
 
55
59
  it('test setAxiosDefaultAuthToken', () => {
@@ -134,6 +138,7 @@ describe('Test utils', () => {
134
138
  configIds: [],
135
139
  });
136
140
  });
141
+
137
142
  it('test objectIds with devices', async () => {
138
143
  const data = await objectIds([
139
144
  {
@@ -166,4 +171,14 @@ describe('Test utils', () => {
166
171
  });
167
172
  expect(mockTranslate).toBeCalledWith('feature_under_development');
168
173
  });
174
+
175
+ it('Test roundNumber', async () => {
176
+ let result;
177
+ result = await roundNumber('NaN kwh');
178
+ expect(result).toBe('0 kwh');
179
+ result = await roundNumber('123.123 kwh');
180
+ expect(result).toBe('123.12 kwh');
181
+ result = await roundNumber(12.12);
182
+ expect(result).toBe(12.12);
183
+ });
169
184
  });