Skip to content

Commit 253e25b

Browse files
committed
For issue #10, restore the session/space dimensions upon opening
1 parent 06985b6 commit 253e25b

File tree

5 files changed

+254
-13
lines changed

5 files changed

+254
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file.
88

99
- Work on [issue #10](https://github.com/codedread/spaces/issues/10): Remember named space window bounds in the internal database.
1010
- Fixed [issue #20](https://github.com/codedread/spaces/issues/20): Close browser windows from the Spaces window.
11-
- Increased unit test coverage from 10.75% to 14.75%.
11+
- Increased unit test coverage from 10.75% to 16.58%.
1212

1313
## [1.1.6] - 2025-09-17
1414

js/background/background.js

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -840,17 +840,25 @@ async function handleLoadSession(sessionId, tabUrl) {
840840
return curTab.url;
841841
});
842842

843-
// Display new session over most of the appropriate display.
844-
const workArea = await getTargetDisplayWorkArea();
845-
const windowHeight = workArea.height - 100;
846-
const windowWidth = workArea.width - 100;
847-
const newWindow = await chrome.windows.create({
848-
url: urls,
849-
height: windowHeight,
850-
width: windowWidth,
851-
top: workArea.top,
852-
left: workArea.left,
853-
});
843+
// Display new session with restored bounds if available, otherwise use default display area.
844+
let windowOptions = { url: urls };
845+
846+
if (session.windowBounds) {
847+
// Use stored window bounds for restoration
848+
windowOptions.height = session.windowBounds.height;
849+
windowOptions.width = session.windowBounds.width;
850+
windowOptions.top = session.windowBounds.top;
851+
windowOptions.left = session.windowBounds.left;
852+
} else {
853+
// Fallback to default display area positioning
854+
const workArea = await getTargetDisplayWorkArea();
855+
windowOptions.height = workArea.height - 100;
856+
windowOptions.width = workArea.width - 100;
857+
windowOptions.top = workArea.top;
858+
windowOptions.left = workArea.left;
859+
}
860+
861+
const newWindow = await chrome.windows.create(windowOptions);
854862

855863
// force match this new window to the session
856864
await spacesService.matchSessionToWindow(session, newWindow);
@@ -1348,5 +1356,6 @@ export {
13481356
focusOrLoadTabInWindow,
13491357
getEffectiveTabUrl,
13501358
getTargetDisplayWorkArea,
1359+
handleLoadSession,
13511360
requestAllSpaces,
13521361
};

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "Spaces",
33
"description": "Intuitive tab management",
4-
"version": "1.1.7.1",
4+
"version": "1.1.7.2",
55
"permissions": [
66
"contextMenus",
77
"favicon",

tests/handleLoadSession.test.js

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import { handleLoadSession } from '../js/background/background.js';
2+
import { spacesService } from '../js/background/spacesService.js';
3+
import { dbService } from '../js/background/dbService.js';
4+
import { jest, setupChromeMocks } from './helpers.js';
5+
6+
describe('handleLoadSession', () => {
7+
let mockSession;
8+
let mockWorkArea;
9+
let mockWindow;
10+
let originalMatchSessionToWindow;
11+
12+
const createBounds = (left, top, width, height) => ({ left, top, width, height });
13+
14+
// Constants for test data consistency
15+
const DEFAULT_WORK_AREA = createBounds(0, 0, 1920, 1080);
16+
const EXPECTED_FALLBACK_BOUNDS = createBounds(0, 0, 1820, 980);
17+
const STORED_BOUNDS = createBounds(100, 50, 800, 600);
18+
19+
beforeEach(() => {
20+
// Setup chrome API mocks using helpers
21+
setupChromeMocks();
22+
23+
// Setup mock session data
24+
mockSession = {
25+
id: 1,
26+
name: 'Test Session',
27+
windowId: null,
28+
tabs: [
29+
{ url: 'https://example.com', pinned: false },
30+
{ url: 'https://test.com', pinned: true }
31+
]
32+
};
33+
34+
mockWorkArea = { ...DEFAULT_WORK_AREA };
35+
36+
mockWindow = {
37+
id: 123,
38+
tabs: [
39+
{ id: 1, url: 'https://example.com' },
40+
{ id: 2, url: 'https://test.com' }
41+
]
42+
};
43+
44+
// Setup chrome API mock returns (only the ones that need to return values)
45+
global.chrome.windows.create.mockResolvedValue(mockWindow);
46+
global.chrome.windows.getCurrent.mockImplementation(() => Promise.resolve(mockWindow));
47+
global.chrome.windows.get.mockImplementation(() => Promise.resolve(mockWindow));
48+
global.chrome.system.display.getInfo.mockResolvedValue([
49+
{
50+
id: 'display1',
51+
bounds: { left: 0, top: 0, width: 1920, height: 1080 },
52+
workArea: mockWorkArea
53+
}
54+
]);
55+
56+
// Mock database service
57+
dbService.fetchSessionById = jest.fn().mockResolvedValue(mockSession);
58+
59+
// Mock spaces service
60+
originalMatchSessionToWindow = spacesService.matchSessionToWindow;
61+
spacesService.matchSessionToWindow = jest.fn().mockResolvedValue();
62+
63+
// Setup global functions
64+
global.getTargetDisplayWorkArea = jest.fn().mockResolvedValue(mockWorkArea);
65+
global.getEffectiveTabUrl = jest.fn().mockImplementation(tab => tab.url);
66+
global.focusOrLoadTabInWindow = jest.fn().mockResolvedValue();
67+
global.handleLoadWindow = jest.fn().mockResolvedValue();
68+
});
69+
70+
afterEach(() => {
71+
// Restore original functions
72+
spacesService.matchSessionToWindow = originalMatchSessionToWindow;
73+
74+
// Clear all mocks
75+
jest.clearAllMocks();
76+
});
77+
78+
describe('bounds restoration', () => {
79+
80+
describe('when session has stored window bounds', () => {
81+
beforeEach(() => { mockSession.windowBounds = { ...STORED_BOUNDS }; });
82+
83+
test('should restore window with stored bounds', async () => {
84+
await handleLoadSession(mockSession.id);
85+
86+
expect(global.chrome.windows.create).toHaveBeenCalledWith({
87+
url: ['https://example.com', 'https://test.com'],
88+
height: STORED_BOUNDS.height,
89+
width: STORED_BOUNDS.width,
90+
top: STORED_BOUNDS.top,
91+
left: STORED_BOUNDS.left
92+
});
93+
expect(global.getTargetDisplayWorkArea).not.toHaveBeenCalled();
94+
});
95+
96+
test('should match session to new window', async () => {
97+
await handleLoadSession(mockSession.id);
98+
99+
expect(spacesService.matchSessionToWindow).toHaveBeenCalledWith(
100+
mockSession,
101+
mockWindow
102+
);
103+
});
104+
105+
test('should restore pinned tabs correctly', async () => {
106+
await handleLoadSession(mockSession.id);
107+
108+
expect(global.chrome.tabs.update).toHaveBeenCalledWith(2, { pinned: true });
109+
});
110+
});
111+
112+
describe('when session has no stored window bounds', () => {
113+
beforeEach(() => { mockSession.windowBounds = undefined; });
114+
115+
test('should use fallback display area bounds', async () => {
116+
await handleLoadSession(mockSession.id);
117+
118+
expect(global.chrome.windows.create).toHaveBeenCalledWith({
119+
url: ['https://example.com', 'https://test.com'],
120+
height: EXPECTED_FALLBACK_BOUNDS.height,
121+
width: EXPECTED_FALLBACK_BOUNDS.width,
122+
top: EXPECTED_FALLBACK_BOUNDS.top,
123+
left: EXPECTED_FALLBACK_BOUNDS.left
124+
});
125+
expect(global.chrome.system.display.getInfo).toHaveBeenCalledTimes(1);
126+
});
127+
});
128+
129+
describe('when session has null window bounds', () => {
130+
beforeEach(() => { mockSession.windowBounds = null; });
131+
132+
test('should use fallback display area bounds', async () => {
133+
await handleLoadSession(mockSession.id);
134+
135+
expect(global.chrome.windows.create).toHaveBeenCalledWith({
136+
url: ['https://example.com', 'https://test.com'],
137+
height: EXPECTED_FALLBACK_BOUNDS.height,
138+
width: EXPECTED_FALLBACK_BOUNDS.width,
139+
top: EXPECTED_FALLBACK_BOUNDS.top,
140+
left: EXPECTED_FALLBACK_BOUNDS.left
141+
});
142+
});
143+
});
144+
145+
describe('when session window already exists', () => {
146+
beforeEach(() => { mockSession.windowId = 456; });
147+
148+
test('should focus existing window instead of creating new window', async () => {
149+
await handleLoadSession(mockSession.id, 'https://focus.com');
150+
151+
expect(global.chrome.windows.update).toHaveBeenCalledWith(456, { focused: true });
152+
expect(global.chrome.windows.create).not.toHaveBeenCalled();
153+
});
154+
155+
test('should not attempt bounds restoration for existing window', async () => {
156+
mockSession.windowBounds = { ...STORED_BOUNDS };
157+
158+
await handleLoadSession(mockSession.id);
159+
160+
expect(global.chrome.windows.create).not.toHaveBeenCalled();
161+
expect(global.chrome.system.display.getInfo).not.toHaveBeenCalled();
162+
});
163+
});
164+
165+
describe('with tab URL parameter', () => {
166+
const TEST_TAB_URL = 'https://focus-tab.com';
167+
168+
test('should create tab when bounds are restored and tab not found', async () => {
169+
mockSession.windowBounds = { ...STORED_BOUNDS };
170+
171+
await handleLoadSession(mockSession.id, TEST_TAB_URL);
172+
173+
// Tab should be created since it's not in the existing tabs
174+
expect(global.chrome.tabs.create).toHaveBeenCalledWith({
175+
url: TEST_TAB_URL,
176+
active: true
177+
});
178+
});
179+
180+
test('should create tab when using fallback bounds and tab not found', async () => {
181+
mockSession.windowBounds = undefined;
182+
183+
await handleLoadSession(mockSession.id, TEST_TAB_URL);
184+
185+
// Tab should be created since it's not in the existing tabs
186+
expect(global.chrome.tabs.create).toHaveBeenCalledWith({
187+
url: TEST_TAB_URL,
188+
active: true
189+
});
190+
});
191+
});
192+
193+
describe('edge cases', () => {
194+
test('should handle session with empty tabs array', async () => {
195+
mockSession.tabs = [];
196+
mockSession.windowBounds = { ...STORED_BOUNDS };
197+
198+
await handleLoadSession(mockSession.id);
199+
200+
expect(global.chrome.windows.create).toHaveBeenCalledWith({
201+
url: [],
202+
height: STORED_BOUNDS.height,
203+
width: STORED_BOUNDS.width,
204+
top: STORED_BOUNDS.top,
205+
left: STORED_BOUNDS.left
206+
});
207+
});
208+
209+
test('should handle partially defined bounds (gracefully fail to fallback)', async () => {
210+
mockSession.windowBounds = {
211+
left: 100,
212+
top: 50
213+
// missing width and height
214+
};
215+
216+
await handleLoadSession(mockSession.id);
217+
218+
// Should use stored bounds even if incomplete
219+
expect(global.chrome.windows.create).toHaveBeenCalledWith({
220+
url: ['https://example.com', 'https://test.com'],
221+
height: undefined,
222+
width: undefined,
223+
top: 50,
224+
left: 100
225+
});
226+
});
227+
});
228+
}); // close bounds restoration describe
229+
}); // close handleLoadSession describe

tests/helpers.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ export const setupChromeMocks = () => {
2626
update: jest.fn(),
2727
},
2828
windows: {
29+
create: jest.fn(),
30+
get: jest.fn(),
2931
getCurrent: jest.fn(),
32+
update: jest.fn(),
3033
}
3134
};
3235
};

0 commit comments

Comments
 (0)