iobroker.roborock
Version:
178 lines (155 loc) • 6.88 kB
text/typescript
import { beforeEach, describe, expect, it } from "vitest";
import { Feature } from "../features/features.enum";
import { V1VacuumFeatures } from "../features/vacuum/v1VacuumFeatures";
import { MockAdapter } from "./MockAdapter";
import { MockRobot } from "./MockRobot";
// We need a concrete implementation of abstract V1VacuumFeatures to test it
class TestVacuumFeatures extends V1VacuumFeatures {
public getDescriptor(): any {
return {};
}
public getDynamicFeatures(): Set<Feature> {
return new Set();
}
public async detectAndApplyRuntimeFeatures(): Promise<boolean> {
return false;
}
// Abstract getters implementation - minimal valid return for test
public getCommonConsumable(): any {
return {};
}
public isResetableConsumable(): boolean {
return false;
}
public getCommonDeviceStates(): any {
return {};
}
public getCommonCleaningRecords(): any {
return {};
}
public getFirmwareFeatureName(): string {
return "";
}
public getCommonCleaningInfo(): any {
return {};
}
// Expose protected method for testing
// Expose protected method for testing
public async publicApplyFeature(feature: Feature): Promise<boolean> {
return this.applyFeature(feature);
}
// Override updateConsumables to use our manual logic for testing "smart" behavior
// Since we cannot easily intercept the "requestAndProcess" which calls helper methods,
// we will simulate the behavior of requestAndProcess -> processing loop here.
// The REAL implementation uses requestAndProcess.
// The REAL implementation of updateConsumables takes NO arguments.
// So we must match that signature.
public async updateConsumables(): Promise<void> {
// We cheat here for the test: we assume the test setup put the data where we can find it
// OR we can't easily test this without mocking sendRequest.
// Let's assume the test will mock the RESPONSE of sendRequest.
// So calling super() is actually what we want, IF the dep injection worked.
return super.updateConsumables();
}
}
describe("Features - State Creation", () => {
let mockAdapter: MockAdapter;
let mockRobot: MockRobot;
beforeEach(() => {
mockAdapter = new MockAdapter();
mockRobot = new MockRobot();
});
it("should create dockingStationStatus states only when supported", async () => {
const deps = {
adapter: mockAdapter as any,
log: {
info: console.log,
warn: console.warn,
error: console.error,
debug: console.log,
silly: console.log,
} as any,
http_api: {} as any,
config: {} as any,
ensureFolder: async (path: string) => mockAdapter.setObjectNotExistsAsync(path, { type: "folder" } as any),
ensureState: async (id: string, common: any) => mockAdapter.setObjectNotExistsAsync(id, { type: "state", common } as any),
};
(deps.adapter as any).translationManager = { get: (key: string, def?: string) => def || key };
// Double-ensure setState is present even if class binding fails
if (!deps.adapter.setState) {
deps.adapter.setState = mockAdapter.setState;
}
const features = new TestVacuumFeatures(deps as any, mockRobot.duid, mockRobot.model, { staticFeatures: [] } as any);
// 1. Initial State: No DSS features applied
const dssFolder = `Devices.${mockRobot.duid}.dockingStationStatus`;
const cleanFluid = `Devices.${mockRobot.duid}.dockingStationStatus.cleanFluidStatus`;
expect(mockAdapter.objects).to.not.have.property(dssFolder);
// 2. Simulate DSS detection (e.g. via runtime status having 'dss')
// We simulate the feature application logic which would happen in detectAndApplyRuntimeFeatures
await features.publicApplyFeature(Feature.DockingStationStatus);
// This should fail currently because implementation is missing
expect(mockAdapter.objects, "dockingStationStatus folder missing after feature applied").to.have.property(dssFolder);
expect(mockAdapter.objects, "cleanFluidStatus state missing after feature applied").to.have.property(cleanFluid);
});
it("should dynamically create resetConsumables states based on data", async () => {
// In this test, we need to mock the sendRequest to return our consumable data
const mockConsumableData = {
main_brush_work_time: 3600,
filter_work_time: 1200,
side_brush_work_time: 1800, // Added for new test case
unknown_consumable: 999
};
const deps = {
adapter: mockAdapter as any,
log: {
info: console.log,
warn: console.warn,
error: console.error,
debug: console.debug,
silly: () => {},
} as any,
http_api: {} as any,
config: {} as any,
ensureFolder: async (path: string) => mockAdapter.setObjectNotExistsAsync(path, { type: "folder" } as any),
ensureState: async (id: string, common: any) => mockAdapter.setObjectNotExistsAsync(id, { type: "state", common } as any),
};
// Patch the adapter request handler to return our data
deps.adapter.requestsHandler = {
sendRequest: async (_duid: string, method: string) => {
if (method === "get_consumable") return mockConsumableData;
return {};
}
};
// Ensure setState is present
if (!deps.adapter.setState) {
deps.adapter.setState = mockAdapter.setState;
}
(deps.adapter as any).translationManager = { get: (key: string, def?: string) => def || key };
const features = new TestVacuumFeatures(deps as any, mockRobot.duid, mockRobot.model, { staticFeatures: [] } as any);
// 1. Initial: No reset buttons
const resetMainBrush = `Devices.${mockRobot.duid}.resetConsumables.main_brush_work_time`;
expect(mockAdapter.objects).to.not.have.property(resetMainBrush);
// 2. Apply Consumables Feature
await features.publicApplyFeature(Feature.ResetConsumables);
// 3. Trigger update
await features.updateConsumables();
// 4. Verification
// Check for RESET buttons - Main Key Checks
// We know resetConsumables contains: main_brush_work_time, side_brush_work_time, filter_work_time, etc.
const duid = mockRobot.duid; // Use duid from mockRobot
const resetMainBrushPath = `Devices.${duid}.resetConsumables.reset_main_brush`;
const resetSideBrush = `Devices.${duid}.resetConsumables.reset_side_brush`;
const resetFilter = `Devices.${duid}.resetConsumables.reset_filter`;
const resetUnknown = `Devices.${duid}.resetConsumables.unknown_consumable`;
expect(mockAdapter.objects).to.have.property(resetMainBrushPath);
expect(mockAdapter.objects).to.have.property(resetSideBrush);
expect(mockAdapter.objects).to.have.property(resetFilter);
// Should NOT create reset button for unknown consumable (only work_time/dirty_time suffixes)
expect(mockAdapter.objects).to.not.have.property(resetUnknown);
// Verify object properties for a created button
const btnObj = mockAdapter.objects[resetMainBrushPath];
expect(btnObj.common.role).to.equal("button");
expect(btnObj.common.type).to.equal("boolean");
expect(btnObj.common.write).to.equal(true);
});
});