Skip to content

Commit 5ab114c

Browse files
committed
extract a function that handles backed-up spaces and remove unnecessary test case
1 parent 381a7b4 commit 5ab114c

File tree

3 files changed

+293
-66
lines changed

3 files changed

+293
-66
lines changed

js/spaces.js

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -397,19 +397,8 @@ async function handleImport() {
397397
}
398398

399399
async function handleBackup() {
400-
// strip out unnessary content from each space
401-
const leanSpaces = (await fetchAllSpaces()).map(space => {
402-
return {
403-
name: space.name,
404-
tabs: space.tabs.map(curTab => {
405-
return {
406-
title: curTab.title,
407-
url: normaliseTabUrl(curTab.url),
408-
favIconUrl: curTab.favIconUrl,
409-
};
410-
}),
411-
};
412-
});
400+
// Get all spaces in lean format for backup
401+
const leanSpaces = await getSpacesForBackup();
413402

414403
const blob = new Blob([JSON.stringify(leanSpaces)], {
415404
type: 'application/json',
@@ -758,5 +747,31 @@ function normaliseTabUrl(url) {
758747
return normalisedUrl;
759748
}
760749

750+
/**
751+
* Gets all spaces and transforms them into lean format for backup/export.
752+
* Strips out unnecessary properties and normalizes URLs.
753+
*
754+
* @returns {Promise<Object[]>} Promise resolving to array of lean space objects with only essential properties
755+
*
756+
* @example
757+
* const leanSpaces = await getSpacesForBackup();
758+
* // returns: [{ name: 'Work', tabs: [{ title: 'Gmail', url: 'https://gmail.com', favIconUrl: 'icon.png' }] }]
759+
*/
760+
async function getSpacesForBackup() {
761+
const allSpaces = await fetchAllSpaces();
762+
return allSpaces.map(space => {
763+
return {
764+
name: space.name,
765+
tabs: space.tabs.map(curTab => {
766+
return {
767+
title: curTab.title,
768+
url: normaliseTabUrl(curTab.url),
769+
favIconUrl: curTab.favIconUrl,
770+
};
771+
}),
772+
};
773+
});
774+
}
775+
761776
// Export for testing
762-
export { normaliseTabUrl };
777+
export { getSpacesForBackup, normaliseTabUrl };

tests/handleBackup.test.js

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import { normaliseTabUrl, getSpacesForBackup } from '../js/spaces.js';
2+
import { jest, setupChromeMocks } from './helpers.js';
3+
4+
// Set up Chrome mocks
5+
setupChromeMocks();
6+
7+
describe('handleBackup functionality', () => {
8+
9+
describe('normaliseTabUrl', () => {
10+
it('should extract original URL from Great Suspender suspended tab URL', () => {
11+
const suspendedUrl = 'chrome-extension://klbibkeccnjlkjkiokjodocebajanakg/suspended.html?uri=https://github.com/user/repo';
12+
const result = normaliseTabUrl(suspendedUrl);
13+
expect(result).toBe('https://github.com/user/repo');
14+
});
15+
16+
it('should return unchanged URL for non-suspended tabs', () => {
17+
const normalUrl = 'https://example.com';
18+
const result = normaliseTabUrl(normalUrl);
19+
expect(result).toBe('https://example.com');
20+
});
21+
22+
it('should handle URLs with multiple query parameters', () => {
23+
const suspendedUrl = 'chrome-extension://abc/suspended.html?param1=value1&uri=https://test.com&param2=value2';
24+
const result = normaliseTabUrl(suspendedUrl);
25+
expect(result).toBe('https://test.com&param2=value2');
26+
});
27+
28+
it('should handle malformed suspended URLs gracefully', () => {
29+
const malformedUrl = 'chrome-extension://abc/suspended.html?noUri=test';
30+
const result = normaliseTabUrl(malformedUrl);
31+
expect(result).toBe(malformedUrl); // Should return unchanged
32+
});
33+
34+
it('should handle hash-based URIs correctly', () => {
35+
const suspendedUrl = 'chrome-extension://klbibkeccnjlkjkiokjodocebajanakg/suspended.html#uri=https://example.com';
36+
const result = normaliseTabUrl(suspendedUrl);
37+
expect(result).toBe('https://example.com');
38+
});
39+
40+
it('should handle complex URLs with query parameters and hash fragments', () => {
41+
const complexUrl = 'chrome-extension://different-id/suspended.html?title=Test&uri=https://docs.example.com/api/v1/users?sort=name&page=5#results';
42+
const result = normaliseTabUrl(complexUrl);
43+
expect(result).toBe('https://docs.example.com/api/v1/users?sort=name&page=5#results');
44+
});
45+
46+
it('should return unchanged URLs that do not match suspended pattern', () => {
47+
expect(normaliseTabUrl('https://example.com#section')).toBe('https://example.com#section');
48+
expect(normaliseTabUrl('chrome://settings/')).toBe('chrome://settings/');
49+
});
50+
51+
it('should require both suspended.html and uri parameter', () => {
52+
expect(normaliseTabUrl('chrome-extension://abc/suspended.html#title=Something')).toBe('chrome-extension://abc/suspended.html#title=Something');
53+
expect(normaliseTabUrl('https://example.com/page.html#uri=https://other.com')).toBe('https://example.com/page.html#uri=https://other.com');
54+
});
55+
56+
it('should require suspended.html not at beginning (indexOf > 0)', () => {
57+
expect(normaliseTabUrl('suspended.html#uri=https://example.com')).toBe('suspended.html#uri=https://example.com');
58+
});
59+
60+
it('should extract from first uri parameter when multiple exist', () => {
61+
const url = 'chrome-extension://abc/suspended.html#uri=https://first.com&other=param&uri=https://second.com';
62+
const result = normaliseTabUrl(url);
63+
expect(result).toBe('https://first.com&other=param&uri=https://second.com');
64+
});
65+
66+
it('should handle edge case inputs', () => {
67+
expect(normaliseTabUrl('')).toBe('');
68+
expect(() => normaliseTabUrl(null)).toThrow();
69+
expect(() => normaliseTabUrl(123)).toThrow();
70+
expect(normaliseTabUrl([])).toEqual([]); // Arrays have indexOf
71+
});
72+
});
73+
74+
describe('getSpacesForBackup', () => {
75+
beforeEach(() => {
76+
jest.clearAllMocks();
77+
});
78+
79+
it('should fetch all spaces and transform to lean format', async () => {
80+
const mockSpaces = [
81+
{
82+
name: 'Work Space',
83+
id: 'should-be-removed',
84+
sessionId: 'should-be-removed',
85+
windowId: 'should-be-removed',
86+
tabs: [
87+
{
88+
title: 'Gmail',
89+
url: 'https://gmail.com',
90+
favIconUrl: 'gmail.ico',
91+
id: 'tab-1',
92+
active: true,
93+
pinned: false,
94+
index: 0,
95+
},
96+
{
97+
title: 'GitHub',
98+
url: 'chrome-extension://abc/suspended.html?uri=https://github.com',
99+
favIconUrl: 'github.ico',
100+
id: 'tab-2',
101+
active: false,
102+
},
103+
],
104+
},
105+
{
106+
name: 'Personal Space',
107+
extraProperty: 'should-be-removed',
108+
tabs: [
109+
{
110+
title: 'YouTube',
111+
url: 'https://youtube.com',
112+
favIconUrl: 'yt.ico',
113+
someOtherProp: 'remove-me',
114+
},
115+
],
116+
},
117+
];
118+
119+
global.chrome.runtime.sendMessage.mockResolvedValue(mockSpaces);
120+
121+
const result = await getSpacesForBackup();
122+
123+
expect(global.chrome.runtime.sendMessage).toHaveBeenCalledWith({
124+
action: 'requestAllSpaces',
125+
});
126+
127+
expect(result).toEqual([
128+
{
129+
name: 'Work Space',
130+
tabs: [
131+
{
132+
title: 'Gmail',
133+
url: 'https://gmail.com',
134+
favIconUrl: 'gmail.ico',
135+
},
136+
{
137+
title: 'GitHub',
138+
url: 'https://github.com', // URL should be normalized
139+
favIconUrl: 'github.ico',
140+
},
141+
],
142+
},
143+
{
144+
name: 'Personal Space',
145+
tabs: [
146+
{
147+
title: 'YouTube',
148+
url: 'https://youtube.com',
149+
favIconUrl: 'yt.ico',
150+
},
151+
],
152+
},
153+
]);
154+
155+
// Verify unwanted properties are not included
156+
expect(result[0]).not.toHaveProperty('id');
157+
expect(result[0]).not.toHaveProperty('sessionId');
158+
expect(result[0]).not.toHaveProperty('windowId');
159+
expect(result[0].tabs[0]).not.toHaveProperty('active');
160+
expect(result[0].tabs[0]).not.toHaveProperty('pinned');
161+
expect(result[0].tabs[0]).not.toHaveProperty('index');
162+
expect(result[1]).not.toHaveProperty('extraProperty');
163+
expect(result[1].tabs[0]).not.toHaveProperty('someOtherProp');
164+
165+
// Verify the result can be serialized to valid JSON
166+
const jsonString = JSON.stringify(result);
167+
expect(() => JSON.parse(jsonString)).not.toThrow();
168+
const parsedContent = JSON.parse(jsonString);
169+
expect(Array.isArray(parsedContent)).toBe(true);
170+
expect(parsedContent).toHaveLength(2);
171+
});
172+
173+
it('should handle empty spaces array', async () => {
174+
global.chrome.runtime.sendMessage.mockResolvedValue([]);
175+
176+
const result = await getSpacesForBackup();
177+
178+
expect(result).toEqual([]);
179+
});
180+
181+
it('should handle spaces with missing properties', async () => {
182+
const mockSpaces = [
183+
{
184+
// missing name
185+
tabs: [
186+
{
187+
title: 'Tab 1',
188+
url: 'https://example.com',
189+
// missing favIconUrl
190+
},
191+
{
192+
title: 'Tab 2',
193+
url: 'https://example2.com',
194+
favIconUrl: null,
195+
},
196+
],
197+
},
198+
{
199+
name: 'Named Space',
200+
tabs: [], // empty tabs
201+
},
202+
];
203+
204+
global.chrome.runtime.sendMessage.mockResolvedValue(mockSpaces);
205+
206+
const result = await getSpacesForBackup();
207+
208+
expect(result).toEqual([
209+
{
210+
name: undefined,
211+
tabs: [
212+
{
213+
title: 'Tab 1',
214+
url: 'https://example.com',
215+
favIconUrl: undefined,
216+
},
217+
{
218+
title: 'Tab 2',
219+
url: 'https://example2.com',
220+
favIconUrl: null,
221+
},
222+
],
223+
},
224+
{
225+
name: 'Named Space',
226+
tabs: [],
227+
},
228+
]);
229+
});
230+
231+
it('should preserve tab title and handle missing titles', async () => {
232+
const mockSpaces = [
233+
{
234+
name: 'Test Space',
235+
tabs: [
236+
{
237+
title: 'Normal Title',
238+
url: 'https://example.com',
239+
favIconUrl: 'icon.ico',
240+
},
241+
{
242+
// missing title
243+
url: 'https://no-title.com',
244+
favIconUrl: 'icon2.ico',
245+
},
246+
{
247+
title: null,
248+
url: 'https://null-title.com',
249+
favIconUrl: 'icon3.ico',
250+
},
251+
],
252+
},
253+
];
254+
255+
global.chrome.runtime.sendMessage.mockResolvedValue(mockSpaces);
256+
257+
const result = await getSpacesForBackup();
258+
259+
expect(result[0].tabs[0].title).toBe('Normal Title');
260+
expect(result[0].tabs[1].title).toBeUndefined();
261+
expect(result[0].tabs[2].title).toBeNull();
262+
});
263+
});
264+
});

tests/normaliseTabUrl.test.js

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)