11// Control UI tests cover agents utils behavior.
2- import { describe , expect , it } from "vitest" ;
2+ import { describe , expect , it , vi } from "vitest" ;
33import { AVATAR_MAX_DATA_URL_CHARS } from "../../../../src/shared/avatar-limits.js" ;
44import {
55 assistantAvatarFallbackUrl ,
@@ -8,7 +8,12 @@ import {
88 resolveAssistantTextAvatar ,
99 resolveChatAvatarRenderUrl ,
1010} from "../avatar.ts" ;
11- import { buildAgentContext , formatBytes , resolveEffectiveModelFallbacks } from "./display.ts" ;
11+ import {
12+ buildAgentContext ,
13+ formatBytes ,
14+ resolveEffectiveModelFallbacks ,
15+ resolveFallbackAvatarInitial ,
16+ } from "./display.ts" ;
1217
1318describe ( "formatBytes" , ( ) => {
1419 it ( "preserves the Control UI byte-size display contract" , ( ) => {
@@ -67,6 +72,61 @@ describe("assistantAvatarFallbackUrl", () => {
6772 } ) ;
6873} ) ;
6974
75+ describe ( "resolveFallbackAvatarInitial" , ( ) => {
76+ it ( "keeps flag and ZWJ emoji clusters intact" , ( ) => {
77+ expect ( resolveFallbackAvatarInitial ( "🇺🇸Team" ) ) . toBe ( "🇺🇸" ) ;
78+ expect ( resolveFallbackAvatarInitial ( "👨💻Dev" ) ) . toBe ( "👨💻" ) ;
79+ expect ( resolveFallbackAvatarInitial ( "alpha" ) ) . toBe ( "A" ) ;
80+ expect ( resolveFallbackAvatarInitial ( " " ) ) . toBe ( "?" ) ;
81+ console . log (
82+ "avatar-initial proof:" ,
83+ JSON . stringify ( {
84+ flag : resolveFallbackAvatarInitial ( "🇺🇸Team" ) ,
85+ zwj : resolveFallbackAvatarInitial ( "👨💻Dev" ) ,
86+ ascii : resolveFallbackAvatarInitial ( "alpha" ) ,
87+ } ) ,
88+ ) ;
89+ } ) ;
90+
91+ it ( "keeps locale-independent ASCII casing (Turkish i)" , ( ) => {
92+ const previous = process . env . LC_ALL ;
93+ try {
94+ // Exercise the same toUpperCase path the Control UI fallback uses.
95+ expect ( resolveFallbackAvatarInitial ( "istanbul" ) ) . toBe ( "I" ) ;
96+ } finally {
97+ if ( previous === undefined ) {
98+ delete process . env . LC_ALL ;
99+ } else {
100+ process . env . LC_ALL = previous ;
101+ }
102+ }
103+ } ) ;
104+
105+ it ( "avoids splitting flag/ZWJ clusters when Intl.Segmenter is unavailable" , async ( ) => {
106+ const originalSegmenter = Intl . Segmenter ;
107+ Object . defineProperty ( Intl , "Segmenter" , { configurable : true , value : undefined } ) ;
108+ vi . resetModules ( ) ;
109+ try {
110+ const { resolveFallbackAvatarInitial : resolveWithoutSegmenter } =
111+ await import ( "./display.ts" ) ;
112+ expect ( resolveWithoutSegmenter ( "alpha" ) ) . toBe ( "A" ) ;
113+ expect ( resolveWithoutSegmenter ( "🇺🇸Team" ) ) . toBe ( "?" ) ;
114+ expect ( resolveWithoutSegmenter ( "👨💻Dev" ) ) . toBe ( "?" ) ;
115+ console . log (
116+ "avatar-initial Segmenter-fallback proof:" ,
117+ JSON . stringify ( {
118+ ascii : resolveWithoutSegmenter ( "alpha" ) ,
119+ flag : resolveWithoutSegmenter ( "🇺🇸Team" ) ,
120+ zwj : resolveWithoutSegmenter ( "👨💻Dev" ) ,
121+ } ) ,
122+ ) ;
123+ } finally {
124+ Object . defineProperty ( Intl , "Segmenter" , { configurable : true , value : originalSegmenter } ) ;
125+ vi . resetModules ( ) ;
126+ }
127+ } ) ;
128+ } ) ;
129+
70130describe ( "resolveAssistantTextAvatar" , ( ) => {
71131 it ( "rejects unsafe invisible controls in assistant text avatars" , ( ) => {
72132 expect ( resolveAssistantTextAvatar ( "VC" ) ) . toBe ( "VC" ) ;
0 commit comments