Introduce a Module API to get application settings. (#34278)
* Add a module API to get application settings. * fix test
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { test, expect } from "../../element-web-test";
|
||||
import { getSampleFilePath } from "../../sample-files";
|
||||
|
||||
test.describe("Settings API", () => {
|
||||
test.use({
|
||||
displayName: "Manny",
|
||||
config: {
|
||||
modules: ["/modules/settings-module.js"],
|
||||
setting_defaults: {
|
||||
language: "de",
|
||||
},
|
||||
},
|
||||
page: async ({ page }, use) => {
|
||||
await page.route("/modules/settings-module.js", async (route) => {
|
||||
await route.fulfill({ path: getSampleFilePath("settings-module.js") });
|
||||
});
|
||||
await use(page);
|
||||
},
|
||||
});
|
||||
|
||||
test("should read a config-resolved setting value via api.settings.getValue", async ({ page }) => {
|
||||
const dialogPromise = page.waitForEvent("dialog");
|
||||
await page.goto("/");
|
||||
const dialog = await dialogPromise;
|
||||
expect(dialog.message()).toBe("de");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
export default class SettingsModule {
|
||||
static moduleApiVersion = "^*";
|
||||
constructor(api) {
|
||||
this.api = api;
|
||||
}
|
||||
async load() {
|
||||
alert(this.api.settings.getValue("language"));
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import { WidgetLifecycleApi } from "./WidgetLifecycleApi.ts";
|
||||
import { WidgetApi } from "./WidgetApi.ts";
|
||||
import { CustomisationsApi } from "./customisationsApi.ts";
|
||||
import { ComposerApi } from "./ComposerApi.ts";
|
||||
import { SettingsApi } from "./SettingsApi.ts";
|
||||
import defaultDispatcher from "../dispatcher/dispatcher.ts";
|
||||
|
||||
const legacyCustomisationsFactory = <T extends object>(baseCustomisations: T) => {
|
||||
@@ -98,6 +99,7 @@ export class ModuleApi implements Api {
|
||||
public readonly client = new ClientApi();
|
||||
public readonly stores = new StoresApi();
|
||||
public readonly composer = new ComposerApi(defaultDispatcher);
|
||||
public readonly settings = new SettingsApi();
|
||||
|
||||
public createRoot(element: Element): Root {
|
||||
return createRoot(element);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { SettingsApi } from "./SettingsApi";
|
||||
|
||||
describe("SettingsApi", () => {
|
||||
it("should return the value from SettingsStore.getValue", () => {
|
||||
const spy = vi.spyOn(SettingsStore, "getValue").mockReturnValue("en" as any);
|
||||
const api = new SettingsApi();
|
||||
expect(api.getValue("language")).toBe("en");
|
||||
expect(spy).toHaveBeenCalledWith("language", undefined, undefined);
|
||||
});
|
||||
|
||||
it("should pass roomId and excludeDefault through to SettingsStore.getValue", () => {
|
||||
const spy = vi.spyOn(SettingsStore, "getValue").mockReturnValue(null);
|
||||
const api = new SettingsApi();
|
||||
api.getValue("m.setting", "!room:example.org", true);
|
||||
expect(spy).toHaveBeenCalledWith("m.setting", "!room:example.org", true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import type { SettingsApi as ISettingsApi } from "@element-hq/element-web-module-api";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
|
||||
export class SettingsApi implements ISettingsApi {
|
||||
public getValue<T = any>(settingName: string, roomId?: string | null, excludeDefault?: boolean): T | undefined {
|
||||
//@ts-expect-error: SettingsStore.getValue will throw on an invalid setting name anyway.
|
||||
return SettingsStore.getValue(settingName, roomId, excludeDefault);
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,8 @@ export interface Api extends LegacyModuleApiExtension, LegacyCustomisationsApiEx
|
||||
readonly i18n: I18nApi;
|
||||
readonly navigation: NavigationApi;
|
||||
readonly rootNode: HTMLElement;
|
||||
// @alpha
|
||||
readonly settings: SettingsApi;
|
||||
readonly stores: StoresApi;
|
||||
// @alpha
|
||||
readonly widget: WidgetApi;
|
||||
@@ -478,6 +480,11 @@ export interface RoomViewProps {
|
||||
// @alpha @deprecated (undocumented)
|
||||
export type RuntimeModuleConstructor = new (api: ModuleApi) => RuntimeModule;
|
||||
|
||||
// @alpha
|
||||
export interface SettingsApi {
|
||||
getValue<T = any>(settingName: string, roomId?: string | null, excludeDefault?: boolean): T | undefined;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export interface SpacePanelItemProps {
|
||||
className?: string;
|
||||
|
||||
@@ -24,6 +24,7 @@ import { type WidgetLifecycleApi } from "./widget-lifecycle.ts";
|
||||
import { type WidgetApi } from "./widget.ts";
|
||||
import { type CustomisationsApi } from "./customisations.ts";
|
||||
import { type ComposerApi } from "./composer.ts";
|
||||
import { type SettingsApi } from "./settings.ts";
|
||||
|
||||
/**
|
||||
* Module interface for modules to implement.
|
||||
@@ -166,6 +167,12 @@ export interface Api
|
||||
*/
|
||||
readonly composer: ComposerApi;
|
||||
|
||||
/**
|
||||
* Allows modules to read application settings.
|
||||
* @alpha Subject to change.
|
||||
*/
|
||||
readonly settings: SettingsApi;
|
||||
|
||||
/**
|
||||
* Create a ReactDOM root for rendering React components.
|
||||
* Exposed to allow modules to avoid needing to bundle their own ReactDOM.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* API for reading application settings.
|
||||
* @alpha Subject to change.
|
||||
*/
|
||||
export interface SettingsApi {
|
||||
/**
|
||||
* Gets the value of a setting, computed across all applicable levels
|
||||
* (device, room, account, config, default, etc.).
|
||||
* @param settingName - The name of the setting to read.
|
||||
* @param roomId - Room ID to read a room-scoped value for, or null/undefined for a
|
||||
* non-room-scoped value.
|
||||
* @param excludeDefault - If true, do not fall back to the setting's default value.
|
||||
*/
|
||||
getValue<T = any>(settingName: string, roomId?: string | null, excludeDefault?: boolean): T | undefined;
|
||||
}
|
||||
@@ -35,5 +35,6 @@ export type * from "./api/widget-lifecycle";
|
||||
export type * from "./api/widget";
|
||||
export type * from "./api/customisations";
|
||||
export { UIComponent } from "./api/customisations";
|
||||
export type * from "./api/settings";
|
||||
export * from "./api/watchable";
|
||||
export type * from "./utils";
|
||||
|
||||
Reference in New Issue
Block a user