diff --git a/src/livekit/backgroundEffects.test.ts b/src/livekit/backgroundEffects.test.ts new file mode 100644 index 000000000..c70981df8 --- /dev/null +++ b/src/livekit/backgroundEffects.test.ts @@ -0,0 +1,65 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { describe, expect, test } from "vitest"; + +import { + parseEffect, + serializeEffect, + shippedBackgrounds, + imagePathFor, +} from "./backgroundEffects"; + +describe("the chosen background effect", () => { + test("round-trips through its stored form", () => { + for (const effect of [ + { kind: "none" } as const, + { kind: "blur" } as const, + { kind: "image", id: shippedBackgrounds[0].id } as const, + ]) + expect(parseEffect(serializeEffect(effect))).toEqual(effect); + }); + + test("falls back to no effect when the stored form names nothing we ship", () => { + // A background removed between releases, or a value from a future one. + expect(parseEffect("image:a-background-we-no-longer-ship")).toEqual({ + kind: "none", + }); + expect(parseEffect("")).toEqual({ kind: "none" }); + }); + + test("gives every shipped background an image to draw", () => { + for (const background of shippedBackgrounds) + expect(imagePathFor(background.id)).toBeTruthy(); + }); +}); + +describe("the blur control in settings", () => { + // The control is a checkbox over a setting that holds more than two states, + // so what it shows and what it writes are both derived. This is that + // derivation, which is what keeps it from disagreeing with the camera menu. + const shows = (stored: string): boolean => + parseEffect(stored).kind === "blur"; + const writes = (on: boolean): string => + serializeEffect(on ? { kind: "blur" } : { kind: "none" }); + + test("reads as off while an image background is in force", () => { + expect(shows(serializeEffect({ kind: "image", id: "indoor" }))).toBe(false); + }); + + test("reads as on while blur is in force", () => { + expect(shows("blur")).toBe(true); + }); + + test("replaces an image background when it is turned on", () => { + expect(writes(true)).toBe("blur"); + }); + + test("leaves no effect behind when it is turned off", () => { + expect(writes(false)).toBe("none"); + }); +}); diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 933ac66f5..7c0472e01 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -22,13 +22,17 @@ import { useHostBridge } from "../HostBridge"; import { useSetting, soundEffectVolume as soundEffectVolumeSetting, - backgroundBlur as backgroundBlurSetting, + backgroundEffect as backgroundEffectSetting, developerMode, } from "./settings"; import { PreferencesSettingsTab } from "./PreferencesSettingsTab"; import { Slider } from "../Slider"; import { DeviceSelection } from "./DeviceSelection"; import { useTrackProcessor } from "../livekit/TrackProcessorContext"; +import { + parseEffect, + serializeEffect, +} from "../livekit/backgroundEffects"; import { DeveloperSettingsTab, type DeveloperSettingsSnapshot, @@ -83,7 +87,13 @@ export const SettingsModal: FC = ({ const BlurCheckbox: React.FC = (): ReactNode => { const { supported } = useTrackProcessor(); - const [blurActive, setBlurActive] = useSetting(backgroundBlurSetting); + // The same setting the camera menu's Background effects grid writes, so + // the two controls cannot disagree. An image background reads here as blur + // off, and turning blur on replaces it. + const [effect, setEffect] = useSetting(backgroundEffectSetting); + const blurActive = parseEffect(effect).kind === "blur"; + const setBlurActive = (on: boolean): void => + setEffect(serializeEffect(on ? { kind: "blur" } : { kind: "none" })); return ( <> @@ -97,7 +107,7 @@ export const SettingsModal: FC = ({ supported ? "" : t("settings.blur_not_supported_by_browser") } type="checkbox" - checked={!!blurActive} + checked={blurActive} onChange={(b): void => setBlurActive(b.target.checked)} disabled={!supported} /> diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 0194891a0..6b7fefdab 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -123,7 +123,10 @@ export const backgroundBlur = new Setting("background-blur", false); */ export const backgroundEffect = new Setting( "background-effect", - "none", + // Defaults to whatever blur the user had already chosen, so someone who + // turned blur on before this existed still has it afterwards. Their own + // choice replaces this the moment they make one. + backgroundBlur.getValue() ? "blur" : "none", ); export const showHandRaisedTimer = new Setting(