File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 22import { describe , expect , it } from "vitest" ;
33import { discordApprovalNativeRuntime } from "./approval-handler.runtime.js" ;
44
5+ async function buildExecApprovalPayloadText ( commandText : string ) : Promise < string > {
6+ const pending = await discordApprovalNativeRuntime . presentation . buildPendingPayload ( {
7+ cfg : { } as never ,
8+ accountId : "main" ,
9+ context : {
10+ token : "discord-token" ,
11+ config : { } as never ,
12+ } ,
13+ request : {
14+ id : "approval-1" ,
15+ request : {
16+ command : commandText ,
17+ } ,
18+ createdAtMs : 0 ,
19+ expiresAtMs : 1_000 ,
20+ } ,
21+ approvalKind : "exec" ,
22+ nowMs : 0 ,
23+ view : {
24+ approvalKind : "exec" ,
25+ phase : "pending" ,
26+ approvalId : "approval-1" ,
27+ title : "Exec Approval Required" ,
28+ commandText,
29+ commandPreview : null ,
30+ expiresAtMs : 1_000 ,
31+ metadata : [ ] ,
32+ actions : [
33+ {
34+ label : "Allow" ,
35+ decision : "allow-once" ,
36+ style : "success" ,
37+ command : "/approve approval-1 allow-once" ,
38+ } ,
39+ ] ,
40+ } ,
41+ } ) ;
42+ return JSON . stringify ( pending ) ;
43+ }
44+
545describe ( "discordApprovalNativeRuntime" , ( ) => {
46+ it ( "does not split emoji graphemes when truncating exec command previews" , async ( ) => {
47+ const prefix = "a" . repeat ( 999 ) ;
48+
49+ await expect ( buildExecApprovalPayloadText ( `${ prefix } 😀x` ) ) . resolves . toContain ( `${ prefix } ...` ) ;
50+ await expect ( buildExecApprovalPayloadText ( `${ prefix } 🇺🇸x` ) ) . resolves . toContain ( `${ prefix } ...` ) ;
51+ } ) ;
52+
653 it ( "routes origin approval updates to the Discord thread channel when threadId is present" , async ( ) => {
754 const prepared = await discordApprovalNativeRuntime . transport . prepareTarget ( {
855 cfg : { } as never ,
Original file line number Diff line number Diff line change @@ -159,10 +159,38 @@ function buildExecApprovalPayload(container: DiscordUiContainer): MessagePayload
159159 return { components } ;
160160}
161161
162+ const commandPreviewSegmenter =
163+ typeof Intl !== "undefined" && "Segmenter" in Intl
164+ ? new Intl . Segmenter ( undefined , { granularity : "grapheme" } )
165+ : null ;
166+
167+ function * iterateCommandPreviewSegments ( commandText : string ) : Iterable < string > {
168+ if ( ! commandPreviewSegmenter ) {
169+ yield * Array . from ( commandText ) ;
170+ return ;
171+ }
172+ try {
173+ for ( const segment of commandPreviewSegmenter . segment ( commandText ) ) {
174+ yield segment . segment ;
175+ }
176+ } catch {
177+ yield * Array . from ( commandText ) ;
178+ }
179+ }
180+
181+ function truncateCommandPreview ( commandText : string , maxChars : number ) : string {
182+ let commandRaw = "" ;
183+ for ( const segment of iterateCommandPreviewSegments ( commandText ) ) {
184+ if ( commandRaw . length + segment . length > maxChars ) {
185+ return `${ commandRaw } ...` ;
186+ }
187+ commandRaw += segment ;
188+ }
189+ return commandText ;
190+ }
191+
162192function formatCommandPreview ( commandText : string , maxChars : number ) : string {
163- const commandRaw =
164- commandText . length > maxChars ? `${ commandText . slice ( 0 , maxChars ) } ...` : commandText ;
165- return commandRaw . replace ( / ` / g, "\u200b`" ) ;
193+ return truncateCommandPreview ( commandText , maxChars ) . replace ( / ` / g, "\u200b`" ) ;
166194}
167195
168196function formatOptionalCommandPreview (
You can’t perform that action at this time.
0 commit comments