@@ -282,6 +282,104 @@ async function expectCaretVisible(input: Locator) {
282282 . toBe ( true )
283283}
284284
285+ test ( "prompt shortcuts rebind through Settings, preserve newline undo, and restore defaults" , async ( { page } ) => {
286+ const prompts : { text : unknown ; delivery : unknown } [ ] = [ ]
287+
288+ const { editor } = await openSession ( page , {
289+ name : "ComposerShortcuts" ,
290+ sessionStatus : { ses_composershortcuts : { type : "running" } } ,
291+ onPrompt : ( { body } ) => void prompts . push ( { text : body . text , delivery : body . delivery } ) ,
292+ } )
293+
294+ const settings = page . getByTestId ( "settings-screen" )
295+ const binding = ( id : string ) => settings . locator ( `[data-keybind-id="prompt.${ id } "]` )
296+
297+ const shortcuts = async ( ) => {
298+ await page . keyboard . press ( "Control+," )
299+ await settings . getByRole ( "tab" , { name : "Shortcuts" , exact : true } ) . click ( )
300+ await expect ( settings . getByRole ( "heading" , { name : "Keyboard shortcuts" , exact : true } ) ) . toBeVisible ( )
301+ }
302+
303+ const record = async ( id : string , keys : string , label : string | RegExp ) => {
304+ await binding ( id ) . click ( )
305+ await expect ( binding ( id ) ) . toHaveText ( "Press keys" )
306+ await page . keyboard . press ( keys )
307+ await expect ( binding ( id ) ) . toHaveText ( label )
308+ }
309+
310+ await editor . fill ( "hello" )
311+ await shortcuts ( )
312+ await expect ( binding ( "submit" ) ) . toHaveText ( "Enter" )
313+ // Move alternate delivery first, freeing Ctrl+Enter for primary submission.
314+ await record ( "submit.alternate" , "Control+Shift+Enter" , / ^ ( C t r l \+ S h i f t \+ | ⌃ ⇧ ) E n t e r $ / )
315+ await record ( "submit" , "Backspace" , "Unassigned" )
316+ await record ( "newline" , "Enter" , "Enter" )
317+ await record ( "submit" , "Control+Enter" , / ^ ( C t r l \+ | ⌃ ) E n t e r $ / )
318+ await settings . getByRole ( "button" , { name : "Back to app" , exact : true } ) . click ( )
319+ await expect ( editor ) . toHaveText ( "hello" )
320+
321+ await editor . press ( "ControlOrMeta+Home" )
322+ await editor . press ( "ArrowRight" )
323+ await editor . press ( "Shift+ArrowRight" )
324+ await editor . press ( "Shift+ArrowRight" )
325+ await editor . press ( "Shift+ArrowRight" )
326+ await expect . poll ( ( ) => page . evaluate ( ( ) => window . getSelection ( ) ?. toString ( ) ) ) . toBe ( "ell" )
327+ await editor . press ( "Enter" )
328+ await expect . poll ( ( ) => editor . innerText ( ) ) . toBe ( "h\no" )
329+ await editor . press ( "ControlOrMeta+z" )
330+ await expect ( editor ) . toHaveText ( "hello" )
331+
332+ await page . locator ( '[data-action="composer-submit"]' ) . hover ( )
333+ await expect ( page . getByRole ( "tooltip" ) . locator ( '[data-slot="keybind-v2-label"]' ) ) . toHaveText ( [ / ^ ( C t r l | ⌃ ) $ / , "Enter" ] )
334+ await editor . press ( "Control+Enter" )
335+ await expect ( editor ) . toBeEmpty ( )
336+ await expect . poll ( ( ) => prompts ) . toEqual ( [ { text : "hello" , delivery : "steer" } ] )
337+
338+ await editor . fill ( "queued follow-up" )
339+ await expect ( page . locator ( '[data-action="composer-alternate-delivery"]' ) ) . toHaveText ( / Q u e u e .* E n t e r / )
340+ await expect ( page . locator ( '[data-action="composer-alternate-delivery"] [data-slot="keybind-v2-label"]' ) ) . toHaveText ( [
341+ / ^ ( C t r l | ⌃ ) $ / ,
342+ / ^ ( S h i f t | ⇧ ) $ / ,
343+ "Enter" ,
344+ ] )
345+ await editor . press ( "Control+Shift+Enter" )
346+ await expect ( editor ) . toBeEmpty ( )
347+ await expect
348+ . poll ( ( ) => prompts )
349+ . toEqual ( [
350+ { text : "hello" , delivery : "steer" } ,
351+ { text : "queued follow-up" , delivery : "queue" } ,
352+ ] )
353+
354+ await shortcuts ( )
355+
356+ for ( const id of [ "submit" , "newline" , "submit.alternate" ] ) await record ( id , "Backspace" , "Unassigned" )
357+ await settings . getByRole ( "button" , { name : "Back to app" , exact : true } ) . click ( )
358+ await editor . fill ( "disabled shortcuts" )
359+
360+ for ( const keys of [ "Enter" , "Control+Enter" , "Control+Shift+Enter" ] ) await editor . press ( keys )
361+ await expect ( editor ) . toHaveText ( "disabled shortcuts" )
362+ await expect ( page . locator ( '[data-action="composer-alternate-delivery"] [data-slot="keybind-v2-label"]' ) ) . toHaveCount (
363+ 0 ,
364+ )
365+ expect ( prompts ) . toHaveLength ( 2 )
366+
367+ await shortcuts ( )
368+ await settings . getByRole ( "button" , { name : "Reset to defaults" , exact : true } ) . click ( )
369+ await expect ( binding ( "submit" ) ) . toHaveText ( "Enter" )
370+ await settings . getByRole ( "button" , { name : "Back to app" , exact : true } ) . click ( )
371+ await editor . fill ( "restored defaults" )
372+ await editor . press ( "Enter" )
373+ await expect ( editor ) . toBeEmpty ( )
374+ await expect
375+ . poll ( ( ) => prompts )
376+ . toEqual ( [
377+ { text : "hello" , delivery : "steer" } ,
378+ { text : "queued follow-up" , delivery : "queue" } ,
379+ { text : "restored defaults" , delivery : "steer" } ,
380+ ] )
381+ } )
382+
285383test ( "keeps a 25000-line crash report editable in a new session" , async ( { page } ) => {
286384 const input = await draft ( page )
287385 const text = "Thread 0 Crashed:\n" + "0 Example 0x0000000100000000 frame + 32\n" . repeat ( 25000 ) + "End of report"
0 commit comments