From 7c46d45e9970c34a7583b17b3bdfe70ec52138de Mon Sep 17 00:00:00 2001 From: WANG Kaixun <12310803@mail.sustech.edu.cn> Date: Sat, 21 Mar 2026 11:53:09 +0800 Subject: [PATCH] feat(settings): customizable global keyboard shortcut --- src/main/events.ts | 5 +- src/main/handlers/system.test.ts | 118 +++++++++++++- src/main/handlers/system.ts | 50 ++++-- src/preload/index.test.ts | 1 + src/preload/index.ts | 28 ++-- src/preload/utils.test.ts | 20 ++- src/preload/utils.ts | 10 ++ src/renderer/__helpers__/test-utils.tsx | 3 + src/renderer/__helpers__/vitest.setup.ts | 2 +- src/renderer/__mocks__/state-mocks.ts | 3 + .../settings/SystemSettings.test.tsx | 19 +++ .../components/settings/SystemSettings.tsx | 153 +++++++++++++++++- src/renderer/context/App.tsx | 65 +++++++- src/renderer/context/defaults.ts | 3 + .../__snapshots__/Settings.test.tsx.snap | 88 +++++++++- src/renderer/types.ts | 3 + src/renderer/utils/system/comms.test.ts | 16 +- src/renderer/utils/system/comms.ts | 14 +- .../utils/system/keyboardShortcut.test.ts | 79 +++++++++ src/renderer/utils/system/keyboardShortcut.ts | 105 ++++++++++++ src/shared/events.ts | 5 + 21 files changed, 738 insertions(+), 52 deletions(-) create mode 100644 src/renderer/utils/system/keyboardShortcut.test.ts create mode 100644 src/renderer/utils/system/keyboardShortcut.ts diff --git a/src/main/events.ts b/src/main/events.ts index c16ff3a5f..9c7901433 100644 --- a/src/main/events.ts +++ b/src/main/events.ts @@ -26,7 +26,10 @@ export function onMainEvent( */ export function handleMainEvent( event: EventType, - listener: (event: Electron.IpcMainInvokeEvent, data: EventData) => void, + listener: ( + event: Electron.IpcMainInvokeEvent, + data: EventData, + ) => unknown | Promise, ) { ipcMain.handle(event, listener); } diff --git a/src/main/handlers/system.test.ts b/src/main/handlers/system.test.ts index c7f4937f2..2351c212e 100644 --- a/src/main/handlers/system.test.ts +++ b/src/main/handlers/system.test.ts @@ -1,3 +1,4 @@ +import { globalShortcut } from 'electron'; import type { Menubar } from 'menubar'; import { EVENTS } from '../../shared/events'; @@ -5,10 +6,12 @@ import { EVENTS } from '../../shared/events'; import { registerSystemHandlers } from './system'; const onMock = vi.fn(); +const handleMock = vi.fn(); vi.mock('electron', () => ({ ipcMain: { on: (...args: unknown[]) => onMock(...args), + handle: (...args: unknown[]) => handleMock(...args), }, globalShortcut: { register: vi.fn(), @@ -27,6 +30,7 @@ describe('main/handlers/system.ts', () => { beforeEach(() => { vi.clearAllMocks(); + vi.mocked(globalShortcut.register).mockReturnValue(true); menubar = { showWindow: vi.fn(), @@ -37,6 +41,20 @@ describe('main/handlers/system.ts', () => { } as unknown as Menubar; }); + function getKeyboardShortcutHandler() { + registerSystemHandlers(menubar); + const handleCall = handleMock.mock.calls.find( + (c) => c[0] === EVENTS.UPDATE_KEYBOARD_SHORTCUT, + ); + if (!handleCall) { + throw new Error('UPDATE_KEYBOARD_SHORTCUT handler not registered'); + } + return handleCall[1] as ( + event: Electron.IpcMainInvokeEvent, + data: { enabled: boolean; keyboardShortcut: string }, + ) => { success: boolean }; + } + describe('registerSystemHandlers', () => { it('registers handlers without throwing', () => { expect(() => registerSystemHandlers(menubar)).not.toThrow(); @@ -45,13 +63,105 @@ describe('main/handlers/system.ts', () => { it('registers expected system IPC event handlers', () => { registerSystemHandlers(menubar); - const registeredEvents = onMock.mock.calls.map( + const onEvents = onMock.mock.calls.map((call: [string]) => call[0]); + const handleEvents = handleMock.mock.calls.map( (call: [string]) => call[0], ); - expect(registeredEvents).toContain(EVENTS.OPEN_EXTERNAL); - expect(registeredEvents).toContain(EVENTS.UPDATE_KEYBOARD_SHORTCUT); - expect(registeredEvents).toContain(EVENTS.UPDATE_AUTO_LAUNCH); + expect(onEvents).toContain(EVENTS.OPEN_EXTERNAL); + expect(onEvents).toContain(EVENTS.UPDATE_AUTO_LAUNCH); + expect(handleEvents).toContain(EVENTS.UPDATE_KEYBOARD_SHORTCUT); + }); + }); + + describe('UPDATE_KEYBOARD_SHORTCUT', () => { + it('registers shortcut when enabled', () => { + const handler = getKeyboardShortcutHandler(); + + const result = handler({} as Electron.IpcMainInvokeEvent, { + enabled: true, + keyboardShortcut: 'CommandOrControl+Shift+G', + }); + + expect(result).toEqual({ success: true }); + expect(globalShortcut.register).toHaveBeenCalledWith( + 'CommandOrControl+Shift+G', + expect.any(Function), + ); + }); + + it('unregisters when disabled after being enabled', () => { + const handler = getKeyboardShortcutHandler(); + + handler({} as Electron.IpcMainInvokeEvent, { + enabled: true, + keyboardShortcut: 'CommandOrControl+Shift+A', + }); + vi.clearAllMocks(); + + const result = handler({} as Electron.IpcMainInvokeEvent, { + enabled: false, + keyboardShortcut: 'CommandOrControl+Shift+A', + }); + + expect(result).toEqual({ success: true }); + expect(globalShortcut.unregister).toHaveBeenCalledWith( + 'CommandOrControl+Shift+A', + ); + expect(globalShortcut.register).not.toHaveBeenCalled(); + }); + + it('unregisters previous shortcut when switching to a new one', () => { + const handler = getKeyboardShortcutHandler(); + + handler({} as Electron.IpcMainInvokeEvent, { + enabled: true, + keyboardShortcut: 'CommandOrControl+Shift+A', + }); + vi.clearAllMocks(); + + handler({} as Electron.IpcMainInvokeEvent, { + enabled: true, + keyboardShortcut: 'CommandOrControl+Shift+B', + }); + + expect(globalShortcut.unregister).toHaveBeenCalledWith( + 'CommandOrControl+Shift+A', + ); + expect(globalShortcut.register).toHaveBeenCalledWith( + 'CommandOrControl+Shift+B', + expect.any(Function), + ); + }); + + it('returns success false and restores previous shortcut when new registration fails', () => { + const handler = getKeyboardShortcutHandler(); + + handler({} as Electron.IpcMainInvokeEvent, { + enabled: true, + keyboardShortcut: 'CommandOrControl+Shift+A', + }); + vi.clearAllMocks(); + vi.mocked(globalShortcut.register) + .mockReturnValueOnce(false) + .mockReturnValue(true); + + const result = handler({} as Electron.IpcMainInvokeEvent, { + enabled: true, + keyboardShortcut: 'CommandOrControl+Shift+B', + }); + + expect(result).toEqual({ success: false }); + expect(globalShortcut.register).toHaveBeenNthCalledWith( + 1, + 'CommandOrControl+Shift+B', + expect.any(Function), + ); + expect(globalShortcut.register).toHaveBeenNthCalledWith( + 2, + 'CommandOrControl+Shift+A', + expect.any(Function), + ); }); }); }); diff --git a/src/main/handlers/system.ts b/src/main/handlers/system.ts index 5b3d71f83..2baabcb59 100644 --- a/src/main/handlers/system.ts +++ b/src/main/handlers/system.ts @@ -3,12 +3,14 @@ import type { Menubar } from 'menubar'; import { EVENTS, + type EventData, type IAutoLaunch, type IKeyboardShortcut, + type IKeyboardShortcutResult, type IOpenExternal, } from '../../shared/events'; -import { onMainEvent } from '../events'; +import { handleMainEvent, onMainEvent } from '../events'; /** * Register IPC handlers for OS-level system operations. @@ -16,6 +18,19 @@ import { onMainEvent } from '../events'; * @param mb - The menubar instance used for show/hide on keyboard shortcut activation. */ export function registerSystemHandlers(mb: Menubar): void { + /** + * Currently registered accelerator for the global shortcut, or `null` when none. + */ + let lastRegisteredAccelerator: string | null = null; + + const toggleWindow = () => { + if (mb.window.isVisible()) { + mb.hideWindow(); + } else { + mb.showWindow(); + } + }; + /** * Open the given URL in the user's default browser, with an option to activate the app. */ @@ -26,21 +41,32 @@ export function registerSystemHandlers(mb: Menubar): void { /** * Register or unregister a global keyboard shortcut that toggles the menubar window visibility. */ - onMainEvent( + handleMainEvent( EVENTS.UPDATE_KEYBOARD_SHORTCUT, - (_, { enabled, keyboardShortcut }: IKeyboardShortcut) => { + (_, data: EventData): IKeyboardShortcutResult => { + const { enabled, keyboardShortcut } = data as IKeyboardShortcut; + const previous = lastRegisteredAccelerator; + + if (lastRegisteredAccelerator) { + globalShortcut.unregister(lastRegisteredAccelerator); + lastRegisteredAccelerator = null; + } + if (!enabled) { - globalShortcut.unregister(keyboardShortcut); - return; + return { success: true }; } - globalShortcut.register(keyboardShortcut, () => { - if (mb.window.isVisible()) { - mb.hideWindow(); - } else { - mb.showWindow(); - } - }); + const ok = globalShortcut.register(keyboardShortcut, toggleWindow); + if (ok) { + lastRegisteredAccelerator = keyboardShortcut; + return { success: true }; + } + + if (previous) { + globalShortcut.register(previous, toggleWindow); + lastRegisteredAccelerator = previous; + } + return { success: false }; }, ); diff --git a/src/preload/index.test.ts b/src/preload/index.test.ts index c9e4bc6a7..65cfe63a9 100644 --- a/src/preload/index.test.ts +++ b/src/preload/index.test.ts @@ -11,6 +11,7 @@ const logErrorMock = vi.fn(); vi.mock('./utils', () => ({ sendMainEvent: (...args: unknown[]) => sendMainEventMock(...args), invokeMainEvent: (...args: unknown[]) => invokeMainEventMock(...args), + invokeMainEventWithData: (...args: unknown[]) => invokeMainEventMock(...args), onRendererEvent: (...args: unknown[]) => onRendererEventMock(...args), })); diff --git a/src/preload/index.ts b/src/preload/index.ts index 695d4ff8b..01c83f12c 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,10 +1,18 @@ import { contextBridge, webFrame } from 'electron'; -import { APPLICATION } from '../shared/constants'; +import type { + IKeyboardShortcut, + IKeyboardShortcutResult, +} from '../shared/events'; import { EVENTS } from '../shared/events'; import { isLinux, isMacOS, isWindows } from '../shared/platform'; -import { invokeMainEvent, onRendererEvent, sendMainEvent } from './utils'; +import { + invokeMainEvent, + invokeMainEventWithData, + onRendererEvent, + sendMainEvent, +} from './utils'; /** * The Gitify Bridge API exposed to the renderer via `contextBridge`. @@ -56,16 +64,16 @@ export const api = { }), /** - * Register or unregister the global keyboard shortcut for toggling the app window. + * Apply the global keyboard shortcut for toggling the app window visibility. * - * @param keyboardShortcut - `true` to register the shortcut, `false` to unregister. + * @param payload - Whether the shortcut is enabled and the Electron accelerator string. + * @returns Whether registration succeeded (when enabled). */ - setKeyboardShortcut: (keyboardShortcut: boolean) => { - sendMainEvent(EVENTS.UPDATE_KEYBOARD_SHORTCUT, { - enabled: keyboardShortcut, - keyboardShortcut: APPLICATION.DEFAULT_KEYBOARD_SHORTCUT, - }); - }, + applyKeyboardShortcut: (payload: IKeyboardShortcut) => + invokeMainEventWithData( + EVENTS.UPDATE_KEYBOARD_SHORTCUT, + payload, + ) as Promise, /** Tray icon controls. */ tray: { diff --git a/src/preload/utils.test.ts b/src/preload/utils.test.ts index 7b6c3008f..46a6c6b44 100644 --- a/src/preload/utils.test.ts +++ b/src/preload/utils.test.ts @@ -1,6 +1,11 @@ import { EVENTS } from '../shared/events'; -import { invokeMainEvent, onRendererEvent, sendMainEvent } from './utils'; +import { + invokeMainEvent, + invokeMainEventWithData, + onRendererEvent, + sendMainEvent, +} from './utils'; vi.mock('electron', () => { type Listener = (event: unknown, ...args: unknown[]) => void; @@ -49,6 +54,19 @@ describe('preload/utils', () => { expect(result).toBe('response'); }); + it('invokeMainEventWithData forwards structured payload and resolves', async () => { + const payload = { enabled: true, keyboardShortcut: 'CommandOrControl+G' }; + const result = await invokeMainEventWithData( + EVENTS.UPDATE_KEYBOARD_SHORTCUT, + payload, + ); + expect(ipcRenderer.invoke).toHaveBeenCalledWith( + EVENTS.UPDATE_KEYBOARD_SHORTCUT, + payload, + ); + expect(result).toBe('response'); + }); + it('onRendererEvent registers listener and receives emitted data', () => { const handlerMock = vi.fn(); onRendererEvent( diff --git a/src/preload/utils.ts b/src/preload/utils.ts index 223e9bb1b..c79c08177 100644 --- a/src/preload/utils.ts +++ b/src/preload/utils.ts @@ -26,6 +26,16 @@ export function invokeMainEvent( return ipcRenderer.invoke(event, data); } +/** + * Invoke a main-process handler with structured `EventData` and await the result. + */ +export function invokeMainEventWithData( + event: EventType, + data?: EventData, +): Promise { + return ipcRenderer.invoke(event, data); +} + /** * Register a listener for an IPC event sent from the main process to the renderer. * diff --git a/src/renderer/__helpers__/test-utils.tsx b/src/renderer/__helpers__/test-utils.tsx index e6cd951e7..05e8d8b8a 100644 --- a/src/renderer/__helpers__/test-utils.tsx +++ b/src/renderer/__helpers__/test-utils.tsx @@ -75,6 +75,9 @@ function AppContextProvider({ updateSetting: vi.fn(), updateFilter: vi.fn(), + shortcutRegistrationError: null, + clearShortcutRegistrationError: vi.fn(), + ...value, } as TestAppContext; }, [value]); diff --git a/src/renderer/__helpers__/vitest.setup.ts b/src/renderer/__helpers__/vitest.setup.ts index 30b4e80a5..d07e7f681 100644 --- a/src/renderer/__helpers__/vitest.setup.ts +++ b/src/renderer/__helpers__/vitest.setup.ts @@ -56,7 +56,7 @@ window.gitify = { onResetApp: vi.fn(), onSystemThemeUpdate: vi.fn(), setAutoLaunch: vi.fn(), - setKeyboardShortcut: vi.fn(), + applyKeyboardShortcut: vi.fn().mockResolvedValue({ success: true }), raiseNativeNotification: vi.fn(), }; diff --git a/src/renderer/__mocks__/state-mocks.ts b/src/renderer/__mocks__/state-mocks.ts index 66246ad44..4c204f5d3 100644 --- a/src/renderer/__mocks__/state-mocks.ts +++ b/src/renderer/__mocks__/state-mocks.ts @@ -1,3 +1,5 @@ +import { APPLICATION } from '../../shared/constants'; + import { Constants } from '../constants'; import { @@ -59,6 +61,7 @@ const mockTraySettings: TraySettingsState = { const mockSystemSettings: SystemSettingsState = { openLinks: OpenPreference.FOREGROUND, keyboardShortcut: true, + openGitifyShortcut: APPLICATION.DEFAULT_KEYBOARD_SHORTCUT, showNotifications: true, playSound: true, notificationVolume: 20 as Percentage, diff --git a/src/renderer/components/settings/SystemSettings.test.tsx b/src/renderer/components/settings/SystemSettings.test.tsx index 7b8bfe85c..1e8cf7164 100644 --- a/src/renderer/components/settings/SystemSettings.test.tsx +++ b/src/renderer/components/settings/SystemSettings.test.tsx @@ -4,6 +4,8 @@ import userEvent from '@testing-library/user-event'; import { renderWithAppContext } from '../../__helpers__/test-utils'; import { mockSettings } from '../../__mocks__/state-mocks'; +import { APPLICATION } from '../../../shared/constants'; + import type { Percentage } from '../../types'; import { SystemSettings } from './SystemSettings'; @@ -41,6 +43,23 @@ describe('renderer/components/settings/SystemSettings.tsx', () => { expect(updateSettingMock).toHaveBeenCalledWith('keyboardShortcut', false); }); + it('should reset global shortcut to default when customized', async () => { + renderWithAppContext(, { + updateSetting: updateSettingMock, + settings: { + ...mockSettings, + openGitifyShortcut: 'CommandOrControl+Shift+X', + }, + }); + + await userEvent.click(screen.getByTestId('button-reset-global-shortcut')); + + expect(updateSettingMock).toHaveBeenCalledWith( + 'openGitifyShortcut', + APPLICATION.DEFAULT_KEYBOARD_SHORTCUT, + ); + }); + it('should toggle the showNotifications checkbox', async () => { await act(async () => { renderWithAppContext(, { diff --git a/src/renderer/components/settings/SystemSettings.tsx b/src/renderer/components/settings/SystemSettings.tsx index 877121a9d..bce8d094f 100644 --- a/src/renderer/components/settings/SystemSettings.tsx +++ b/src/renderer/components/settings/SystemSettings.tsx @@ -1,7 +1,14 @@ -import type { FC } from 'react'; +import { type FC, useEffect, useRef, useState } from 'react'; import { DeviceDesktopIcon, SyncIcon } from '@primer/octicons-react'; -import { Button, ButtonGroup, IconButton, Stack, Text } from '@primer/react'; +import { + Banner, + Button, + ButtonGroup, + IconButton, + Stack, + Text, +} from '@primer/react'; import { APPLICATION } from '../../../shared/constants'; @@ -20,11 +27,70 @@ import { decreaseVolume, increaseVolume, } from '../../utils/system/audio'; +import { + formatAcceleratorForDisplay, + keyboardEventToAccelerator, +} from '../../utils/system/keyboardShortcut'; import { VolumeDownIcon } from '../icons/VolumeDownIcon'; import { VolumeUpIcon } from '../icons/VolumeUpIcon'; export const SystemSettings: FC = () => { - const { settings, updateSetting } = useAppContext(); + const { + settings, + updateSetting, + shortcutRegistrationError, + clearShortcutRegistrationError, + } = useAppContext(); + + const [recordingShortcut, setRecordingShortcut] = useState(false); + const shortcutRowRef = useRef(null); + const isMac = window.gitify.platform.isMacOS(); + + useEffect(() => { + if (!recordingShortcut) { + return; + } + + const onPointerDown = (event: PointerEvent) => { + if (shortcutRowRef.current?.contains(event.target as Node)) { + return; + } + setRecordingShortcut(false); + }; + + document.addEventListener('pointerdown', onPointerDown, true); + return () => { + document.removeEventListener('pointerdown', onPointerDown, true); + }; + }, [recordingShortcut]); + + useEffect(() => { + if (!recordingShortcut) { + return; + } + + const onKeyDown = (event: KeyboardEvent) => { + event.preventDefault(); + event.stopPropagation(); + + const accelerator = keyboardEventToAccelerator(event); + if (accelerator) { + clearShortcutRegistrationError(); + updateSetting('openGitifyShortcut', accelerator); + setRecordingShortcut(false); + } + }; + + window.addEventListener('keydown', onKeyDown, true); + return () => { + window.removeEventListener('keydown', onKeyDown, true); + }; + }, [recordingShortcut, updateSetting, clearShortcutRegistrationError]); + + const shortcutDisplay = formatAcceleratorForDisplay( + settings.openGitifyShortcut, + isMac, + ); return (
@@ -59,6 +125,27 @@ export const SystemSettings: FC = () => { value={settings.openLinks} /> + {shortcutRegistrationError && ( + + {shortcutRegistrationError} + + + } + hideTitle + title="Shortcut error" + variant="critical" + /> + )} + { } tooltip={
- When enabled you can use the hotkeys{' '} + When enabled you can use{' '} - {APPLICATION.DEFAULT_KEYBOARD_SHORTCUT} + {shortcutDisplay} {' '} to show or hide {APPLICATION.NAME}.
} /> + {settings.keyboardShortcut && ( + + + Global shortcut:{' '} + + {recordingShortcut + ? 'Press keys… (click outside this area to cancel)' + : shortcutDisplay} + + + + + + + + )} + void; updateSetting: (name: keyof SettingsState, value: SettingsValue) => void; + + /** Shown when the OS could not register the chosen global shortcut. */ + shortcutRegistrationError: string | null; + clearShortcutRegistrationError: () => void; } export const AppContext = createContext | undefined>( @@ -133,6 +138,15 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { : defaultSettings, ); + const lastAppliedOpenGitifyShortcutRef = useRef(settings.openGitifyShortcut); + const [shortcutRegistrationError, setShortcutRegistrationError] = useState< + string | null + >(null); + + const clearShortcutRegistrationError = useCallback(() => { + setShortcutRegistrationError(null); + }, []); + const { setColorMode, setDayScheme, setNightScheme } = useTheme(); const { @@ -312,8 +326,41 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { ]); useEffect(() => { - setKeyboardShortcut(settings.keyboardShortcut); - }, [settings.keyboardShortcut]); + let cancelled = false; + + void (async () => { + const result = await applyKeyboardShortcut({ + enabled: settings.keyboardShortcut, + accelerator: settings.openGitifyShortcut, + }); + + if (cancelled) { + return; + } + + if (!result.success) { + setSettings((prev) => { + const reverted = { + ...prev, + openGitifyShortcut: lastAppliedOpenGitifyShortcutRef.current, + }; + saveState({ auth, settings: reverted }); + return reverted; + }); + setShortcutRegistrationError( + 'This shortcut could not be registered. It may already be in use.', + ); + return; + } + + lastAppliedOpenGitifyShortcutRef.current = settings.openGitifyShortcut; + setShortcutRegistrationError(null); + })(); + + return () => { + cancelled = true; + }; + }, [auth, settings.keyboardShortcut, settings.openGitifyShortcut]); useEffect(() => { setAutoLaunch(settings.openAtStartup); @@ -324,6 +371,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { clearState(); setAuth(defaultAuth); setSettings(defaultSettings); + lastAppliedOpenGitifyShortcutRef.current = + defaultSettings.openGitifyShortcut; + setShortcutRegistrationError(null); }); }, []); @@ -332,6 +382,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { saveState({ auth, settings: defaultSettings }); return defaultSettings; }); + lastAppliedOpenGitifyShortcutRef.current = + defaultSettings.openGitifyShortcut; + setShortcutRegistrationError(null); }, [auth]); const updateSetting = useCallback( @@ -567,6 +620,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { settings, resetSettings, updateSetting, + + shortcutRegistrationError, + clearShortcutRegistrationError, }), [ auth, @@ -597,6 +653,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { settings, resetSettings, updateSetting, + + shortcutRegistrationError, + clearShortcutRegistrationError, ], ); diff --git a/src/renderer/context/defaults.ts b/src/renderer/context/defaults.ts index 1bbab9848..6701b1682 100644 --- a/src/renderer/context/defaults.ts +++ b/src/renderer/context/defaults.ts @@ -1,3 +1,5 @@ +import { APPLICATION } from '../../shared/constants'; + import { Constants } from '../constants'; import { @@ -50,6 +52,7 @@ const defaultTraySettings: TraySettingsState = { const defaultSystemSettings: SystemSettingsState = { openLinks: OpenPreference.FOREGROUND, keyboardShortcut: true, + openGitifyShortcut: APPLICATION.DEFAULT_KEYBOARD_SHORTCUT, showNotifications: true, playSound: true, notificationVolume: 20 as Percentage, diff --git a/src/renderer/routes/__snapshots__/Settings.test.tsx.snap b/src/renderer/routes/__snapshots__/Settings.test.tsx.snap index 107ea30cf..9644dec77 100644 --- a/src/renderer/routes/__snapshots__/Settings.test.tsx.snap +++ b/src/renderer/routes/__snapshots__/Settings.test.tsx.snap @@ -1735,6 +1735,82 @@ exports[`renderer/routes/Settings.tsx > should render itself & its children 1`] +
+ + Global shortcut: + + + ⌘·⇧·G + + +
+ + +
+
should render itself & its children 1`] data-wrap="nowrap" >