@@ -143,6 +143,89 @@ describe("export then import round trip", () => {
143143 expect ( after . fonts . font_size ) . toBe ( DEFAULT_KITTY_CONFIG . fonts . font_size ) ;
144144 } ) ;
145145
146+ it ( "keeps a mouse mapping" , ( ) => {
147+ const config = structuredClone ( DEFAULT_KITTY_CONFIG ) ;
148+ config . mouse_mappings = [
149+ {
150+ button : "left" ,
151+ event : "click" ,
152+ modes : "ungrabbed" ,
153+ action : "mouse_handle_click selection link prompt" ,
154+ } ,
155+ ] ;
156+
157+ expect ( roundTrip ( config ) . mouse_mappings ) . toEqual ( config . mouse_mappings ) ;
158+ } ) ;
159+
160+ it ( "keeps environment variables" , ( ) => {
161+ // env is the one section written as several directives sharing a name,
162+ // which is a different path through both sides.
163+ const config = structuredClone ( DEFAULT_KITTY_CONFIG ) ;
164+ ( config . advanced as unknown as Record < string , unknown > ) [ "env" ] = {
165+ EDITOR : "nvim" ,
166+ PATH_EXTRA : "/opt/bin" ,
167+ } ;
168+
169+ const after = roundTrip ( config ) . advanced as unknown as Record <
170+ string ,
171+ unknown
172+ > ;
173+ expect ( after [ "env" ] ) . toEqual ( { EDITOR : "nvim" , PATH_EXTRA : "/opt/bin" } ) ;
174+ } ) ;
175+
176+ it ( "writes each setting once, whatever has been changed" , ( ) => {
177+ // Kitty keeps the last of a repeated directive, so a duplicate is a
178+ // setting that silently does something other than what the editor shows.
179+ const config = structuredClone ( DEFAULT_KITTY_CONFIG ) as unknown as Record <
180+ string ,
181+ Record < string , unknown >
182+ > ;
183+ for ( const [ section , values ] of Object . entries (
184+ DEFAULT_KITTY_CONFIG as unknown as Record < string , unknown > ,
185+ ) ) {
186+ if ( ! values || typeof values !== "object" || Array . isArray ( values ) ) {
187+ continue ;
188+ }
189+ for ( const [ key , value ] of Object . entries (
190+ values as Record < string , unknown > ,
191+ ) ) {
192+ const slice = config [ section ] ;
193+ if ( ! slice ) continue ;
194+ if ( typeof value === "boolean" ) slice [ key ] = ! value ;
195+ else if ( typeof value === "number" ) slice [ key ] = value + 1 ;
196+ }
197+ }
198+
199+ // These are the directives Kitty expects more than one of.
200+ const REPEATABLE = new Set ( [
201+ "map" ,
202+ "mouse_map" ,
203+ "env" ,
204+ "symbol_map" ,
205+ "narrow_symbols" ,
206+ "font_features" ,
207+ "modify_font" ,
208+ "watcher" ,
209+ "exe_search_path" ,
210+ "menu_map" ,
211+ "action_alias" ,
212+ "remote_control_password" ,
213+ ] ) ;
214+
215+ const counts = new Map < string , number > ( ) ;
216+ for ( const line of generator
217+ . generateConfig ( config as unknown as KittyConfigAST )
218+ . split ( "\n" ) ) {
219+ const trimmed = line . trim ( ) ;
220+ if ( ! trimmed || trimmed . startsWith ( "#" ) ) continue ;
221+ const key = trimmed . split ( / \s + / ) [ 0 ] ;
222+ if ( ! key || REPEATABLE . has ( key ) ) continue ;
223+ counts . set ( key , ( counts . get ( key ) ?? 0 ) + 1 ) ;
224+ }
225+
226+ expect ( [ ...counts ] . filter ( ( [ , times ] ) => times > 1 ) ) . toEqual ( [ ] ) ;
227+ } ) ;
228+
146229 it ( "keeps a shortcut whose action carries arguments and quotes" , ( ) => {
147230 const config = structuredClone ( DEFAULT_KITTY_CONFIG ) ;
148231 config . keyboard_shortcuts = [
0 commit comments