Skip to content

Commit acf4355

Browse files
tests: add service tests
1 parent 26bfbeb commit acf4355

File tree

10 files changed

+1591
-699
lines changed

10 files changed

+1591
-699
lines changed

src/background.ts

Lines changed: 145 additions & 699 deletions
Large diffs are not rendered by default.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Service responsible for managing context menus
3+
* Single Responsibility: Handle all context menu operations
4+
*/
5+
export class ContextMenuService {
6+
/**
7+
* Initialize all context menus
8+
*/
9+
initialize(): void {
10+
this.createRenameTabMenu();
11+
this.createMergeWindowsMenu();
12+
this.createSendToHiveMenu();
13+
}
14+
15+
/**
16+
* Create the "Rename Tab" context menu
17+
*/
18+
private createRenameTabMenu(): void {
19+
chrome.contextMenus.create({
20+
id: 'rename-tab',
21+
title: '✏️ Rename Tab',
22+
contexts: ['all'],
23+
});
24+
}
25+
26+
/**
27+
* Create the "Merge All Windows" context menu
28+
*/
29+
private createMergeWindowsMenu(): void {
30+
chrome.contextMenus.create({
31+
id: 'merge-windows',
32+
title: '🪟 Merge All Windows',
33+
contexts: ['all'],
34+
});
35+
}
36+
37+
/**
38+
* Create the "Send to Tab Hive" context menu
39+
*/
40+
private createSendToHiveMenu(): void {
41+
chrome.contextMenus.create({
42+
id: 'send-to-hive',
43+
title: '🍯 Send to Tab Hive',
44+
contexts: ['all'],
45+
});
46+
}
47+
}

src/background/TabGroupsService.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { Group, Rule, TabModifierSettings } from '../common/types';
2+
import { _getStorageAsync } from '../common/storage';
3+
4+
/**
5+
* Service responsible for managing tab groups
6+
* Single Responsibility: Handle all tab grouping operations
7+
*/
8+
export class TabGroupsService {
9+
private handleTabGroupsMaxRetries = 600;
10+
private createAndSetupGroupMaxRetries = 600;
11+
private updateTabGroupMaxRetries = 600;
12+
13+
/**
14+
* Ungroup a tab if it doesn't have a group rule
15+
*/
16+
async ungroupTab(rule: Rule | undefined, tab: chrome.tabs.Tab): Promise<void> {
17+
if (!tab.id) return;
18+
19+
let isRuleHasGroup = false;
20+
21+
if (rule && rule.tab.group_id && rule.tab.group_id !== '') {
22+
isRuleHasGroup = true;
23+
}
24+
25+
if (!isRuleHasGroup && tab.groupId && tab.groupId !== -1) {
26+
// Check if the group is one of user's groups
27+
const group = await chrome.tabGroups.get(tab.groupId);
28+
29+
const tabModifier = await _getStorageAsync();
30+
if (!tabModifier) return;
31+
32+
const tmGroup = tabModifier.groups.find((g) => g.title === group.title);
33+
if (tmGroup) await chrome.tabs.ungroup(tab.id);
34+
}
35+
}
36+
37+
/**
38+
* Apply a group rule to a tab
39+
*/
40+
async applyGroupRuleToTab(
41+
rule: Rule,
42+
tab: chrome.tabs.Tab,
43+
tabModifier: TabModifierSettings
44+
): Promise<void> {
45+
if (!tab.id) return;
46+
47+
// remove tab from group if it's already in one
48+
if (!rule || !rule.tab.group_id) {
49+
await this.ungroupTab(rule, tab);
50+
return;
51+
}
52+
53+
const tmGroup = tabModifier.groups.find((g) => g.id === rule.tab.group_id);
54+
55+
if (!tmGroup) return;
56+
57+
const tabGroupsQueryInfo = {
58+
title: tmGroup.title,
59+
color: tmGroup.color as chrome.tabGroups.ColorEnum,
60+
windowId: tab.windowId,
61+
};
62+
63+
chrome.tabGroups.query(tabGroupsQueryInfo, (groups: chrome.tabGroups.TabGroup[]) =>
64+
this.handleTabGroups(groups, tab, tmGroup)
65+
);
66+
}
67+
68+
/**
69+
* Handle tab groups - create or add to existing
70+
*/
71+
private async handleTabGroups(
72+
groups: chrome.tabGroups.TabGroup[],
73+
tab: chrome.tabs.Tab,
74+
tmGroup: Group
75+
): Promise<void> {
76+
if (!tab.id) return;
77+
78+
if (groups.length === 0) {
79+
await this.createAndSetupGroup([tab.id], tmGroup);
80+
} else if (groups.length === 1) {
81+
const group = groups[0];
82+
83+
const execute = () => {
84+
if (!tab.id) return;
85+
86+
chrome.tabs.group({ groupId: group.id, tabIds: [tab.id] }, (groupId: number) => {
87+
if (chrome.runtime.lastError && this.handleTabGroupsMaxRetries > 0) {
88+
setTimeout(() => execute(), 100);
89+
this.handleTabGroupsMaxRetries--;
90+
return;
91+
} else {
92+
this.handleTabGroupsMaxRetries = 600;
93+
this.updateTabGroup(groupId, tmGroup);
94+
}
95+
});
96+
};
97+
98+
execute();
99+
}
100+
}
101+
102+
/**
103+
* Create and setup a new tab group
104+
*/
105+
private async createAndSetupGroup(tabIds: number[], tmGroup: Group): Promise<void> {
106+
const execute = () => {
107+
chrome.tabs.group({ tabIds: tabIds }, (groupId: number) => {
108+
if (chrome.runtime.lastError && this.createAndSetupGroupMaxRetries > 0) {
109+
setTimeout(() => execute(), 100);
110+
this.createAndSetupGroupMaxRetries--;
111+
return;
112+
} else {
113+
this.createAndSetupGroupMaxRetries = 600;
114+
this.updateTabGroup(groupId, tmGroup);
115+
}
116+
});
117+
};
118+
119+
execute();
120+
}
121+
122+
/**
123+
* Update tab group properties
124+
*/
125+
private updateTabGroup(groupId: number, tmGroup: Group): void {
126+
if (!groupId) return;
127+
128+
const updateProperties = {
129+
title: tmGroup.title,
130+
color: tmGroup.color,
131+
collapsed: tmGroup.collapsed,
132+
} as chrome.tabGroups.UpdateProperties;
133+
134+
const execute = () => {
135+
chrome.tabGroups.update(groupId, updateProperties, () => {
136+
if (chrome.runtime.lastError && this.updateTabGroupMaxRetries > 0) {
137+
setTimeout(() => execute(), 100);
138+
this.updateTabGroupMaxRetries--;
139+
return;
140+
}
141+
});
142+
};
143+
144+
execute();
145+
}
146+
147+
/**
148+
* Handle setting a group from a message
149+
*/
150+
async handleSetGroup(rule: Rule, tab: chrome.tabs.Tab): Promise<void> {
151+
if (tab.url?.startsWith('chrome')) return;
152+
153+
const tabModifier = await _getStorageAsync();
154+
if (tabModifier) await this.applyGroupRuleToTab(rule, tab, tabModifier);
155+
}
156+
}

0 commit comments

Comments
 (0)