diff --git a/apps/web/src/slash-commands/SlashCommands.tsx b/apps/web/src/slash-commands/SlashCommands.tsx index 5aa80d7093c..1263d6f156b 100644 --- a/apps/web/src/slash-commands/SlashCommands.tsx +++ b/apps/web/src/slash-commands/SlashCommands.tsx @@ -72,8 +72,12 @@ export const Commands = [ command: "spoiler", args: "", description: _td("slash_command|spoiler"), - runFn: function (cli, roomId, threadId, message = "") { - return successSync(ContentHelpers.makeHtmlMessage(message, `${message}`)); + runFn: function (cli, roomId, threadId, message) { + if (!message?.trim()) { + return reject(this.getUsage()); + } + const htmlMessage = htmlSerializeFromMdIfNeeded(message, { forceHTML: true }); + return successSync(ContentHelpers.makeHtmlMessage(message, `${htmlMessage}`)); }, category: CommandCategories.messages, }), diff --git a/apps/web/src/slash-commands/spoiler.test.ts b/apps/web/src/slash-commands/spoiler.test.ts new file mode 100644 index 00000000000..75c55375ac7 --- /dev/null +++ b/apps/web/src/slash-commands/spoiler.test.ts @@ -0,0 +1,49 @@ +/* + * 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. + */ + +// @vitest-environment happy-dom + +import { describe, it, expect } from "vitest"; + +import { setUpCommandTest } from "./__mocks__"; + +describe("/spoiler", () => { + const roomId = "!room:example.com"; + + it("should return usage if no args", () => { + const { client, command } = setUpCommandTest(roomId, `/spoiler`); + expect(command.run(client, roomId, null, undefined).error).toBe(command.getUsage()); + }); + + it("should wrap plain text in a spoiler span", async () => { + const { client, command } = setUpCommandTest(roomId, `/spoiler`); + const result = command.run(client, roomId, null, "plain text"); + expect(result.error).toBeUndefined(); + const content = await result.promise; + expect(content?.formatted_body).toContain(""); + expect(content?.formatted_body).toContain("plain text"); + }); + + it("should convert markdown bold to HTML inside the spoiler span", async () => { + const { client, command } = setUpCommandTest(roomId, `/spoiler`); + const result = command.run(client, roomId, null, "**secret** message"); + expect(result.error).toBeUndefined(); + const content = await result.promise; + // Markdown should be serialized to HTML — raw ** chars must not appear + expect(content?.formatted_body).not.toContain("**secret**"); + expect(content?.formatted_body).toContain(""); + expect(content?.formatted_body).toContain(""); + }); + + it("should not double-escape plain text body", async () => { + const { client, command } = setUpCommandTest(roomId, `/spoiler`); + const result = command.run(client, roomId, null, "just text"); + const content = await result.promise; + expect(content?.body).toBe("just text"); + expect(content?.formatted_body).toContain("just text"); + }); +});