fix(mattermost): truncate draft previews on code-point boundaries (#9… · openclaw/openclaw@881ec2f
ly-wang19
·
2026-06-29
·
via Recent Commits to openclaw:main
File tree
extensions/mattermost/src/mattermost
| Original file line number | Diff line number | Diff line change |
|---|
@@ -205,6 +205,32 @@ describe("createMattermostDraftStream", () => {
|
205 | 205 | expect(stream.postId()).toBeUndefined(); |
206 | 206 | }); |
207 | 207 | |
| 208 | +it("truncates on a code-point boundary so a straddling emoji is dropped whole", async () => { |
| 209 | +const { client, calls } = createMockClient(); |
| 210 | +// maxChars=12 => cut point is maxChars-3=9. The emoji 😀 occupies UTF-16 |
| 211 | +// indices 8-9, so a raw slice(0,9) would keep the lone high surrogate at |
| 212 | +// index 8 and drop its low surrogate at index 9, leaking a dangling half. |
| 213 | +const stream = createMattermostDraftStream({ |
| 214 | + client, |
| 215 | +channelId: "channel-1", |
| 216 | +throttleMs: 0, |
| 217 | +maxChars: 12, |
| 218 | +}); |
| 219 | + |
| 220 | +const input = `${"a".repeat(8)}\u{1F600}${"b".repeat(5)}`; |
| 221 | +stream.update(input); |
| 222 | +await stream.flush(); |
| 223 | + |
| 224 | +expect(calls).toHaveLength(1); |
| 225 | +const message = parseRequestJson(calls[0]?.init).message; |
| 226 | +expect(typeof message).toBe("string"); |
| 227 | +const sent = message as string; |
| 228 | +// The straddling emoji must be dropped whole, leaving no dangling surrogate half. |
| 229 | +expect(/[\uD800-\uDFFF]/u.test(sent)).toBe(false); |
| 230 | +expect(sent.length).toBeLessThanOrEqual(12); |
| 231 | +expect(sent).toBe("aaaaaaaa..."); |
| 232 | +}); |
| 233 | + |
208 | 234 | it("does not resend after an update failure followed by stop", async () => { |
209 | 235 | const warn = vi.fn(); |
210 | 236 | const calls: RequestRecord[] = []; |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | // Mattermost plugin module implements draft stream behavior. |
2 | 2 | import { createFinalizableDraftLifecycle } from "openclaw/plugin-sdk/channel-outbound"; |
| 3 | +import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime"; |
3 | 4 | import { |
4 | 5 | createMattermostPost, |
5 | 6 | deleteMattermostPost, |
@@ -29,7 +30,7 @@ function normalizeMattermostDraftText(text: string, maxChars: number): string {
|
29 | 30 | if (trimmed.length <= maxChars) { |
30 | 31 | return trimmed; |
31 | 32 | } |
32 | | -return `${trimmed.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`; |
| 33 | +return `${sliceUtf16Safe(trimmed, 0, Math.max(0, maxChars - 3)).trimEnd()}...`; |
33 | 34 | } |
34 | 35 | |
35 | 36 | export function createMattermostDraftStream(params: { |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。