layout and remote fix
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m31s
Traceability Validation / Check Requirement Traces (push) Successful in 20s
Build & Release / Run Tests (push) Successful in 5m24s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 4m29s
Build & Release / Build Linux (push) Successful in 17m27s
Build & Release / Build Android (push) Successful in 22m14s
Build & Release / Create Release (push) Successful in 12s
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m31s
Traceability Validation / Check Requirement Traces (push) Successful in 20s
Build & Release / Run Tests (push) Successful in 5m24s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 4m29s
Build & Release / Build Linux (push) Successful in 17m27s
Build & Release / Build Android (push) Successful in 22m14s
Build & Release / Create Release (push) Successful in 12s
This commit is contained in:
@@ -26,6 +26,18 @@ vi.mock("./sessions", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// Capture the playerStatusEvent listener so tests can drive backend
|
||||
// `playback_mode_changed` events through the reconciler. The commands still flow
|
||||
// to the real bindings (which call the mocked `invoke`), so the existing
|
||||
// refresh/transfer tests keep exercising the true command path.
|
||||
let capturedStatusListener: ((event: { payload: any }) => void) | null = null;
|
||||
vi.mock("@tauri-apps/api/event", () => ({
|
||||
listen: vi.fn((_name: string, cb: (event: { payload: any }) => void) => {
|
||||
capturedStatusListener = cb;
|
||||
return Promise.resolve(() => {});
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock auth store
|
||||
const mockGetHandle = vi.fn(() => "repo-handle-1");
|
||||
vi.mock("./auth", () => ({
|
||||
@@ -42,6 +54,7 @@ describe("playbackMode store", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
currentSelectedSession = null;
|
||||
capturedStatusListener = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -327,6 +340,56 @@ describe("playbackMode store", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("backend playback_mode_changed reconciler", () => {
|
||||
// Regression: commit 2a1f168 made Rust re-broadcast PlaybackModeChanged on
|
||||
// every set_mode. Local playback drives set_mode("local") from both the
|
||||
// frontend and Rust, so the same mode arrives repeatedly. The reconciler
|
||||
// used to run selectSession(null) on each one, deselecting the remote
|
||||
// session mid-cast and tripping the disconnect watchdog — which broke the
|
||||
// lockscreen card, remote volume, and (via the mode flap) local audio.
|
||||
async function initListener() {
|
||||
const { playbackMode } = await import("./playbackMode");
|
||||
playbackMode.initializeSessionMonitoring();
|
||||
expect(capturedStatusListener).not.toBeNull();
|
||||
return playbackMode;
|
||||
}
|
||||
|
||||
it("ignores a no-op remote re-broadcast (no session churn)", async () => {
|
||||
currentSelectedSession = { id: "sess-1" };
|
||||
const playbackMode = await initListener();
|
||||
playbackMode.setMode("remote", "sess-1");
|
||||
mockSelectSession.mockClear();
|
||||
|
||||
// Rust re-broadcasts the SAME remote mode (e.g. a position tick path).
|
||||
capturedStatusListener!({
|
||||
payload: { type: "playback_mode_changed", mode: "remote", session_id: "sess-1" },
|
||||
});
|
||||
|
||||
const state = get(playbackMode);
|
||||
expect(state.mode).toBe("remote");
|
||||
expect(state.remoteSessionId).toBe("sess-1");
|
||||
// Must NOT re-select (which would churn the watchdog) on a no-op.
|
||||
expect(mockSelectSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("adopts a genuine remote→local change and clears the session", async () => {
|
||||
currentSelectedSession = { id: "sess-1" };
|
||||
const playbackMode = await initListener();
|
||||
playbackMode.setMode("remote", "sess-1");
|
||||
mockSelectSession.mockClear();
|
||||
|
||||
capturedStatusListener!({
|
||||
payload: { type: "playback_mode_changed", mode: "local", session_id: null },
|
||||
});
|
||||
|
||||
const state = get(playbackMode);
|
||||
expect(state.mode).toBe("local");
|
||||
expect(state.remoteSessionId).toBeNull();
|
||||
expect(mockSelectSession).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("transfer reconciles to Rust on completion", () => {
|
||||
it("refreshes from Rust after a successful transferToRemote", async () => {
|
||||
const { playbackMode } = await import("./playbackMode");
|
||||
|
||||
Reference in New Issue
Block a user