Skip to content

Commit 0f656f2

Browse files
SDA-4891 - Validate bounds on switching mini view (#2370)
* SDA-4891 - Validate bounds on switching mini view * SDA-4891 - switch should constrain bounds within the same display
1 parent a8dd3d9 commit 0f656f2

File tree

6 files changed

+301
-15
lines changed

6 files changed

+301
-15
lines changed

spec/__mocks__/electron.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ export const session = {
276276

277277
export const screen = {
278278
getAllDisplays: jest.fn(),
279+
getDisplayMatching: jest.fn(),
279280
getPrimaryDisplay: jest.fn(() => {
280281
return {
281282
workArea: {

spec/miniViewHandler.spec.ts

Lines changed: 235 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Display, Rectangle } from 'electron';
12
import { config } from '../src/app/config-handler';
23
import { mainEvents } from '../src/app/main-event-handler';
34
import {
@@ -6,6 +7,7 @@ import {
67
miniViewHandler,
78
} from '../src/app/mini-view-handler';
89
import { windowHandler } from '../src/app/window-handler';
10+
import { screen } from './__mocks__/electron';
911

1012
jest.mock('../src/app/config-handler', () => ({
1113
config: {
@@ -34,18 +36,23 @@ jest.mock('../src/app/window-handler', () => ({
3436
jest.mock('../src/app/window-utils', () => {
3537
return {
3638
windowExists: jest.fn(() => true),
39+
isValidBounds: jest.fn(() => true),
3740
};
3841
});
3942

43+
jest.mock('electron');
44+
4045
describe('MiniViewHandler', () => {
4146
let mockMainWindow: any;
4247
let mockMainWebContents: any;
48+
let mockDisplay: Partial<Display>;
4349

4450
beforeEach(() => {
4551
jest.clearAllMocks();
4652

4753
mockMainWindow = {
4854
setBounds: jest.fn(),
55+
getBounds: jest.fn(),
4956
getSize: jest.fn(() => [800, 600]),
5057
isFullScreen: jest.fn(() => false),
5158
isMaximized: jest.fn(() => false),
@@ -56,6 +63,14 @@ describe('MiniViewHandler', () => {
5663
once: jest.fn(),
5764
};
5865

66+
mockDisplay = {
67+
id: 1,
68+
bounds: { x: 0, y: 0, width: 1920, height: 1080 },
69+
workArea: { x: 0, y: 0, width: 1920, height: 1080 },
70+
size: { width: 1920, height: 1080 },
71+
workAreaSize: { width: 1920, height: 1080 },
72+
};
73+
5974
mockMainWebContents = {
6075
send: jest.fn(),
6176
isDestroyed: jest.fn(() => false),
@@ -71,22 +86,91 @@ describe('MiniViewHandler', () => {
7186
);
7287
(config.getUserConfigFields as jest.Mock).mockReturnValue({});
7388
(config.updateUserConfig as jest.Mock).mockResolvedValue(undefined);
89+
(screen.getDisplayMatching as jest.Mock).mockReturnValue(mockDisplay);
90+
});
91+
92+
describe('constrainBoundsToCurrentDisplay', () => {
93+
it('should constrain bounds within the current display', async () => {
94+
const bounds: Rectangle = { x: -100, y: -50, width: 500, height: 400 };
95+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
96+
mainWinPosInMiniView: bounds,
97+
});
98+
await miniViewHandler.activateMiniView();
99+
expect(mockMainWindow.setBounds).toHaveBeenCalledWith(
100+
expect.objectContaining({ x: 0, y: 0 }),
101+
);
102+
});
103+
104+
it('should not modify bounds if already within the display', async () => {
105+
const bounds: Rectangle = { x: 100, y: 50, width: 500, height: 400 };
106+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
107+
mainWinPosInMiniView: bounds,
108+
});
109+
await miniViewHandler.activateMiniView();
110+
expect(mockMainWindow.setBounds).toHaveBeenCalledWith(
111+
expect.objectContaining(bounds),
112+
);
113+
});
114+
115+
it('should handle bounds exceeding display width', async () => {
116+
const bounds: Rectangle = { x: 1800, y: 100, width: 500, height: 400 };
117+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
118+
mainWinPosInMiniView: bounds,
119+
});
120+
await miniViewHandler.activateMiniView();
121+
expect(mockMainWindow.setBounds).toHaveBeenCalledWith(
122+
expect.objectContaining({ x: 1420 }),
123+
);
124+
});
125+
126+
it('should handle bounds exceeding display height', async () => {
127+
const bounds: Rectangle = { x: 100, y: 900, width: 500, height: 400 };
128+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
129+
mainWinPosInMiniView: bounds,
130+
});
131+
await miniViewHandler.activateMiniView();
132+
expect(mockMainWindow.setBounds).toHaveBeenCalledWith(
133+
expect.objectContaining({ y: 680 }),
134+
);
135+
});
74136
});
75137

76138
describe('activateMiniView', () => {
139+
const validBounds: Rectangle = {
140+
x: 10,
141+
y: 20,
142+
width: 600,
143+
height: 600,
144+
};
145+
146+
const invalidBounds = {
147+
x: 100,
148+
y: 100,
149+
};
150+
77151
it('should set correct bounds when mainWinPosInMiniView exists', async () => {
152+
(screen.getDisplayMatching as jest.Mock).mockReturnValue(mockDisplay);
78153
(config.getUserConfigFields as jest.Mock).mockReturnValue({
79-
mainWinPosInMiniView: { x: 10, y: 20, width: 500, height: 400 },
154+
mainWinPosInMiniView: validBounds,
80155
});
81156

82157
await miniViewHandler.activateMiniView();
83158

84-
expect(mockMainWindow.setBounds).toHaveBeenCalledWith({
85-
x: 10,
86-
y: 20,
87-
width: 500,
88-
height: 400,
159+
expect(mockMainWindow.setBounds).toHaveBeenCalledWith(validBounds);
160+
});
161+
162+
it('should use primary display when saved bounds are invalid', async () => {
163+
(screen.getDisplayMatching as jest.Mock).mockReturnValue({});
164+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
165+
mainWinPosInMiniView: invalidBounds,
89166
});
167+
168+
await miniViewHandler.activateMiniView();
169+
170+
expect(mockMainWindow.setSize).toHaveBeenCalledWith(
171+
DEFAULT_MINI_VIEW_WINDOW_WIDTH,
172+
600,
173+
);
90174
});
91175

92176
it('should set default width and preserve height when mainWinPosInMiniView does not exist or has width > DEFAULT_MINI_VIEW_WINDOW_WIDTH', async () => {
@@ -146,9 +230,77 @@ describe('MiniViewHandler', () => {
146230
expect(mockMainWindow.unmaximize).toHaveBeenCalled();
147231
expect(mainEvents.publish).toHaveBeenCalledWith('unmaximize');
148232
});
233+
234+
it('should constrain bounds to current display when activating mini view with out-of-bounds position - top-left', async () => {
235+
const outOfBounds: Rectangle = {
236+
x: -100,
237+
y: -50,
238+
width: 500,
239+
height: 400,
240+
};
241+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
242+
mainWinPosInMiniView: outOfBounds,
243+
});
244+
245+
await miniViewHandler.activateMiniView();
246+
247+
expect(mockMainWindow.setBounds).toHaveBeenCalledWith({
248+
x: 0,
249+
y: 0,
250+
width: 500,
251+
height: 400,
252+
});
253+
});
254+
255+
it('should constrain bounds to current display when activating mini view with out-of-bounds position - bottom-right', async () => {
256+
const outOfBounds: Rectangle = {
257+
x: 1800,
258+
y: 900,
259+
width: 500,
260+
height: 400,
261+
};
262+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
263+
mainWinPosInMiniView: outOfBounds,
264+
});
265+
266+
await miniViewHandler.activateMiniView();
267+
268+
expect(mockMainWindow.setBounds).toHaveBeenCalledWith({
269+
x: 1420,
270+
y: 680,
271+
width: 500,
272+
height: 400,
273+
});
274+
});
275+
276+
it('should constrain bounds to current display when activating mini view with out-of-bounds position - partially out', async () => {
277+
const outOfBounds: Rectangle = {
278+
x: -50,
279+
y: -25,
280+
width: 600,
281+
height: 500,
282+
};
283+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
284+
mainWinPosInMiniView: outOfBounds,
285+
});
286+
287+
await miniViewHandler.activateMiniView();
288+
289+
expect(mockMainWindow.setBounds).toHaveBeenCalledWith({
290+
x: 0,
291+
y: 0,
292+
width: 600,
293+
height: 500,
294+
});
295+
});
149296
});
150297

151298
describe('deactivateMiniView', () => {
299+
const invalidBounds = {
300+
x: 100,
301+
y: 100,
302+
};
303+
152304
it('should set correct bounds when mainWinPos exists and width > MINI_VIEW_THRESHOLD_WINDOW_WIDTH', () => {
153305
(config.getUserConfigFields as jest.Mock).mockReturnValue({
154306
mainWinPos: { x: 10, y: 20, width: 800, height: 400 },
@@ -164,6 +316,20 @@ describe('MiniViewHandler', () => {
164316
});
165317
});
166318

319+
it('should use primary display when saved bounds are invalid', async () => {
320+
(screen.getDisplayMatching as jest.Mock).mockReturnValue({});
321+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
322+
mainWinPosInMiniView: invalidBounds,
323+
});
324+
325+
await miniViewHandler.deactivateMiniView();
326+
327+
expect(mockMainWindow.setSize).toHaveBeenCalledWith(
328+
MINI_VIEW_THRESHOLD_WINDOW_WIDTH,
329+
600,
330+
);
331+
});
332+
167333
it('should set default width and preserve height when mainWinPos does not exist or has width <= MINI_VIEW_THRESHOLD_WINDOW_WIDTH', () => {
168334
(config.getUserConfigFields as jest.Mock).mockReturnValue({
169335
mainWinPos: { x: 10, y: 20, width: 600, height: 400 },
@@ -193,6 +359,69 @@ describe('MiniViewHandler', () => {
193359
);
194360
done();
195361
});
362+
363+
it('should constrain bounds to current display when deactivating mini view with out-of-bounds position - top-left', async () => {
364+
const outOfBounds: Rectangle = {
365+
x: -100,
366+
y: -50,
367+
width: 800,
368+
height: 600,
369+
};
370+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
371+
mainWinPos: outOfBounds,
372+
});
373+
374+
await miniViewHandler.deactivateMiniView();
375+
376+
expect(mockMainWindow.setBounds).toHaveBeenCalledWith({
377+
x: 0,
378+
y: 0,
379+
width: 800,
380+
height: 600,
381+
});
382+
});
383+
384+
it('should constrain bounds to current display when deactivating mini view with out-of-bounds position - bottom-right', async () => {
385+
const outOfBounds: Rectangle = {
386+
x: 1800,
387+
y: 900,
388+
width: 800,
389+
height: 600,
390+
};
391+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
392+
mainWinPos: outOfBounds,
393+
});
394+
395+
await miniViewHandler.deactivateMiniView();
396+
397+
expect(mockMainWindow.setBounds).toHaveBeenCalledWith({
398+
x: 1120,
399+
y: 480,
400+
width: 800,
401+
height: 600,
402+
});
403+
});
404+
405+
it('should constrain bounds to current display when deactivating mini view with out-of-bounds position - partially out', async () => {
406+
const outOfBounds: Rectangle = {
407+
x: -50,
408+
y: -25,
409+
width: 900,
410+
height: 700,
411+
};
412+
(config.getUserConfigFields as jest.Mock).mockReturnValue({
413+
mainWinPos: outOfBounds,
414+
});
415+
416+
await miniViewHandler.deactivateMiniView();
417+
418+
expect(mockMainWindow.setBounds).toHaveBeenCalledWith({
419+
x: 0,
420+
y: 0,
421+
width: 900,
422+
height: 700,
423+
});
424+
});
196425
});
197426

198427
describe('notifyClient', () => {

src/app/main-api-handler.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,8 +603,6 @@ ipcMain.handle(
603603
switch (arg.cmd) {
604604
case apiCmds.getCurrentOriginUrl:
605605
return windowHandler.getMainWindow()?.origin;
606-
case apiCmds.isAeroGlassEnabled:
607-
return systemPreferences.isAeroGlassEnabled();
608606
case apiCmds.showScreenSharePermissionDialog: {
609607
const focusedWindow = BrowserWindow.getFocusedWindow();
610608
if (focusedWindow && !focusedWindow.isDestroyed()) {

0 commit comments

Comments
 (0)