UNPKG

matterbridge-roborock-vacuum-plugin

Version:
560 lines 29.9 kB
import { MatterbridgeIdentifyServer, MatterbridgeServiceAreaServer } from 'matterbridge'; import { MatterbridgeRvcOperationalStateServer } from 'matterbridge/devices'; import { RvcCleanMode, ServiceArea } from 'matterbridge/matter/clusters'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { MapInfo } from '../core/application/models/MapInfo.js'; import { RoomMap } from '../core/application/models/RoomMap.js'; import { HomeEntity } from '../core/domain/entities/Home.js'; import { PlatformConfigManager } from '../platform/platformConfigManager.js'; import { RoborockVacuumCleaner } from '../types/roborockVacuumCleaner.js'; import { asPartial, asType, setReadOnlyProperty } from './testUtils.js'; function createMockLogger() { return asType({ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), notice: vi.fn(), logLevel: 'info', }); } describe('RoborockVacuumCleaner', () => { let device; let homeInfo; let logger; let vacuum; let configManager; let roborockService; beforeEach(() => { device = { duid: 'duid-123', specs: { model: 'roborock.s5', firmwareVersion: '1.0.0' }, serialNumber: 'serial-123', deviceName: 'TestVac', name: 'TestVac', scenes: [], }; const roomMap = new RoomMap([]); const mapInfo = MapInfo.empty(); homeInfo = new HomeEntity(1, 'Test Home', roomMap, mapInfo, 0); configManager = PlatformConfigManager.create(asPartial({ pluginConfiguration: asPartial({ enableMultipleMap: false, enableServerMode: false, }), }), createMockLogger()); logger = createMockLogger(); roborockService = asPartial({ setSupportedRoutines: vi.fn(), setSupportedAreas: vi.fn(), setSupportedAreaIndexMap: vi.fn(), getSupportedAreas: vi.fn().mockReturnValue([]), switchMap: vi.fn().mockResolvedValue(undefined), }); vacuum = new RoborockVacuumCleaner(device, homeInfo, configManager, roborockService, logger); vi.spyOn(vacuum.log, 'info').mockImplementation(() => { }); vi.spyOn(vacuum.log, 'warn').mockImplementation(() => { }); vi.spyOn(vacuum.log, 'debug').mockImplementation(() => { }); vi.spyOn(vacuum.log, 'error').mockImplementation(() => { }); vi.spyOn(vacuum, 'stateOf').mockReturnValue({}); }); it('should construct with correct properties', () => { expect(vacuum).toBeInstanceOf(RoborockVacuumCleaner); expect(vacuum.device).toBe(device); }); describe('resolveAllRoomsForActiveMap', () => { it('should return rooms for active map when single map exists', () => { const mockSupportedAreas = [ { areaId: 1, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 2, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, ]; roborockService.getSupportedAreas = vi.fn().mockReturnValue(mockSupportedAreas); vi.spyOn(vacuum, 'getAttribute').mockReturnValue([]); const result = vacuum.resolveAllRoomsForActiveMap(); expect(result).toEqual([1, 2]); }); it('should infer active map from current selectedAreas attribute', () => { const mockSupportedAreas = [ { areaId: 1, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 2, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 3, mapId: 1, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 4, mapId: 1, areaInfo: { locationInfo: null, landmarkInfo: null } }, ]; roborockService.getSupportedAreas = vi.fn().mockReturnValue(mockSupportedAreas); vi.spyOn(vacuum, 'getAttribute').mockReturnValue([3]); const result = vacuum.resolveAllRoomsForActiveMap(); expect(result).toEqual([3, 4]); }); it('should use homeInFo.activeMapId when no selectedAreas hint exists', () => { const mockSupportedAreas = [ { areaId: 1, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 2, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 3, mapId: 1, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 4, mapId: 1, areaInfo: { locationInfo: null, landmarkInfo: null } }, ]; roborockService.getSupportedAreas = vi.fn().mockReturnValue(mockSupportedAreas); vi.spyOn(vacuum, 'getAttribute').mockReturnValue([]); homeInfo = new HomeEntity(1, 'Test Home', new RoomMap([]), MapInfo.empty(), 0); setReadOnlyProperty(homeInfo, 'activeMapId', 1); const newVacuum = new RoborockVacuumCleaner(device, homeInfo, configManager, roborockService, logger); vi.spyOn(newVacuum, 'getAttribute').mockReturnValue([]); vi.spyOn(newVacuum.log, 'info').mockImplementation(() => { }); vi.spyOn(newVacuum.log, 'warn').mockImplementation(() => { }); vi.spyOn(newVacuum.log, 'debug').mockImplementation(() => { }); vi.spyOn(newVacuum.log, 'error').mockImplementation(() => { }); vi.spyOn(newVacuum, 'stateOf').mockReturnValue({}); const result = newVacuum.resolveAllRoomsForActiveMap(); expect(result).toEqual([3, 4]); }); it('should fall back to first map rooms when no hints exist', () => { const mockSupportedAreas = [ { areaId: 1, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 2, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 3, mapId: 1, areaInfo: { locationInfo: null, landmarkInfo: null } }, ]; roborockService.getSupportedAreas = vi.fn().mockReturnValue(mockSupportedAreas); vi.spyOn(vacuum, 'getAttribute').mockReturnValue([]); // homeInfo.activeMapId is -1 by default const result = vacuum.resolveAllRoomsForActiveMap(); expect(result).toEqual([1, 2]); }); it('should return empty list when no rooms exist', () => { roborockService.getSupportedAreas = vi.fn().mockReturnValue([]); vi.spyOn(vacuum, 'getAttribute').mockReturnValue([]); const result = vacuum.resolveAllRoomsForActiveMap(); expect(result).toEqual([]); }); }); it('should call behaviorHandler for identify command', async () => { const behaviorHandler = { executeCommand: vi.fn(), setCommandHandler: vi.fn(), log: logger, commands: {}, }; vacuum.configureHandler(behaviorHandler); await vacuum.executeCommandHandler('identify', { identifyTime: 5 }, 'identify', vacuum.stateOf(MatterbridgeIdentifyServer), vacuum); expect(behaviorHandler.executeCommand).toHaveBeenCalledWith('identify', 5); }); describe('SELECT_AREAS command with non-empty input (regression tests)', () => { it('should execute non-empty selectAreas without regressing to empty behavior', async () => { const mockSupportedAreas = [ { areaId: 1, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 2, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, ]; vi.mocked(roborockService.getSupportedAreas).mockReturnValue(mockSupportedAreas); vi.spyOn(vacuum, 'getAttribute').mockReturnValue([]); const behaviorHandler = { executeCommand: vi.fn(), setCommandHandler: vi.fn(), log: logger, commands: {}, }; vacuum.configureHandler(behaviorHandler); await vacuum.executeCommandHandler('selectAreas', { newAreas: [1, 2] }, 'serviceArea', vacuum.stateOf(MatterbridgeServiceAreaServer), vacuum); // Most importantly: non-empty input should NOT be populated; should pass through unchanged expect(behaviorHandler.executeCommand).toHaveBeenCalledWith('selectAreas', [1, 2]); }); it('should not populate all rooms when non-empty areas explicitly provided (regression guard)', async () => { const mockSupportedAreas = [ { areaId: 1, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 2, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 3, mapId: 1, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 4, mapId: 1, areaInfo: { locationInfo: null, landmarkInfo: null } }, ]; vi.mocked(roborockService.getSupportedAreas).mockReturnValue(mockSupportedAreas); vi.spyOn(vacuum, 'getAttribute').mockReturnValue([]); const behaviorHandler = { executeCommand: vi.fn(), setCommandHandler: vi.fn(), log: logger, commands: {}, }; vacuum.configureHandler(behaviorHandler); // Explicitly select only areas [1, 2] (not all areas, not all of any specific map) await vacuum.executeCommandHandler('selectAreas', { newAreas: [1, 2] }, 'serviceArea', vacuum.stateOf(MatterbridgeServiceAreaServer), vacuum); // Should pass through exactly as provided, not populate with all map rooms expect(behaviorHandler.executeCommand).toHaveBeenCalledWith('selectAreas', [1, 2]); // Verify it wasn't expanded to other areas expect(behaviorHandler.executeCommand).not.toHaveBeenCalledWith('selectAreas', [1, 2, 3, 4]); }); it('should NOT call updateAttribute for selectedAreas when explicit rooms are provided (regression guard)', async () => { const mockSupportedAreas = [ { areaId: 1, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, { areaId: 2, mapId: 0, areaInfo: { locationInfo: null, landmarkInfo: null } }, ]; vi.mocked(roborockService.getSupportedAreas).mockReturnValue(mockSupportedAreas); vi.spyOn(vacuum, 'getAttribute').mockReturnValue([]); const updateAttributeSpy = vi.spyOn(vacuum, 'updateAttribute').mockImplementation(() => Promise.resolve(true)); const behaviorHandler = { executeCommand: vi.fn(), setCommandHandler: vi.fn(), log: logger, commands: {}, }; vacuum.configureHandler(behaviorHandler); await vacuum.executeCommandHandler('selectAreas', { newAreas: [1, 2] }, 'serviceArea', vacuum.stateOf(MatterbridgeServiceAreaServer), vacuum); expect(behaviorHandler.executeCommand).toHaveBeenCalledWith('selectAreas', [1, 2]); // Explicit-rooms branch must NOT call updateAttribute for selectedAreas (user-approved scope restriction) expect(updateAttributeSpy).not.toHaveBeenCalledWith(ServiceArea.id, 'selectedAreas', expect.anything(), expect.anything()); }); }); it('should call behaviorHandler for selectAreas command', async () => { const behaviorHandler = { executeCommand: vi.fn(), setCommandHandler: vi.fn(), log: logger, commands: {}, }; vacuum.configureHandler(behaviorHandler); await vacuum.executeCommandHandler('selectAreas', { newAreas: [1, 2] }, 'serviceArea', vacuum.stateOf(MatterbridgeServiceAreaServer), vacuum); expect(behaviorHandler.executeCommand).toHaveBeenCalledWith('selectAreas', [1, 2]); }); it('should call behaviorHandler for changeToMode command', async () => { const behaviorHandler = { executeCommand: vi.fn(), setCommandHandler: vi.fn(), log: logger, commands: {}, }; vacuum.configureHandler(behaviorHandler); const request = { newMode: 42 }; await vacuum.executeCommandHandler('changeToMode', request, 'modeSelect', vacuum.stateOf(MatterbridgeRvcOperationalStateServer), vacuum); expect(behaviorHandler.executeCommand).toHaveBeenCalledWith('changeToMode', 42); }); it('should call behaviorHandler for pause command', async () => { const behaviorHandler = { executeCommand: vi.fn(), setCommandHandler: vi.fn(), log: logger, commands: {}, }; vacuum.configureHandler(behaviorHandler); await vacuum.executeCommandHandler('pause', {}, 'operationalState', vacuum.stateOf(MatterbridgeRvcOperationalStateServer), vacuum); expect(behaviorHandler.executeCommand).toHaveBeenCalledWith('pause'); }); it('should call behaviorHandler for resume command', async () => { const behaviorHandler = { executeCommand: vi.fn(), setCommandHandler: vi.fn(), log: logger, commands: {}, }; vacuum.configureHandler(behaviorHandler); await vacuum.executeCommandHandler('resume', {}, 'operationalState', vacuum.stateOf(MatterbridgeRvcOperationalStateServer), vacuum); expect(behaviorHandler.executeCommand).toHaveBeenCalledWith('resume'); }); it('should call behaviorHandler for goHome command', async () => { const behaviorHandler = { executeCommand: vi.fn(), setCommandHandler: vi.fn(), log: logger, commands: {}, }; vacuum.configureHandler(behaviorHandler); await vacuum.executeCommandHandler('goHome', {}, 'rvcOperationalState', vacuum.stateOf(MatterbridgeRvcOperationalStateServer), vacuum); expect(behaviorHandler.executeCommand).toHaveBeenCalledWith('goHome'); }); it('should cover initializeDeviceConfiguration with experimental features', () => { const expLogger = createMockLogger(); const expConfig = asPartial({ authentication: { username: 'user', region: 'US', forceAuthentication: false, authenticationMethod: 'Password', }, pluginConfiguration: { whiteList: [], enableServerMode: true, enableMultipleMap: true, sanitizeSensitiveLogs: false, refreshInterval: 60, debug: false, unregisterOnShutdown: false, }, advancedFeature: { enableAdvancedFeature: true, settings: { clearStorageOnStartup: false, enableLiveMapUpdates: false, showRoutinesAsRoom: false, includeDockStationStatus: false, includeVacuumErrorStatus: false, forceRunAtDefault: true, useVacationModeToSendVacuumToDock: false, enableCleanModeMapping: false, cleanModeSettings: {}, overrideMatterConfiguration: false, matterOverrideSettings: { matterVendorName: 'xxx', matterVendorId: 123, matterProductName: 'yy', matterProductId: 456, }, enableEmailNotification: false, emailNotificationSettings: {}, }, }, }); const configManager = PlatformConfigManager.create(expConfig, expLogger); const dev = { ...device, data: { model: 'roborock.s7', firmwareVersion: '2.0.0' } }; const testHomeInfo = new HomeEntity(1, 'Test', new RoomMap([]), MapInfo.empty(), 0); const result = RoborockVacuumCleaner['initializeDeviceConfiguration'](dev, testHomeInfo, configManager, roborockService, expLogger); expect(result.cleanModes).toBeDefined(); expect(result.supportedAreas).toBeDefined(); expect(result.supportedMaps).toBeDefined(); expect(result.supportedAreaAndRoutines).toBeDefined(); expect(result.deviceName).toContain(dev.name); expect(result.bridgeMode).toBe('server'); }); it('should cover initializeDeviceConfiguration with minimal config', () => { const minLogger = createMockLogger(); const minConfig = asPartial({ authentication: { username: 'user', region: 'US', forceAuthentication: false, authenticationMethod: 'Password', }, pluginConfiguration: { whiteList: [], enableServerMode: false, enableMultipleMap: false, sanitizeSensitiveLogs: false, refreshInterval: 60, debug: false, unregisterOnShutdown: false, }, advancedFeature: { enableAdvancedFeature: false, settings: { clearStorageOnStartup: false, enableLiveMapUpdates: false, showRoutinesAsRoom: false, includeDockStationStatus: false, includeVacuumErrorStatus: false, forceRunAtDefault: false, useVacationModeToSendVacuumToDock: false, enableCleanModeMapping: false, cleanModeSettings: {}, overrideMatterConfiguration: false, matterOverrideSettings: { matterVendorName: 'xxx', matterVendorId: 123, matterProductName: 'yy', matterProductId: 456, }, enableEmailNotification: false, emailNotificationSettings: {}, }, }, }); const configManager = PlatformConfigManager.create(minConfig, minLogger); const testHomeInfo = new HomeEntity(1, 'Test', new RoomMap([]), MapInfo.empty(), 0); const result = RoborockVacuumCleaner['initializeDeviceConfiguration'](device, testHomeInfo, configManager, roborockService, minLogger); expect(result.cleanModes).toBeDefined(); expect(result.supportedAreas).toBeDefined(); expect(result.supportedMaps).toBeDefined(); expect(result.supportedAreaAndRoutines).toBeDefined(); expect(result.deviceName).toContain(device.name); expect(result.bridgeMode).toBe('matter'); }); describe('ServiceArea Feature Configuration', () => { it('should have ServiceArea cluster server configured', () => { // Verify the cluster server exists const serviceAreaServer = vacuum.stateOf(MatterbridgeServiceAreaServer); expect(serviceAreaServer).toBeDefined(); }); }); describe('initializeDeviceConfiguration with resolved areas and maps', () => { it('should use resolved areas instead of empty array when passed', () => { const mockDevice = { duid: 'test-duid', specs: { model: 'roborock.s5', firmwareVersion: '1.0.0' }, serialNumber: 'test-serial', name: 'TestVac', scenes: [], }; const mockHomeInfo = new HomeEntity(1, 'Test Home', RoomMap.empty(), MapInfo.empty(), 0); const resolvedAreas = [ { areaId: 1, mapId: 1, name: 'Living Room' }, { areaId: 2, mapId: 1, name: 'Kitchen' }, ]; const mockConfigManager = PlatformConfigManager.create(asPartial({ pluginConfiguration: asPartial({ enableMultipleMap: false, enableServerMode: false, }), advancedFeature: asPartial({ enableAdvancedFeature: false, settings: asPartial({ showRoutinesAsRoom: false }), }), }), logger); const config = RoborockVacuumCleaner.initializeDeviceConfiguration(mockDevice, mockHomeInfo, mockConfigManager, roborockService, logger, resolvedAreas, []); expect(config.supportedAreas).toEqual(resolvedAreas); expect(config.supportedAreaAndRoutines).toEqual(resolvedAreas); }); it('should include resolved maps when passed', () => { const mockDevice = { duid: 'test-duid', specs: { model: 'roborock.s5', firmwareVersion: '1.0.0' }, serialNumber: 'test-serial', name: 'TestVac', scenes: [], }; const mockHomeInfo = new HomeEntity(1, 'Test Home', RoomMap.empty(), MapInfo.empty(), 0); const resolvedMaps = [{ mapId: 1, name: 'Map 1' }]; const mockConfigManager = PlatformConfigManager.create(asPartial({ pluginConfiguration: asPartial({ enableMultipleMap: false, enableServerMode: false, }), advancedFeature: asPartial({ enableAdvancedFeature: false, settings: asPartial({ showRoutinesAsRoom: false }), }), }), logger); const config = RoborockVacuumCleaner.initializeDeviceConfiguration(mockDevice, mockHomeInfo, mockConfigManager, roborockService, logger, [], resolvedMaps); // Should include the resolved map expect(config.supportedMaps).toHaveLength(1); expect(config.supportedMaps[0]).toEqual(resolvedMaps[0]); }); it('should support routine maps when showRoutinesAsRoom is enabled', () => { const mockDevice = { duid: 'test-duid', specs: { model: 'roborock.s5', firmwareVersion: '1.0.0' }, serialNumber: 'test-serial', name: 'TestVac', scenes: [], }; const mockHomeInfo = new HomeEntity(1, 'Test Home', RoomMap.empty(), MapInfo.empty(), 0); const resolvedMaps = [{ mapId: 1, name: 'Map 1' }]; const mockConfigManager = PlatformConfigManager.create(asPartial({ pluginConfiguration: asPartial({ enableMultipleMap: false, enableServerMode: false, }), advancedFeature: asPartial({ enableAdvancedFeature: false, settings: asPartial({ showRoutinesAsRoom: true }), }), }), logger); const config = RoborockVacuumCleaner.initializeDeviceConfiguration(mockDevice, mockHomeInfo, mockConfigManager, roborockService, logger, [], resolvedMaps); // Should include the resolved map expect(config.supportedMaps).toContainEqual(resolvedMaps[0]); }); it('should place resolved areas before any routine areas when both exist', () => { const mockDevice = { duid: 'test-duid', specs: { model: 'roborock.s5', firmwareVersion: '1.0.0' }, serialNumber: 'test-serial', name: 'TestVac', scenes: [], }; const mockHomeInfo = new HomeEntity(1, 'Test Home', RoomMap.empty(), MapInfo.empty(), 0); const resolvedAreas = [{ areaId: 1, mapId: 1 }, { areaId: 2, mapId: 1 }]; const mockConfigManager = PlatformConfigManager.create(asPartial({ pluginConfiguration: asPartial({ enableMultipleMap: false, enableServerMode: false, }), advancedFeature: asPartial({ enableAdvancedFeature: false, settings: asPartial({ showRoutinesAsRoom: false }), }), }), logger); const config = RoborockVacuumCleaner.initializeDeviceConfiguration(mockDevice, mockHomeInfo, mockConfigManager, roborockService, logger, resolvedAreas, []); // Resolved areas should come before routine areas in the combined list expect(config.supportedAreaAndRoutines.slice(0, resolvedAreas.length)).toEqual(resolvedAreas); }); it('should use empty arrays when resolved areas and maps are empty', () => { const mockDevice = { duid: 'test-duid', specs: { model: 'roborock.s5', firmwareVersion: '1.0.0' }, serialNumber: 'test-serial', name: 'TestVac', scenes: [], }; const mockHomeInfo = new HomeEntity(1, 'Test Home', RoomMap.empty(), MapInfo.empty(), 0); const mockConfigManager = PlatformConfigManager.create(asPartial({ pluginConfiguration: asPartial({ enableMultipleMap: false, enableServerMode: false, }), advancedFeature: asPartial({ enableAdvancedFeature: false, settings: asPartial({ showRoutinesAsRoom: false }), }), }), logger); const config = RoborockVacuumCleaner.initializeDeviceConfiguration(mockDevice, mockHomeInfo, mockConfigManager, roborockService, logger, [], []); expect(config.supportedAreas).toEqual([]); expect(config.supportedMaps).toEqual([]); expect(config.supportedAreaAndRoutines).toEqual([]); }); }); describe('RoborockVacuumCleaner constructor with resolved areas', () => { it('should construct successfully with resolved areas', () => { const resolvedAreas = [{ areaId: 1, mapId: 1 }]; const resolvedMaps = [{ mapId: 1, name: 'Map 1' }]; const vac = new RoborockVacuumCleaner(device, homeInfo, configManager, roborockService, logger, resolvedAreas, resolvedMaps); expect(vac).toBeInstanceOf(RoborockVacuumCleaner); expect(vac.device).toBe(device); }); it('should construct successfully with empty resolved areas', () => { const vac = new RoborockVacuumCleaner(device, homeInfo, configManager, roborockService, logger, [], []); expect(vac).toBeInstanceOf(RoborockVacuumCleaner); }); it('should construct with default empty arrays when parameters not provided', () => { const vac = new RoborockVacuumCleaner(device, homeInfo, configManager, roborockService, logger); expect(vac).toBeInstanceOf(RoborockVacuumCleaner); }); }); describe('createDefaultRvcCleanModeClusterServer', () => { it('should create Clean Mode cluster with DirectModeChange feature enabled', () => { // Call the method to initialize the cluster vacuum.createDefaultRvcCleanModeClusterServer(); // Verify behaviors.require was called with MatterbridgeRvcCleanModeServer // and DirectModeChange feature (cannot directly inspect .with() but can verify the method was called) expect(vacuum).toBeInstanceOf(RoborockVacuumCleaner); }); it('should use default supportedModes when no args provided', () => { const result = vacuum.createDefaultRvcCleanModeClusterServer(); // Verify method returns this for chaining expect(result).toBe(vacuum); }); it('should use default currentMode=1 when no args provided', () => { const result = vacuum.createDefaultRvcCleanModeClusterServer(); // Verify method returns this for chaining expect(result).toBe(vacuum); }); it('should accept custom currentMode and supportedModes parameters', () => { const customModes = [ { label: 'Custom Vacuum', mode: 1, modeTags: [{ value: RvcCleanMode.ModeTag.Vacuum }] }, { label: 'Custom Mop', mode: 2, modeTags: [{ value: RvcCleanMode.ModeTag.Mop }] }, ]; vacuum.createDefaultRvcCleanModeClusterServer(2, customModes); // Verify the instance is still valid and chainable expect(vacuum).toBeInstanceOf(RoborockVacuumCleaner); }); it('should return this for method chaining', () => { const result = vacuum.createDefaultRvcCleanModeClusterServer(); expect(result).toBe(vacuum); }); it('should include all three default modes (Vacuum, Mop, DeepClean)', () => { vacuum.createDefaultRvcCleanModeClusterServer(); // Verify instance is created successfully with defaults expect(vacuum).toBeInstanceOf(RoborockVacuumCleaner); }); it('should allow mode change during active cleaning (DirectModeChange feature)', () => { // Set operational state to Running vacuum.createDefaultRvcCleanModeClusterServer(); // The feature DirectModeChange enables mode changes without requiring Idle state // Verification: this is tested by the feature flag declaration, which is // read by controllers to know they can call CHANGE_TO_MODE at any time. // No explicit test needed here beyond verifying the method completes. expect(vacuum).toBeInstanceOf(RoborockVacuumCleaner); }); }); }); //# sourceMappingURL=roborockVacuumCleaner.test.js.map