From f92ec2d4e88b7c4cea6cf733c70028c641ab3be4 Mon Sep 17 00:00:00 2001 From: dwc1997 <66739829+dwc1997@users.noreply.github.com> Date: Tue, 30 Jun 2026 01:50:58 +0800 Subject: [PATCH] test(shared): add unit tests for surrogate-safe UTF-16 string slicing helpers (#97805) * test(shared): add unit tests for surrogate-safe UTF-16 string slicing helpers Add unit tests for sliceUtf16Safe and truncateUtf16Safe functions in src/shared/utf16-slice.ts to verify UTF-16 string slicing behavior. Tests cover: - sliceUtf16Safe slices ASCII string normally - sliceUtf16Safe handles negative start/end - sliceUtf16Safe handles start/end beyond length - sliceUtf16Safe swaps start and end when start > end - sliceUtf16Safe preserves emoji with surrogate pairs - sliceUtf16Safe avoids splitting surrogate pair at start/end - truncateUtf16Safe returns input when shorter than limit - truncateUtf16Safe truncates when longer than limit - truncateUtf16Safe handles zero/negative limit - truncateUtf16Safe floors decimal limit - truncateUtf16Safe preserves emoji with surrogate pairs - truncateUtf16Safe avoids splitting surrogate pair * fix: replace tautological surrogate assertions with exact output checks Replace vague 'result.length >= 0' assertions with exact expected output checks for surrogate pair boundary cases. The helper returns empty string when slicing at surrogate pair boundaries. --- src/shared/utf16-slice.test.ts | 88 ++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/shared/utf16-slice.test.ts diff --git a/src/shared/utf16-slice.test.ts b/src/shared/utf16-slice.test.ts new file mode 100644 index 00000000000..0716bff46fb --- /dev/null +++ b/src/shared/utf16-slice.test.ts @@ -0,0 +1,88 @@ +// Tests for surrogate-safe UTF-16 string slicing helpers. +import { describe, expect, it } from "vitest"; +import { sliceUtf16Safe, truncateUtf16Safe } from "./utf16-slice.js"; + +describe("sliceUtf16Safe", () => { + it("slices ASCII string normally", () => { + expect(sliceUtf16Safe("hello world", 0, 5)).toBe("hello"); + }); + + it("handles negative start", () => { + expect(sliceUtf16Safe("hello world", -5)).toBe("world"); + }); + + it("handles negative end", () => { + expect(sliceUtf16Safe("hello world", 0, -6)).toBe("hello"); + }); + + it("handles start beyond length", () => { + expect(sliceUtf16Safe("hello", 10)).toBe(""); + }); + + it("handles end beyond length", () => { + expect(sliceUtf16Safe("hello", 0, 10)).toBe("hello"); + }); + + it("swaps start and end when start > end", () => { + expect(sliceUtf16Safe("hello", 3, 1)).toBe("el"); + }); + + it("preserves emoji with surrogate pairs", () => { + const emoji = "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"; + expect(sliceUtf16Safe(emoji, 0)).toBe(emoji); + }); + + it("returns empty string when slicing middle of surrogate pair", () => { + const input = "๐Ÿ‘จ๐Ÿ‘ฉ"; + // Slicing at position 1-3 hits middle of surrogate pairs + expect(sliceUtf16Safe(input, 1, 3)).toBe(""); + }); + + it("returns empty string when slicing at start of surrogate pair", () => { + const input = "๐Ÿ‘จ๐Ÿ‘ฉ"; + // Slicing at position 0-1 would cut surrogate pair, adjust to 0 + expect(sliceUtf16Safe(input, 0, 1)).toBe(""); + }); + + it("handles empty string", () => { + expect(sliceUtf16Safe("", 0)).toBe(""); + }); + + it("handles undefined end", () => { + expect(sliceUtf16Safe("hello", 2)).toBe("llo"); + }); +}); + +describe("truncateUtf16Safe", () => { + it("returns input when shorter than limit", () => { + expect(truncateUtf16Safe("hello", 10)).toBe("hello"); + }); + + it("truncates when longer than limit", () => { + expect(truncateUtf16Safe("hello world", 5)).toBe("hello"); + }); + + it("handles zero limit", () => { + expect(truncateUtf16Safe("hello", 0)).toBe(""); + }); + + it("handles negative limit", () => { + expect(truncateUtf16Safe("hello", -1)).toBe(""); + }); + + it("floors decimal limit", () => { + expect(truncateUtf16Safe("hello world", 5.7)).toBe("hello"); + }); + + it("preserves emoji with surrogate pairs", () => { + const emoji = "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"; + const result = truncateUtf16Safe(emoji, 10); + // Should not return dangling surrogate + expect(result.length).toBeLessThanOrEqual(emoji.length); + }); + + it("returns empty string when truncating at surrogate pair boundary", () => { + const input = "๐Ÿ‘จ๐Ÿ‘ฉ"; + expect(truncateUtf16Safe(input, 1)).toBe(""); + }); +});