@@ -97,11 +97,45 @@ describe("engine/tools/remind-logic", () => {
|
97 | 97 | expect(generateJobName("drink water")).toBe("Reminder: drink water"); |
98 | 98 | }); |
99 | 99 | |
100 | | -it("truncates long content", () => { |
101 | | -const long = "a very long reminder content that exceeds twenty characters"; |
102 | | -const name = generateJobName(long); |
103 | | -expect(name.length).toBeLessThan(40); |
104 | | -expect(name).toContain("…"); |
| 100 | +it("truncates long content to a 20 UTF-16-unit budget with an ellipsis", () => { |
| 101 | +expect(generateJobName("a very long reminder content")).toBe( |
| 102 | +"Reminder: a very long reminder…", |
| 103 | +); |
| 104 | +}); |
| 105 | + |
| 106 | +it("keeps an exactly-fitting all-emoji content unchanged", () => { |
| 107 | +// 10 emoji = 20 UTF-16 units, exactly at the budget, so no truncation. |
| 108 | +expect(generateJobName("😀".repeat(10))).toBe(`Reminder: ${"😀".repeat(10)}`); |
| 109 | +}); |
| 110 | + |
| 111 | +it("does not split surrogate pairs when truncating", () => { |
| 112 | +const hasLoneSurrogate = (value: string): boolean => { |
| 113 | +for (let index = 0; index < value.length; index++) { |
| 114 | +const code = value.charCodeAt(index); |
| 115 | +if (code >= 0xd800 && code <= 0xdbff) { |
| 116 | +const next = value.charCodeAt(index + 1); |
| 117 | +if (!(next >= 0xdc00 && next <= 0xdfff)) { |
| 118 | +return true; |
| 119 | +} |
| 120 | +index++; |
| 121 | +} else if (code >= 0xdc00 && code <= 0xdfff) { |
| 122 | +return true; |
| 123 | +} |
| 124 | +} |
| 125 | +return false; |
| 126 | +}; |
| 127 | + |
| 128 | +// 11 emoji = 22 UTF-16 units > 20; the 11th emoji straddles the cap and is |
| 129 | +// dropped whole rather than split into a lone surrogate. |
| 130 | +const allEmoji = generateJobName("😀".repeat(11)); |
| 131 | +expect(allEmoji).toBe(`Reminder: ${"😀".repeat(10)}…`); |
| 132 | +expect(hasLoneSurrogate(allEmoji)).toBe(false); |
| 133 | + |
| 134 | +// 19 ASCII + emoji: the emoji's high surrogate would land at unit 20, so the |
| 135 | +// whole pair is dropped to stay within the 20-unit budget. |
| 136 | +const name = generateJobName(`${"x".repeat(19)}😀tail`); |
| 137 | +expect(name).toBe(`Reminder: ${"x".repeat(19)}…`); |
| 138 | +expect(hasLoneSurrogate(name)).toBe(false); |
105 | 139 | }); |
106 | 140 | }); |
107 | 141 | |
|