Skip to content

Commit

Permalink
test: increase test coverage on fan accessory
Browse files Browse the repository at this point in the history
  • Loading branch information
jvandenaardweg committed Dec 30, 2022
1 parent 3eb18f0 commit 2a0f6ae
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 18 deletions.
270 changes: 270 additions & 0 deletions src/fan-accessory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import { ConfigSchema } from './config.schema';
import { FanAccessory } from './fan-accessory';
import { accessoryMock, platformMock } from './mocks/platform';
import {
PLATFORM_NAME,
DEFAULT_BRIDGE_NAME,
DEFAULT_FAN_NAME,
ACTIVE_SPEED_THRESHOLD,
} from './settings';

const configMock: ConfigSchema = {
platform: PLATFORM_NAME,
name: DEFAULT_BRIDGE_NAME,
api: {
ip: '192.168.0.10',
port: 1883,
protocol: 'mqtt',
},
device: {
co2Sensor: true,
},
};

describe('FanAccessory', () => {
it('should create an instance', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

expect(fanAccessory).toBeTruthy();
});

it('should have the correct displayName', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

expect(fanAccessory['accessory'].displayName).toBe(DEFAULT_FAN_NAME);
});

describe('allowsManualSpeedControl', () => {
it('should return false when config options device.co2Sensor is true', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, {
...configMock,
device: {
co2Sensor: true,
},
});

expect(fanAccessory['allowsManualSpeedControl']).toBe(false);
});

it('should return false when config options device.nonCve is true', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, {
...configMock,
device: {
nonCve: true,
},
});

expect(fanAccessory['allowsManualSpeedControl']).toBe(false);
});

it('should return false when both config options device.co2Sensor ánd device.nonCve are true', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, {
...configMock,
device: {
co2Sensor: true,
nonCve: true,
},
});

expect(fanAccessory['allowsManualSpeedControl']).toBe(false);
});

it('should return true when both config options device.co2Sensor ánd device.nonCve are false', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, {
...configMock,
device: {
co2Sensor: false,
nonCve: false,
},
});

expect(fanAccessory['allowsManualSpeedControl']).toBe(true);
});

it('should return true when the device option is undefined', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, {
...configMock,
device: undefined,
});

expect(fanAccessory['allowsManualSpeedControl']).toBe(true);
});
});

describe('getTargetFanStateName()', () => {
it('should return the correct name for the target fan state', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

expect(fanAccessory['getTargetFanStateName'](0)).toBe('MANUAL');
expect(fanAccessory['getTargetFanStateName'](1)).toBe('AUTO');
});
});

describe('getCurrentFanStateName()', () => {
it('should return the correct name for the current fan state', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

expect(fanAccessory['getCurrentFanStateName'](0)).toBe('INACTIVE');
expect(fanAccessory['getCurrentFanStateName'](1)).toBe('IDLE');
expect(fanAccessory['getCurrentFanStateName'](2)).toBe('BLOWING_AIR');
});
});

describe('getActiveName()', () => {
it('should return the correct name for the active state', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

expect(fanAccessory['getActiveName'](0)).toBe('INACTIVE');
expect(fanAccessory['getActiveName'](1)).toBe('ACTIVE');
});
});

describe('getActiveStateByRotationSpeed()', () => {
it('should return the correct active state for the given rotation speed', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

expect(fanAccessory['getActiveStateByRotationSpeed'](0)).toBe(0);
expect(fanAccessory['getActiveStateByRotationSpeed'](19)).toBe(0);

// 20 or 20+ should be active
expect(fanAccessory['getActiveStateByRotationSpeed'](20)).toBe(1);
expect(fanAccessory['getActiveStateByRotationSpeed'](21)).toBe(1);
});
});

describe('setRotationSpeed()', () => {
it('should set the correct rotation speed', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

const mockRotationSpeed = 20;

const updateCharacteristicSpy = vi.fn();

fanAccessory['service'].updateCharacteristic = updateCharacteristicSpy;

fanAccessory['setRotationSpeed'](mockRotationSpeed);

expect(updateCharacteristicSpy).toHaveBeenCalledWith(
platformMock.Characteristic.RotationSpeed,
mockRotationSpeed,
);
});

it('should not set the rotation speed if the same value is already set', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

const mockRotationSpeed = 20;

const updateCharacteristicSpy = vi.fn();

fanAccessory['service'].updateCharacteristic = updateCharacteristicSpy;

fanAccessory['service'].getCharacteristic = vi.fn().mockReturnValue({
value: mockRotationSpeed,
});

fanAccessory['setRotationSpeed'](mockRotationSpeed);

expect(updateCharacteristicSpy).not.toHaveBeenCalledWith(
platformMock.Characteristic.RotationSpeed,
mockRotationSpeed,
);
});
});

describe('setActive()', () => {
it('should set the correct active state', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

const mockActiveState = 1; // active

const updateCharacteristicSpy = vi.fn();

fanAccessory['service'].updateCharacteristic = updateCharacteristicSpy;

fanAccessory['setActive'](mockActiveState);

expect(updateCharacteristicSpy).toHaveBeenCalledWith(
platformMock.Characteristic.Active,
mockActiveState,
);
});

it('should not set the active state if the same value is already set', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

const mockActiveState = 1;

const updateCharacteristicSpy = vi.fn();

fanAccessory['service'].updateCharacteristic = updateCharacteristicSpy;

fanAccessory['service'].getCharacteristic = vi.fn().mockReturnValue({
value: mockActiveState,
});

fanAccessory['setActive'](mockActiveState);

expect(updateCharacteristicSpy).not.toHaveBeenCalledWith(
platformMock.Characteristic.Active,
mockActiveState,
);
});
});

describe('setCurrentFanState()', () => {
it('should set current fan state to INACTIVE when rotation speed is 0', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

const mockRotationSpeed = 0;
const expected = 0; // INACTIVE

const updateCharacteristicSpy = vi.fn();

fanAccessory['service'].updateCharacteristic = updateCharacteristicSpy;

fanAccessory['setCurrentFanState'](mockRotationSpeed);

expect(updateCharacteristicSpy).toHaveBeenCalledWith(
platformMock.Characteristic.CurrentFanState,
expected,
);
});

it('should set the current fan state to IDLE when rotation speed is below the active speed threshold but above 0', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

const mockRotationSpeed = ACTIVE_SPEED_THRESHOLD - 1;
const expected = 1; // IDLE

const updateCharacteristicSpy = vi.fn();

fanAccessory['service'].updateCharacteristic = updateCharacteristicSpy;

fanAccessory['setCurrentFanState'](mockRotationSpeed);

expect(updateCharacteristicSpy).toHaveBeenCalledWith(
platformMock.Characteristic.CurrentFanState,
expected,
);
});

it('should set the current fan state to BLOWING_AIR when rotation speed is above the speed threshold', () => {
const fanAccessory = new FanAccessory(platformMock, accessoryMock, configMock);

const mockRotationSpeed = ACTIVE_SPEED_THRESHOLD + 1;
const expected = 2; // BLOWING_AIR

const updateCharacteristicSpy = vi.fn();

fanAccessory['service'].updateCharacteristic = updateCharacteristicSpy;

fanAccessory['setCurrentFanState'](mockRotationSpeed);

expect(updateCharacteristicSpy).toHaveBeenCalledWith(
platformMock.Characteristic.CurrentFanState,
expected,
);
});
});
});
18 changes: 1 addition & 17 deletions src/fan-accessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,6 @@ export class FanAccessory {
};
}

get isActive(): boolean {
return (
this.service.getCharacteristic(this.platform.Characteristic.Active).value ===
this.platform.Characteristic.Active.ACTIVE
);
}

get allowsManualSpeedControl(): boolean {
// The I2C to PWM protocol (manual speed control from 0 - 254) is overruled by the CO2 sensor. Virtual remote commands work as expected.
// So, if the box has an internal CO2 sensor, we can't control the fan speed manually on a 0-254 (0-100 in homekit) scale.
Expand Down Expand Up @@ -285,11 +278,6 @@ export class FanAccessory {
this.platform.Characteristic.RotationSpeed,
).value;

if (isNaN(value)) {
this.log.error(`RotationSpeed: Value is not a number: ${value}`);
return;
}

if (currentValue === value) {
this.log.debug(`RotationSpeed: Already set to: ${value}. Ignoring.`);
return;
Expand All @@ -303,11 +291,6 @@ export class FanAccessory {
setActive(value: number): void {
const currentValue = this.service.getCharacteristic(this.platform.Characteristic.Active).value;

if (isNaN(value)) {
this.log.error(`Active: Value is not a number: ${value}`);
return;
}

if (currentValue === value) {
this.log.debug(`Active: Already set to: ${value}. Ignoring.`);
return;
Expand Down Expand Up @@ -382,6 +365,7 @@ export class FanAccessory {
key => this.platform.Characteristic.CurrentFanState[key] === value,
);
}

getActiveName(value: number): string | undefined {
return Object.keys(this.platform.Characteristic.Active).find(
key => this.platform.Characteristic.Active[key] === value,
Expand Down
3 changes: 2 additions & 1 deletion src/mocks/fan-accessory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { DEFAULT_FAN_NAME } from '@/settings';
import { IthoDaalderopAccessoryContext } from '@/types';
import hap from 'hap-nodejs';

export const mockDisplayName = `CVE ECO`;
export const mockDisplayName = DEFAULT_FAN_NAME;
export const mockUUID = hap.uuid.generate(mockDisplayName);

export const mockAccessoryContext = {
Expand Down
15 changes: 15 additions & 0 deletions src/mocks/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const mockGetCharacteristics = () => {
onGet: vi.fn().mockReturnThis(),
getCharacteristics: mockGetCharacteristics,
setCharacteristics: mockSetCharacteristics,
setProps: vi.fn().mockReturnThis(),
};
};

Expand Down Expand Up @@ -85,6 +86,20 @@ export const platformMock = {
On: 'On',
Name: 'Name',
OutletInUse: 'OutletInUse',
RotationSpeed: 'RotationSpeed',
Active: {
INACTIVE: 0,
ACTIVE: 1,
},
TargetFanState: {
MANUAL: 0,
AUTO: 1,
},
CurrentFanState: {
INACTIVE: 0,
IDLE: 1,
BLOWING_AIR: 2,
},
},
} as unknown as HomebridgeIthoDaalderop;

Expand Down

0 comments on commit 2a0f6ae

Please sign in to comment.