@switchbot/homebridge-switchbot 5.0.0-beta.52 → 5.0.0-beta.53
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/dist/devices-matter/RoboticVacuumAccessory.d.ts +28 -51
- package/dist/devices-matter/RoboticVacuumAccessory.d.ts.map +1 -1
- package/dist/devices-matter/RoboticVacuumAccessory.js +256 -258
- package/dist/devices-matter/RoboticVacuumAccessory.js.map +1 -1
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.d.ts +2 -0
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.d.ts.map +1 -0
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.js +366 -0
- package/dist/test/matter/devices-matter/roboticVacuumAccessory.test.js.map +1 -0
- package/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/src/devices-matter/RoboticVacuumAccessory.ts +308 -309
- package/src/test/matter/devices-matter/roboticVacuumAccessory.test.ts +453 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import type { API, Logger } from 'homebridge'
|
|
2
|
+
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
// Mock the base accessory
|
|
6
|
+
vi.mock('../../../devices-matter/BaseMatterAccessory.js', () => ({
|
|
7
|
+
BaseMatterAccessory: class {
|
|
8
|
+
api: any
|
|
9
|
+
log: any
|
|
10
|
+
config: any
|
|
11
|
+
context: any
|
|
12
|
+
|
|
13
|
+
constructor(api: API, log: Logger, config: any) {
|
|
14
|
+
this.api = api
|
|
15
|
+
this.log = log
|
|
16
|
+
this.config = config
|
|
17
|
+
this.context = config.context
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
logInfo = vi.fn()
|
|
21
|
+
logWarn = vi.fn()
|
|
22
|
+
logError = vi.fn()
|
|
23
|
+
sendOpenAPICommand = vi.fn(async () => {})
|
|
24
|
+
updateState = vi.fn()
|
|
25
|
+
},
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
describe('roboticVacuumAccessory', () => {
|
|
29
|
+
let mockAPI: any
|
|
30
|
+
let mockLogger: Logger
|
|
31
|
+
let RoboticVacuumAccessory: any
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
// Clear all mocks
|
|
35
|
+
vi.clearAllMocks()
|
|
36
|
+
|
|
37
|
+
// Mock API
|
|
38
|
+
mockAPI = {
|
|
39
|
+
matter: {
|
|
40
|
+
uuid: {
|
|
41
|
+
generate: vi.fn((serial: string) => `uuid-${serial}`),
|
|
42
|
+
},
|
|
43
|
+
deviceTypes: {
|
|
44
|
+
RoboticVacuumCleaner: 'RoboticVacuumCleaner',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Mock Logger
|
|
50
|
+
mockLogger = {
|
|
51
|
+
info: vi.fn(),
|
|
52
|
+
warn: vi.fn(),
|
|
53
|
+
error: vi.fn(),
|
|
54
|
+
debug: vi.fn(),
|
|
55
|
+
} as any
|
|
56
|
+
|
|
57
|
+
// Dynamically import the class after mocks are set up
|
|
58
|
+
const module = await import('../../../devices-matter/RoboticVacuumAccessory.js')
|
|
59
|
+
RoboticVacuumAccessory = module.RoboticVacuumAccessory
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
describe('model detection', () => {
|
|
63
|
+
it('should detect S1 model from Robot Vacuum Cleaner S1', () => {
|
|
64
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
65
|
+
context: { deviceType: 'Robot Vacuum Cleaner S1' },
|
|
66
|
+
})
|
|
67
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
|
|
68
|
+
expect((vacuum as any).capabilities.suctionKind).toBe('powLevel-0-3')
|
|
69
|
+
expect((vacuum as any).capabilities.pause).toBe(false)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should detect S1 Plus model', () => {
|
|
73
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
74
|
+
context: { deviceType: 'Robot Vacuum Cleaner S1 Plus' },
|
|
75
|
+
})
|
|
76
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
|
|
77
|
+
expect((vacuum as any).capabilities.pause).toBe(false)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should detect S1 Pro model', () => {
|
|
81
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
82
|
+
context: { deviceType: 'Robot Vacuum Cleaner S1 Pro' },
|
|
83
|
+
})
|
|
84
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should detect S1 Mini model', () => {
|
|
88
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
89
|
+
context: { deviceType: 'Robot Vacuum Cleaner S1 Mini' },
|
|
90
|
+
})
|
|
91
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should detect WoSweeper model', () => {
|
|
95
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
96
|
+
context: { deviceType: 'WoSweeper' },
|
|
97
|
+
})
|
|
98
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
|
|
99
|
+
expect((vacuum as any).capabilities.suctionKind).toBe('powLevel-0-3')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('should detect WoSweeperMini model', () => {
|
|
103
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
104
|
+
context: { deviceType: 'WoSweeperMini' },
|
|
105
|
+
})
|
|
106
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should detect K10+ model', () => {
|
|
110
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
111
|
+
context: { deviceType: 'K10+' },
|
|
112
|
+
})
|
|
113
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
|
|
114
|
+
expect((vacuum as any).capabilities.pause).toBe(false)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should detect K10+ Pro model', () => {
|
|
118
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
119
|
+
context: { deviceType: 'K10+ Pro' },
|
|
120
|
+
})
|
|
121
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
|
|
122
|
+
expect((vacuum as any).capabilities.pause).toBe(false)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should detect K10+ Pro Combo model with mop capability', () => {
|
|
126
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
127
|
+
context: { deviceType: 'Robot Vacuum Cleaner K10+ Pro Combo' },
|
|
128
|
+
})
|
|
129
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-or-mop')
|
|
130
|
+
expect((vacuum as any).capabilities.suctionKind).toBe('fanLevel-1-4')
|
|
131
|
+
expect((vacuum as any).capabilities.pause).toBe(true)
|
|
132
|
+
expect((vacuum as any).capabilities.waterLevel).toBe(false)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should detect S10 model with vacuum+mop and advanced commands', () => {
|
|
136
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
137
|
+
context: { deviceType: 'Robot Vacuum Cleaner S10' },
|
|
138
|
+
})
|
|
139
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-or-vacmop')
|
|
140
|
+
expect((vacuum as any).capabilities.suctionKind).toBe('fanLevel-1-4')
|
|
141
|
+
expect((vacuum as any).capabilities.pause).toBe(true)
|
|
142
|
+
expect((vacuum as any).capabilities.waterLevel).toBe(true)
|
|
143
|
+
expect((vacuum as any).capabilities.advancedCommands?.selfClean).toBe(true)
|
|
144
|
+
expect((vacuum as any).capabilities.advancedCommands?.addWaterForHumi).toBe(true)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('should detect S20 model with vacuum+mop and advanced commands', () => {
|
|
148
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
149
|
+
context: { deviceType: 'Robot Vacuum Cleaner S20' },
|
|
150
|
+
})
|
|
151
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-or-vacmop')
|
|
152
|
+
expect((vacuum as any).capabilities.advancedCommands?.selfClean).toBe(true)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('should detect K11+ model', () => {
|
|
156
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
157
|
+
context: { deviceType: 'Robot Vacuum Cleaner K11+' },
|
|
158
|
+
})
|
|
159
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-or-mop')
|
|
160
|
+
expect((vacuum as any).capabilities.waterLevel).toBe(true)
|
|
161
|
+
expect((vacuum as any).capabilities.advancedCommands?.setVolume).toBe(true)
|
|
162
|
+
expect((vacuum as any).capabilities.advancedCommands?.selfClean).toBeUndefined()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('should detect K20+ Pro model', () => {
|
|
166
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
167
|
+
context: { deviceType: 'Robot Vacuum Cleaner K20 Plus Pro' },
|
|
168
|
+
})
|
|
169
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-or-mop')
|
|
170
|
+
expect((vacuum as any).capabilities.pause).toBe(true)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('should default to basic vacuum for unknown models', () => {
|
|
174
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
175
|
+
context: { deviceType: 'Unknown Model' },
|
|
176
|
+
})
|
|
177
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
|
|
178
|
+
expect((vacuum as any).capabilities.pause).toBe(false)
|
|
179
|
+
expect((vacuum as any).capabilities.suctionKind).toBe('powLevel-0-3')
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('should handle missing context', () => {
|
|
183
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {})
|
|
184
|
+
expect((vacuum as any).capabilities.cleanAction).toBe('vacuum-only')
|
|
185
|
+
expect((vacuum as any).capabilities.pause).toBe(false)
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe('capability matrix', () => {
|
|
190
|
+
it('should configure correct clusters for basic vacuum (S1)', () => {
|
|
191
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
192
|
+
context: { deviceType: 'Robot Vacuum Cleaner S1' },
|
|
193
|
+
})
|
|
194
|
+
const clusters = (vacuum as any).config.clusters
|
|
195
|
+
|
|
196
|
+
// Should have rvcRunMode with Idle and Cleaning only
|
|
197
|
+
expect(clusters.rvcRunMode.supportedModes).toHaveLength(2)
|
|
198
|
+
expect(clusters.rvcRunMode.supportedModes[0].label).toBe('Idle')
|
|
199
|
+
expect(clusters.rvcRunMode.supportedModes[1].label).toBe('Cleaning')
|
|
200
|
+
|
|
201
|
+
// Should have rvcCleanMode with suction levels
|
|
202
|
+
expect(clusters.rvcCleanMode.supportedModes).toHaveLength(4)
|
|
203
|
+
expect(clusters.rvcCleanMode.supportedModes.map((m: any) => m.label)).toEqual([
|
|
204
|
+
'Quiet',
|
|
205
|
+
'Standard',
|
|
206
|
+
'Strong',
|
|
207
|
+
'MAX',
|
|
208
|
+
])
|
|
209
|
+
|
|
210
|
+
// Should not have serviceArea cluster
|
|
211
|
+
expect(clusters.serviceArea).toBeUndefined()
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('should configure correct clusters for K10+ Pro Combo', () => {
|
|
215
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
216
|
+
context: { deviceType: 'K10+ Pro Combo' },
|
|
217
|
+
})
|
|
218
|
+
const clusters = (vacuum as any).config.clusters
|
|
219
|
+
|
|
220
|
+
// Should have vacuum/mop action modes
|
|
221
|
+
expect(clusters.rvcCleanMode.supportedModes).toHaveLength(2)
|
|
222
|
+
expect(clusters.rvcCleanMode.supportedModes.map((m: any) => m.label)).toEqual(['Vacuum', 'Mop'])
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should configure correct clusters for S10', () => {
|
|
226
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
227
|
+
context: { deviceType: 'Robot Vacuum Cleaner S10' },
|
|
228
|
+
})
|
|
229
|
+
const clusters = (vacuum as any).config.clusters
|
|
230
|
+
|
|
231
|
+
// Should have vacuum and vacuum+mop modes
|
|
232
|
+
expect(clusters.rvcCleanMode.supportedModes).toHaveLength(2)
|
|
233
|
+
expect(clusters.rvcCleanMode.supportedModes.map((m: any) => m.label)).toEqual(['Vacuum', 'Vacuum & Mop'])
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('should include pause handler for models that support it', () => {
|
|
237
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
238
|
+
context: { deviceType: 'K10+ Pro Combo' },
|
|
239
|
+
})
|
|
240
|
+
const handlers = (vacuum as any).config.handlers
|
|
241
|
+
|
|
242
|
+
expect(handlers.rvcOperationalState.pause).toBeDefined()
|
|
243
|
+
expect(handlers.rvcOperationalState.start).toBeDefined()
|
|
244
|
+
expect(handlers.rvcOperationalState.stop).toBeDefined()
|
|
245
|
+
expect(handlers.rvcOperationalState.goHome).toBeDefined()
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('should not include pause handler for basic models', () => {
|
|
249
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
250
|
+
context: { deviceType: 'Robot Vacuum Cleaner S1' },
|
|
251
|
+
})
|
|
252
|
+
const handlers = (vacuum as any).config.handlers
|
|
253
|
+
|
|
254
|
+
expect(handlers.rvcOperationalState.pause).toBeUndefined()
|
|
255
|
+
expect(handlers.rvcOperationalState.start).toBeDefined()
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
describe('openAPI command mapping', () => {
|
|
260
|
+
it('should send start command for basic vacuum', async () => {
|
|
261
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
262
|
+
context: { deviceType: 'Robot Vacuum Cleaner S1' },
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
await (vacuum as any).handleStart()
|
|
266
|
+
|
|
267
|
+
expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('start')
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('should send stop command for basic vacuum', async () => {
|
|
271
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
272
|
+
context: { deviceType: 'K10+' },
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
await (vacuum as any).handleStop()
|
|
276
|
+
|
|
277
|
+
expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('stop')
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('should send dock command on goHome', async () => {
|
|
281
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
282
|
+
context: { deviceType: 'Robot Vacuum Cleaner S1' },
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
await (vacuum as any).handleGoHome()
|
|
286
|
+
|
|
287
|
+
expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('dock')
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('should send PowLevel command for basic vacuum suction change', async () => {
|
|
291
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
292
|
+
context: { deviceType: 'WoSweeper' },
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
await (vacuum as any).handleChangeCleanMode({ newMode: 2 }) // Strong
|
|
296
|
+
|
|
297
|
+
expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('PowLevel', '2')
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('should send startClean with action for K10+ Pro Combo', async () => {
|
|
301
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
302
|
+
context: { deviceType: 'K10+ Pro Combo' },
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// Set to vacuum mode first
|
|
306
|
+
await (vacuum as any).handleChangeCleanMode({ newMode: 0 })
|
|
307
|
+
expect((vacuum as any).currentCleanAction).toBe('vacuum')
|
|
308
|
+
|
|
309
|
+
await (vacuum as any).handleStart()
|
|
310
|
+
|
|
311
|
+
const expectedPayload = JSON.stringify({
|
|
312
|
+
action: 'vacuum',
|
|
313
|
+
param: { fanLevel: 2 },
|
|
314
|
+
})
|
|
315
|
+
expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('startClean', expectedPayload)
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('should send startClean with sweep_mop action for S10 vacuum+mop mode', async () => {
|
|
319
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
320
|
+
context: { deviceType: 'Robot Vacuum Cleaner S10' },
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
// Set to vacuum+mop mode
|
|
324
|
+
await (vacuum as any).handleChangeCleanMode({ newMode: 1 })
|
|
325
|
+
expect((vacuum as any).currentCleanAction).toBe('vacuum_mop')
|
|
326
|
+
|
|
327
|
+
await (vacuum as any).handleStart()
|
|
328
|
+
|
|
329
|
+
const calls = (vacuum as any).sendOpenAPICommand.mock.calls
|
|
330
|
+
const startCleanCall = calls.find((call: any) => call[0] === 'startClean')
|
|
331
|
+
expect(startCleanCall).toBeDefined()
|
|
332
|
+
|
|
333
|
+
const payload = JSON.parse(startCleanCall[1])
|
|
334
|
+
expect(payload.action).toBe('sweep_mop')
|
|
335
|
+
expect(payload.param.fanLevel).toBe(2)
|
|
336
|
+
expect(payload.param.waterLevel).toBe(1)
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('should include waterLevel for S20 models', async () => {
|
|
340
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
341
|
+
context: { deviceType: 'Robot Vacuum Cleaner S20' },
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
await (vacuum as any).handleStart()
|
|
345
|
+
|
|
346
|
+
const calls = (vacuum as any).sendOpenAPICommand.mock.calls
|
|
347
|
+
const startCleanCall = calls.find((call: any) => call[0] === 'startClean')
|
|
348
|
+
const payload = JSON.parse(startCleanCall[1])
|
|
349
|
+
expect(payload.param.waterLevel).toBe(1)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
it('should not include waterLevel for K10+ Pro Combo', async () => {
|
|
353
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
354
|
+
context: { deviceType: 'K10+ Pro Combo' },
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
await (vacuum as any).handleStart()
|
|
358
|
+
|
|
359
|
+
const calls = (vacuum as any).sendOpenAPICommand.mock.calls
|
|
360
|
+
const startCleanCall = calls.find((call: any) => call[0] === 'startClean')
|
|
361
|
+
const payload = JSON.parse(startCleanCall[1])
|
|
362
|
+
expect(payload.param.waterLevel).toBeUndefined()
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('should send pause command for models that support it', async () => {
|
|
366
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
367
|
+
context: { deviceType: 'S10' },
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
await (vacuum as any).handlePause()
|
|
371
|
+
|
|
372
|
+
expect((vacuum as any).sendOpenAPICommand).toHaveBeenCalledWith('pause')
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
it('should update local action state for clean mode changes on K10+ Pro Combo', async () => {
|
|
376
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
377
|
+
context: { deviceType: 'K10+ Pro Combo' },
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
// Change to mop mode
|
|
381
|
+
await (vacuum as any).handleChangeCleanMode({ newMode: 1 })
|
|
382
|
+
expect((vacuum as any).currentCleanAction).toBe('mop')
|
|
383
|
+
|
|
384
|
+
// Change back to vacuum mode
|
|
385
|
+
await (vacuum as any).handleChangeCleanMode({ newMode: 0 })
|
|
386
|
+
expect((vacuum as any).currentCleanAction).toBe('vacuum')
|
|
387
|
+
|
|
388
|
+
// Clean mode changes should just update state, not call OpenAPI
|
|
389
|
+
expect((vacuum as any).sendOpenAPICommand).not.toHaveBeenCalled()
|
|
390
|
+
})
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
describe('state updates', () => {
|
|
394
|
+
it('should update run mode correctly', () => {
|
|
395
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
396
|
+
context: { deviceType: 'Robot Vacuum Cleaner S1' },
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
vacuum.updateRunMode(1) // Cleaning
|
|
400
|
+
|
|
401
|
+
expect((vacuum as any).updateState).toHaveBeenCalledWith('rvcRunMode', { currentMode: 1 })
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
it('should update clean mode correctly', () => {
|
|
405
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
406
|
+
context: { deviceType: 'K10+' },
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
vacuum.updateCleanMode(2) // Strong
|
|
410
|
+
|
|
411
|
+
expect((vacuum as any).updateState).toHaveBeenCalledWith('rvcCleanMode', { currentMode: 2 })
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
it('should update operational state correctly', () => {
|
|
415
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
416
|
+
context: { deviceType: 'S10' },
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
vacuum.updateOperationalState(64) // Seeking Charger
|
|
420
|
+
|
|
421
|
+
expect((vacuum as any).updateState).toHaveBeenCalledWith('rvcOperationalState', { operationalState: 64 })
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
it('should update battery percentage without PowerSource cluster', async () => {
|
|
425
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
426
|
+
context: { deviceType: 'Robot Vacuum Cleaner S1' },
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
await vacuum.updateBatteryPercentage(75)
|
|
430
|
+
|
|
431
|
+
// Should log but not update any cluster
|
|
432
|
+
expect((vacuum as any).logInfo).toHaveBeenCalledWith(expect.stringContaining('75%'))
|
|
433
|
+
expect((vacuum as any).context.batteryPercentage).toBe(75)
|
|
434
|
+
})
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
describe('error handling', () => {
|
|
438
|
+
it('should handle OpenAPI command failures gracefully', async () => {
|
|
439
|
+
const vacuum = new RoboticVacuumAccessory(mockAPI, mockLogger, {
|
|
440
|
+
context: { deviceType: 'Robot Vacuum Cleaner S1' },
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
// Mock failure
|
|
444
|
+
;(vacuum as any).sendOpenAPICommand = vi.fn(async () => {
|
|
445
|
+
throw new Error('API Error')
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
await (vacuum as any).handleStart()
|
|
449
|
+
|
|
450
|
+
expect((vacuum as any).logWarn).toHaveBeenCalledWith(expect.stringContaining('failed'))
|
|
451
|
+
})
|
|
452
|
+
})
|
|
453
|
+
})
|