|
1 | 1 | import { describe, expect, test } from "bun:test"; |
2 | | -import { parseAriaSnapshot, parseDescriptor } from "../src/aria-snapshot"; |
| 2 | +import { |
| 3 | + cutAtCodeUnits, |
| 4 | + parseAriaSnapshot, |
| 5 | + parseDescriptor, |
| 6 | +} from "../src/aria-snapshot"; |
3 | 7 |
|
4 | 8 | /** |
5 | 9 | * The parser, tested against captured Playwright output. |
@@ -200,6 +204,46 @@ describe("a name or value too long to keep whole", () => { |
200 | 204 | }); |
201 | 205 | }); |
202 | 206 |
|
| 207 | +/** |
| 208 | + * The cut itself, at any limit. |
| 209 | + * |
| 210 | + * Tested directly as well as through the parser because `index.ts` cuts the readable page text with |
| 211 | + * it at 6000 rather than 200, and that caller imports Playwright at load, so there is no test that |
| 212 | + * can reach it. The rule is the thing worth pinning, so it is pinned where it is declared. |
| 213 | + */ |
| 214 | +describe("cutting at code units", () => { |
| 215 | + const EMOJI = "\u{1F600}"; |
| 216 | + |
| 217 | + test("a limit landing between the halves of a character drops the character", () => { |
| 218 | + const text = `${"a".repeat(5999)}${EMOJI}tail`; |
| 219 | + const cut = cutAtCodeUnits(text, 6000); |
| 220 | + expect(cut).toBe("a".repeat(5999)); |
| 221 | + // Not a lone high surrogate, which is what a bare `slice` leaves and what reads as U+FFFD. |
| 222 | + expect(cut.charCodeAt(cut.length - 1)).toBeLessThan(0xd800); |
| 223 | + }); |
| 224 | + |
| 225 | + test("a character that ends exactly at the limit is kept whole", () => { |
| 226 | + const text = `${"a".repeat(5998)}${EMOJI}tail`; |
| 227 | + expect(cutAtCodeUnits(text, 6000)).toBe(`${"a".repeat(5998)}${EMOJI}`); |
| 228 | + }); |
| 229 | + |
| 230 | + test("text that fits is returned unchanged, emoji and all", () => { |
| 231 | + const text = `hello ${EMOJI} world`; |
| 232 | + expect(cutAtCodeUnits(text, 6000)).toBe(text); |
| 233 | + }); |
| 234 | + |
| 235 | + test("empty text is empty rather than a thrown index", () => { |
| 236 | + // `charCodeAt(-1)` is NaN and every comparison against it is false, so this returns "". |
| 237 | + expect(cutAtCodeUnits("", 6000)).toBe(""); |
| 238 | + }); |
| 239 | + |
| 240 | + test("a low surrogate last is a whole character and is kept", () => { |
| 241 | + // The guard must look only for an UNPAIRED high surrogate. A complete pair ends on its low half, |
| 242 | + // and dropping that would cost a character the limit had room for. |
| 243 | + expect(cutAtCodeUnits(EMOJI, 2)).toBe(EMOJI); |
| 244 | + }); |
| 245 | +}); |
| 246 | + |
203 | 247 | describe("values a real parser handles and a pattern got wrong", () => { |
204 | 248 | test("a quoted numeric value is not left with its quotes", () => { |
205 | 249 | // Numeric-looking text remains a string, so one-time codes are not coerced. |
|
0 commit comments