@@ -27,6 +27,104 @@ async function expectCaretVisible(input: Locator) {
2727 . toBe ( true )
2828}
2929
30+ test ( "prompt shortcuts rebind through Settings, preserve newline undo, and restore defaults" , async ( { page } ) => {
31+ const prompts : { text : unknown ; delivery : unknown } [ ] = [ ]
32+
33+ const { editor } = await openSession ( page , {
34+ name : "ComposerShortcuts" ,
35+ sessionStatus : { ses_composershortcuts : { type : "running" } } ,
36+ onPrompt : ( { body } ) => void prompts . push ( { text : body . text , delivery : body . delivery } ) ,
37+ } )
38+
39+ const settings = page . getByTestId ( "settings-screen" )
40+ const binding = ( id : string ) => settings . locator ( `[data-keybind-id="prompt.${ id } "]` )
41+
42+ const shortcuts = async ( ) => {
43+ await page . keyboard . press ( "Control+," )
44+ await settings . getByRole ( "tab" , { name : "Shortcuts" , exact : true } ) . click ( )
45+ await expect ( settings . getByRole ( "heading" , { name : "Keyboard shortcuts" , exact : true } ) ) . toBeVisible ( )
46+ }
47+
48+ const record = async ( id : string , keys : string , label : string | RegExp ) => {
49+ await binding ( id ) . click ( )
50+ await expect ( binding ( id ) ) . toHaveText ( "Press keys" )
51+ await page . keyboard . press ( keys )
52+ await expect ( binding ( id ) ) . toHaveText ( label )
53+ }
54+
55+ await editor . fill ( "hello" )
56+ await shortcuts ( )
57+ await expect ( binding ( "submit" ) ) . toHaveText ( "Enter" )
58+ // Move alternate delivery first, freeing Ctrl+Enter for primary submission.
59+ await record ( "submit.alternate" , "Control+Shift+Enter" , / ^ ( C t r l \+ S h i f t \+ | ⌃ ⇧ ) E n t e r $ / )
60+ await record ( "submit" , "Backspace" , "Unassigned" )
61+ await record ( "newline" , "Enter" , "Enter" )
62+ await record ( "submit" , "Control+Enter" , / ^ ( C t r l \+ | ⌃ ) E n t e r $ / )
63+ await settings . getByRole ( "button" , { name : "Back to app" , exact : true } ) . click ( )
64+ await expect ( editor ) . toHaveText ( "hello" )
65+
66+ await editor . press ( "ControlOrMeta+Home" )
67+ await editor . press ( "ArrowRight" )
68+ await editor . press ( "Shift+ArrowRight" )
69+ await editor . press ( "Shift+ArrowRight" )
70+ await editor . press ( "Shift+ArrowRight" )
71+ await expect . poll ( ( ) => page . evaluate ( ( ) => window . getSelection ( ) ?. toString ( ) ) ) . toBe ( "ell" )
72+ await editor . press ( "Enter" )
73+ await expect . poll ( ( ) => editor . innerText ( ) ) . toBe ( "h\no" )
74+ await editor . press ( "ControlOrMeta+z" )
75+ await expect ( editor ) . toHaveText ( "hello" )
76+
77+ await page . locator ( '[data-action="composer-submit"]' ) . hover ( )
78+ await expect ( page . getByRole ( "tooltip" ) . locator ( '[data-slot="keybind-v2-label"]' ) ) . toHaveText ( [ / ^ ( C t r l | ⌃ ) $ / , "Enter" ] )
79+ await editor . press ( "Control+Enter" )
80+ await expect ( editor ) . toBeEmpty ( )
81+ await expect . poll ( ( ) => prompts ) . toEqual ( [ { text : "hello" , delivery : "steer" } ] )
82+
83+ await editor . fill ( "queued follow-up" )
84+ await expect ( page . locator ( '[data-action="composer-alternate-delivery"]' ) ) . toHaveText ( / Q u e u e .* E n t e r / )
85+ await expect ( page . locator ( '[data-action="composer-alternate-delivery"] [data-slot="keybind-v2-label"]' ) ) . toHaveText ( [
86+ / ^ ( C t r l | ⌃ ) $ / ,
87+ / ^ ( S h i f t | ⇧ ) $ / ,
88+ "Enter" ,
89+ ] )
90+ await editor . press ( "Control+Shift+Enter" )
91+ await expect ( editor ) . toBeEmpty ( )
92+ await expect
93+ . poll ( ( ) => prompts )
94+ . toEqual ( [
95+ { text : "hello" , delivery : "steer" } ,
96+ { text : "queued follow-up" , delivery : "queue" } ,
97+ ] )
98+
99+ await shortcuts ( )
100+
101+ for ( const id of [ "submit" , "newline" , "submit.alternate" ] ) await record ( id , "Backspace" , "Unassigned" )
102+ await settings . getByRole ( "button" , { name : "Back to app" , exact : true } ) . click ( )
103+ await editor . fill ( "disabled shortcuts" )
104+
105+ for ( const keys of [ "Enter" , "Control+Enter" , "Control+Shift+Enter" ] ) await editor . press ( keys )
106+ await expect ( editor ) . toHaveText ( "disabled shortcuts" )
107+ await expect ( page . locator ( '[data-action="composer-alternate-delivery"] [data-slot="keybind-v2-label"]' ) ) . toHaveCount (
108+ 0 ,
109+ )
110+ expect ( prompts ) . toHaveLength ( 2 )
111+
112+ await shortcuts ( )
113+ await settings . getByRole ( "button" , { name : "Reset to defaults" , exact : true } ) . click ( )
114+ await expect ( binding ( "submit" ) ) . toHaveText ( "Enter" )
115+ await settings . getByRole ( "button" , { name : "Back to app" , exact : true } ) . click ( )
116+ await editor . fill ( "restored defaults" )
117+ await editor . press ( "Enter" )
118+ await expect ( editor ) . toBeEmpty ( )
119+ await expect
120+ . poll ( ( ) => prompts )
121+ . toEqual ( [
122+ { text : "hello" , delivery : "steer" } ,
123+ { text : "queued follow-up" , delivery : "queue" } ,
124+ { text : "restored defaults" , delivery : "steer" } ,
125+ ] )
126+ } )
127+
30128test ( "keeps a 25000-line crash report editable in a new session" , async ( { page } ) => {
31129 const input = await draft ( page )
32130 const text = "Thread 0 Crashed:\n" + "0 Example 0x0000000100000000 frame + 32\n" . repeat ( 25000 ) + "End of report"
0 commit comments