matterbridge-roborock-vacuum-plugin-regions 1.1.1-jb.1

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 (261) hide show
  1. package/.github/workflows/build.yml +56 -0
  2. package/.github/workflows/coverage.yml +59 -0
  3. package/.github/workflows/publish.yml +37 -0
  4. package/.tarignore +5 -0
  5. package/CHANGELOG.md +62 -0
  6. package/LICENSE +202 -0
  7. package/README.md +135 -0
  8. package/README_CLEANMODE.md +29 -0
  9. package/README_DEV.md +75 -0
  10. package/README_REPORT_ISSUE.md +34 -0
  11. package/README_SUPPORTED.md +67 -0
  12. package/dist/behaviorFactory.js +26 -0
  13. package/dist/behaviors/BehaviorDeviceGeneric.js +22 -0
  14. package/dist/behaviors/roborock.vacuum/default/default.js +183 -0
  15. package/dist/behaviors/roborock.vacuum/default/initalData.js +143 -0
  16. package/dist/behaviors/roborock.vacuum/default/runtimes.js +21 -0
  17. package/dist/behaviors/roborock.vacuum/smart/initalData.js +18 -0
  18. package/dist/behaviors/roborock.vacuum/smart/runtimes.js +11 -0
  19. package/dist/behaviors/roborock.vacuum/smart/smart.js +119 -0
  20. package/dist/clientManager.js +17 -0
  21. package/dist/helper.js +76 -0
  22. package/dist/index.js +4 -0
  23. package/dist/initialData/getBatteryStatus.js +24 -0
  24. package/dist/initialData/getOperationalStates.js +82 -0
  25. package/dist/initialData/getSupportedAreas.js +120 -0
  26. package/dist/initialData/getSupportedCleanModes.js +17 -0
  27. package/dist/initialData/getSupportedRunModes.js +11 -0
  28. package/dist/initialData/getSupportedScenes.js +26 -0
  29. package/dist/initialData/index.js +6 -0
  30. package/dist/model/CloudMessageModel.js +1 -0
  31. package/dist/model/DockingStationStatus.js +26 -0
  32. package/dist/model/ExperimentalFeatureSetting.js +30 -0
  33. package/dist/model/RoomMap.js +19 -0
  34. package/dist/model/roomIndexMap.js +21 -0
  35. package/dist/notifyMessageTypes.js +9 -0
  36. package/dist/platform.js +312 -0
  37. package/dist/platformRunner.js +90 -0
  38. package/dist/roborockCommunication/RESTAPI/roborockAuthenticateApi.js +213 -0
  39. package/dist/roborockCommunication/RESTAPI/roborockIoTApi.js +95 -0
  40. package/dist/roborockCommunication/Zenum/additionalPropCode.js +4 -0
  41. package/dist/roborockCommunication/Zenum/authenticateResponseCode.js +7 -0
  42. package/dist/roborockCommunication/Zenum/dockType.js +4 -0
  43. package/dist/roborockCommunication/Zenum/operationStatusCode.js +44 -0
  44. package/dist/roborockCommunication/Zenum/vacuumAndDockErrorCode.js +68 -0
  45. package/dist/roborockCommunication/Zmodel/apiResponse.js +1 -0
  46. package/dist/roborockCommunication/Zmodel/authenticateFlowState.js +1 -0
  47. package/dist/roborockCommunication/Zmodel/authenticateResponse.js +1 -0
  48. package/dist/roborockCommunication/Zmodel/baseURL.js +1 -0
  49. package/dist/roborockCommunication/Zmodel/batteryMessage.js +1 -0
  50. package/dist/roborockCommunication/Zmodel/device.js +1 -0
  51. package/dist/roborockCommunication/Zmodel/deviceModel.js +28 -0
  52. package/dist/roborockCommunication/Zmodel/deviceSchema.js +1 -0
  53. package/dist/roborockCommunication/Zmodel/deviceStatus.js +22 -0
  54. package/dist/roborockCommunication/Zmodel/dockInfo.js +6 -0
  55. package/dist/roborockCommunication/Zmodel/home.js +1 -0
  56. package/dist/roborockCommunication/Zmodel/homeInfo.js +1 -0
  57. package/dist/roborockCommunication/Zmodel/map.js +1 -0
  58. package/dist/roborockCommunication/Zmodel/mapInfo.js +29 -0
  59. package/dist/roborockCommunication/Zmodel/messageResult.js +7 -0
  60. package/dist/roborockCommunication/Zmodel/multipleMap.js +1 -0
  61. package/dist/roborockCommunication/Zmodel/networkInfo.js +1 -0
  62. package/dist/roborockCommunication/Zmodel/product.js +1 -0
  63. package/dist/roborockCommunication/Zmodel/room.js +1 -0
  64. package/dist/roborockCommunication/Zmodel/roomInfo.js +22 -0
  65. package/dist/roborockCommunication/Zmodel/scene.js +16 -0
  66. package/dist/roborockCommunication/Zmodel/userData.js +1 -0
  67. package/dist/roborockCommunication/Zmodel/vacuumError.js +27 -0
  68. package/dist/roborockCommunication/broadcast/abstractClient.js +55 -0
  69. package/dist/roborockCommunication/broadcast/client/LocalNetworkClient.js +174 -0
  70. package/dist/roborockCommunication/broadcast/client/LocalNetworkUDPClient.js +129 -0
  71. package/dist/roborockCommunication/broadcast/client/MQTTClient.js +139 -0
  72. package/dist/roborockCommunication/broadcast/client.js +1 -0
  73. package/dist/roborockCommunication/broadcast/clientRouter.js +82 -0
  74. package/dist/roborockCommunication/broadcast/listener/abstractConnectionListener.js +1 -0
  75. package/dist/roborockCommunication/broadcast/listener/abstractMessageHandler.js +1 -0
  76. package/dist/roborockCommunication/broadcast/listener/abstractMessageListener.js +1 -0
  77. package/dist/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.js +26 -0
  78. package/dist/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.js +11 -0
  79. package/dist/roborockCommunication/broadcast/listener/implementation/connectionStateListener.js +43 -0
  80. package/dist/roborockCommunication/broadcast/listener/implementation/generalSyncMessageListener.js +28 -0
  81. package/dist/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.js +27 -0
  82. package/dist/roborockCommunication/broadcast/listener/implementation/syncMessageListener.js +33 -0
  83. package/dist/roborockCommunication/broadcast/listener/index.js +1 -0
  84. package/dist/roborockCommunication/broadcast/messageProcessor.js +148 -0
  85. package/dist/roborockCommunication/broadcast/model/contentMessage.js +1 -0
  86. package/dist/roborockCommunication/broadcast/model/dps.js +1 -0
  87. package/dist/roborockCommunication/broadcast/model/headerMessage.js +1 -0
  88. package/dist/roborockCommunication/broadcast/model/messageContext.js +37 -0
  89. package/dist/roborockCommunication/broadcast/model/protocol.js +28 -0
  90. package/dist/roborockCommunication/broadcast/model/requestMessage.js +38 -0
  91. package/dist/roborockCommunication/broadcast/model/responseMessage.js +14 -0
  92. package/dist/roborockCommunication/helper/chunkBuffer.js +17 -0
  93. package/dist/roborockCommunication/helper/cryptoHelper.js +23 -0
  94. package/dist/roborockCommunication/helper/messageDeserializer.js +98 -0
  95. package/dist/roborockCommunication/helper/messageSerializer.js +84 -0
  96. package/dist/roborockCommunication/helper/nameDecoder.js +66 -0
  97. package/dist/roborockCommunication/helper/sequence.js +16 -0
  98. package/dist/roborockCommunication/index.js +13 -0
  99. package/dist/roborockService.js +494 -0
  100. package/dist/runtimes/handleCloudMessage.js +110 -0
  101. package/dist/runtimes/handleHomeDataMessage.js +57 -0
  102. package/dist/runtimes/handleLocalMessage.js +169 -0
  103. package/dist/rvc.js +51 -0
  104. package/dist/settings.js +1 -0
  105. package/dist/share/function.js +93 -0
  106. package/dist/share/runtimeHelper.js +17 -0
  107. package/dist/tests/testData/mockData.js +359 -0
  108. package/eslint.config.js +80 -0
  109. package/jest.config.js +22 -0
  110. package/jest.setup.js +2 -0
  111. package/logo.png +0 -0
  112. package/matterbridge-roborock-vacuum-plugin.config.json +46 -0
  113. package/matterbridge-roborock-vacuum-plugin.schema.json +293 -0
  114. package/misc/status.md +119 -0
  115. package/package.json +111 -0
  116. package/prettier.config.js +49 -0
  117. package/screenshot/IMG_1.PNG +0 -0
  118. package/screenshot/IMG_2.PNG +0 -0
  119. package/screenshot/IMG_3.PNG +0 -0
  120. package/screenshot/IMG_4.PNG +0 -0
  121. package/screenshot/IMG_5.PNG +0 -0
  122. package/screenshot/IMG_6.PNG +0 -0
  123. package/screenshot/IMG_7.PNG +0 -0
  124. package/src/behaviorFactory.ts +41 -0
  125. package/src/behaviors/BehaviorDeviceGeneric.ts +31 -0
  126. package/src/behaviors/roborock.vacuum/default/default.ts +238 -0
  127. package/src/behaviors/roborock.vacuum/default/initalData.ts +152 -0
  128. package/src/behaviors/roborock.vacuum/default/runtimes.ts +23 -0
  129. package/src/behaviors/roborock.vacuum/smart/initalData.ts +20 -0
  130. package/src/behaviors/roborock.vacuum/smart/runtimes.ts +15 -0
  131. package/src/behaviors/roborock.vacuum/smart/smart.ts +159 -0
  132. package/src/clientManager.ts +23 -0
  133. package/src/helper.ts +97 -0
  134. package/src/index.ts +16 -0
  135. package/src/initialData/getBatteryStatus.ts +26 -0
  136. package/src/initialData/getOperationalStates.ts +94 -0
  137. package/src/initialData/getSupportedAreas.ts +162 -0
  138. package/src/initialData/getSupportedCleanModes.ts +22 -0
  139. package/src/initialData/getSupportedRunModes.ts +14 -0
  140. package/src/initialData/getSupportedScenes.ts +32 -0
  141. package/src/initialData/index.ts +6 -0
  142. package/src/model/CloudMessageModel.ts +11 -0
  143. package/src/model/DockingStationStatus.ts +41 -0
  144. package/src/model/ExperimentalFeatureSetting.ts +77 -0
  145. package/src/model/RoomMap.ts +38 -0
  146. package/src/model/roomIndexMap.ts +26 -0
  147. package/src/notifyMessageTypes.ts +8 -0
  148. package/src/platform.ts +424 -0
  149. package/src/platformRunner.ts +103 -0
  150. package/src/roborockCommunication/RESTAPI/roborockAuthenticateApi.ts +302 -0
  151. package/src/roborockCommunication/RESTAPI/roborockIoTApi.ts +107 -0
  152. package/src/roborockCommunication/Zenum/additionalPropCode.ts +3 -0
  153. package/src/roborockCommunication/Zenum/authenticateResponseCode.ts +6 -0
  154. package/src/roborockCommunication/Zenum/dockType.ts +3 -0
  155. package/src/roborockCommunication/Zenum/operationStatusCode.ts +43 -0
  156. package/src/roborockCommunication/Zenum/vacuumAndDockErrorCode.ts +68 -0
  157. package/src/roborockCommunication/Zmodel/apiResponse.ts +3 -0
  158. package/src/roborockCommunication/Zmodel/authenticateFlowState.ts +6 -0
  159. package/src/roborockCommunication/Zmodel/authenticateResponse.ts +5 -0
  160. package/src/roborockCommunication/Zmodel/baseURL.ts +5 -0
  161. package/src/roborockCommunication/Zmodel/batteryMessage.ts +16 -0
  162. package/src/roborockCommunication/Zmodel/device.ts +50 -0
  163. package/src/roborockCommunication/Zmodel/deviceModel.ts +27 -0
  164. package/src/roborockCommunication/Zmodel/deviceSchema.ts +8 -0
  165. package/src/roborockCommunication/Zmodel/deviceStatus.ts +30 -0
  166. package/src/roborockCommunication/Zmodel/dockInfo.ts +9 -0
  167. package/src/roborockCommunication/Zmodel/home.ts +13 -0
  168. package/src/roborockCommunication/Zmodel/homeInfo.ts +5 -0
  169. package/src/roborockCommunication/Zmodel/map.ts +20 -0
  170. package/src/roborockCommunication/Zmodel/mapInfo.ts +54 -0
  171. package/src/roborockCommunication/Zmodel/messageResult.ts +75 -0
  172. package/src/roborockCommunication/Zmodel/multipleMap.ts +8 -0
  173. package/src/roborockCommunication/Zmodel/networkInfo.ts +7 -0
  174. package/src/roborockCommunication/Zmodel/product.ts +9 -0
  175. package/src/roborockCommunication/Zmodel/room.ts +4 -0
  176. package/src/roborockCommunication/Zmodel/roomInfo.ts +30 -0
  177. package/src/roborockCommunication/Zmodel/scene.ts +44 -0
  178. package/src/roborockCommunication/Zmodel/userData.ts +26 -0
  179. package/src/roborockCommunication/Zmodel/vacuumError.ts +35 -0
  180. package/src/roborockCommunication/broadcast/abstractClient.ts +80 -0
  181. package/src/roborockCommunication/broadcast/client/LocalNetworkClient.ts +218 -0
  182. package/src/roborockCommunication/broadcast/client/LocalNetworkUDPClient.ts +157 -0
  183. package/src/roborockCommunication/broadcast/client/MQTTClient.ts +174 -0
  184. package/src/roborockCommunication/broadcast/client.ts +19 -0
  185. package/src/roborockCommunication/broadcast/clientRouter.ts +104 -0
  186. package/src/roborockCommunication/broadcast/listener/abstractConnectionListener.ts +6 -0
  187. package/src/roborockCommunication/broadcast/listener/abstractMessageHandler.ts +11 -0
  188. package/src/roborockCommunication/broadcast/listener/abstractMessageListener.ts +5 -0
  189. package/src/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.ts +33 -0
  190. package/src/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.ts +16 -0
  191. package/src/roborockCommunication/broadcast/listener/implementation/connectionStateListener.ts +57 -0
  192. package/src/roborockCommunication/broadcast/listener/implementation/generalSyncMessageListener.ts +38 -0
  193. package/src/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.ts +37 -0
  194. package/src/roborockCommunication/broadcast/listener/implementation/syncMessageListener.ts +50 -0
  195. package/src/roborockCommunication/broadcast/listener/index.ts +3 -0
  196. package/src/roborockCommunication/broadcast/messageProcessor.ts +184 -0
  197. package/src/roborockCommunication/broadcast/model/contentMessage.ts +5 -0
  198. package/src/roborockCommunication/broadcast/model/dps.ts +19 -0
  199. package/src/roborockCommunication/broadcast/model/headerMessage.ts +7 -0
  200. package/src/roborockCommunication/broadcast/model/messageContext.ts +53 -0
  201. package/src/roborockCommunication/broadcast/model/protocol.ts +28 -0
  202. package/src/roborockCommunication/broadcast/model/requestMessage.ts +51 -0
  203. package/src/roborockCommunication/broadcast/model/responseMessage.ts +19 -0
  204. package/src/roborockCommunication/helper/chunkBuffer.ts +18 -0
  205. package/src/roborockCommunication/helper/cryptoHelper.ts +30 -0
  206. package/src/roborockCommunication/helper/messageDeserializer.ts +119 -0
  207. package/src/roborockCommunication/helper/messageSerializer.ts +101 -0
  208. package/src/roborockCommunication/helper/nameDecoder.ts +78 -0
  209. package/src/roborockCommunication/helper/sequence.ts +18 -0
  210. package/src/roborockCommunication/index.ts +25 -0
  211. package/src/roborockService.ts +657 -0
  212. package/src/runtimes/handleCloudMessage.ts +134 -0
  213. package/src/runtimes/handleHomeDataMessage.ts +67 -0
  214. package/src/runtimes/handleLocalMessage.ts +209 -0
  215. package/src/rvc.ts +97 -0
  216. package/src/settings.ts +1 -0
  217. package/src/share/function.ts +103 -0
  218. package/src/share/runtimeHelper.ts +23 -0
  219. package/src/tests/behaviors/roborock.vacuum/default/default.test.ts +134 -0
  220. package/src/tests/behaviors/roborock.vacuum/smart/runtimes.test.ts +64 -0
  221. package/src/tests/behaviors/roborock.vacuum/smart/smart.test.ts +215 -0
  222. package/src/tests/helper.test.ts +162 -0
  223. package/src/tests/initialData/getSupportedAreas.test.ts +181 -0
  224. package/src/tests/model/DockingStationStatus.test.ts +39 -0
  225. package/src/tests/platformRunner.test.ts +188 -0
  226. package/src/tests/platformRunner2.test.ts +228 -0
  227. package/src/tests/platformRunner3.test.ts +46 -0
  228. package/src/tests/roborockCommunication/RESTAPI/roborockAuthenticateApi.test.ts +144 -0
  229. package/src/tests/roborockCommunication/RESTAPI/roborockIoTApi.test.ts +106 -0
  230. package/src/tests/roborockCommunication/broadcast/client/LocalNetworkClient.test.ts +189 -0
  231. package/src/tests/roborockCommunication/broadcast/client/MQTTClient.test.ts +208 -0
  232. package/src/tests/roborockCommunication/broadcast/clientRouter.test.ts +168 -0
  233. package/src/tests/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.test.ts +59 -0
  234. package/src/tests/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.test.ts +46 -0
  235. package/src/tests/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.test.ts +71 -0
  236. package/src/tests/roborockCommunication/broadcast/listener/implementation/syncMessageListener.test.ts +86 -0
  237. package/src/tests/roborockCommunication/broadcast/messageProcessor.test.ts +126 -0
  238. package/src/tests/roborockService.setSelectedAreas.test.ts +61 -0
  239. package/src/tests/roborockService.test.ts +517 -0
  240. package/src/tests/roborockService2.test.ts +69 -0
  241. package/src/tests/roborockService3.test.ts +133 -0
  242. package/src/tests/roborockService4.test.ts +76 -0
  243. package/src/tests/roborockService5.test.ts +79 -0
  244. package/src/tests/runtimes/handleCloudMessage.test.ts +200 -0
  245. package/src/tests/runtimes/handleHomeDataMessage.test.ts +54 -0
  246. package/src/tests/runtimes/handleLocalMessage.test.ts +227 -0
  247. package/src/tests/testData/mockData.ts +370 -0
  248. package/src/tests/testData/mockHomeData-a187.json +286 -0
  249. package/tsconfig.jest.json +21 -0
  250. package/tsconfig.json +37 -0
  251. package/tsconfig.production.json +19 -0
  252. package/tslint.json +9 -0
  253. package/web-for-testing/README.md +47 -0
  254. package/web-for-testing/nodemon.json +7 -0
  255. package/web-for-testing/package-lock.json +6600 -0
  256. package/web-for-testing/package.json +36 -0
  257. package/web-for-testing/src/app.ts +194 -0
  258. package/web-for-testing/tsconfig-ext.json +19 -0
  259. package/web-for-testing/tsconfig.json +23 -0
  260. package/web-for-testing/views/index.ejs +172 -0
  261. package/web-for-testing/watch.mjs +93 -0
@@ -0,0 +1,517 @@
1
+ import { AnsiLogger } from 'matterbridge/logger';
2
+ import { ServiceArea } from 'matterbridge/matter/clusters';
3
+ import RoborockService from '../roborockService';
4
+ import { MessageProcessor } from '../roborockCommunication/broadcast/messageProcessor';
5
+ import { Device, MultipleMap, RequestMessage } from '../roborockCommunication';
6
+ import { RoomIndexMap } from '../model/roomIndexMap';
7
+
8
+ describe('RoborockService - startClean', () => {
9
+ let roborockService: RoborockService;
10
+ let mockLogger: AnsiLogger;
11
+ let mockMessageProcessor: jest.Mocked<MessageProcessor>;
12
+ let mockLoginApi: any;
13
+ let mockMapInfo: any;
14
+ let mockMessageClient: any;
15
+ let mockIotApi: any;
16
+
17
+ beforeEach(() => {
18
+ mockLogger = {
19
+ debug: jest.fn(),
20
+ notice: jest.fn(),
21
+ error: jest.fn(),
22
+ warn: jest.fn(),
23
+ } as any;
24
+
25
+ mockMessageProcessor = {
26
+ startClean: jest.fn(),
27
+ startRoomClean: jest.fn(),
28
+ } as any;
29
+
30
+ mockLoginApi = {
31
+ loginWithPassword: jest.fn(),
32
+ loginWithUserData: jest.fn(),
33
+ };
34
+
35
+ mockMapInfo = jest.fn();
36
+ roborockService = new RoborockService(() => mockLoginApi, jest.fn(), 10, {} as any, mockLogger);
37
+ roborockService['auth'] = jest.fn((ud) => ud);
38
+ roborockService['messageProcessorMap'] = new Map<string, MessageProcessor>([['test-duid', mockMessageProcessor]]);
39
+
40
+ mockIotApi = { getCustom: jest.fn() };
41
+ roborockService['iotApi'] = mockIotApi;
42
+ });
43
+
44
+ it('should return result from iotApi.getCustom', async () => {
45
+ mockIotApi.getCustom.mockResolvedValue({ foo: 'bar' });
46
+ const result = await roborockService.getCustomAPI('http://test');
47
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - getCustomAPI', 'http://test');
48
+ expect(result).toEqual({ foo: 'bar' });
49
+ });
50
+
51
+ it('should log error and return error object if iotApi.getCustom throws', async () => {
52
+ mockIotApi.getCustom.mockRejectedValue(new Error('fail'));
53
+ const result = await roborockService.getCustomAPI('http://test');
54
+ expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining('Failed to get custom API with url http://test:'));
55
+ expect(result).toEqual({ result: undefined, error: expect.stringContaining('Failed to get custom API with url http://test') });
56
+ });
57
+
58
+ it('should return MapInfo if response contains maps', async () => {
59
+ const mapData = [
60
+ {
61
+ map_info: [
62
+ {
63
+ rooms: [{ id: 1, iot_name_id: 'room1', tag: 0, iot_name: 'Living Room' }],
64
+ mapFlag: 1,
65
+ name: 'Living Room Map',
66
+ },
67
+ ],
68
+ },
69
+ ] as MultipleMap[];
70
+ mockMessageClient = {
71
+ get: jest.fn(),
72
+ };
73
+ roborockService.messageClient = mockMessageClient;
74
+ mockMessageClient.get.mockResolvedValue(mapData);
75
+ mockMapInfo.mockImplementation((data) => ({ map: data }));
76
+
77
+ const result = await roborockService.getMapInformation('duid');
78
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - getMapInformation', 'duid');
79
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - getMapInformation response', expect.anything());
80
+ expect(result?.maps.length).toEqual(1);
81
+ });
82
+
83
+ it('should return undefined if response is empty', async () => {
84
+ mockMessageClient = { get: jest.fn() };
85
+ mockMessageClient.get.mockResolvedValue(undefined);
86
+ roborockService.messageClient = mockMessageClient;
87
+
88
+ const result = await roborockService.getMapInformation('duid');
89
+ expect(result).toBeUndefined();
90
+ });
91
+
92
+ it('should return vacuumRoom if present', async () => {
93
+ roborockService.customGet = jest.fn();
94
+ (roborockService.customGet as jest.Mock).mockResolvedValue({ vacuumRoom: 42 });
95
+ const result = await roborockService.getRoomIdFromMap('duid');
96
+ expect(roborockService.customGet).toHaveBeenCalledWith('duid', expect.any(Object));
97
+ expect(result).toBe(42);
98
+ });
99
+
100
+ it('should return undefined if vacuumRoom is not present', async () => {
101
+ roborockService.customGet = jest.fn();
102
+ (roborockService.customGet as jest.Mock).mockResolvedValue({});
103
+ const result = await roborockService.getRoomIdFromMap('duid');
104
+ expect(result).toBeUndefined();
105
+ });
106
+
107
+ it('should login with password if no saved user data', async () => {
108
+ const username = 'user';
109
+ const password = 'pass';
110
+ const userData = { foo: 'bar' };
111
+ mockLoginApi.loginWithPassword.mockResolvedValue(userData);
112
+ const loadSavedUserData = jest.fn().mockResolvedValue(undefined);
113
+ const savedUserData = jest.fn().mockResolvedValue(undefined);
114
+
115
+ const result = await roborockService.loginWithPassword(username, password, loadSavedUserData, savedUserData);
116
+
117
+ expect(mockLogger.debug).toHaveBeenCalledWith('No saved user data found, logging in with password');
118
+ expect(mockLoginApi.loginWithPassword).toHaveBeenCalledWith(username, password);
119
+ expect(savedUserData).toHaveBeenCalledWith(userData);
120
+ expect(roborockService['auth']).toHaveBeenCalledWith(userData);
121
+ expect(result).toBe(userData);
122
+ });
123
+
124
+ it('should login with user data if saved user data exists', async () => {
125
+ const username = 'user';
126
+ const password = 'pass';
127
+ const userData = { foo: 'bar' };
128
+ mockLoginApi.loginWithUserData.mockResolvedValue(userData);
129
+ const loadSavedUserData = jest.fn().mockResolvedValue(userData);
130
+ const savedUserData = jest.fn();
131
+
132
+ const result = await roborockService.loginWithPassword(username, password, loadSavedUserData, savedUserData);
133
+
134
+ expect(mockLogger.debug).toHaveBeenCalledWith('Using saved user data for login', expect.anything());
135
+ expect(mockLoginApi.loginWithUserData).toHaveBeenCalledWith(username, userData);
136
+ expect(roborockService['auth']).toHaveBeenCalledWith(userData);
137
+ expect(result).toBe(userData);
138
+ });
139
+
140
+ it('should start global clean when no areas or selected areas are provided', async () => {
141
+ const duid = 'test-duid';
142
+ roborockService['supportedAreas'].set(duid, []);
143
+ roborockService['selectedAreas'].set(duid, []);
144
+
145
+ await roborockService.startClean(duid);
146
+
147
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - startGlobalClean');
148
+ expect(mockMessageProcessor.startClean).toHaveBeenCalledWith(duid);
149
+ });
150
+
151
+ it('should start room clean when selected areas match supported areas', async () => {
152
+ const duid = 'test-duid';
153
+ const supportedAreas: ServiceArea.Area[] = [
154
+ { areaId: 1, mapId: null, areaInfo: {} as any },
155
+ { areaId: 2, mapId: null, areaInfo: {} as any },
156
+ ];
157
+ const selectedAreas = [1];
158
+
159
+ roborockService['supportedAreas'].set(duid, supportedAreas);
160
+ roborockService['selectedAreas'].set(duid, selectedAreas);
161
+
162
+ await roborockService.startClean(duid);
163
+
164
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - startRoomClean', expect.anything());
165
+ expect(mockMessageProcessor.startRoomClean).toHaveBeenCalledWith(duid, selectedAreas, 1);
166
+ });
167
+
168
+ it('should start global clean when all selected areas match all supported areas', async () => {
169
+ const duid = 'test-duid';
170
+ const supportedAreas: ServiceArea.Area[] = [
171
+ { areaId: 1, mapId: null, areaInfo: {} as any },
172
+ { areaId: 2, mapId: null, areaInfo: {} as any },
173
+ ];
174
+ const selectedAreas = [1, 2];
175
+
176
+ roborockService['supportedAreas'].set(duid, supportedAreas);
177
+ roborockService['selectedAreas'].set(duid, selectedAreas);
178
+
179
+ await roborockService.startClean(duid);
180
+
181
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - startGlobalClean');
182
+ expect(mockMessageProcessor.startClean).toHaveBeenCalledWith(duid);
183
+ });
184
+
185
+ it('should start scene when a routine is selected', async () => {
186
+ const duid = 'test-duid';
187
+ const supportedAreas: ServiceArea.Area[] = [
188
+ { areaId: 1, mapId: null, areaInfo: {} as any },
189
+ { areaId: 2, mapId: null, areaInfo: {} as any },
190
+ ];
191
+ const supportedRoutines: ServiceArea.Area[] = [{ areaId: 99, mapId: null, areaInfo: {} as any }];
192
+ const selectedAreas = [99];
193
+
194
+ roborockService['supportedAreas'].set(duid, supportedAreas);
195
+ roborockService['supportedRoutines'].set(duid, supportedRoutines);
196
+ roborockService['selectedAreas'].set(duid, selectedAreas);
197
+
198
+ roborockService['iotApi'] = {
199
+ startScene: jest.fn(),
200
+ } as any;
201
+
202
+ await roborockService.startClean(duid);
203
+
204
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - startScene', expect.anything());
205
+ expect(roborockService['iotApi']!.startScene).toHaveBeenCalledWith(99);
206
+ });
207
+
208
+ it('should warn when multiple routines are selected', async () => {
209
+ const duid = 'test-duid';
210
+ const supportedAreas: ServiceArea.Area[] = [
211
+ { areaId: 1, mapId: null, areaInfo: {} as any },
212
+ { areaId: 2, mapId: null, areaInfo: {} as any },
213
+ ];
214
+ const supportedRoutines: ServiceArea.Area[] = [
215
+ { areaId: 99, mapId: null, areaInfo: {} as any },
216
+ { areaId: 100, mapId: null, areaInfo: {} as any },
217
+ ];
218
+ const selectedAreas = [99, 100];
219
+
220
+ roborockService['supportedAreas'].set(duid, supportedAreas);
221
+ roborockService['supportedRoutines'].set(duid, supportedRoutines);
222
+ roborockService['selectedAreas'].set(duid, selectedAreas);
223
+
224
+ await roborockService.startClean(duid);
225
+
226
+ expect(mockLogger.warn).toHaveBeenCalledWith('RoborockService - Multiple routines selected, which is not supported.', expect.anything());
227
+ });
228
+
229
+ it('should start global clean if all selected rooms match supportedRooms even with routines defined', async () => {
230
+ const duid = 'test-duid';
231
+ const supportedAreas: ServiceArea.Area[] = [
232
+ { areaId: 1, mapId: null, areaInfo: {} as any },
233
+ { areaId: 2, mapId: null, areaInfo: {} as any },
234
+ ];
235
+ const supportedRoutines: ServiceArea.Area[] = [{ areaId: 99, mapId: null, areaInfo: {} as any }];
236
+ const selectedAreas = [1, 2];
237
+
238
+ roborockService['supportedAreas'].set(duid, supportedAreas);
239
+ roborockService['supportedRoutines'].set(duid, supportedRoutines);
240
+ roborockService['selectedAreas'].set(duid, selectedAreas);
241
+
242
+ await roborockService.startClean(duid);
243
+
244
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - startGlobalClean');
245
+ expect(mockMessageProcessor.startClean).toHaveBeenCalledWith(duid);
246
+ });
247
+
248
+ it('should start room clean if only rooms are selected and not all rooms', async () => {
249
+ const duid = 'test-duid';
250
+ const supportedAreas: ServiceArea.Area[] = [
251
+ { areaId: 1, mapId: null, areaInfo: {} as any },
252
+ { areaId: 2, mapId: null, areaInfo: {} as any },
253
+ { areaId: 3, mapId: null, areaInfo: {} as any },
254
+ ];
255
+ const supportedRoutines: ServiceArea.Area[] = [{ areaId: 99, mapId: null, areaInfo: {} as any }];
256
+ const selectedAreas = [1, 3];
257
+
258
+ roborockService['supportedAreas'].set(duid, supportedAreas);
259
+ roborockService['supportedRoutines'].set(duid, supportedRoutines);
260
+ roborockService['selectedAreas'].set(duid, selectedAreas);
261
+
262
+ await roborockService.startClean(duid);
263
+
264
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - startRoomClean', expect.anything());
265
+ expect(mockMessageProcessor.startRoomClean).toHaveBeenCalledWith(duid, [1, 3], 1);
266
+ });
267
+
268
+ it('should initialize and store MessageProcessor for the given duid', () => {
269
+ const duid = 'test-duid';
270
+ roborockService.initializeMessageClientForLocal({ duid } as Device);
271
+ const storedProcessor = roborockService['messageProcessorMap'].get(duid);
272
+ expect(storedProcessor).not.toBeUndefined();
273
+ });
274
+ });
275
+
276
+ describe('RoborockService - basic setters/getters', () => {
277
+ let roborockService: RoborockService;
278
+ let mockLogger: AnsiLogger;
279
+
280
+ beforeEach(() => {
281
+ mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
282
+ roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
283
+ });
284
+
285
+ it('setSelectedAreas should set selected areas', () => {
286
+ roborockService.setSupportedAreaIndexMap(
287
+ 'duid',
288
+ new RoomIndexMap(
289
+ new Map([
290
+ [1, { roomId: 1, mapId: 0 }],
291
+ [2, { roomId: 2, mapId: 1 }],
292
+ ]),
293
+ ),
294
+ );
295
+ roborockService.setSelectedAreas('duid', [1, 2]);
296
+
297
+ expect(roborockService['selectedAreas'].get('duid')).toEqual([1, 2]);
298
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - setSelectedAreas', [1, 2]);
299
+ });
300
+
301
+ it('setSupportedAreas should set supported areas', () => {
302
+ const areas = [{ areaId: 1, mapId: null, areaInfo: {} as any }];
303
+ roborockService.setSupportedAreas('duid', areas);
304
+ expect(roborockService['supportedAreas'].get('duid')).toEqual(areas);
305
+ });
306
+
307
+ it('setSupportedScenes should set supported routines', () => {
308
+ const routines = [{ areaId: 99, mapId: null, areaInfo: {} as any }];
309
+ roborockService.setSupportedScenes('duid', routines);
310
+ expect(roborockService['supportedRoutines'].get('duid')).toEqual(routines);
311
+ });
312
+
313
+ it('getSupportedAreas should return supported areas', () => {
314
+ const areas = [{ areaId: 1, mapId: null, areaInfo: {} as any }];
315
+ roborockService['supportedAreas'].set('duid', areas);
316
+ expect(roborockService.getSupportedAreas('duid')).toEqual(areas);
317
+ });
318
+ });
319
+
320
+ describe('RoborockService - getMessageProcessor', () => {
321
+ let roborockService: RoborockService;
322
+ let mockLogger: AnsiLogger;
323
+ let mockMessageProcessor: jest.Mocked<MessageProcessor>;
324
+
325
+ beforeEach(() => {
326
+ mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
327
+ mockMessageProcessor = { startClean: jest.fn() } as any;
328
+ roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
329
+ });
330
+
331
+ it('should return processor if present', () => {
332
+ roborockService['messageProcessorMap'].set('duid', mockMessageProcessor);
333
+ expect(roborockService.getMessageProcessor('duid')).toBe(mockMessageProcessor);
334
+ });
335
+
336
+ it('should log error if processor not present', () => {
337
+ expect(roborockService.getMessageProcessor('unknown')).toBeUndefined();
338
+ expect(mockLogger.error).toHaveBeenCalledWith('MessageApi is not initialized.');
339
+ });
340
+ });
341
+
342
+ describe('RoborockService - getCleanModeData', () => {
343
+ let roborockService: RoborockService;
344
+ let mockLogger: AnsiLogger;
345
+ let mockMessageProcessor: jest.Mocked<MessageProcessor>;
346
+
347
+ beforeEach(() => {
348
+ mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
349
+ mockMessageProcessor = { getCleanModeData: jest.fn() } as any;
350
+ roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
351
+ roborockService['messageProcessorMap'].set('duid', mockMessageProcessor);
352
+ });
353
+
354
+ it('should return clean mode data', async () => {
355
+ mockMessageProcessor.getCleanModeData.mockResolvedValue({ suctionPower: 1, waterFlow: 2, distance_off: 3, mopRoute: 4 });
356
+ const result = await roborockService.getCleanModeData('duid');
357
+ expect(result).toEqual({ suctionPower: 1, waterFlow: 2, distance_off: 3, mopRoute: 4 });
358
+ expect(mockLogger.notice).toHaveBeenCalledWith('RoborockService - getCleanModeData');
359
+ });
360
+ });
361
+
362
+ describe('RoborockService - changeCleanMode', () => {
363
+ let roborockService: RoborockService;
364
+ let mockLogger: AnsiLogger;
365
+ let mockMessageProcessor: jest.Mocked<MessageProcessor>;
366
+
367
+ beforeEach(() => {
368
+ mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
369
+ mockMessageProcessor = { changeCleanMode: jest.fn() } as any;
370
+ roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
371
+ roborockService['messageProcessorMap'].set('duid', mockMessageProcessor);
372
+ });
373
+
374
+ it('should call changeCleanMode on processor', async () => {
375
+ await roborockService.changeCleanMode('duid', { suctionPower: 1, waterFlow: 2, distance_off: 3, mopRoute: 4 });
376
+ expect(mockLogger.notice).toHaveBeenCalledWith('RoborockService - changeCleanMode');
377
+ expect(mockMessageProcessor.changeCleanMode).toHaveBeenCalledWith('duid', 1, 2, 4, 3);
378
+ });
379
+ });
380
+
381
+ describe('RoborockService - pause/stop/resume/playSound', () => {
382
+ let roborockService: RoborockService;
383
+ let mockLogger: AnsiLogger;
384
+ let mockMessageProcessor: jest.Mocked<MessageProcessor>;
385
+
386
+ beforeEach(() => {
387
+ mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
388
+ mockMessageProcessor = {
389
+ pauseClean: jest.fn(),
390
+ gotoDock: jest.fn(),
391
+ resumeClean: jest.fn(),
392
+ findMyRobot: jest.fn(),
393
+ } as any;
394
+ roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
395
+ roborockService['messageProcessorMap'].set('duid', mockMessageProcessor);
396
+ });
397
+
398
+ it('pauseClean should call processor and log', async () => {
399
+ await roborockService.pauseClean('duid');
400
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - pauseClean');
401
+ expect(mockMessageProcessor.pauseClean).toHaveBeenCalledWith('duid');
402
+ });
403
+
404
+ it('stopAndGoHome should call processor and log', async () => {
405
+ await roborockService.stopAndGoHome('duid');
406
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - stopAndGoHome');
407
+ expect(mockMessageProcessor.gotoDock).toHaveBeenCalledWith('duid');
408
+ });
409
+
410
+ it('resumeClean should call processor and log', async () => {
411
+ await roborockService.resumeClean('duid');
412
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - resumeClean');
413
+ expect(mockMessageProcessor.resumeClean).toHaveBeenCalledWith('duid');
414
+ });
415
+
416
+ it('playSoundToLocate should call processor and log', async () => {
417
+ await roborockService.playSoundToLocate('duid');
418
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - findMe');
419
+ expect(mockMessageProcessor.findMyRobot).toHaveBeenCalledWith('duid');
420
+ });
421
+ });
422
+
423
+ describe('RoborockService - customGet/customGetInSecure/customSend', () => {
424
+ let roborockService: RoborockService;
425
+ let mockLogger: AnsiLogger;
426
+ let mockMessageProcessor: jest.Mocked<MessageProcessor>;
427
+
428
+ beforeEach(() => {
429
+ mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
430
+ mockMessageProcessor = {
431
+ getCustomMessage: jest.fn(),
432
+ sendCustomMessage: jest.fn(),
433
+ } as any;
434
+ roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
435
+ roborockService['messageProcessorMap'].set('duid', mockMessageProcessor);
436
+ });
437
+
438
+ it('customGet should call getCustomMessage', async () => {
439
+ mockMessageProcessor.getCustomMessage.mockResolvedValue('result');
440
+ const result = await roborockService.customGet('duid', { method: 'method', params: undefined, secure: true } as RequestMessage);
441
+ expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - customSend-message', 'method', undefined, true);
442
+ expect(mockMessageProcessor.getCustomMessage).toHaveBeenCalledWith('duid', expect.any(Object));
443
+ expect(result).toBe('result');
444
+ });
445
+
446
+ it('customSend should call sendCustomMessage', async () => {
447
+ const req = { foo: 'bar' } as any;
448
+ await roborockService.customSend('duid', req);
449
+ expect(mockMessageProcessor.sendCustomMessage).toHaveBeenCalledWith('duid', req);
450
+ });
451
+ });
452
+
453
+ describe('RoborockService - stopService', () => {
454
+ let roborockService: RoborockService;
455
+ let mockLogger: AnsiLogger;
456
+ let mockMessageClient: any;
457
+ let mockLocalClient: any;
458
+ let mockMessageProcessor: any;
459
+
460
+ beforeEach(() => {
461
+ mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
462
+ mockMessageClient = { disconnect: jest.fn() };
463
+ mockLocalClient = { disconnect: jest.fn(), isConnected: jest.fn() };
464
+ mockMessageProcessor = {};
465
+ roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
466
+ roborockService.messageClient = mockMessageClient;
467
+ roborockService.localClientMap.set('duid', mockLocalClient);
468
+ roborockService.messageProcessorMap.set('duid', mockMessageProcessor);
469
+ roborockService.requestDeviceStatusInterval = setInterval(() => {
470
+ jest.fn();
471
+ }, 1000);
472
+ });
473
+
474
+ afterEach(() => {
475
+ jest.clearAllTimers();
476
+ });
477
+
478
+ it('should disconnect messageClient, localClient, remove processors, clear interval', () => {
479
+ roborockService.stopService();
480
+ expect(mockMessageClient.disconnect).toHaveBeenCalled();
481
+ expect(mockLocalClient.disconnect).toHaveBeenCalled();
482
+ expect(roborockService.localClientMap.size).toBe(0);
483
+ expect(roborockService.messageProcessorMap.size).toBe(0);
484
+ expect(roborockService.requestDeviceStatusInterval).toBeUndefined();
485
+ });
486
+ });
487
+
488
+ describe('RoborockService - setDeviceNotify', () => {
489
+ it('should set deviceNotify callback', () => {
490
+ const roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, {} as any);
491
+ const cb = jest.fn();
492
+ roborockService.setDeviceNotify(cb);
493
+ expect(roborockService.deviceNotify).toBe(cb);
494
+ });
495
+ });
496
+
497
+ describe('RoborockService - sleep', () => {
498
+ it('should resolve after ms', async () => {
499
+ const roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, {} as any);
500
+ const start = Date.now();
501
+ await roborockService['sleep'](100);
502
+ expect(Date.now() - start).toBeGreaterThanOrEqual(0);
503
+ });
504
+ });
505
+
506
+ describe('RoborockService - auth', () => {
507
+ it('should set userdata and iotApi', () => {
508
+ const mockLogger = {} as any;
509
+ const mockIotApiFactory = jest.fn().mockReturnValue('iotApi');
510
+ const roborockService = new RoborockService(jest.fn(), mockIotApiFactory, 10, {} as any, mockLogger);
511
+ const userData = { foo: 'bar' } as any;
512
+ const result = roborockService['auth'](userData);
513
+ expect(roborockService['userdata']).toBe(userData);
514
+ expect(roborockService['iotApi']).toBe('iotApi');
515
+ expect(result).toBe(userData);
516
+ });
517
+ });
@@ -0,0 +1,69 @@
1
+ import { AnsiLogger } from 'matterbridge/logger';
2
+ import RoborockService from '../roborockService';
3
+ import { MessageProcessor } from '../roborockCommunication/broadcast/messageProcessor';
4
+ import { Device, DeviceStatus } from '../roborockCommunication';
5
+
6
+ jest.useFakeTimers();
7
+
8
+ describe('RoborockService - activateDeviceNotify', () => {
9
+ let roborockService: RoborockService;
10
+ let mockLogger: AnsiLogger;
11
+ let mockMessageProcessor: jest.Mocked<MessageProcessor>;
12
+ let mockDeviceNotify: jest.Mock;
13
+
14
+ beforeEach(() => {
15
+ mockLogger = {
16
+ debug: jest.fn(),
17
+ notice: jest.fn(),
18
+ error: jest.fn(),
19
+ warn: jest.fn(),
20
+ } as any;
21
+
22
+ mockMessageProcessor = {
23
+ getDeviceStatus: jest.fn(),
24
+ } as any;
25
+
26
+ mockDeviceNotify = jest.fn(() => {
27
+ jest.fn();
28
+ });
29
+
30
+ roborockService = new RoborockService(jest.fn(), jest.fn(), 1, {} as any, mockLogger); // refreshInterval = 1
31
+ roborockService['deviceNotify'] = mockDeviceNotify;
32
+ roborockService['getMessageProcessor'] = jest.fn().mockReturnValue(mockMessageProcessor);
33
+ roborockService['refreshInterval'] = 1;
34
+ });
35
+
36
+ it('should call getDeviceStatus periodically and notify with status message', async () => {
37
+ const duid = 'test-duid';
38
+ const device: Device = { duid } as Device;
39
+
40
+ const fakeStatus: DeviceStatus = {
41
+ errorStatus: { errorCode: 0 },
42
+ message: { battery: 80 },
43
+ } as any;
44
+
45
+ (roborockService['getMessageProcessor'] as jest.Mock).mockReturnValue(mockMessageProcessor);
46
+ mockMessageProcessor.getDeviceStatus.mockResolvedValue(fakeStatus);
47
+
48
+ await roborockService.activateDeviceNotify(device);
49
+
50
+ jest.advanceTimersByTime(2000); // 1s = 1 cycle
51
+
52
+ expect(mockLogger.debug).toHaveBeenCalledWith('Requesting device info for device', duid);
53
+ expect(mockMessageProcessor.getDeviceStatus).toHaveBeenCalledWith(duid);
54
+ });
55
+
56
+ it('should log error if message processor is not found', async () => {
57
+ const duid = 'not-found-duid';
58
+ const device: Device = { duid } as Device;
59
+
60
+ (roborockService['getMessageProcessor'] as jest.Mock).mockReturnValue(undefined);
61
+
62
+ await roborockService.activateDeviceNotify(device);
63
+
64
+ jest.advanceTimersByTime(1000); // trigger the interval
65
+
66
+ expect(mockLogger.error).toHaveBeenCalledWith('Local client not initialized');
67
+ expect(mockDeviceNotify).not.toHaveBeenCalled();
68
+ });
69
+ });