Files
openclaw/src/media/parse.test.ts
Tak Hoffman cc5c691f00 feat(ui): render assistant directives and add embed tag (#64104)
* Add embed rendering for Control UI assistant output

* Add changelog entry for embed rendering

* Harden canvas path resolution and stage isolation

* Secure assistant media route and preserve UI avatar override

* Fix chat media and history regressions

* Harden embed iframe URL handling

* Fix embed follow-up review regressions

* Restore offloaded chat attachment persistence

* Harden hook and media routing

* Fix embed review follow-ups

* feat(ui): add configurable embed sandbox mode

* fix(gateway): harden assistant media and auth rotation

* fix(gateway): restore websocket pairing handshake flows

* fix(gateway): restore ws hello policy details

* Restore dropped control UI shell wiring

* Fix control UI reconnect cleanup regressions

* fix(gateway): restore media root and auth getter compatibility

* feat(ui): rename public canvas tag to embed

* fix(ui): address remaining media and gateway review issues

* fix(ui): address remaining embed and attachment review findings

* fix(ui): restore stop control and tool card inputs

* fix(ui): address history and attachment review findings

* fix(ui): restore prompt contribution wiring

* fix(ui): address latest history and directive reviews

* fix(ui): forward password auth for assistant media

* fix(ui): suppress silent transcript tokens with media

* feat(ui): add granular embed sandbox modes

* fix(ui): preserve relative media directives in history

* docs(ui): document embed sandbox modes

* fix(gateway): restrict canvas history hoisting to tool entries

* fix(gateway): tighten embed follow-up review fixes

* fix(ci): repair merged branch type drift

* fix(prompt): restore stable runtime prompt rendering

* fix(ui): harden local attachment preview checks

* fix(prompt): restore channel-aware approval guidance

* fix(gateway): enforce auth rotation and media cleanup

* feat(ui): gate external embed urls behind config

* fix(ci): repair rebased branch drift

* fix(ci): resolve remaining branch check failures
2026-04-11 07:32:53 -05:00

107 lines
3.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { splitMediaFromOutput } from "./parse.js";
describe("splitMediaFromOutput", () => {
function expectParsedMediaOutputCase(
input: string,
expected: {
mediaUrls?: string[];
text?: string;
audioAsVoice?: boolean;
},
) {
const result = splitMediaFromOutput(input);
expect(result.text).toBe(expected.text ?? "");
if ("audioAsVoice" in expected) {
expect(result.audioAsVoice).toBe(expected.audioAsVoice);
} else {
expect(result.audioAsVoice).toBeUndefined();
}
if ("mediaUrls" in expected) {
expect(result.mediaUrls).toEqual(expected.mediaUrls);
expect(result.mediaUrl).toBe(expected.mediaUrls?.[0]);
} else {
expect(result.mediaUrls).toBeUndefined();
expect(result.mediaUrl).toBeUndefined();
}
}
function expectStableAudioAsVoiceDetectionCase(input: string) {
for (const output of [splitMediaFromOutput(input), splitMediaFromOutput(input)]) {
expect(output.audioAsVoice).toBe(true);
}
}
function expectAcceptedMediaPathCase(expectedPath: string, input: string) {
expectParsedMediaOutputCase(input, { mediaUrls: [expectedPath] });
}
function expectRejectedMediaPathCase(input: string) {
expectParsedMediaOutputCase(input, { mediaUrls: undefined });
}
it.each([
["/Users/pete/My File.png", "MEDIA:/Users/pete/My File.png"],
["/Users/pete/My File.png", 'MEDIA:"/Users/pete/My File.png"'],
["./screenshots/image.png", "MEDIA:./screenshots/image.png"],
["media/inbound/image.png", "MEDIA:media/inbound/image.png"],
["./screenshot.png", " MEDIA:./screenshot.png"],
["C:\\Users\\pete\\Pictures\\snap.png", "MEDIA:C:\\Users\\pete\\Pictures\\snap.png"],
["/tmp/tts-fAJy8C/voice-1770246885083.opus", "MEDIA:/tmp/tts-fAJy8C/voice-1770246885083.opus"],
["image.png", "MEDIA:image.png"],
] as const)("accepts supported media path variant: %s", (expectedPath, input) => {
expectAcceptedMediaPathCase(expectedPath, input);
});
it.each([
"MEDIA:../../../etc/passwd",
"MEDIA:../../.env",
"MEDIA:~/.ssh/id_rsa",
"MEDIA:~/Pictures/My File.png",
"MEDIA:./foo/../../../etc/shadow",
] as const)("rejects traversal and home-dir path: %s", (input) => {
expectRejectedMediaPathCase(input);
});
it.each([
{
name: "detects audio_as_voice tag and strips it",
input: "Hello [[audio_as_voice]] world",
expected: { audioAsVoice: true, text: "Hello world" },
},
{
name: "keeps MEDIA mentions in prose",
input: "The MEDIA: tag fails to deliver",
expected: { mediaUrls: undefined, text: "The MEDIA: tag fails to deliver" },
},
{
name: "rejects bare words without file extensions",
input: "MEDIA:screenshot",
expected: { mediaUrls: undefined, text: "MEDIA:screenshot" },
},
{
name: "keeps audio_as_voice detection stable across calls",
input: "Hello [[audio_as_voice]]",
expected: { audioAsVoice: true, text: "Hello" },
assertStable: true,
},
] as const)("$name", ({ input, expected, assertStable }) => {
expectParsedMediaOutputCase(input, expected);
if (assertStable) {
expectStableAudioAsVoiceDetectionCase(input);
}
});
it("returns ordered text and media segments while ignoring fenced MEDIA lines", () => {
const result = splitMediaFromOutput(
"Before\nMEDIA:https://example.com/a.png\n```text\nMEDIA:https://example.com/ignored.png\n```\nAfter",
);
expect(result.segments).toEqual([
{ type: "text", text: "Before" },
{ type: "media", url: "https://example.com/a.png" },
{ type: "text", text: "```text\nMEDIA:https://example.com/ignored.png\n```\nAfter" },
]);
});
});