Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ dist/
# Shared package files
packages/plugin/README.md
packages/plugin/LICENSE

# OS
.DS_Store
4 changes: 4 additions & 0 deletions packages/plugin/src/plugin/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { describe, expect, it, vi } from "vitest";
import { BarSubType, DeviceType, Target } from "../../api/index.js";
import { SingletonAction } from "../actions/singleton-action.js";
import { connection } from "../connection.js";
import { bridge } from "../bridge/adapter.js";
import streamDeckAsDefaultExport, { streamDeck } from "../index.js";
import { logger } from "../logging/index.js";

vi.mock("../../common/i18n.js");
vi.mock("../logging/index.js");
vi.mock("../manifest.js");
vi.mock("../connection.js");
vi.mock("../bridge/adapter.js");

describe("index", () => {
/**
Expand Down Expand Up @@ -47,10 +49,12 @@ describe("index", () => {
it("connects", async () => {
// Arrange.
const spyOnConnect = vi.spyOn(connection, "connect");
const spyOnStart = vi.spyOn(bridge, "start");

// Act, assert.
await streamDeck.connect();
expect(spyOnConnect).toHaveBeenCalledTimes(1);
expect(spyOnStart).toHaveBeenCalledTimes(1);
});

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/plugin/src/plugin/bridge/__mocks__/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { vi } from "vitest";

export const bridge = {
start: vi.fn().mockResolvedValue(undefined),
};
308 changes: 308 additions & 0 deletions packages/plugin/src/plugin/bridge/__tests__/adapter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
import type { JsonRpcRequest, JsonRpcResponse } from "@elgato/utils/rpc";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { DeviceType, type DeviceDidChange, type DidReceiveSettings, type WillAppear, type WillDisappear } from "../../../api/index.js";

vi.mock("../../connection.js");
vi.mock("../../manifest.js");

describe("debug adapter", () => {
let adapter: Awaited<typeof import("../adapter.js")>["bridge"];
let connection: Awaited<typeof import("../../connection.js")>["connection"];
let sent: Array<JsonRpcRequest | JsonRpcResponse>;

beforeEach(async () => {
vi.resetModules();
sent = [];

({ connection } = await import("../../connection.js"));
({ bridge: adapter } = await import("../adapter.js"));
adapter.attachRpc(async (value) => {
sent.push(value);
});
});

afterEach(() => {
vi.clearAllMocks();
});

it("builds a snapshot from manifest actions and visible instances", () => {
connection.emit("willAppear", createKeyWillAppear());

expect(adapter.getSnapshot()).toEqual({
actions: [
{
instances: [
{
context: "ctx_001",
controller: "Keypad",
device: "Device One",
position: {
column: 2,
kind: "key",
row: 3,
},
settings: {
count: 42,
},
},
],
name: "Action One",
uuid: "com.elgato.test.key",
},
{
instances: [],
name: "Action Two",
uuid: "com.elgato.test.dial",
},
],
plugin: {
name: "Test Plugin",
uuid: "com.elgato.test",
version: "1.0.0",
},
});
});

it("publishes snapshot changes for visible instances", () => {
connection.emit("willAppear", createKeyWillAppear());

expect(sent).toEqual([
{
jsonrpc: "2.0",
method: "streamDeck.bridge.snapshotChanged",
params: adapter.getSnapshot(),
},
]);
});

it("normalizes dial and multi-action positions", () => {
connection.emit("willAppear", createDialWillAppear());

expect(adapter.getSnapshot().actions[1].instances[0]?.position).toEqual({
column: 1,
index: 1,
kind: "dial",
row: 0,
});

connection.emit("willAppear", createMultiActionWillAppear());

expect(adapter.getSnapshot().actions[0].instances[0]?.position).toEqual({
kind: "multi-action",
});
});

it("updates settings, device details, and removals from connection events", () => {
connection.emit("willAppear", createKeyWillAppear());
clearMessages(sent);

connection.emit("didReceiveSettings", createDidReceiveSettings());
expect(adapter.getSnapshot().actions[0].instances[0]?.settings).toEqual({
count: 7,
});
expect(sent).toHaveLength(1);

clearMessages(sent);
connection.emit("deviceDidChange", createDeviceDidChange());
expect(adapter.getSnapshot().actions[0].instances[0]?.device).toBe("Renamed Device");
expect(sent).toHaveLength(1);

clearMessages(sent);
connection.emit("willDisappear", createWillDisappear());
expect(adapter.getSnapshot().actions[0].instances).toEqual([]);
expect(sent).toHaveLength(1);
});

it("responds to getSnapshot RPC requests", async () => {
connection.emit("willAppear", createKeyWillAppear());
clearMessages(sent);

const handled = await adapter.receive({
id: "request-1",
jsonrpc: "2.0",
method: "streamDeck.bridge.getSnapshot",
});

expect(handled).toBe(true);
expect(sent).toEqual([
{
id: "request-1",
jsonrpc: "2.0",
result: adapter.getSnapshot(),
},
]);
});

it("does not start the websocket transport outside debug mode", async () => {
Comment thread
ChekTek marked this conversation as resolved.
Outdated
vi.resetModules();
vi.doMock("../../common/utils.js", async () => {
const actual = await vi.importActual<typeof import("../../common/utils.js")>("../../common/utils.js");

return {
...actual,
isDebugMode: vi.fn().mockReturnValue(false),
};
});
vi.doMock("../socket.js", () => ({
socket: {
start: vi.fn().mockResolvedValue(undefined),
},
}));

const { bridge } = await import("../adapter.js");
const { socket } = await import("../socket.js");

await bridge.start();

expect(socket.start).not.toHaveBeenCalled();
});
});

/**
* Clears recorded JSON-RPC messages between assertions.
* @param messages Recorded messages.
*/
function clearMessages(messages: Array<JsonRpcRequest | JsonRpcResponse>): void {
messages.splice(0, messages.length);
}

/**
* Creates a device-change event for the seeded mock device.
* @returns Device-change event.
*/
function createDeviceDidChange(): DeviceDidChange {
return {
device: "DEV1",
deviceInfo: {
name: "Renamed Device",
size: {
columns: 5,
rows: 3,
},
type: DeviceType.StreamDeckXL,
},
event: "deviceDidChange",
};
}

/**
* Creates a settings update for the visible test key.
* @returns Did-receive-settings event.
*/
function createDidReceiveSettings(): DidReceiveSettings<{ count: number }> {
return {
action: "com.elgato.test.key",
context: "ctx_001",
device: "DEV1",
event: "didReceiveSettings",
payload: {
controller: "Keypad",
coordinates: {
column: 2,
row: 3,
},
isInMultiAction: false,
resources: {},
settings: {
count: 7,
},
},
};
}

/**
* Creates a visible dial instance event.
* @returns Will-appear event for a dial.
*/
function createDialWillAppear(): WillAppear<{ target: string }> {
return {
action: "com.elgato.test.dial",
context: "ctx_002",
device: "DEV1",
event: "willAppear",
payload: {
controller: "Encoder",
coordinates: {
column: 1,
row: 0,
},
isInMultiAction: false,
resources: {},
settings: {
target: "master",
},
},
};
}

/**
* Creates a visible key instance event.
* @returns Will-appear event for a key.
*/
function createKeyWillAppear(): WillAppear<{ count: number }> {
return {
action: "com.elgato.test.key",
context: "ctx_001",
device: "DEV1",
event: "willAppear",
payload: {
controller: "Keypad",
coordinates: {
column: 2,
row: 3,
},
isInMultiAction: false,
resources: {},
settings: {
count: 42,
},
},
};
}

/**
* Creates a visible multi-action key instance event.
* @returns Will-appear event for a multi-action key.
*/
function createMultiActionWillAppear(): WillAppear<{ count: number }> {
return {
action: "com.elgato.test.key",
context: "ctx_003",
device: "DEV1",
event: "willAppear",
payload: {
controller: "Keypad",
isInMultiAction: true,
resources: {},
settings: {
count: 99,
},
},
};
}

/**
* Creates a disappearance event for the visible test key.
* @returns Will-disappear event for a key.
*/
function createWillDisappear(): WillDisappear<{ count: number }> {
return {
action: "com.elgato.test.key",
context: "ctx_001",
device: "DEV1",
event: "willDisappear",
payload: {
controller: "Keypad",
coordinates: {
column: 2,
row: 3,
},
isInMultiAction: false,
resources: {},
settings: {
count: 7,
},
},
};
}
Loading
Loading