diff --git a/extensions/matrix/src/matrix/monitor/index.test.ts b/extensions/matrix/src/matrix/monitor/index.test.ts index 3c18c6103ab..0eb9a39cebc 100644 --- a/extensions/matrix/src/matrix/monitor/index.test.ts +++ b/extensions/matrix/src/matrix/monitor/index.test.ts @@ -273,9 +273,20 @@ vi.mock("./startup-verification.js", () => ({ ensureMatrixStartupVerification: vi.fn(), })); +const { monitorMatrixProvider } = await import("./index.js"); + describe("monitorMatrixProvider", () => { + async function startMonitorAndAbortAfterStartup(): Promise { + const abortController = new AbortController(); + const monitorPromise = monitorMatrixProvider({ abortSignal: abortController.signal }); + await vi.waitFor(() => { + expect(hoisted.callOrder).toContain("start-client"); + }); + abortController.abort(); + await monitorPromise; + } + beforeEach(() => { - vi.resetModules(); hoisted.callOrder.length = 0; hoisted.state.startClientError = null; hoisted.resolveTextChunkLimit.mockReset().mockReturnValue(4000); @@ -296,11 +307,7 @@ describe("monitorMatrixProvider", () => { }); it("registers Matrix thread bindings before starting the client", async () => { - const { monitorMatrixProvider } = await import("./index.js"); - const abortController = new AbortController(); - abortController.abort(); - - await monitorMatrixProvider({ abortSignal: abortController.signal }); + await startMonitorAndAbortAfterStartup(); expect(hoisted.callOrder).toEqual([ "prepare-client", @@ -312,11 +319,7 @@ describe("monitorMatrixProvider", () => { }); it("resolves text chunk limit for the effective Matrix account", async () => { - const { monitorMatrixProvider } = await import("./index.js"); - const abortController = new AbortController(); - abortController.abort(); - - await monitorMatrixProvider({ abortSignal: abortController.signal }); + await startMonitorAndAbortAfterStartup(); expect(hoisted.resolveTextChunkLimit).toHaveBeenCalledWith( expect.anything(), @@ -326,7 +329,6 @@ describe("monitorMatrixProvider", () => { }); it("cleans up thread bindings and shared clients when startup fails", async () => { - const { monitorMatrixProvider } = await import("./index.js"); hoisted.state.startClientError = new Error("start failed"); await expect(monitorMatrixProvider()).rejects.toThrow("start failed"); @@ -340,11 +342,7 @@ describe("monitorMatrixProvider", () => { it("disables cold-start backlog dropping only when sync state is cleanly persisted", async () => { hoisted.client.hasPersistedSyncState.mockReturnValue(true); - const { monitorMatrixProvider } = await import("./index.js"); - const abortController = new AbortController(); - abortController.abort(); - - await monitorMatrixProvider({ abortSignal: abortController.signal }); + await startMonitorAndAbortAfterStartup(); expect(hoisted.createMatrixRoomMessageHandler).toHaveBeenCalledWith( expect.objectContaining({ @@ -354,7 +352,6 @@ describe("monitorMatrixProvider", () => { }); it("stops sync, drains decryptions, then waits for in-flight handlers before persisting", async () => { - const { monitorMatrixProvider } = await import("./index.js"); const abortController = new AbortController(); let resolveHandler: (() => void) | null = null; diff --git a/extensions/matrix/src/matrix/send.test.ts b/extensions/matrix/src/matrix/send.test.ts index 45c70a39b19..4ce6e7951b5 100644 --- a/extensions/matrix/src/matrix/send.test.ts +++ b/extensions/matrix/src/matrix/send.test.ts @@ -1,5 +1,6 @@ -import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import type { PluginRuntime } from "../../runtime-api.js"; +import { setMatrixRuntime } from "../runtime.js"; const loadWebMediaMock = vi.fn().mockResolvedValue({ buffer: Buffer.from("media"), @@ -45,20 +46,10 @@ const runtimeStub = { }, } as unknown as PluginRuntime; -let sendMessageMatrix: typeof import("./send.js").sendMessageMatrix; -let sendSingleTextMessageMatrix: typeof import("./send.js").sendSingleTextMessageMatrix; -let sendTypingMatrix: typeof import("./send.js").sendTypingMatrix; -let voteMatrixPoll: typeof import("./actions/polls.js").voteMatrixPoll; - -async function loadMatrixSendModules() { - vi.resetModules(); - const runtimeModule = await import("../runtime.js"); - runtimeModule.setMatrixRuntime(runtimeStub); - ({ sendMessageMatrix } = await import("./send.js")); - ({ sendSingleTextMessageMatrix } = await import("./send.js")); - ({ sendTypingMatrix } = await import("./send.js")); - ({ voteMatrixPoll } = await import("./actions/polls.js")); -} +setMatrixRuntime(runtimeStub); +const { sendMessageMatrix, sendSingleTextMessageMatrix, sendTypingMatrix } = + await import("./send.js"); +const { voteMatrixPoll } = await import("./actions/polls.js"); function createEncryptedMediaPayload() { return { @@ -106,34 +97,27 @@ function makeEncryptedMediaClient() { return result; } -async function resetMatrixSendRuntimeMocks() { +function resetMatrixSendRuntimeMocks() { + setMatrixRuntime(runtimeStub); + loadWebMediaMock.mockReset().mockResolvedValue({ + buffer: Buffer.from("media"), + fileName: "photo.png", + contentType: "image/png", + kind: "image", + }); loadConfigMock.mockReset().mockReturnValue({}); + getImageMetadataMock.mockReset().mockResolvedValue(null); + resizeToJpegMock.mockReset(); mediaKindFromMimeMock.mockReset().mockReturnValue("image"); isVoiceCompatibleAudioMock.mockReset().mockReturnValue(false); - await loadMatrixSendModules(); + resolveTextChunkLimitMock.mockReset().mockReturnValue(4000); + resolveMarkdownTableModeMock.mockReset().mockReturnValue("code"); + convertMarkdownTablesMock.mockReset().mockImplementation((text: string) => text); } describe("sendMessageMatrix media", () => { - beforeAll(async () => { - await loadMatrixSendModules(); - }); - - beforeEach(async () => { - loadWebMediaMock.mockReset().mockResolvedValue({ - buffer: Buffer.from("media"), - fileName: "photo.png", - contentType: "image/png", - kind: "image", - }); - loadConfigMock.mockReset().mockReturnValue({}); - getImageMetadataMock.mockReset().mockResolvedValue(null); - resizeToJpegMock.mockReset(); - mediaKindFromMimeMock.mockReset().mockReturnValue("image"); - isVoiceCompatibleAudioMock.mockReset().mockReturnValue(false); - resolveTextChunkLimitMock.mockReset().mockReturnValue(4000); - resolveMarkdownTableModeMock.mockReset().mockReturnValue("code"); - convertMarkdownTablesMock.mockReset().mockImplementation((text: string) => text); - await loadMatrixSendModules(); + beforeEach(() => { + resetMatrixSendRuntimeMocks(); }); it("uploads media with url payloads", async () => { @@ -370,9 +354,9 @@ describe("sendMessageMatrix media", () => { }); describe("sendMessageMatrix threads", () => { - beforeEach(async () => { + beforeEach(() => { vi.clearAllMocks(); - await resetMatrixSendRuntimeMocks(); + resetMatrixSendRuntimeMocks(); }); it("includes thread relation metadata when threadId is set", async () => { @@ -411,11 +395,9 @@ describe("sendMessageMatrix threads", () => { }); describe("sendSingleTextMessageMatrix", () => { - beforeEach(async () => { + beforeEach(() => { vi.clearAllMocks(); - await resetMatrixSendRuntimeMocks(); - resolveMarkdownTableModeMock.mockReset().mockReturnValue("code"); - convertMarkdownTablesMock.mockReset().mockImplementation((text: string) => text); + resetMatrixSendRuntimeMocks(); }); it("rejects single-event sends when converted text exceeds the Matrix limit", async () => { @@ -434,13 +416,9 @@ describe("sendSingleTextMessageMatrix", () => { }); describe("voteMatrixPoll", () => { - beforeAll(async () => { - await loadMatrixSendModules(); - }); - - beforeEach(async () => { + beforeEach(() => { vi.clearAllMocks(); - await resetMatrixSendRuntimeMocks(); + resetMatrixSendRuntimeMocks(); }); it("maps 1-based option indexes to Matrix poll answer ids", async () => { @@ -576,16 +554,9 @@ describe("voteMatrixPoll", () => { }); describe("sendTypingMatrix", () => { - beforeAll(async () => { - await loadMatrixSendModules(); - }); - - beforeEach(async () => { + beforeEach(() => { vi.clearAllMocks(); - loadConfigMock.mockReset().mockReturnValue({}); - mediaKindFromMimeMock.mockReset().mockReturnValue("image"); - isVoiceCompatibleAudioMock.mockReset().mockReturnValue(false); - await loadMatrixSendModules(); + resetMatrixSendRuntimeMocks(); }); it("normalizes room-prefixed targets before sending typing state", async () => {