Skip to content

Commit 5b9cb98

Browse files
authored
feat(compass): add confirmation dialog when quitting compass COMPASS-6435 (#5920)
1 parent a4904bd commit 5b9cb98

File tree

4 files changed

+97
-10
lines changed

4 files changed

+97
-10
lines changed

packages/compass-preferences-model/src/preferences-schema.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export type UserConfigurablePreferences = PermanentFeatureFlags &
5858
enableGenAISampleDocumentPassing: boolean;
5959
enablePerformanceAdvisorBanner: boolean;
6060
maximumNumberOfActiveConnections?: number;
61+
enableShowDialogOnQuit: boolean;
6162
};
6263

6364
export type InternalUserPreferences = {
@@ -747,6 +748,18 @@ export const storedUserPreferencesProps: Required<{
747748
type: 'number',
748749
},
749750

751+
enableShowDialogOnQuit: {
752+
ui: true,
753+
cli: true,
754+
global: true,
755+
description: {
756+
short: 'Show Quit Confirmation Dialog',
757+
long: 'Toggle whether confirmation dialog is shown when quitting Compass (cmd/ctrl-Q).',
758+
},
759+
validator: z.boolean().default(true),
760+
type: 'boolean',
761+
},
762+
750763
...allFeatureFlagsProps,
751764
};
752765

packages/compass-settings/src/components/settings/general.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const generalFields = [
1111
...(['darwin', 'win32'].includes(process.platform)
1212
? (['installURLHandlers'] as const)
1313
: []),
14+
'enableShowDialogOnQuit',
1415
] as const;
1516

1617
export const GeneralSettings: React.FunctionComponent = () => {

packages/compass/src/main/menu.spec.ts

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import EventEmitter from 'events';
22
import type { MenuItemConstructorOptions } from 'electron';
3-
import { BrowserWindow, ipcMain, Menu, app } from 'electron';
3+
import { BrowserWindow, ipcMain, Menu, app, dialog } from 'electron';
44
import { expect } from 'chai';
55
import sinon from 'sinon';
66
import { createSandboxFromDefaultPreferences } from 'compass-preferences-model';
77

88
import type { CompassApplication } from './application';
99
import type { CompassMenu as _CompassMenu } from './menu';
10+
import { quitItem } from './menu';
1011
import { AutoUpdateManagerState } from './auto-update-manager';
1112

1213
function serializable<T>(obj: T): T {
@@ -32,6 +33,7 @@ describe('CompassMenu', function () {
3233
afterEach(function () {
3334
App.removeAllListeners();
3435
ipcMain.removeAllListeners();
36+
sinon.restore();
3537
});
3638

3739
it('should create an instance of Compass menu handler with initial state where no window is loaded', function () {
@@ -131,10 +133,6 @@ describe('CompassMenu', function () {
131133
});
132134

133135
describe('getTemplate', function () {
134-
afterEach(function () {
135-
sinon.restore();
136-
});
137-
138136
it('should generate a menu template that can be passed to the Electron Menu without errors', function () {
139137
expect(() => {
140138
const template = CompassMenu.getTemplate(0);
@@ -675,4 +673,57 @@ describe('CompassMenu', function () {
675673
});
676674
});
677675
});
676+
677+
describe('quitItem', () => {
678+
it('should show box if enableShowDialogOnQuit is true, then cancels and does not save changes', async function () {
679+
await App.preferences.savePreferences({
680+
enableShowDialogOnQuit: true,
681+
});
682+
const showMessageBoxStub = sinon
683+
.stub(dialog, 'showMessageBox')
684+
.resolves({ response: 1, checkboxChecked: true });
685+
const quitStub = sinon.stub(app, 'quit');
686+
const item = quitItem('Quit', App);
687+
await (item as any).click();
688+
689+
expect(showMessageBoxStub).to.have.been.called;
690+
expect(quitStub).not.to.have.been.called;
691+
expect(App.preferences.getPreferences().enableShowDialogOnQuit).to.be
692+
.true;
693+
});
694+
695+
it('should show box if enableShowDialogOnQuit is true, then quits app and saves changes', async function () {
696+
await App.preferences.savePreferences({
697+
enableShowDialogOnQuit: true,
698+
});
699+
const showMessageBoxStub = sinon
700+
.stub(dialog, 'showMessageBox')
701+
.resolves({ response: 0, checkboxChecked: true });
702+
const quitStub = sinon.stub(app, 'quit');
703+
const item = quitItem('Quit', App);
704+
await (item as any).click();
705+
706+
expect(showMessageBoxStub).to.have.been.called;
707+
expect(quitStub).to.have.been.called;
708+
expect(App.preferences.getPreferences().enableShowDialogOnQuit).to.be
709+
.false;
710+
});
711+
712+
it('should quit app immediately if enableShowDialogOnQuit is false and keeps changes', async function () {
713+
await App.preferences.savePreferences({
714+
enableShowDialogOnQuit: false,
715+
});
716+
const showMessageBoxStub = sinon
717+
.stub(dialog, 'showMessageBox')
718+
.resolves({ response: 0, checkboxChecked: true });
719+
const quitStub = sinon.stub(app, 'quit');
720+
const item = quitItem('Quit', App);
721+
(item as any).click();
722+
723+
expect(showMessageBoxStub).not.to.have.been.called;
724+
expect(quitStub).to.have.been.called;
725+
expect(App.preferences.getPreferences().enableShowDialogOnQuit).to.be
726+
.false;
727+
});
728+
});
678729
});

packages/compass/src/main/menu.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,34 @@ function separator(): MenuItemConstructorOptions {
2424
};
2525
}
2626

27-
function quitItem(label: string): MenuItemConstructorOptions {
27+
function quitItem(
28+
label: string,
29+
compassApp: typeof CompassApplication
30+
): MenuItemConstructorOptions {
2831
return {
2932
label: label,
3033
accelerator: 'CmdOrCtrl+Q',
3134
click() {
32-
app.quit();
35+
!compassApp.preferences.getPreferences().enableShowDialogOnQuit
36+
? app.quit()
37+
: void dialog
38+
.showMessageBox({
39+
type: 'warning',
40+
title: `Quit ${app.getName()}`,
41+
icon: COMPASS_ICON,
42+
message: 'Are you sure you want to quit?',
43+
buttons: ['Quit', 'Cancel'],
44+
checkboxLabel: 'Do not ask me again',
45+
})
46+
.then((result) => {
47+
if (result.response === 0) {
48+
if (result.checkboxChecked)
49+
void compassApp.preferences.savePreferences({
50+
enableShowDialogOnQuit: false,
51+
});
52+
app.quit();
53+
}
54+
});
3355
},
3456
};
3557
}
@@ -98,7 +120,7 @@ function darwinCompassSubMenu(
98120
role: 'unhide',
99121
},
100122
separator(),
101-
quitItem('Quit'),
123+
quitItem('Quit', compassApp),
102124
],
103125
};
104126
}
@@ -155,7 +177,7 @@ function connectSubMenu(
155177

156178
if (nonDarwin) {
157179
subMenu.push(separator());
158-
subMenu.push(quitItem('E&xit'));
180+
subMenu.push(quitItem('E&xit', app));
159181
}
160182

161183
return {
@@ -714,4 +736,4 @@ class CompassMenu {
714736
}
715737
}
716738

717-
export { CompassMenu };
739+
export { CompassMenu, quitItem };

0 commit comments

Comments
 (0)