@homebridge-plugins/homebridge-tuya 3.0.0-beta.2 → 3.0.0-beta.21

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 (298) hide show
  1. package/README.md +354 -142
  2. package/config.schema.json +1221 -893
  3. package/dist/cloud/api/TuyaOpenAPI.js +108 -60
  4. package/dist/cloud/api/TuyaOpenAPI.js.map +1 -1
  5. package/dist/cloud/api/TuyaOpenMQ.js +45 -22
  6. package/dist/cloud/api/TuyaOpenMQ.js.map +1 -1
  7. package/dist/cloud/device/{TuyaDeviceManager.js → TuyaCloudDeviceManager.js} +146 -96
  8. package/dist/cloud/device/TuyaCloudDeviceManager.js.map +1 -0
  9. package/dist/cloud/device/TuyaCustomDeviceManager.js +75 -5
  10. package/dist/cloud/device/TuyaCustomDeviceManager.js.map +1 -1
  11. package/dist/cloud/device/TuyaDevice.js.map +1 -1
  12. package/dist/cloud/device/TuyaHomeDeviceManager.js +81 -13
  13. package/dist/cloud/device/TuyaHomeDeviceManager.js.map +1 -1
  14. package/dist/config.js +26 -5
  15. package/dist/config.js.map +1 -1
  16. package/dist/local/DynamicChildDiscovery.js +2 -1
  17. package/dist/local/DynamicChildDiscovery.js.map +1 -1
  18. package/dist/local/LocalDevice.js +16 -3
  19. package/dist/local/LocalDevice.js.map +1 -1
  20. package/dist/local/LocalDeviceManager.js +361 -164
  21. package/dist/local/LocalDeviceManager.js.map +1 -1
  22. package/dist/local/TuyaDiscovery.js +16 -7
  23. package/dist/local/TuyaDiscovery.js.map +1 -1
  24. package/dist/local/ZigbeeGatewayDetection.js +7 -4
  25. package/dist/local/ZigbeeGatewayDetection.js.map +1 -1
  26. package/dist/local/protocol/ProtocolUtilities.js +34 -17
  27. package/dist/local/protocol/ProtocolUtilities.js.map +1 -1
  28. package/dist/local/protocol/ProtocolV31V32.js +4 -2
  29. package/dist/local/protocol/ProtocolV31V32.js.map +1 -1
  30. package/dist/local/protocol/ProtocolV33.js +4 -2
  31. package/dist/local/protocol/ProtocolV33.js.map +1 -1
  32. package/dist/local/protocol/ProtocolV34.js +10 -5
  33. package/dist/local/protocol/ProtocolV34.js.map +1 -1
  34. package/dist/local/protocol/ProtocolV35.js +8 -4
  35. package/dist/local/protocol/ProtocolV35.js.map +1 -1
  36. package/dist/platform.js +253 -493
  37. package/dist/platform.js.map +1 -1
  38. package/dist/settings.js +0 -2
  39. package/dist/settings.js.map +1 -1
  40. package/dist/shared/TuyaDeviceManager.js +147 -0
  41. package/dist/shared/TuyaDeviceManager.js.map +1 -0
  42. package/dist/shared/TuyaHybridDeviceManager.js +215 -0
  43. package/dist/shared/TuyaHybridDeviceManager.js.map +1 -0
  44. package/dist/shared/accessory/AccessoryFactory.js +350 -0
  45. package/dist/shared/accessory/AccessoryFactory.js.map +1 -0
  46. package/dist/shared/{accessories → accessory}/AirConditionerAccessory.js +21 -15
  47. package/dist/shared/accessory/AirConditionerAccessory.js.map +1 -0
  48. package/dist/shared/{accessories → accessory}/AirPurifierAccessory.js.map +1 -1
  49. package/dist/shared/accessory/AirQualitySensorAccessory.js.map +1 -0
  50. package/dist/shared/{accessories → accessory}/BaseAccessory.js +16 -95
  51. package/dist/shared/accessory/BaseAccessory.js.map +1 -0
  52. package/dist/shared/{accessories → accessory}/BlindsAccessory.js.map +1 -1
  53. package/dist/shared/{accessories → accessory}/CameraAccessory.js +2 -2
  54. package/dist/shared/accessory/CameraAccessory.js.map +1 -0
  55. package/dist/shared/{accessories → accessory}/CarbonDioxideSensorAccessory.js.map +1 -1
  56. package/dist/shared/{accessories → accessory}/CarbonMonoxideSensorAccessory.js.map +1 -1
  57. package/dist/shared/{accessories → accessory}/ContactSensorAccessory.js.map +1 -1
  58. package/dist/shared/{accessories → accessory}/DehumidifierAccessory.js.map +1 -1
  59. package/dist/shared/{accessories → accessory}/DiffuserAccessory.js.map +1 -1
  60. package/dist/shared/{accessories → accessory}/DimmerAccessory.js.map +1 -1
  61. package/dist/shared/{accessories → accessory}/DoorbellAccessory.js.map +1 -1
  62. package/dist/shared/{accessories → accessory}/ExtractionHoodAccessory.js.map +1 -1
  63. package/dist/shared/{accessories → accessory}/FanAccessory.js.map +1 -1
  64. package/dist/shared/{accessories → accessory}/GarageDoorAccessory.js +19 -2
  65. package/dist/shared/accessory/GarageDoorAccessory.js.map +1 -0
  66. package/dist/shared/{accessories → accessory}/HeaterAccessory.js.map +1 -1
  67. package/dist/shared/{accessories → accessory}/HeaterAccessory_old.js.map +1 -1
  68. package/dist/shared/accessory/HumanPresenceSensorAccessory.js.map +1 -0
  69. package/dist/shared/{accessories → accessory}/HumidifierAccessory.js.map +1 -1
  70. package/dist/shared/accessory/IRAdapter.js +167 -0
  71. package/dist/shared/accessory/IRAdapter.js.map +1 -0
  72. package/dist/shared/accessory/IRAirConditionerAccessory.js.map +1 -0
  73. package/dist/shared/accessory/IRControlHubAccessory.js.map +1 -0
  74. package/dist/shared/accessory/IRControlHubSubAccessory.js.map +1 -0
  75. package/dist/shared/accessory/IRFanAccessory.js +90 -0
  76. package/dist/shared/accessory/IRFanAccessory.js.map +1 -0
  77. package/dist/shared/accessory/IRGenericAccessory.js.map +1 -0
  78. package/dist/shared/{accessories → accessory}/LeakSensorAccessory.js.map +1 -1
  79. package/dist/shared/{accessories → accessory}/LightAccessory.js.map +1 -1
  80. package/dist/shared/{accessories → accessory}/LightSensorAccessory.js.map +1 -1
  81. package/dist/shared/{accessories → accessory}/LocationWeatherAccessory.js +1 -1
  82. package/dist/shared/accessory/LocationWeatherAccessory.js.map +1 -0
  83. package/dist/shared/{accessories → accessory}/LockAccessory.js +3 -3
  84. package/dist/shared/{accessories → accessory}/LockAccessory.js.map +1 -1
  85. package/dist/shared/accessory/MotionSensorAccessory.js.map +1 -0
  86. package/dist/shared/accessory/OutletAccessory.js.map +1 -0
  87. package/dist/shared/{accessories → accessory}/PetFeederAccessory.js.map +1 -1
  88. package/dist/shared/{accessories → accessory}/SceneAccessory.js +1 -1
  89. package/dist/shared/accessory/SceneAccessory.js.map +1 -0
  90. package/dist/shared/accessory/SceneSwitchAccessory.js.map +1 -0
  91. package/dist/shared/accessory/SecuritySystemAccessory.js.map +1 -0
  92. package/dist/shared/{accessories → accessory}/SmokeSensorAccessory.js.map +1 -1
  93. package/dist/shared/{accessories → accessory}/SwitchAccessory.js +1 -45
  94. package/dist/shared/accessory/SwitchAccessory.js.map +1 -0
  95. package/dist/shared/accessory/TemperatureHumiditySensorAccessory.js.map +1 -0
  96. package/dist/shared/{accessories → accessory}/ThermostatAccessory.js.map +1 -1
  97. package/dist/shared/{accessories → accessory}/TowerRackAccessory.js.map +1 -1
  98. package/dist/shared/{accessories → accessory}/ValveAccessory.js.map +1 -1
  99. package/dist/shared/{accessories → accessory}/VibrationSensorAccessory.js.map +1 -1
  100. package/dist/shared/accessory/WeatherStationAccessory.js.map +1 -0
  101. package/dist/shared/accessory/WetBulbGlobeTemperatureAccessory.js.map +1 -0
  102. package/dist/shared/{accessories → accessory}/WhiteNoiseLightAccessory.js +1 -0
  103. package/dist/shared/accessory/WhiteNoiseLightAccessory.js.map +1 -0
  104. package/dist/shared/accessory/WindowAccessory.js.map +1 -0
  105. package/dist/shared/{accessories → accessory}/WindowCoveringAccessory.js.map +1 -1
  106. package/dist/shared/{accessories → accessory}/WirelessSwitchAccessory.js.map +1 -1
  107. package/dist/shared/accessory/characteristic/Active.js.map +1 -0
  108. package/dist/shared/accessory/characteristic/AirQuality.js.map +1 -0
  109. package/dist/shared/{accessories → accessory}/characteristic/CurrentRelativeHumidity.js +3 -0
  110. package/dist/shared/accessory/characteristic/CurrentRelativeHumidity.js.map +1 -0
  111. package/dist/shared/accessory/characteristic/CurrentTemperature.js.map +1 -0
  112. package/dist/shared/accessory/characteristic/CurrentWeather.js.map +1 -0
  113. package/dist/shared/accessory/characteristic/CurrentWeatherByOpenMeteo.js.map +1 -0
  114. package/dist/shared/{accessories → accessory}/characteristic/CurrentWetBulbGlobeTemperature.js +3 -2
  115. package/dist/shared/accessory/characteristic/CurrentWetBulbGlobeTemperature.js.map +1 -0
  116. package/dist/shared/{accessories → accessory}/characteristic/EnergyUsage.js +0 -5
  117. package/dist/shared/accessory/characteristic/EnergyUsage.js.map +1 -0
  118. package/dist/shared/{accessories → accessory}/characteristic/Light.js +28 -5
  119. package/dist/shared/accessory/characteristic/Light.js.map +1 -0
  120. package/dist/shared/accessory/characteristic/LightSensor.js.map +1 -0
  121. package/dist/shared/{accessories → accessory}/characteristic/LockPhysicalControls.js +3 -0
  122. package/dist/shared/accessory/characteristic/LockPhysicalControls.js.map +1 -0
  123. package/dist/shared/accessory/characteristic/MotionDetected.js.map +1 -0
  124. package/dist/shared/accessory/characteristic/Name.js.map +1 -0
  125. package/dist/shared/accessory/characteristic/OccupancyDetected.js.map +1 -0
  126. package/dist/shared/{accessories → accessory}/characteristic/On.js +1 -0
  127. package/dist/shared/accessory/characteristic/On.js.map +1 -0
  128. package/dist/shared/accessory/characteristic/OutletInUse.js.map +1 -0
  129. package/dist/shared/accessory/characteristic/ProgrammableSwitchEvent.js.map +1 -0
  130. package/dist/shared/{accessories → accessory}/characteristic/RelativeHumidityDehumidifierThreshold.js +3 -0
  131. package/dist/shared/accessory/characteristic/RelativeHumidityDehumidifierThreshold.js.map +1 -0
  132. package/dist/shared/accessory/characteristic/RotationSpeed.js.map +1 -0
  133. package/dist/shared/accessory/characteristic/SecuritySystemState.js.map +1 -0
  134. package/dist/shared/{accessories → accessory}/characteristic/SwingMode.js +3 -0
  135. package/dist/shared/accessory/characteristic/SwingMode.js.map +1 -0
  136. package/dist/shared/accessory/characteristic/TargetTemperature.js.map +1 -0
  137. package/dist/shared/accessory/characteristic/TemperatureDisplayUnits.js.map +1 -0
  138. package/dist/shared/util/ConfigHash.js +2 -2
  139. package/dist/shared/util/ConfigHash.js.map +1 -1
  140. package/dist/shared/util/ConfigMigrator.js +222 -0
  141. package/dist/shared/util/ConfigMigrator.js.map +1 -0
  142. package/dist/shared/util/FfmpegStreamingProcess.js +62 -0
  143. package/dist/shared/util/FfmpegStreamingProcess.js.map +1 -1
  144. package/dist/shared/util/InfraredTool.js +0 -2
  145. package/dist/shared/util/InfraredTool.js.map +1 -1
  146. package/dist/shared/util/Logger.js +85 -10
  147. package/dist/shared/util/Logger.js.map +1 -1
  148. package/dist/shared/util/TuyaStreamDelegate.js +194 -97
  149. package/dist/shared/util/TuyaStreamDelegate.js.map +1 -1
  150. package/dist/shared/util/util.js +59 -4
  151. package/dist/shared/util/util.js.map +1 -1
  152. package/docs/ADVANCED_OPTIONS.md +603 -0
  153. package/docs/CONFIG_GUIDE.md +616 -0
  154. package/docs/Get-Local-Keys-for-Your-Devices.md +246 -0
  155. package/{SUPPORTED_DEVICES.md → docs/SUPPORTED_DEVICES.md} +24 -3
  156. package/eslint.config.mjs +0 -1
  157. package/{jest.config.js → jest.config.ts} +5 -2
  158. package/package.json +12 -9
  159. package/test/ConfigMigrator.test.ts +1060 -0
  160. package/test/Logger.test.ts +482 -0
  161. package/test/accessoryFactory.data.test.ts +172 -0
  162. package/test/accessoryFactory.test.ts +64 -35
  163. package/test/airConditioner.test.ts +742 -0
  164. package/test/config.test.ts +17 -13
  165. package/test/custom.test.ts +2 -2
  166. package/test/errorHandling.test.ts +12 -12
  167. package/test/home.test.ts +1 -1
  168. package/test/hybridDeviceManager.test.ts +808 -0
  169. package/test/light.test.ts +250 -0
  170. package/test/localDevice.test.ts +21 -50
  171. package/test/localDeviceManager.behavior.test.ts +196 -31
  172. package/test/localDeviceManager.test.ts +124 -41
  173. package/test/mqttMessages.test.ts +33 -14
  174. package/test/platform.test.ts +337 -814
  175. package/test/platform.validation.local.test.ts +58 -119
  176. package/test/tuyaCustomDeviceManager.test.ts +106 -16
  177. package/test/tuyaDeviceManager.test.ts +636 -639
  178. package/test/tuyaDiscovery.test.ts +21 -33
  179. package/test/tuyaHomeDeviceManager.test.ts +109 -18
  180. package/test/tuyaOpenAPI.test.ts +25 -44
  181. package/test/tuyaOpenMQ.test.ts +15 -23
  182. package/test/util.test.ts +107 -7
  183. package/test/zigbee/LocalDevice.zigbee.test.ts +14 -18
  184. package/test/zigbee/LocalDeviceManager.cloud-discovery.test.ts +19 -20
  185. package/test/zigbee/LocalDeviceManager.zigbee.reconnect.test.ts +19 -20
  186. package/test/zigbee/LocalDeviceManager.zigbee.test.ts +73 -35
  187. package/test/zigbee/ZigbeeGatewayDetection.test.ts +2 -2
  188. package/ADVANCED_OPTIONS.md +0 -267
  189. package/dist/cloud/device/TuyaDeviceManager.js.map +0 -1
  190. package/dist/local/CloudLocalReconciliation.js +0 -180
  191. package/dist/local/CloudLocalReconciliation.js.map +0 -1
  192. package/dist/shared/AccessoryFactory.js +0 -276
  193. package/dist/shared/AccessoryFactory.js.map +0 -1
  194. package/dist/shared/accessories/AccessoryFactory.js +0 -305
  195. package/dist/shared/accessories/AccessoryFactory.js.map +0 -1
  196. package/dist/shared/accessories/AirConditionerAccessory.js.map +0 -1
  197. package/dist/shared/accessories/AirQualitySensorAccessory.js.map +0 -1
  198. package/dist/shared/accessories/BaseAccessory.js.map +0 -1
  199. package/dist/shared/accessories/CameraAccessory.js.map +0 -1
  200. package/dist/shared/accessories/GarageDoorAccessory.js.map +0 -1
  201. package/dist/shared/accessories/HumanPresenceSensorAccessory.js.map +0 -1
  202. package/dist/shared/accessories/IRAirConditionerAccessory.js.map +0 -1
  203. package/dist/shared/accessories/IRControlHubAccessory.js.map +0 -1
  204. package/dist/shared/accessories/IRControlHubSubAccessory.js.map +0 -1
  205. package/dist/shared/accessories/IRGenericAccessory.js.map +0 -1
  206. package/dist/shared/accessories/LocationWeatherAccessory.js.map +0 -1
  207. package/dist/shared/accessories/MotionSensorAccessory.js.map +0 -1
  208. package/dist/shared/accessories/OutletAccessory.js.map +0 -1
  209. package/dist/shared/accessories/SceneAccessory.js.map +0 -1
  210. package/dist/shared/accessories/SceneSwitchAccessory.js.map +0 -1
  211. package/dist/shared/accessories/SecuritySystemAccessory.js.map +0 -1
  212. package/dist/shared/accessories/SwitchAccessory.js.map +0 -1
  213. package/dist/shared/accessories/TemperatureHumiditySensorAccessory.js.map +0 -1
  214. package/dist/shared/accessories/WeatherStationAccessory.js.map +0 -1
  215. package/dist/shared/accessories/WetBulbGlobeTemperatureAccessory.js.map +0 -1
  216. package/dist/shared/accessories/WhiteNoiseLightAccessory.js.map +0 -1
  217. package/dist/shared/accessories/WindowAccessory.js.map +0 -1
  218. package/dist/shared/accessories/characteristic/Active.js.map +0 -1
  219. package/dist/shared/accessories/characteristic/AirQuality.js.map +0 -1
  220. package/dist/shared/accessories/characteristic/CurrentRelativeHumidity.js.map +0 -1
  221. package/dist/shared/accessories/characteristic/CurrentTemperature.js.map +0 -1
  222. package/dist/shared/accessories/characteristic/CurrentWeather.js.map +0 -1
  223. package/dist/shared/accessories/characteristic/CurrentWeatherByOpenMeteo.js.map +0 -1
  224. package/dist/shared/accessories/characteristic/CurrentWetBulbGlobeTemperature.js.map +0 -1
  225. package/dist/shared/accessories/characteristic/EnergyUsage.js.map +0 -1
  226. package/dist/shared/accessories/characteristic/Light.js.map +0 -1
  227. package/dist/shared/accessories/characteristic/LightSensor.js.map +0 -1
  228. package/dist/shared/accessories/characteristic/LockPhysicalControls.js.map +0 -1
  229. package/dist/shared/accessories/characteristic/MotionDetected.js.map +0 -1
  230. package/dist/shared/accessories/characteristic/Name.js.map +0 -1
  231. package/dist/shared/accessories/characteristic/OccupancyDetected.js.map +0 -1
  232. package/dist/shared/accessories/characteristic/On.js.map +0 -1
  233. package/dist/shared/accessories/characteristic/OutletInUse.js.map +0 -1
  234. package/dist/shared/accessories/characteristic/ProgrammableSwitchEvent.js.map +0 -1
  235. package/dist/shared/accessories/characteristic/RelativeHumidityDehumidifierThreshold.js.map +0 -1
  236. package/dist/shared/accessories/characteristic/RotationSpeed.js.map +0 -1
  237. package/dist/shared/accessories/characteristic/SecuritySystemState.js.map +0 -1
  238. package/dist/shared/accessories/characteristic/SwingMode.js.map +0 -1
  239. package/dist/shared/accessories/characteristic/TargetTemperature.js.map +0 -1
  240. package/dist/shared/accessories/characteristic/TemperatureDisplayUnits.js.map +0 -1
  241. package/test/zigbee/CloudLocalReconciliation.test.ts +0 -284
  242. package/tuya-local-configs/dev-1.hash +0 -1
  243. package/tuya-local-configs/device1.hash +0 -1
  244. package/wiki/Get-Local-Keys-for-Your-Devices.md +0 -85
  245. /package/dist/shared/{accessories → accessory}/AirPurifierAccessory.js +0 -0
  246. /package/dist/shared/{accessories → accessory}/AirQualitySensorAccessory.js +0 -0
  247. /package/dist/shared/{accessories → accessory}/BlindsAccessory.js +0 -0
  248. /package/dist/shared/{accessories → accessory}/CarbonDioxideSensorAccessory.js +0 -0
  249. /package/dist/shared/{accessories → accessory}/CarbonMonoxideSensorAccessory.js +0 -0
  250. /package/dist/shared/{accessories → accessory}/ContactSensorAccessory.js +0 -0
  251. /package/dist/shared/{accessories → accessory}/DehumidifierAccessory.js +0 -0
  252. /package/dist/shared/{accessories → accessory}/DiffuserAccessory.js +0 -0
  253. /package/dist/shared/{accessories → accessory}/DimmerAccessory.js +0 -0
  254. /package/dist/shared/{accessories → accessory}/DoorbellAccessory.js +0 -0
  255. /package/dist/shared/{accessories → accessory}/ExtractionHoodAccessory.js +0 -0
  256. /package/dist/shared/{accessories → accessory}/FanAccessory.js +0 -0
  257. /package/dist/shared/{accessories → accessory}/HeaterAccessory.js +0 -0
  258. /package/dist/shared/{accessories → accessory}/HeaterAccessory_old.js +0 -0
  259. /package/dist/shared/{accessories → accessory}/HumanPresenceSensorAccessory.js +0 -0
  260. /package/dist/shared/{accessories → accessory}/HumidifierAccessory.js +0 -0
  261. /package/dist/shared/{accessories → accessory}/IRAirConditionerAccessory.js +0 -0
  262. /package/dist/shared/{accessories → accessory}/IRControlHubAccessory.js +0 -0
  263. /package/dist/shared/{accessories → accessory}/IRControlHubSubAccessory.js +0 -0
  264. /package/dist/shared/{accessories → accessory}/IRGenericAccessory.js +0 -0
  265. /package/dist/shared/{accessories → accessory}/LeakSensorAccessory.js +0 -0
  266. /package/dist/shared/{accessories → accessory}/LightAccessory.js +0 -0
  267. /package/dist/shared/{accessories → accessory}/LightSensorAccessory.js +0 -0
  268. /package/dist/shared/{accessories → accessory}/MotionSensorAccessory.js +0 -0
  269. /package/dist/shared/{accessories → accessory}/OutletAccessory.js +0 -0
  270. /package/dist/shared/{accessories → accessory}/PetFeederAccessory.js +0 -0
  271. /package/dist/shared/{accessories → accessory}/SceneSwitchAccessory.js +0 -0
  272. /package/dist/shared/{accessories → accessory}/SecuritySystemAccessory.js +0 -0
  273. /package/dist/shared/{accessories → accessory}/SmokeSensorAccessory.js +0 -0
  274. /package/dist/shared/{accessories → accessory}/TemperatureHumiditySensorAccessory.js +0 -0
  275. /package/dist/shared/{accessories → accessory}/ThermostatAccessory.js +0 -0
  276. /package/dist/shared/{accessories → accessory}/TowerRackAccessory.js +0 -0
  277. /package/dist/shared/{accessories → accessory}/ValveAccessory.js +0 -0
  278. /package/dist/shared/{accessories → accessory}/VibrationSensorAccessory.js +0 -0
  279. /package/dist/shared/{accessories → accessory}/WeatherStationAccessory.js +0 -0
  280. /package/dist/shared/{accessories → accessory}/WetBulbGlobeTemperatureAccessory.js +0 -0
  281. /package/dist/shared/{accessories → accessory}/WindowAccessory.js +0 -0
  282. /package/dist/shared/{accessories → accessory}/WindowCoveringAccessory.js +0 -0
  283. /package/dist/shared/{accessories → accessory}/WirelessSwitchAccessory.js +0 -0
  284. /package/dist/shared/{accessories → accessory}/characteristic/Active.js +0 -0
  285. /package/dist/shared/{accessories → accessory}/characteristic/AirQuality.js +0 -0
  286. /package/dist/shared/{accessories → accessory}/characteristic/CurrentTemperature.js +0 -0
  287. /package/dist/shared/{accessories → accessory}/characteristic/CurrentWeather.js +0 -0
  288. /package/dist/shared/{accessories → accessory}/characteristic/CurrentWeatherByOpenMeteo.js +0 -0
  289. /package/dist/shared/{accessories → accessory}/characteristic/LightSensor.js +0 -0
  290. /package/dist/shared/{accessories → accessory}/characteristic/MotionDetected.js +0 -0
  291. /package/dist/shared/{accessories → accessory}/characteristic/Name.js +0 -0
  292. /package/dist/shared/{accessories → accessory}/characteristic/OccupancyDetected.js +0 -0
  293. /package/dist/shared/{accessories → accessory}/characteristic/OutletInUse.js +0 -0
  294. /package/dist/shared/{accessories → accessory}/characteristic/ProgrammableSwitchEvent.js +0 -0
  295. /package/dist/shared/{accessories → accessory}/characteristic/RotationSpeed.js +0 -0
  296. /package/dist/shared/{accessories → accessory}/characteristic/SecuritySystemState.js +0 -0
  297. /package/dist/shared/{accessories → accessory}/characteristic/TargetTemperature.js +0 -0
  298. /package/dist/shared/{accessories → accessory}/characteristic/TemperatureDisplayUnits.js +0 -0
@@ -2,24 +2,17 @@
2
2
  import { describe, expect, test, beforeEach, jest } from '@jest/globals';
3
3
  import EventEmitter from 'events';
4
4
  import TuyaDiscovery, { DiscoveryResult } from '../src/local/TuyaDiscovery';
5
+ import { ExLogger, initLogger } from '../src/shared/util/Logger';
5
6
 
6
7
  // Mock Logger
7
- jest.mock('../src/shared/util/Logger', () => ({
8
- __esModule: true,
9
- default: class Logger {
10
- log() {}
11
- info() {}
12
- warn() {}
13
- error() {}
14
- debug() {}
15
- },
16
- PrefixLogger: class PrefixLogger {
17
- constructor(public log: any, public name: string, public debug: boolean) {}
18
- info() {}
19
- warn() {}
20
- error() {}
21
- },
22
- }));
8
+ const mockLog: ExLogger = {
9
+ debug: jest.fn(),
10
+ info: jest.fn(),
11
+ warn: jest.fn(),
12
+ error: jest.fn(),
13
+ log: jest.fn(),
14
+ success: jest.fn(),
15
+ } as unknown as ExLogger;
23
16
 
24
17
  // Mock dgram
25
18
  let mockServers: Record<number, EventEmitter> = {};
@@ -38,17 +31,12 @@ jest.mock('dgram', () => ({
38
31
 
39
32
  describe('TuyaDiscovery', () => {
40
33
  let discovery: TuyaDiscovery;
41
- let mockLogger: any;
42
34
 
43
35
  beforeEach(() => {
44
- mockLogger = {
45
- log: jest.fn(),
46
- info: jest.fn(),
47
- warn: jest.fn(),
48
- error: jest.fn(),
49
- };
50
-
51
- discovery = new TuyaDiscovery(mockLogger, false);
36
+ jest.clearAllMocks();
37
+ initLogger(mockLog);
38
+
39
+ discovery = new TuyaDiscovery(false);
52
40
  });
53
41
 
54
42
  describe('initialization', () => {
@@ -62,12 +50,12 @@ describe('TuyaDiscovery', () => {
62
50
  });
63
51
 
64
52
  test('initializes with debug disabled', () => {
65
- const disc = new TuyaDiscovery(mockLogger, false);
53
+ const disc = new TuyaDiscovery(false);
66
54
  expect(disc).toBeDefined();
67
55
  });
68
56
 
69
57
  test('initializes with debug enabled', () => {
70
- const disc = new TuyaDiscovery(mockLogger, true);
58
+ const disc = new TuyaDiscovery(true);
71
59
  expect(disc).toBeDefined();
72
60
  });
73
61
 
@@ -361,20 +349,20 @@ describe('TuyaDiscovery', () => {
361
349
 
362
350
  describe('logging and debugging', () => {
363
351
  test('creates logger with debug disabled', () => {
364
- const disc = new TuyaDiscovery(mockLogger, false);
352
+ const disc = new TuyaDiscovery(false);
365
353
  expect(disc.log).toBeDefined();
366
354
  });
367
355
 
368
356
  test('creates logger with debug enabled', () => {
369
- const disc = new TuyaDiscovery(mockLogger, true);
357
+ const disc = new TuyaDiscovery(true);
370
358
  expect(disc.log).toBeDefined();
371
359
  });
372
360
  });
373
361
 
374
362
  describe('multiple discovery instances', () => {
375
363
  test('multiple instances maintain separate state', () => {
376
- const disc1 = new TuyaDiscovery(mockLogger, false);
377
- const disc2 = new TuyaDiscovery(mockLogger, false);
364
+ const disc1 = new TuyaDiscovery(false);
365
+ const disc2 = new TuyaDiscovery(false);
378
366
 
379
367
  disc1['discovered'].set('device_1', '192.168.1.100');
380
368
  disc2['discovered'].set('device_2', '192.168.1.101');
@@ -386,8 +374,8 @@ describe('TuyaDiscovery', () => {
386
374
  });
387
375
 
388
376
  test('multiple instances have separate servers', () => {
389
- const disc1 = new TuyaDiscovery(mockLogger, false);
390
- const disc2 = new TuyaDiscovery(mockLogger, false);
377
+ const disc1 = new TuyaDiscovery(false);
378
+ const disc2 = new TuyaDiscovery(false);
391
379
 
392
380
  disc1.start();
393
381
  disc2.start();
@@ -1,24 +1,18 @@
1
1
  /* eslint-disable no-console */
2
2
  import { describe, expect, test, beforeEach, jest } from '@jest/globals';
3
3
  import TuyaHomeDeviceManager from '../src/cloud/device/TuyaHomeDeviceManager';
4
- import TuyaDevice from '../src/cloud/device/TuyaDevice';
4
+ import { TuyaPlatformHomeConfig, TuyaPluginMode } from '../src/config';
5
+ import { ExLogger, initLogger } from '../src/shared/util/Logger';
5
6
 
6
7
  // Mock Logger
7
- jest.mock('../src/shared/util/Logger', () => ({
8
- __esModule: true,
9
- default: class Logger {
10
- info() {}
11
- warn() {}
12
- error() {}
13
- debug() {}
14
- },
15
- PrefixLogger: class PrefixLogger {
16
- constructor(public log: any, public name: string, public debug: boolean) {}
17
- info() {}
18
- warn() {}
19
- error() {}
20
- },
21
- }));
8
+ const mockLog: ExLogger = {
9
+ debug: jest.fn(),
10
+ info: jest.fn(),
11
+ warn: jest.fn(),
12
+ error: jest.fn(),
13
+ log: jest.fn(),
14
+ success: jest.fn(),
15
+ } as unknown as ExLogger;
22
16
 
23
17
  // Mock TuyaOpenMQ
24
18
  jest.mock('../src/cloud/api/TuyaOpenMQ', () => {
@@ -33,8 +27,11 @@ jest.mock('../src/cloud/api/TuyaOpenMQ', () => {
33
27
  describe('TuyaHomeDeviceManager', () => {
34
28
  let manager: TuyaHomeDeviceManager;
35
29
  let mockAPI: any;
30
+ let mockConfig: any;
36
31
 
37
32
  beforeEach(() => {
33
+ jest.clearAllMocks();
34
+ initLogger(mockLog);
38
35
  mockAPI = {
39
36
  log: { log: console.log } as any,
40
37
  post: jest.fn(),
@@ -42,7 +39,25 @@ describe('TuyaHomeDeviceManager', () => {
42
39
  tokenInfo: { uid: 'test_uid' },
43
40
  };
44
41
 
45
- manager = new TuyaHomeDeviceManager(mockAPI, false);
42
+ mockConfig = {
43
+ platform: 'TuyaPlatform',
44
+ name: 'Tuya',
45
+ mode: 'cloud',
46
+ cloud: {
47
+ projectType: '2',
48
+ accessId: 'test_id',
49
+ accessKey: 'test_key',
50
+ countryCode: 1,
51
+ username: 'user@example.com',
52
+ password: 'password',
53
+ appSchema: 'tuyaSmart',
54
+ generateWeatherAccessory: false,
55
+ weatherAPI: '',
56
+ forceIPv4: false,
57
+ },
58
+ };
59
+
60
+ manager = new TuyaHomeDeviceManager(mockAPI, mockConfig.cloud, false);
46
61
  });
47
62
 
48
63
  describe('initialization', () => {
@@ -57,7 +72,7 @@ describe('TuyaHomeDeviceManager', () => {
57
72
  });
58
73
 
59
74
  test('supports debug mode', () => {
60
- const debugManager = new TuyaHomeDeviceManager(mockAPI, true);
75
+ const debugManager = new TuyaHomeDeviceManager(mockAPI, mockConfig.cloud, true);
61
76
  expect(debugManager.debug).toBe(true);
62
77
  });
63
78
 
@@ -537,4 +552,80 @@ describe('TuyaHomeDeviceManager', () => {
537
552
  }
538
553
  });
539
554
  });
555
+
556
+ describe('get device config', () => {
557
+ test('resolves device config precedence: device, then product, then global', () => {
558
+ const config = {
559
+ platform: 'TuyaPlatform',
560
+ name: 'Tuya',
561
+ mode: 'cloud',
562
+ cloud: {
563
+ projectType: '2',
564
+ accessId: 'id',
565
+ accessKey: 'key',
566
+ countryCode: 1,
567
+ username: 'user@example.com',
568
+ password: 'secret',
569
+ appSchema: 'tuyaSmart',
570
+ generateWeatherAccessory: false,
571
+ weatherAPI: '',
572
+ deviceOverrides: [
573
+ { id: 'global', category: 'global-cat', configFor: TuyaPluginMode.cloud },
574
+ { id: 'prod-1', category: 'product-cat', configFor: TuyaPluginMode.cloud },
575
+ { id: 'device-1', category: 'device-cat', configFor: TuyaPluginMode.cloud },
576
+ ],
577
+ } as TuyaPlatformHomeConfig,
578
+ };
579
+
580
+ const deviceManager = new TuyaHomeDeviceManager(mockAPI, config.cloud, false);
581
+
582
+ const exact = deviceManager.getDeviceConfig({ id: 'device-1', uuid: 'uuid-1', product_id: 'prod-1' } as any);
583
+ const byProduct = deviceManager.getDeviceConfig({ id: 'device-2', uuid: 'uuid-2', product_id: 'prod-1' } as any);
584
+ const fallback = deviceManager.getDeviceConfig({ id: 'device-3', uuid: 'uuid-3', product_id: 'prod-3' } as any);
585
+
586
+ expect(exact?.category).toBe('device-cat');
587
+ expect(byProduct?.category).toBe('product-cat');
588
+ expect(fallback?.category).toBe('global-cat');
589
+ });
590
+ });
591
+
592
+ describe('get device config', () => {
593
+ test('finds schema config case-insensitively', () => {
594
+ const config = {
595
+ platform: 'TuyaPlatform',
596
+ name: 'Tuya',
597
+ mode: 'cloud',
598
+ cloud: {
599
+ projectType: '2',
600
+ accessId: 'id',
601
+ accessKey: 'key',
602
+ countryCode: 1,
603
+ username: 'user@example.com',
604
+ password: 'secret',
605
+ appSchema: 'tuyaSmart',
606
+ generateWeatherAccessory: false,
607
+ weatherAPI: '',
608
+ deviceOverrides: [
609
+ {
610
+ id: 'device-1',
611
+ schema: [
612
+ { code: 'switch_1', newCode: 'new_switch_1', hidden: true },
613
+ ],
614
+ configFor: TuyaPluginMode.cloud
615
+ },
616
+ ],
617
+ } as TuyaPlatformHomeConfig,
618
+ };
619
+
620
+ const device = { id: 'device-1', uuid: 'device-1', product_id: 'prod-1' } as any;
621
+
622
+ const deviceManager = new TuyaHomeDeviceManager(mockAPI, config.cloud, false);
623
+ const result = deviceManager!.getDeviceSchemaConfig(device, 'SWITCH_1');
624
+
625
+ expect(result).toBeDefined();
626
+ expect(result?.code).toBe('switch_1');
627
+ expect((result as any).newCode).toBe('new_switch_1');
628
+ expect(result?.hidden).toBe(true);
629
+ });
630
+ });
540
631
  });
@@ -1,24 +1,22 @@
1
1
  /* eslint-disable no-console */
2
2
  import { describe, expect, test, beforeEach, jest } from '@jest/globals';
3
3
  import TuyaOpenAPI, { TuyaOpenAPIResponse } from '../src/cloud/api/TuyaOpenAPI';
4
+ import { ExLogger, initLogger } from '../src/shared/util/Logger';
4
5
 
5
6
  // Mock Logger
6
- jest.mock('../src/shared/util/Logger', () => ({
7
- __esModule: true,
8
- default: class Logger {
9
- log() {}
10
- info() {}
11
- warn() {}
12
- error() {}
13
- },
14
- PrefixLogger: class PrefixLogger {
15
- constructor(public log: any, public name: string, public debug: boolean) {}
16
- },
17
- }));
7
+ const mockLog: ExLogger = {
8
+ debug: jest.fn(),
9
+ info: jest.fn(),
10
+ warn: jest.fn(),
11
+ error: jest.fn(),
12
+ log: jest.fn(),
13
+ success: jest.fn(),
14
+ } as unknown as ExLogger;
18
15
 
19
16
  // Mock https module
20
17
  jest.mock('https', () => ({
21
18
  request: jest.fn(),
19
+ Agent: jest.fn(),
22
20
  }));
23
21
 
24
22
  // Mock util
@@ -32,18 +30,12 @@ describe('TuyaOpenAPI', () => {
32
30
  let mockLogger: any;
33
31
 
34
32
  beforeEach(() => {
35
- mockLogger = {
36
- log: jest.fn(),
37
- info: jest.fn(),
38
- warn: jest.fn(),
39
- error: jest.fn(),
40
- };
33
+ initLogger(mockLog);
41
34
 
42
35
  api = new TuyaOpenAPI(
43
36
  TuyaOpenAPI.Endpoints.AMERICA,
44
37
  'test_access_id',
45
38
  'test_access_key',
46
- mockLogger,
47
39
  'en',
48
40
  false
49
41
  );
@@ -88,7 +80,8 @@ describe('TuyaOpenAPI', () => {
88
80
  });
89
81
 
90
82
  test('creates logger with API name', () => {
91
- expect(api.log).toBeDefined();
83
+ expect(api['log']).toBeDefined();
84
+ expect((api['log'] as any)['prefix']).toEqual('TuyaOpenAPI');
92
85
  });
93
86
  });
94
87
 
@@ -150,8 +143,7 @@ describe('TuyaOpenAPI', () => {
150
143
  const testApi = new TuyaOpenAPI(
151
144
  TuyaOpenAPI.Endpoints.AMERICA,
152
145
  'my_access_id',
153
- 'my_access_key',
154
- mockLogger
146
+ 'my_access_key'
155
147
  );
156
148
  expect(testApi.accessId).toBe('my_access_id');
157
149
  });
@@ -160,8 +152,7 @@ describe('TuyaOpenAPI', () => {
160
152
  const testApi = new TuyaOpenAPI(
161
153
  TuyaOpenAPI.Endpoints.AMERICA,
162
154
  'my_access_id',
163
- 'my_access_key',
164
- mockLogger
155
+ 'my_access_key'
165
156
  );
166
157
  expect(testApi.accessKey).toBe('my_access_key');
167
158
  });
@@ -170,14 +161,12 @@ describe('TuyaOpenAPI', () => {
170
161
  const api1 = new TuyaOpenAPI(
171
162
  TuyaOpenAPI.Endpoints.AMERICA,
172
163
  'id1',
173
- 'key1',
174
- mockLogger
164
+ 'key1'
175
165
  );
176
166
  const api2 = new TuyaOpenAPI(
177
167
  TuyaOpenAPI.Endpoints.AMERICA,
178
168
  'id2',
179
- 'key2',
180
- mockLogger
169
+ 'key2'
181
170
  );
182
171
 
183
172
  expect(api1.accessId).not.toBe(api2.accessId);
@@ -261,8 +250,7 @@ describe('TuyaOpenAPI', () => {
261
250
  const apiEU = new TuyaOpenAPI(
262
251
  TuyaOpenAPI.Endpoints.EUROPE,
263
252
  'id',
264
- 'key',
265
- mockLogger
253
+ 'key'
266
254
  );
267
255
  expect(apiEU.endpoint).toBe(TuyaOpenAPI.Endpoints.EUROPE);
268
256
  });
@@ -272,8 +260,7 @@ describe('TuyaOpenAPI', () => {
272
260
  TuyaOpenAPI.Endpoints.CHINA,
273
261
  'id',
274
262
  'key',
275
- mockLogger,
276
- 'zh'
263
+ 'zh',
277
264
  );
278
265
  expect(apiZH.lang).toBe('zh');
279
266
  });
@@ -282,8 +269,7 @@ describe('TuyaOpenAPI', () => {
282
269
  const apiDefault = new TuyaOpenAPI(
283
270
  TuyaOpenAPI.Endpoints.AMERICA,
284
271
  'id',
285
- 'key',
286
- mockLogger
272
+ 'key'
287
273
  );
288
274
  expect(apiDefault.lang).toBe('en');
289
275
  });
@@ -293,9 +279,8 @@ describe('TuyaOpenAPI', () => {
293
279
  TuyaOpenAPI.Endpoints.AMERICA,
294
280
  'id',
295
281
  'key',
296
- mockLogger,
297
282
  'en',
298
- true
283
+ true,
299
284
  );
300
285
  expect(apiDebug.debug).toBe(true);
301
286
  });
@@ -306,14 +291,12 @@ describe('TuyaOpenAPI', () => {
306
291
  const api1 = new TuyaOpenAPI(
307
292
  TuyaOpenAPI.Endpoints.AMERICA,
308
293
  'id1',
309
- 'key1',
310
- mockLogger
294
+ 'key1'
311
295
  );
312
296
  const api2 = new TuyaOpenAPI(
313
297
  TuyaOpenAPI.Endpoints.EUROPE,
314
298
  'id2',
315
- 'key2',
316
- mockLogger
299
+ 'key2'
317
300
  );
318
301
 
319
302
  api1.assetIDArr.push('asset_1');
@@ -328,14 +311,12 @@ describe('TuyaOpenAPI', () => {
328
311
  const api1 = new TuyaOpenAPI(
329
312
  TuyaOpenAPI.Endpoints.AMERICA,
330
313
  'id1',
331
- 'key1',
332
- mockLogger
314
+ 'key1'
333
315
  );
334
316
  const api2 = new TuyaOpenAPI(
335
317
  TuyaOpenAPI.Endpoints.EUROPE,
336
318
  'id2',
337
- 'key2',
338
- mockLogger
319
+ 'key2'
339
320
  );
340
321
 
341
322
  api1.deviceArr = [{ id: 'dev1' }];
@@ -7,24 +7,14 @@ jest.mock('mqtt', () => ({
7
7
  }));
8
8
 
9
9
  // Mock Logger
10
- jest.mock('../src/shared/util/Logger', () => ({
11
- __esModule: true,
12
- default: class Logger {
13
- log = jest.fn();
14
- info = jest.fn();
15
- warn = jest.fn();
16
- error = jest.fn();
17
- debug = jest.fn();
18
- },
19
- PrefixLogger: class PrefixLogger {
20
- log = jest.fn();
21
- info = jest.fn();
22
- warn = jest.fn();
23
- error = jest.fn();
24
- debug = jest.fn();
25
- constructor(public logger: any, public name: string, public isDebug: boolean) {}
26
- },
27
- }));
10
+ const mockLog: ExLogger = {
11
+ debug: jest.fn(),
12
+ info: jest.fn(),
13
+ warn: jest.fn(),
14
+ error: jest.fn(),
15
+ log: jest.fn(),
16
+ success: jest.fn(),
17
+ } as unknown as ExLogger;
28
18
 
29
19
  // Mock util
30
20
  jest.mock('../src/shared/util/util', () => ({
@@ -38,6 +28,7 @@ jest.mock('../src/cloud/device/TuyaDevice', () => ({
38
28
 
39
29
  import TuyaOpenMQ from '../src/cloud/api/TuyaOpenMQ';
40
30
  import mqtt from 'mqtt';
31
+ import { ExLogger, initLogger } from '../src/shared/util/Logger';
41
32
 
42
33
  describe('TuyaOpenMQ', () => {
43
34
  let mq: TuyaOpenMQ;
@@ -46,6 +37,7 @@ describe('TuyaOpenMQ', () => {
46
37
 
47
38
  beforeEach(() => {
48
39
  jest.clearAllMocks();
40
+ initLogger(mockLog);
49
41
 
50
42
  // Create mock MQTT client
51
43
  mockClient = {
@@ -64,7 +56,7 @@ describe('TuyaOpenMQ', () => {
64
56
  post: jest.fn(),
65
57
  } as any;
66
58
 
67
- mq = new TuyaOpenMQ(mockAPI, undefined, false);
59
+ mq = new TuyaOpenMQ(mockAPI, false);
68
60
  });
69
61
 
70
62
  describe('initialization', () => {
@@ -274,18 +266,18 @@ describe('TuyaOpenMQ', () => {
274
266
  });
275
267
 
276
268
  test('debug flag can be true', () => {
277
- const debugMQ = new TuyaOpenMQ(mockAPI, undefined, true);
269
+ const debugMQ = new TuyaOpenMQ(mockAPI, true);
278
270
  expect(debugMQ.debug).toBe(true);
279
271
  });
280
272
 
281
273
  test('debug flag can be false', () => {
282
- const noDebugMQ = new TuyaOpenMQ(mockAPI, undefined, false);
274
+ const noDebugMQ = new TuyaOpenMQ(mockAPI, false);
283
275
  expect(noDebugMQ.debug).toBe(false);
284
276
  });
285
277
 
286
278
  test('link ID is unique between instances', () => {
287
- const mq1 = new TuyaOpenMQ(mockAPI, undefined, false);
288
- const mq2 = new TuyaOpenMQ(mockAPI, undefined, false);
279
+ const mq1 = new TuyaOpenMQ(mockAPI, false);
280
+ const mq2 = new TuyaOpenMQ(mockAPI, false);
289
281
 
290
282
  expect(mq1.linkId).toBeDefined();
291
283
  expect(mq2.linkId).toBeDefined();
package/test/util.test.ts CHANGED
@@ -1,30 +1,49 @@
1
- import { describe, expect, test } from '@jest/globals';
1
+ import { describe, expect, jest, test } from '@jest/globals';
2
2
  import {
3
3
  sanitizeName,
4
4
  remap,
5
5
  limit,
6
6
  deepEqual,
7
+ deepClone,
7
8
  debounce,
8
9
  toHapProperty,
10
+ createDelegate,
9
11
  } from '../src/shared/util/util';
10
12
  import { TuyaDeviceSchemaProperty } from '../src/cloud/device/TuyaDevice';
11
13
 
14
+ // based on https://github.com/homebridge/HAP-NodeJS/blob/843453605a949f50e2e26aa7ab74364edcd8137d/src/lib/util/checkName.ts#L23
15
+ function isValidName(value: any) {
16
+ if (
17
+ typeof value === "string" &&
18
+ !/^[\p{L}\p{N}][\p{L}\p{N}\p{Zs}\u2019'&!._:;()/,-]*[\p{L}\p{N}]$/u.test(value)
19
+ ) {
20
+ return false;
21
+ } else {
22
+ return true;
23
+ }
24
+ }
25
+
12
26
  describe('sanitizeName', () => {
13
27
  test('removes underscores and collapses to spaces', () => {
14
28
  expect(sanitizeName('switch_1')).toBe('switch 1');
29
+ expect(isValidName(sanitizeName('switch_1'))).toBe(true);
15
30
  });
16
31
 
17
32
  test('preserves ASCII alphanumeric and apostrophes', () => {
18
33
  expect(sanitizeName("John's Lamp")).toBe("John's Lamp");
34
+ expect(isValidName(sanitizeName("John's Lamp"))).toBe(true);
19
35
  });
20
36
 
21
37
  test('preserves accented and non-Latin letters (Unicode letters)', () => {
22
38
  expect(sanitizeName('Café')).toBe('Café');
23
39
  expect(sanitizeName('Тест123')).toBe('Тест123');
40
+ expect(isValidName(sanitizeName('Café'))).toBe(true);
41
+ expect(isValidName(sanitizeName('Тест123'))).toBe(true);
24
42
  });
25
43
 
26
44
  test('preserves CJK characters', () => {
27
45
  expect(sanitizeName('中文测试')).toBe('中文测试');
46
+ expect(isValidName(sanitizeName('中文测试'))).toBe(true);
28
47
  });
29
48
 
30
49
  test('returns undefined if sanitized name is empty or invalid', () => {
@@ -40,25 +59,29 @@ describe('sanitizeName', () => {
40
59
  test('handles mixed content correctly', () => {
41
60
  expect(sanitizeName('device_123_name')).toBe('device 123 name');
42
61
  expect(sanitizeName('My-Device-Name')).toBe('My Device Name');
62
+ expect(isValidName(sanitizeName('device_123_name'))).toBe(true);
63
+ expect(isValidName(sanitizeName('My-Device-Name'))).toBe(true);
43
64
  });
44
65
 
45
66
  test('collapses multiple consecutive spaces', () => {
46
67
  expect(sanitizeName('name with spaces')).toBe('name with spaces');
68
+ expect(isValidName(sanitizeName('name with spaces'))).toBe(true);
47
69
  });
48
70
 
49
71
  test('trims leading and trailing spaces', () => {
50
72
  expect(sanitizeName(' My Device ')).toBe('My Device');
51
- });
52
-
53
- test('rejects names that start or end with non-alphanumeric', () => {
54
- expect(sanitizeName('_device')).toBeUndefined();
55
- expect(sanitizeName('device_')).toBeUndefined();
56
- expect(sanitizeName('_device_')).toBeUndefined();
73
+ expect(isValidName(sanitizeName(' My Device '))).toBe(true);
57
74
  });
58
75
 
59
76
  test('handles single character names', () => {
60
77
  expect(sanitizeName('A')).toBeUndefined(); // Single char fails the regex
61
78
  expect(sanitizeName('AB')).toBe('AB');
79
+ expect(isValidName(sanitizeName('AB'))).toBe(true);
80
+ });
81
+
82
+ test('name with braces', () => {
83
+ expect(sanitizeName('AB(C)')).toBe('AB C');
84
+ expect(isValidName(sanitizeName('AB(C)'))).toBe(true);
62
85
  });
63
86
  });
64
87
 
@@ -170,6 +193,25 @@ describe('deepEqual', () => {
170
193
  });
171
194
  });
172
195
 
196
+ describe('deepClone', () => {
197
+ test('clones nested objects and arrays', () => {
198
+ const value = { a: 1, b: { c: [2, 3] } };
199
+ const cloned = deepClone(value);
200
+
201
+ expect(cloned).toEqual(value);
202
+ expect(cloned).not.toBe(value);
203
+ expect(cloned.b).not.toBe(value.b);
204
+ expect(cloned.b.c).not.toBe(value.b.c);
205
+ });
206
+
207
+ test('preserves primitives and null', () => {
208
+ expect(deepClone('test')).toBe('test');
209
+ expect(deepClone(42)).toBe(42);
210
+ expect(deepClone(null)).toBeNull();
211
+ expect(deepClone(undefined)).toBeUndefined();
212
+ });
213
+ });
214
+
173
215
  describe('debounce', () => {
174
216
  test('debounces function execution', (done) => {
175
217
  let callCount = 0;
@@ -316,3 +358,61 @@ describe('toHapProperty', () => {
316
358
  expect(typeof result).toBe('object');
317
359
  });
318
360
  });
361
+
362
+ class DelegateTargetClass {
363
+ do(x: number): number;
364
+ do(x: number, y: number): number;
365
+ do(x: any, y?: any): any {
366
+ if (y === undefined) {
367
+ return `original1:${x}`;
368
+ }
369
+ return `original2:${x},${y}`;
370
+ }
371
+ }
372
+ describe("createDelegate with overloaded methods (different arg count)", () => {
373
+ test("overrides both overload signatures", () => {
374
+ const worker = new DelegateTargetClass();
375
+
376
+ const delegate = createDelegate(worker, {
377
+ do: ((original: any) => (...args: any[]) => {
378
+ return `override:${args.join(",")}`;
379
+ }) as any,
380
+ });
381
+
382
+ expect(delegate.do(10)).toBe("override:10");
383
+ expect(delegate.do(10, 20)).toBe("override:10,20");
384
+ });
385
+
386
+ test("overrides only the single-argument overload and delegates the two-argument overload", () => {
387
+ const worker = new DelegateTargetClass();
388
+
389
+ const delegate = createDelegate(worker, {
390
+ do: ((original: any) => (...args: any[]) => {
391
+ if (args.length === 1) {
392
+ return `override1:${args[0]}`;
393
+ }
394
+ return original(...args);
395
+ }) as any,
396
+ });
397
+
398
+ expect(delegate.do(10)).toBe("override1:10");
399
+ expect(delegate.do(10, 20)).toBe("original2:10,20");
400
+ });
401
+
402
+ test("overrides only the two-argument overload and delegates the single-argument overload", () => {
403
+ const worker = new DelegateTargetClass();
404
+
405
+ const delegate = createDelegate(worker, {
406
+ do: ((original: any) => (...args: any[]) => {
407
+ if (args.length === 2) {
408
+ return `override2:${args[0]},${args[1]}`;
409
+ }
410
+ return original(...args);
411
+ }) as any,
412
+ });
413
+
414
+ expect(delegate.do(10)).toBe("original1:10");
415
+ expect(delegate.do(10, 20)).toBe("override2:10,20");
416
+ });
417
+
418
+ });