Add support for fusing/unfusing JellyLMS zones into synchronized
multi-room groups, addressed by MAC (derived from the `lms-{mac}` device id).
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { get } from "svelte/store";
|
||||
import type { Session } from "$lib/api/types";
|
||||
|
||||
const mockInvoke = vi.fn();
|
||||
vi.mock("$lib/api/bindings", () => ({
|
||||
commands: {
|
||||
lmsGetSyncGroups: () => mockInvoke("lms_get_sync_groups"),
|
||||
lmsCreateSyncGroup: (masterMac: string, slaveMacs: string[]) =>
|
||||
mockInvoke("lms_create_sync_group", { masterMac, slaveMacs }),
|
||||
lmsUnsyncPlayer: (mac: string) => mockInvoke("lms_unsync_player", { mac }),
|
||||
lmsDissolveSyncGroup: (masterMac: string) =>
|
||||
mockInvoke("lms_dissolve_sync_group", { masterMac }),
|
||||
},
|
||||
}));
|
||||
|
||||
import { lmsSync, isLmsSession, macForSession } from "./lmsSync";
|
||||
|
||||
function session(deviceId: string | null): Session {
|
||||
return { id: "s", deviceId } as unknown as Session;
|
||||
}
|
||||
|
||||
describe("LMS session detection", () => {
|
||||
it("detects LMS zones by the lms- device-id prefix", () => {
|
||||
expect(isLmsSession(session("lms-aa:bb:cc:dd:ee:ff"))).toBe(true);
|
||||
expect(isLmsSession(session("chromecast-123"))).toBe(false);
|
||||
expect(isLmsSession(session(null))).toBe(false);
|
||||
expect(isLmsSession(null)).toBe(false);
|
||||
});
|
||||
|
||||
it("recovers the MAC from an LMS session, null otherwise", () => {
|
||||
expect(macForSession(session("lms-aa:bb:cc:dd:ee:ff"))).toBe("aa:bb:cc:dd:ee:ff");
|
||||
expect(macForSession(session("web-xyz"))).toBeNull();
|
||||
expect(macForSession(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("lmsSync store", () => {
|
||||
beforeEach(() => {
|
||||
mockInvoke.mockReset();
|
||||
lmsSync.reset();
|
||||
});
|
||||
|
||||
it("fusing preserves existing slaves and adds the new zone", async () => {
|
||||
// Initial group: master M with slave A.
|
||||
mockInvoke.mockResolvedValueOnce([{ masterMac: "M", masterName: "M", slaveMacs: ["A"], slaveNames: [] }]);
|
||||
await lmsSync.refresh();
|
||||
|
||||
// create returns void; refresh after returns the updated group.
|
||||
mockInvoke.mockResolvedValueOnce(undefined);
|
||||
mockInvoke.mockResolvedValueOnce([{ masterMac: "M", masterName: "M", slaveMacs: ["A", "B"], slaveNames: [] }]);
|
||||
|
||||
await lmsSync.fuseZone("M", "B");
|
||||
|
||||
expect(mockInvoke).toHaveBeenCalledWith("lms_create_sync_group", {
|
||||
masterMac: "M",
|
||||
slaveMacs: ["A", "B"],
|
||||
});
|
||||
expect(get(lmsSync).groups[0].slaveMacs).toEqual(["A", "B"]);
|
||||
});
|
||||
|
||||
it("decoupling a slave unsyncs just that player", async () => {
|
||||
mockInvoke.mockResolvedValueOnce([{ masterMac: "M", masterName: "M", slaveMacs: ["A"], slaveNames: [] }]);
|
||||
await lmsSync.refresh();
|
||||
|
||||
mockInvoke.mockResolvedValueOnce(undefined); // unsync
|
||||
mockInvoke.mockResolvedValueOnce([{ masterMac: "M", masterName: "M", slaveMacs: [], slaveNames: [] }]);
|
||||
|
||||
await lmsSync.decoupleZone("A");
|
||||
|
||||
expect(mockInvoke).toHaveBeenCalledWith("lms_unsync_player", { mac: "A" });
|
||||
});
|
||||
|
||||
it("decoupling the master dissolves the whole group", async () => {
|
||||
mockInvoke.mockResolvedValueOnce([{ masterMac: "M", masterName: "M", slaveMacs: ["A"], slaveNames: [] }]);
|
||||
await lmsSync.refresh();
|
||||
|
||||
mockInvoke.mockResolvedValueOnce(undefined); // dissolve
|
||||
mockInvoke.mockResolvedValueOnce([]);
|
||||
|
||||
await lmsSync.decoupleZone("M");
|
||||
|
||||
expect(mockInvoke).toHaveBeenCalledWith("lms_dissolve_sync_group", { masterMac: "M" });
|
||||
});
|
||||
|
||||
it("treats a missing plugin (refresh error) as no groups, not fatal", async () => {
|
||||
mockInvoke.mockRejectedValueOnce(new Error("404"));
|
||||
await lmsSync.refresh();
|
||||
expect(get(lmsSync).groups).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user