From f3b4c531c1d7c4be301041005de69eb61377a6fc Mon Sep 17 00:00:00 2001 From: shogun444 Date: Thu, 16 Jul 2026 11:45:01 +0530 Subject: [PATCH 01/10] Render markdown in Vue chat TextContent (#730) Parse TextContent text using marked, sanitize with DOMPurify, render via v-html. Uses a complete Renderer instance with html() overridden to escape raw HTML. Scoped markdown styles added for headings, lists, code, blockquotes, tables, etc. Includes regression tests for plain text, bold, inline code, and raw HTML escaping. Closes #728 --- .../components/openui/TextContent.vue | 123 +++++++++++++++++- .../openui/__tests__/TextContent.test.ts | 38 ++++++ examples/vue-chat/package.json | 7 + examples/vue-chat/vitest.config.ts | 10 ++ 4 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 examples/vue-chat/components/openui/__tests__/TextContent.test.ts create mode 100644 examples/vue-chat/vitest.config.ts diff --git a/examples/vue-chat/components/openui/TextContent.vue b/examples/vue-chat/components/openui/TextContent.vue index 59a6f7a57..b1457dfae 100644 --- a/examples/vue-chat/components/openui/TextContent.vue +++ b/examples/vue-chat/components/openui/TextContent.vue @@ -1,7 +1,126 @@ + + diff --git a/examples/vue-chat/components/openui/__tests__/TextContent.test.ts b/examples/vue-chat/components/openui/__tests__/TextContent.test.ts new file mode 100644 index 000000000..ad5558072 --- /dev/null +++ b/examples/vue-chat/components/openui/__tests__/TextContent.test.ts @@ -0,0 +1,38 @@ +import { mount } from "@vue/test-utils"; +import { describe, expect, it } from "vitest"; +import { nextTick } from "vue"; +import TextContent from "../TextContent.vue"; + +const mountText = (text: string) => + mount(TextContent, { + props: { props: { text } }, + }); + +describe("TextContent", () => { + it("renders plain text", async () => { + const wrapper = mountText("Hello world"); + await nextTick(); + expect(wrapper.text()).toBe("Hello world"); + }); + + it("renders bold markdown", async () => { + const wrapper = mountText("**bold**"); + await nextTick(); + expect(wrapper.find("strong").exists()).toBe(true); + expect(wrapper.find("strong").text()).toBe("bold"); + }); + + it("renders inline code", async () => { + const wrapper = mountText("`code`"); + await nextTick(); + expect(wrapper.find("code").exists()).toBe(true); + expect(wrapper.find("code").text()).toBe("code"); + }); + + it("escapes raw HTML", async () => { + const wrapper = mountText(''); + await nextTick(); + expect(wrapper.html()).not.toContain("