Skip to content

Commit b4ab864

Browse files
committed
Fix issue #21 and add some unit tests
1 parent 5ab114c commit b4ab864

File tree

3 files changed

+126
-18
lines changed

3 files changed

+126
-18
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
- Fixed [issue #16](https://github.com/codedread/spaces/issues/16): Stop duplicating tabs of a closed Space when tab is clicked from the Spaces window.
1010
- Fixed [issue #14](https://github.com/codedread/spaces/issues/14): Open Spaces windows on the currently-active display.
11-
- Increased unit test coverage from 8.11% to 10.12%.
11+
- Increased unit test coverage from 8.11% to 10.32%.
1212

1313

1414
## [1.1.5] - 2025-09-08

js/spaces.js

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
/* global chrome */
22

3+
/**
4+
* @typedef {import('./common.js').Space} Space
5+
*/
6+
37
import { getHashVariable } from './common.js';
48
import { checkSessionOverwrite, escapeHtml } from './utils.js';
59

@@ -662,22 +666,6 @@ async function updateSpaceDetail(useCachedSpace) {
662666
}
663667
}
664668

665-
function addDuplicateMetadata(space) {
666-
const dupeCounts = {};
667-
668-
space.tabs.forEach(tab => {
669-
// eslint-disable-next-line no-param-reassign
670-
tab.title = tab.title || tab.url;
671-
dupeCounts[tab.title] = dupeCounts[tab.title]
672-
? dupeCounts[tab.title] + 1
673-
: 1;
674-
});
675-
space.tabs.forEach(tab => {
676-
// eslint-disable-next-line no-param-reassign
677-
tab.duplicate = dupeCounts[tab.title] > 1;
678-
});
679-
}
680-
681669
/**
682670
* Initialize the spaces window.
683671
* This function should be called from the HTML page after the DOM is loaded.
@@ -725,6 +713,32 @@ if (typeof window !== 'undefined') {
725713

726714
// Module-level helper functions.
727715

716+
/**
717+
* Adds duplicate metadata to tabs within a space.
718+
* Normalizes tab titles (using URL if title is missing) and marks tabs as duplicates
719+
* if multiple tabs have the same title.
720+
*
721+
* @param {Space} space - The space object containing an array of tabs
722+
*/
723+
function addDuplicateMetadata(space) {
724+
if (!space || !Array.isArray(space.tabs)) {
725+
return;
726+
}
727+
const dupeCounts = {};
728+
729+
space.tabs.forEach(tab => {
730+
// eslint-disable-next-line no-param-reassign
731+
tab.title = tab.title || tab.url;
732+
dupeCounts[tab.title] = dupeCounts[tab.title]
733+
? dupeCounts[tab.title] + 1
734+
: 1;
735+
});
736+
space.tabs.forEach(tab => {
737+
// eslint-disable-next-line no-param-reassign
738+
tab.duplicate = dupeCounts[tab.title] > 1;
739+
});
740+
}
741+
728742
/**
729743
* Extracts the original URL from a Great Suspender extension suspended tab URL.
730744
* Great Suspender URLs have the format: chrome-extension://id/suspended.html?uri=originalUrl
@@ -774,4 +788,4 @@ async function getSpacesForBackup() {
774788
}
775789

776790
// Export for testing
777-
export { getSpacesForBackup, normaliseTabUrl };
791+
export { addDuplicateMetadata, getSpacesForBackup, normaliseTabUrl };

tests/addDuplicateMetadata.test.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { addDuplicateMetadata } from '../js/spaces.js';
2+
3+
describe('addDuplicateMetadata', () => {
4+
it('should mark duplicate tabs based on title', () => {
5+
const space = {
6+
tabs: [
7+
{ title: 'Gmail', url: 'https://gmail.com' },
8+
{ title: 'GitHub', url: 'https://github.com' },
9+
{ title: 'Gmail', url: 'https://mail.google.com' },
10+
{ title: 'Unique Tab', url: 'https://unique.com' },
11+
],
12+
};
13+
14+
addDuplicateMetadata(space);
15+
16+
expect(space.tabs[0].duplicate).toBe(true); // Gmail (duplicate)
17+
expect(space.tabs[1].duplicate).toBe(false); // GitHub (unique)
18+
expect(space.tabs[2].duplicate).toBe(true); // Gmail (duplicate)
19+
expect(space.tabs[3].duplicate).toBe(false); // Unique Tab (unique)
20+
});
21+
22+
it('should use URL as title when title is missing', () => {
23+
const space = {
24+
tabs: [
25+
{ url: 'https://example.com' }, // no title
26+
{ title: '', url: 'https://empty-title.com' }, // empty title
27+
{ title: null, url: 'https://null-title.com' }, // null title
28+
{ title: 'https://example.com', url: 'https://different.com' }, // same as first URL
29+
],
30+
};
31+
32+
addDuplicateMetadata(space);
33+
34+
expect(space.tabs[0].title).toBe('https://example.com');
35+
expect(space.tabs[1].title).toBe('https://empty-title.com');
36+
expect(space.tabs[2].title).toBe('https://null-title.com');
37+
expect(space.tabs[3].title).toBe('https://example.com');
38+
39+
// Check duplicates
40+
expect(space.tabs[0].duplicate).toBe(true); // https://example.com (duplicate)
41+
expect(space.tabs[1].duplicate).toBe(false); // https://empty-title.com (unique)
42+
expect(space.tabs[2].duplicate).toBe(false); // https://null-title.com (unique)
43+
expect(space.tabs[3].duplicate).toBe(true); // https://example.com (duplicate)
44+
});
45+
46+
it('should handle empty tabs array', () => {
47+
const space = { tabs: [] };
48+
49+
addDuplicateMetadata(space);
50+
51+
expect(space.tabs).toEqual([]);
52+
});
53+
54+
it('should handle null or undefined space safely', () => {
55+
expect(() => addDuplicateMetadata(null)).not.toThrow();
56+
expect(() => addDuplicateMetadata(undefined)).not.toThrow();
57+
expect(() => addDuplicateMetadata({})).not.toThrow();
58+
expect(() => addDuplicateMetadata({ tabs: null })).not.toThrow();
59+
});
60+
61+
it('should handle space without tabs array', () => {
62+
const space = { name: 'Test Space' }; // no tabs property
63+
64+
expect(() => addDuplicateMetadata(space)).not.toThrow();
65+
});
66+
67+
it('should mark all tabs as duplicates when all have same title', () => {
68+
const space = {
69+
tabs: [
70+
{ title: 'Same Title', url: 'https://url1.com' },
71+
{ title: 'Same Title', url: 'https://url2.com' },
72+
{ title: 'Same Title', url: 'https://url3.com' },
73+
],
74+
};
75+
76+
addDuplicateMetadata(space);
77+
78+
expect(space.tabs[0].duplicate).toBe(true);
79+
expect(space.tabs[1].duplicate).toBe(true);
80+
expect(space.tabs[2].duplicate).toBe(true);
81+
});
82+
83+
it('should handle single tab as non-duplicate', () => {
84+
const space = {
85+
tabs: [
86+
{ title: 'Only Tab', url: 'https://only.com' },
87+
],
88+
};
89+
90+
addDuplicateMetadata(space);
91+
92+
expect(space.tabs[0].duplicate).toBe(false);
93+
});
94+
});

0 commit comments

Comments
 (0)