Skip to content
Open
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
73 changes: 57 additions & 16 deletions src/plugin/__tests__/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,25 +76,66 @@ describe("settings", () => {
} satisfies Settings);
});

/**
* Asserts {@link setGlobalSettings} sends the command to the {@link connection}.
*/
it("setGlobalSettings", async () => {
// Arrange, act.
await setGlobalSettings({
name: "Elgato",
});
describe("setGlobalSettings", () => {

// Assert.
expect(connection.send).toHaveBeenCalledTimes(1);
expect(connection.send).toHaveBeenCalledWith<[SetGlobalSettings]>({
event: "setGlobalSettings",
context: connection.registrationParameters.pluginUUID,
payload: {
/**
* Asserts {@link setGlobalSettings} sends the command to the {@link connection}.
*/
it("with object", async () => {
// Arrange, act.
await setGlobalSettings({
name: "Elgato",
},
});

// Assert.
expect(connection.send).toHaveBeenCalledTimes(1);
expect(connection.send).toHaveBeenCalledWith<[SetGlobalSettings]>({
event: "setGlobalSettings",
context: connection.registrationParameters.pluginUUID,
payload: {
name: "Elgato",
},
});
});
});

/**
* Asserts {@link setGlobalSettings} invokes the provided callback with the current settings
* object and passes the return value via send command using {@link connection}.
*/
it("with callback", async () => {

// Arrange

const current = {
name: "Current"
}
const didReceiveGlobalSettingsEvent: DidReceiveGlobalSettings<typeof current> = {
event: "didReceiveGlobalSettings",
payload: {
settings: current,
},
};
const expected = {
name: "Changed",
}
const callback = vi.fn(() => expected);

// Act

const execution = setGlobalSettings(callback);
connection.emit("didReceiveGlobalSettings", didReceiveGlobalSettingsEvent);
await execution;

// Assert

expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith(current);
expect(connection.send).toHaveBeenCalledWith(
expect.objectContaining({
payload: expected,
}));
});
})
});

describe("receiving emits with useExperimentalMessageIdentifiers set to false", () => {
Expand Down
84 changes: 68 additions & 16 deletions src/plugin/actions/__tests__/action.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { beforeAll, describe, expect, it, test, vi } from "vitest";

import type { Settings } from "../../../api/__mocks__/events.js";
import { DeviceType, type GetSettings, type SetSettings, type ShowAlert, type WillAppear } from "../../../api/index.js";
import { DeviceType, type DidReceiveSettings, type GetSettings, type SetSettings, type ShowAlert, type WillAppear } from "../../../api/index.js";
import type { JsonObject } from "../../../common/json.js";
import { connection } from "../../connection.js";
import { Device } from "../../devices/device.js";
Expand Down Expand Up @@ -169,26 +169,78 @@ describe("Action", () => {
let action!: Action;
beforeAll(() => (action = new Action(source)));

/**
* Asserts {@link Action.setSettings} forwards the command to the {@link connection}.
*/
it("setSettings", async () => {
// Arrange, act.
await action.setSettings({
name: "Elgato",
});
describe("setSettings", async () => {

// Assert.
expect(connection.send).toHaveBeenCalledTimes(1);
expect(connection.send).toHaveBeenCalledWith<[SetSettings]>({
context: action.id,
event: "setSettings",
payload: {
/**
* Asserts {@link Action.setSettings} forwards the command to the {@link connection}.
*/
it("with object", async () => {
// Arrange, act.
await action.setSettings({
name: "Elgato",
},
});

// Assert.
expect(connection.send).toHaveBeenCalledTimes(1);
expect(connection.send).toHaveBeenCalledWith<[SetSettings]>({
context: action.id,
event: "setSettings",
payload: {
name: "Elgato",
},
});
});

it("with callback", async () => {

// Arrange

const current = {
name: "Current",
}
const expected = {
name: "Changed",
};

const callback = vi.fn(() => expected);

const didReceiveSettingsEvent: DidReceiveSettings<typeof current> = {
action: "any.acton",
context: action.id,
event: "didReceiveSettings",
device: "any",
payload: {
controller: "Keypad",
coordinates: {
column: 0,
row: 0,
},
isInMultiAction: false,
resources: {},
settings: current,
},
};

// Act

const execution = action.setSettings(callback);

connection.emit("didReceiveSettings", didReceiveSettingsEvent);

await execution;

// Assert

expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith(current);
expect(connection.send).toHaveBeenCalledWith(
expect.objectContaining({
payload: expected,
}));
});
});


/**
* Asserts {@link Action.showAlert} forwards the command to the {@link connection}.
*/
Expand Down
18 changes: 14 additions & 4 deletions src/plugin/actions/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,25 @@ export class Action<T extends JsonObject = JsonObject> extends ActionContext {
}

/**
* Sets the {@link settings} associated with this action instance. Use in conjunction with {@link Action.getSettings}.
* @param settings Settings to persist.
* Sets the {@link settings} associated with this action instance. See also {@link Action.getSettings}.
*
* In addition to passing a settings object directly, this method also supports a functional
* updater. When a function is provided, it will be invoked with the current settings and must
* return the updated settings, either synchronously or asynchronously.
* @param settings Either a settings object to persist, or a function that receives the current
* settings and returns the new settings (or a Promise resolving to them).
* @returns `Promise` resolved when the {@link settings} are sent to Stream Deck.
*/
public setSettings<U extends JsonObject = T>(settings: U): Promise<void> {
public async setSettings<U extends JsonObject = T>(settings: U | ((settings: U) => Promise<U> | U)): Promise<void> {

const payload = typeof settings === "function"
? await settings(await this.getSettings())
: settings;

return connection.send({
event: "setSettings",
context: this.id,
payload: settings,
payload,
});
}

Expand Down
31 changes: 19 additions & 12 deletions src/plugin/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ import { requiresVersion } from "./validation.js";

let __useExperimentalMessageIdentifiers = false;

const getGlobalSettings = <T extends JsonObject = JsonObject>(): Promise<T> => {
return new Promise((resolve) => {
connection.once("didReceiveGlobalSettings", (ev: DidReceiveGlobalSettings<T>) => resolve(ev.payload.settings));
connection.send({
event: "getGlobalSettings",
context: connection.registrationParameters.pluginUUID,
id: randomUUID(),
});
});
};

export const settings = {
/**
* Available from Stream Deck 7.1; determines whether message identifiers should be sent when getting
Expand Down Expand Up @@ -40,16 +51,7 @@ export const settings = {
* @template T The type of global settings associated with the plugin.
* @returns Promise containing the plugin's global settings.
*/
getGlobalSettings: <T extends JsonObject = JsonObject>(): Promise<T> => {
return new Promise((resolve) => {
connection.once("didReceiveGlobalSettings", (ev: DidReceiveGlobalSettings<T>) => resolve(ev.payload.settings));
connection.send({
event: "getGlobalSettings",
context: connection.registrationParameters.pluginUUID,
id: randomUUID(),
});
});
},
getGlobalSettings,

/**
* Occurs when the global settings are requested, or when the the global settings were updated in
Expand Down Expand Up @@ -104,11 +106,16 @@ export const settings = {
* connectedDate: new Date()
* })
*/
setGlobalSettings: async <T extends JsonObject>(settings: T): Promise<void> => {
setGlobalSettings: async <T extends JsonObject>(settings: T | ((current: T) => Promise<T> | T)): Promise<void> => {

const payload = typeof settings === "function"
? await settings(await getGlobalSettings())
: settings;

await connection.send({
event: "setGlobalSettings",
context: connection.registrationParameters.pluginUUID,
payload: settings,
payload,
});
},
};