@@ -110,6 +110,104 @@ async function expectCaretVisible(input: Locator) {
110110 . toBe ( true )
111111}
112112
113+ test ( "prompt shortcuts rebind through Settings, preserve newline undo, and restore defaults" , async ( { page } ) => {
114+ const prompts : { text : unknown ; delivery : unknown } [ ] = [ ]
115+
116+ const { editor } = await openSession ( page , {
117+ name : "ComposerShortcuts" ,
118+ sessionStatus : { ses_composershortcuts : { type : "running" } } ,
119+ onPrompt : ( { body } ) => void prompts . push ( { text : body . text , delivery : body . delivery } ) ,
120+ } )
121+
122+ const settings = page . getByTestId ( "settings-screen" )
123+ const binding = ( id : string ) => settings . locator ( `[data-keybind-id="prompt.${ id } "]` )
124+
125+ const shortcuts = async ( ) => {
126+ await page . keyboard . press ( "Control+," )
127+ await settings . getByRole ( "tab" , { name : "Shortcuts" , exact : true } ) . click ( )
128+ await expect ( settings . getByRole ( "heading" , { name : "Keyboard shortcuts" , exact : true } ) ) . toBeVisible ( )
129+ }
130+
131+ const record = async ( id : string , keys : string , label : string | RegExp ) => {
132+ await binding ( id ) . click ( )
133+ await expect ( binding ( id ) ) . toHaveText ( "Press keys" )
134+ await page . keyboard . press ( keys )
135+ await expect ( binding ( id ) ) . toHaveText ( label )
136+ }
137+
138+ await editor . fill ( "hello" )
139+ await shortcuts ( )
140+ await expect ( binding ( "submit" ) ) . toHaveText ( "Enter" )
141+ // Move alternate delivery first, freeing Ctrl+Enter for primary submission.
142+ await record ( "submit.alternate" , "Control+Shift+Enter" , / ^ ( C t r l \+ S h i f t \+ | ⌃ ⇧ ) E n t e r $ / )
143+ await record ( "submit" , "Backspace" , "Unassigned" )
144+ await record ( "newline" , "Enter" , "Enter" )
145+ await record ( "submit" , "Control+Enter" , / ^ ( C t r l \+ | ⌃ ) E n t e r $ / )
146+ await settings . getByRole ( "button" , { name : "Back to app" , exact : true } ) . click ( )
147+ await expect ( editor ) . toHaveText ( "hello" )
148+
149+ await editor . press ( "ControlOrMeta+Home" )
150+ await editor . press ( "ArrowRight" )
151+ await editor . press ( "Shift+ArrowRight" )
152+ await editor . press ( "Shift+ArrowRight" )
153+ await editor . press ( "Shift+ArrowRight" )
154+ await expect . poll ( ( ) => page . evaluate ( ( ) => window . getSelection ( ) ?. toString ( ) ) ) . toBe ( "ell" )
155+ await editor . press ( "Enter" )
156+ await expect . poll ( ( ) => editor . innerText ( ) ) . toBe ( "h\no" )
157+ await editor . press ( "ControlOrMeta+z" )
158+ await expect ( editor ) . toHaveText ( "hello" )
159+
160+ await page . locator ( '[data-action="composer-submit"]' ) . hover ( )
161+ await expect ( page . getByRole ( "tooltip" ) . locator ( '[data-slot="keybind-v2-label"]' ) ) . toHaveText ( [ / ^ ( C t r l | ⌃ ) $ / , "Enter" ] )
162+ await editor . press ( "Control+Enter" )
163+ await expect ( editor ) . toBeEmpty ( )
164+ await expect . poll ( ( ) => prompts ) . toEqual ( [ { text : "hello" , delivery : "steer" } ] )
165+
166+ await editor . fill ( "queued follow-up" )
167+ await expect ( page . locator ( '[data-action="composer-alternate-delivery"]' ) ) . toHaveText ( / Q u e u e .* E n t e r / )
168+ await expect ( page . locator ( '[data-action="composer-alternate-delivery"] [data-slot="keybind-v2-label"]' ) ) . toHaveText ( [
169+ / ^ ( C t r l | ⌃ ) $ / ,
170+ / ^ ( S h i f t | ⇧ ) $ / ,
171+ "Enter" ,
172+ ] )
173+ await editor . press ( "Control+Shift+Enter" )
174+ await expect ( editor ) . toBeEmpty ( )
175+ await expect
176+ . poll ( ( ) => prompts )
177+ . toEqual ( [
178+ { text : "hello" , delivery : "steer" } ,
179+ { text : "queued follow-up" , delivery : "queue" } ,
180+ ] )
181+
182+ await shortcuts ( )
183+
184+ for ( const id of [ "submit" , "newline" , "submit.alternate" ] ) await record ( id , "Backspace" , "Unassigned" )
185+ await settings . getByRole ( "button" , { name : "Back to app" , exact : true } ) . click ( )
186+ await editor . fill ( "disabled shortcuts" )
187+
188+ for ( const keys of [ "Enter" , "Control+Enter" , "Control+Shift+Enter" ] ) await editor . press ( keys )
189+ await expect ( editor ) . toHaveText ( "disabled shortcuts" )
190+ await expect ( page . locator ( '[data-action="composer-alternate-delivery"] [data-slot="keybind-v2-label"]' ) ) . toHaveCount (
191+ 0 ,
192+ )
193+ expect ( prompts ) . toHaveLength ( 2 )
194+
195+ await shortcuts ( )
196+ await settings . getByRole ( "button" , { name : "Reset to defaults" , exact : true } ) . click ( )
197+ await expect ( binding ( "submit" ) ) . toHaveText ( "Enter" )
198+ await settings . getByRole ( "button" , { name : "Back to app" , exact : true } ) . click ( )
199+ await editor . fill ( "restored defaults" )
200+ await editor . press ( "Enter" )
201+ await expect ( editor ) . toBeEmpty ( )
202+ await expect
203+ . poll ( ( ) => prompts )
204+ . toEqual ( [
205+ { text : "hello" , delivery : "steer" } ,
206+ { text : "queued follow-up" , delivery : "queue" } ,
207+ { text : "restored defaults" , delivery : "steer" } ,
208+ ] )
209+ } )
210+
113211test ( "keeps a 25000-line crash report editable in a new session" , async ( { page } ) => {
114212 const input = await draft ( page )
115213 const text = "Thread 0 Crashed:\n" + "0 Example 0x0000000100000000 frame + 32\n" . repeat ( 25000 ) + "End of report"
0 commit comments