Skip to content

Commit 73eefe2

Browse files
solodmdclaude
andcommitted
test(twitch): add UTF-16 safe truncation tests
Add two tests for the debug-log preview truncation: - Lone-surrogate check: an emoji at position 100 is not split - Plain ASCII: short messages pass through unchanged Co-Authored-By: Claude <[email protected]>
1 parent d6806eb commit 73eefe2

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

extensions/twitch/src/twitch-client.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,76 @@ describe("TwitchClientManager", () => {
461461
});
462462
});
463463

464+
describe("UTF-16 safe truncation", () => {
465+
let debugMock: ReturnType<typeof vi.fn>;
466+
467+
beforeEach(() => {
468+
debugMock = vi.fn();
469+
mockLogger.debug = debugMock as unknown as typeof mockLogger.debug;
470+
});
471+
472+
it("should not emit lone surrogates in debug log when message has emoji at truncation boundary", async () => {
473+
await manager.getClient(testAccount);
474+
const handler = vi.fn();
475+
manager.onMessage(testAccount, handler);
476+
477+
// Build a message where the emoji lands at position 100
478+
const textBeforeEmoji = "x".repeat(99); // 99 chars
479+
const emoji = "😀";
480+
const messageText = `${textBeforeEmoji}${emoji}rest`;
481+
482+
// Invoke the onMessage callback stored by the mock
483+
const msg = {
484+
id: "test-msg-utf16",
485+
userInfo: {
486+
userName: "testuser",
487+
displayName: "TestUser",
488+
isMod: false,
489+
isBroadcaster: false,
490+
isVip: false,
491+
isSubscriber: false,
492+
},
493+
};
494+
messageHandlers[0]("#testchannel", "testuser", messageText, msg);
495+
496+
// The debug log preview should not contain a lone high surrogate
497+
const debugCall = debugMock.mock.calls.find((call: string[]) =>
498+
call[0]?.startsWith("twitch inbound:"),
499+
);
500+
expect(debugCall).toBeDefined();
501+
const preview = debugCall![0] as string;
502+
expect(preview).not.toMatch(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/);
503+
});
504+
505+
it("should handle plain ASCII text without truncation", async () => {
506+
await manager.getClient(testAccount);
507+
const handler = vi.fn();
508+
manager.onMessage(testAccount, handler);
509+
510+
const messageText = "short message without emoji";
511+
512+
const msg = {
513+
id: "test-msg-ascii",
514+
userInfo: {
515+
userName: "testuser",
516+
displayName: "TestUser",
517+
isMod: false,
518+
isBroadcaster: false,
519+
isVip: false,
520+
isSubscriber: false,
521+
},
522+
};
523+
messageHandlers[0]("#testchannel", "testuser", messageText, msg);
524+
525+
const debugCall = debugMock.mock.calls.find((call: string[]) =>
526+
call[0]?.startsWith("twitch inbound:"),
527+
);
528+
expect(debugCall).toBeDefined();
529+
const preview = debugCall![0] as string;
530+
expect(preview).toContain(messageText);
531+
});
532+
});
533+
464534
describe("disconnect", () => {
465535
it("should disconnect a connected client", async () => {
466536
await manager.getClient(testAccount);

0 commit comments

Comments
 (0)