Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import preferArrowFunctions from "eslint-plugin-prefer-arrow-functions";
import globals from "globals";
import stylistic from "@stylistic/eslint-plugin";
import sortClassMembers from "eslint-plugin-sort-class-members";
import vitest from '@vitest/eslint-plugin'

export default defineConfig([
globalIgnores(["**/*.{js,cjs,mjs}", "!src/**/*.{js,cjs,mjs}"]),
Expand Down Expand Up @@ -293,7 +294,31 @@ export default defineConfig([
},
},
{
files: ["src/**/*.{js,cjs,mjs}"],
files: ["tests/**/*.ts"], // or any other pattern
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: ["./tests/tsconfig.json", "./tests/type/tsconfig.json"],
sourceType: "module",
},
globals: {
...vitest.environments.env.globals,
},
},
settings: {
vitest: {
typecheck: true,
},
},
plugins: {
vitest,
},
rules: {
...vitest.configs.recommended.rules, // you can also use vitest.configs.all.rules to enable all rules
},
},
{
files: ["src/**/*.{js,cjs,mjs}", "tests/**/*.{js,cjs,mjs}"],
rules: {
"no-restricted-syntax": [
"error",
Expand Down
32 changes: 32 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@types/w3c-web-usb": "^1.0.14",
"@types/ws": "^8.18.1",
"@vitest/coverage-v8": "^4.0.16",
"@vitest/eslint-plugin": "^1.6.26",
"@vitest/expect": "^4.1.10",
"eslint": "^10.1.0",
"eslint-plugin-jsdoc": "^62.8.0",
Expand Down
7 changes: 7 additions & 0 deletions tests/integration/deviceEvents.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ describe('Device events', () => {
settingsManager.replace(settingsWithOneDevice);

await deviceDisconnected;

const remainingDevices = deviceManager.getConnectedDevices();

expect(remainingDevices).toHaveLength(1);
expect(remainingDevices[0]?.getDeviceId).toStrictEqual(TEST_DEVICE_ID);
}, 1000);

it('disabling a known device closes it and re-enabling it reconnects it', async () => {
Expand Down Expand Up @@ -250,5 +255,7 @@ describe('Device events', () => {

await device?.close();
await disconnected;

expect(deviceManager.getConnectedDevices()).toHaveLength(0);
});
});
8 changes: 4 additions & 4 deletions tests/unit/automation/scriptRuntime.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,7 @@ describe('ScriptRuntime (isolated-vm)', () => {
});

it('runForEvent does nothing when not loaded', () => {
// Should not throw
runtime.runForEvent({ type: DeviceManagerEvent.deviceConnected, device: deviceA, args: [] });
expect(() => runtime.runForEvent({ type: DeviceManagerEvent.deviceConnected, device: deviceA, args: [] })).not.toThrow();
});

it('onStart handler runs before scriptStarted', async () => {
Expand Down Expand Up @@ -435,8 +434,9 @@ describe('ScriptRuntime (isolated-vm)', () => {
});
`);

// Should complete without error
await dispatchAndCollect(eventEmitter, runtime, deviceA, TEST_END_MARKER);
const logs = await dispatchAndCollect(eventEmitter, runtime, deviceA, TEST_END_MARKER);

expect(logs).toContain(TEST_END_MARKER);
});

// -----------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/controller/getDevicesController.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { createTestDevice } from '../device/testDevice.js';

describe('getDevicesController', () => {

it('it returns all connected devices', async () => {
it('returns all connected devices', async () => {
const fwVersion = 10000;
const deviceUuid = 'foo-bar-baz';
const deviceName = 'Aston Martin';
Expand Down
9 changes: 4 additions & 5 deletions tests/unit/device/deviceManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('deviceManager', () => {
return offerPromise;
};

it('it adds device to managed devices and emits an event', async () => {
it('adds device to managed devices and emits an event', async () => {

const mockedDeviceManagerEventEmitter = mock<EventEmitter>();
mockedDeviceManagerEventEmitter.emit.mockReturnValue(true);
Expand Down Expand Up @@ -75,7 +75,7 @@ describe('deviceManager', () => {
});


it('it removes device from managed devices and emits event on disconnect', async () => {
it('removes device from managed devices and emits event on disconnect', async () => {

const deviceId = DeviceId.create('test-device-id');
const device = new TestDevice(deviceId, 'Foo', new Date(), false, new EventEmitter());
Expand All @@ -100,7 +100,7 @@ describe('deviceManager', () => {
expect(mockedLogger.child).toBeCalledWith({ name: DeviceManager.name });
});

it('it emits an event on device update', async () => {
it('emits an event on device update', async () => {

const deviceId = DeviceId.create('test-device-id');
const device = new TestDevice(deviceId, 'Foo', new Date(), false, new EventEmitter());
Expand Down Expand Up @@ -189,8 +189,7 @@ describe('deviceManager', () => {
manager.announceDetectedDevice(deviceInfo);
manager.announceDetectedDevice(deviceInfo);

expect(mockedEventEmitter.emit).toHaveBeenCalledOnce();
expect(mockedEventEmitter.emit).toHaveBeenCalledWith(DeviceManagerEvent.deviceDetected, deviceInfo);
expect(mockedEventEmitter.emit).toHaveBeenCalledExactlyOnceWith(DeviceManagerEvent.deviceDetected, deviceInfo);
});

it('allows re-announcing a device after it was revoked (tombstone must not permanently block it)', () => {
Expand Down
3 changes: 1 addition & 2 deletions tests/unit/device/knownDeviceRegistry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ describe('KnownDeviceRegistry', () => {

registry.persist(knownDevice);

expect(mockSettings.addKnownDevice).toHaveBeenCalledOnce();
expect(mockSettings.addKnownDevice).toHaveBeenCalledWith(knownDevice);
expect(mockSettings.addKnownDevice).toHaveBeenCalledExactlyOnceWith(knownDevice);
});

it('does not touch settings when persisting an already-known, unchanged identity', () => {
Expand Down
17 changes: 6 additions & 11 deletions tests/unit/device/protocol/airotic/airoticDevice.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,7 @@ describe('AiroticDevice', () => {

await device.setAttribute('resetColors', true);

expect(mockHandler.send).toHaveBeenCalledOnce();
expect(mockHandler.send).toHaveBeenCalledWith(AiroticProtocol.createResetColorsMessage());
expect(mockHandler.send).toHaveBeenCalledExactlyOnceWith(AiroticProtocol.createResetColorsMessage());
});

it('does not send any message when value is false', async () => {
Expand All @@ -165,8 +164,7 @@ describe('AiroticDevice', () => {

await device.setAttribute('reboot', true);

expect(mockHandler.send).toHaveBeenCalledOnce();
expect(mockHandler.send).toHaveBeenCalledWith(AiroticProtocol.createRebootMessage());
expect(mockHandler.send).toHaveBeenCalledExactlyOnceWith(AiroticProtocol.createRebootMessage());
expect(closeSpy).toHaveBeenCalled();
});

Expand Down Expand Up @@ -251,8 +249,7 @@ describe('AiroticDevice', () => {
it('registers an onReceive callback on the transport', () => {
const device = createDevice();

expect(mockTransport.onReceive).toHaveBeenCalledOnce();
expect(mockTransport.onReceive).toHaveBeenCalledWith(expect.any(Function));
expect(mockTransport.onReceive).toHaveBeenCalledExactlyOnceWith(expect.any(Function));
});

});
Expand All @@ -272,8 +269,7 @@ describe('AiroticDevice', () => {

onReceiveCb!(Buffer.from('*B', 'utf-8'));

expect(listener).toHaveBeenCalledOnce();
expect(listener).toHaveBeenCalledWith(device, {
expect(listener).toHaveBeenCalledExactlyOnceWith(device, {
type: 'colorChange',
data: { colorType: 'breathInColor' },
});
Expand All @@ -285,8 +281,7 @@ describe('AiroticDevice', () => {

onReceiveCb!(Buffer.from('*R', 'utf-8'));

expect(listener).toHaveBeenCalledOnce();
expect(listener).toHaveBeenCalledWith(device, {
expect(listener).toHaveBeenCalledExactlyOnceWith(device, {
type: 'colorChange',
data: { colorType: 'restColor' },
});
Expand Down Expand Up @@ -500,7 +495,7 @@ describe('AiroticDevice', () => {
});
});

describe('setAttribute reboot', () => {
describe('setAttribute reboot - close timing', () => {
it('calls sleep(500) between sending the reboot message and closing', async () => {
const { sleep } = await import('../../../../../src/util/async.js');
const device = createDevice();
Expand Down
33 changes: 15 additions & 18 deletions tests/unit/device/protocol/buttplugIo/buttplugIoDevice.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,19 @@ describe('ButtplugIoDevice', () => {
);
}

it('it throws an error if non-existing attribute is set', async () => {
it('throws an error if non-existing attribute is set', async () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
const device = createDevice(buttplugDeviceMock, {});

const attrName: ButtplugIoDeviceAttributeKey = 'Vibrate-1';

// Act
const result = expect(device.setAttribute(attrName, false));

// Assert
await result.rejects.toThrow(`Attribute with name '${attrName}' does not exist for this device`);
// Act & Assert
await expect(device.setAttribute(attrName, false)).rejects.toThrow(`Attribute with name '${attrName}' does not exist for this device`);
});

it('it updates device data and calls buttplugClientDevice on setting boolean attribute', async () => {
it('updates device data and calls buttplugClientDevice on setting boolean attribute', async () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand All @@ -72,7 +69,7 @@ describe('ButtplugIoDevice', () => {
expect(buttplugDeviceMock.scalar).toHaveBeenCalledWith({ActuatorType: 'Rotate', Index: 1, Scalar: 0});
});

it('it updates device data and calls buttplugClientDevice on setting range attribute', async () => {
it('updates device data and calls buttplugClientDevice on setting range attribute', async () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand Down Expand Up @@ -109,7 +106,7 @@ describe('ButtplugIoDevice', () => {
});
});

it('it updates device data and calls buttplugClientDevice on setting boolean attribute to true', async () => {
it('updates device data and calls buttplugClientDevice on setting boolean attribute to true', async () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand All @@ -126,7 +123,7 @@ describe('ButtplugIoDevice', () => {
expect(buttplugDeviceMock.scalar).toHaveBeenCalledWith({ActuatorType: 'Rotate', Index: 1, Scalar: 1});
});

it('it throws an error if attribute is read-only', async () => {
it('throws an error if attribute is read-only', async () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand All @@ -142,7 +139,7 @@ describe('ButtplugIoDevice', () => {
expect(buttplugDeviceMock.scalar).not.toHaveBeenCalled();
});

it('it throws an error for attribute with a sensor-type key', async () => {
it('throws an error for attribute with a sensor-type key', async () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand All @@ -158,7 +155,7 @@ describe('ButtplugIoDevice', () => {
expect(buttplugDeviceMock.scalar).not.toHaveBeenCalled();
});

it('it throws when setting attribute with undefined value', async () => {
it('throws when setting attribute with undefined value', async () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand All @@ -174,7 +171,7 @@ describe('ButtplugIoDevice', () => {
expect(buttplugDeviceMock.scalar).not.toHaveBeenCalled();
});

it('it updates device data and calls buttplugClientDevice on setting int attribute', async () => {
it('updates device data and calls buttplugClientDevice on setting int attribute', async () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand All @@ -193,7 +190,7 @@ describe('ButtplugIoDevice', () => {
expect(buttplugDeviceMock.scalar).toHaveBeenCalledWith({ ActuatorType: 'Oscillate', Index: 1, Scalar: newValue });
});

it('it updates sensor attribute values on refresh', async () => {
it('updates sensor attribute values on refresh', async () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand All @@ -217,7 +214,7 @@ describe('ButtplugIoDevice', () => {
expect((await device.getAttribute(sensorAttrKey))?.value).toStrictEqual(Int.from(85));
});

it('it skips sensor refresh when device has no SensorReadCmd', async () => {
it('skips sensor refresh when device has no SensorReadCmd', async () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand All @@ -235,7 +232,7 @@ describe('ButtplugIoDevice', () => {
expect(buttplugDeviceMock.sensorRead).not.toHaveBeenCalled();
});

it('it reports a refresh interval when the device has sensors', () => {
it('reports a refresh interval when the device has sensors', () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand All @@ -252,7 +249,7 @@ describe('ButtplugIoDevice', () => {
expect(device.getRefreshInterval).toBe(100);
});

it('it reports no refresh interval when the device has no sensors (actuator-only)', () => {
it('reports no refresh interval when the device has no sensors (actuator-only)', () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand All @@ -267,7 +264,7 @@ describe('ButtplugIoDevice', () => {
expect(device.getRefreshInterval).toBeUndefined();
});

it('it reports no refresh interval when SensorReadCmd is an empty array', () => {
it('reports no refresh interval when SensorReadCmd is an empty array', () => {

// Arrange
const buttplugDeviceMock = mock<ButtplugClientDevice>();
Expand Down
Loading
Loading