349 lines
10 KiB
JavaScript
349 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
|
|
const {
|
|
buildSessionSnapshot,
|
|
createSavedTabSessionFromTab,
|
|
createRestoredSessionGroups,
|
|
renameSavedTabSession,
|
|
appendSavedTabSessionTabs,
|
|
updateSavedTabSessionTabs,
|
|
normalizeSavedTabSessions,
|
|
} = require('./tab-sessions.js');
|
|
|
|
test('buildSessionSnapshot captures selected tabs with canonical urls and manual group metadata', () => {
|
|
const snapshot = buildSessionSnapshot({
|
|
tabs: [
|
|
{
|
|
id: 11,
|
|
url: 'extension://noogafoofpebimajpfpamcfhoaifemoa/suspended.html#ttl=Issue%20Thread&uri=https%3A%2F%2Fgithub.com%2FV-IOLE-T%2Ftab-harbor%2Fissues%2F25',
|
|
title: 'Suspended tab',
|
|
favIconUrl: 'https://github.com/favicon.ico',
|
|
windowId: 1,
|
|
},
|
|
{
|
|
id: 12,
|
|
url: 'chrome://settings',
|
|
title: 'Settings',
|
|
windowId: 1,
|
|
},
|
|
],
|
|
groupLookup: new Map([
|
|
['11', { key: '__session_group__:research', label: 'Research', manualGroupId: 'research' }],
|
|
]),
|
|
selectedTabIds: ['11', '12'],
|
|
source: 'selected',
|
|
now: '2026-05-22T08:00:00.000Z',
|
|
});
|
|
|
|
assert.equal(snapshot.tabs.length, 1);
|
|
assert.equal(snapshot.tabs[0].url, 'https://github.com/V-IOLE-T/tab-harbor/issues/25');
|
|
assert.equal(snapshot.tabs[0].title, 'Issue Thread');
|
|
assert.deepEqual(snapshot.groups, [
|
|
{ key: '__session_group__:research', label: 'Research', manualGroupId: 'research', tabUrls: ['https://github.com/V-IOLE-T/tab-harbor/issues/25'] },
|
|
]);
|
|
});
|
|
|
|
test('buildSessionSnapshot rejects empty selected tabs', () => {
|
|
assert.throws(
|
|
() => buildSessionSnapshot({
|
|
tabs: [{ id: 1, url: 'chrome://newtab/', title: 'New tab' }],
|
|
selectedTabIds: ['1'],
|
|
}),
|
|
/no restorable tabs/i
|
|
);
|
|
});
|
|
|
|
test('normalizeSavedTabSessions drops invalid sessions and tabs', () => {
|
|
const sessions = normalizeSavedTabSessions([
|
|
{
|
|
id: 'session-a',
|
|
name: 'Morning research',
|
|
savedAt: '2026-05-22T08:00:00.000Z',
|
|
tabs: [
|
|
{ url: 'https://example.com/a', title: 'A' },
|
|
{ url: 'chrome://settings', title: 'Settings' },
|
|
],
|
|
groups: [{ key: 'example.com', label: 'Example', tabUrls: ['https://example.com/a', 'chrome://settings'] }],
|
|
},
|
|
{ id: 'empty', tabs: [] },
|
|
]);
|
|
|
|
assert.equal(sessions.length, 1);
|
|
assert.equal(sessions[0].name, 'Morning research');
|
|
assert.deepEqual(sessions[0].groups[0].tabUrls, ['https://example.com/a']);
|
|
});
|
|
|
|
test('createRestoredSessionGroups creates manual groups for restored manual session groups', () => {
|
|
const restored = createRestoredSessionGroups({
|
|
existingState: { groups: [], assignments: {} },
|
|
session: {
|
|
groups: [{ key: '__session_group__:research', label: 'Research', manualGroupId: 'research', tabUrls: ['https://a.test', 'https://b.test'] }],
|
|
},
|
|
restoredTabs: [
|
|
{ id: 101, url: 'https://a.test' },
|
|
{ id: 102, url: 'https://b.test' },
|
|
],
|
|
now: '2026-05-22T08:00:00.000Z',
|
|
});
|
|
|
|
assert.equal(restored.groups.length, 1);
|
|
assert.equal(restored.groups[0].name, 'Research');
|
|
assert.equal(restored.assignments['101'], restored.groups[0].id);
|
|
assert.equal(restored.assignments['102'], restored.groups[0].id);
|
|
});
|
|
|
|
test('renameSavedTabSession updates the matching session name', async () => {
|
|
const store = {
|
|
savedTabSessions: [
|
|
{
|
|
id: 'session-a',
|
|
name: 'Before',
|
|
savedAt: '2026-05-22T08:00:00.000Z',
|
|
source: 'manual',
|
|
tabs: [{ url: 'https://example.com', title: 'Example' }],
|
|
groups: [],
|
|
},
|
|
],
|
|
};
|
|
global.chrome = {
|
|
storage: {
|
|
local: {
|
|
async get(key) {
|
|
return { [key]: store[key] };
|
|
},
|
|
async set(next) {
|
|
Object.assign(store, next);
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const renamed = await renameSavedTabSession('session-a', 'After');
|
|
assert.equal(renamed[0].name, 'After');
|
|
assert.equal(store.savedTabSessions[0].name, 'After');
|
|
});
|
|
|
|
test('updateSavedTabSessionTabs reorders tabs and rebuilds groups', async () => {
|
|
const store = {
|
|
savedTabSessions: [
|
|
{
|
|
id: 'session-a',
|
|
name: 'Session',
|
|
savedAt: '2026-05-22T08:00:00.000Z',
|
|
source: 'manual',
|
|
tabs: [
|
|
{ url: 'https://a.test', title: 'A', groupKey: 'group-a', groupLabel: 'Group A' },
|
|
{ url: 'https://b.test', title: 'B', groupKey: 'group-b', groupLabel: 'Group B' },
|
|
],
|
|
groups: [
|
|
{ key: 'group-a', label: 'Group A', manualGroupId: '', tabUrls: ['https://a.test'] },
|
|
{ key: 'group-b', label: 'Group B', manualGroupId: '', tabUrls: ['https://b.test'] },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
global.chrome = {
|
|
storage: {
|
|
local: {
|
|
async get(key) {
|
|
return { [key]: store[key] };
|
|
},
|
|
async set(next) {
|
|
Object.assign(store, next);
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const updated = await updateSavedTabSessionTabs('session-a', [
|
|
{ url: 'https://b.test', title: 'B', groupKey: 'group-b', groupLabel: 'Group B' },
|
|
{ url: 'https://a.test', title: 'A', groupKey: 'group-a', groupLabel: 'Group A' },
|
|
]);
|
|
assert.deepEqual(updated[0].tabs.map(tab => tab.url), ['https://b.test', 'https://a.test']);
|
|
assert.deepEqual(updated[0].groups.map(group => group.key), ['group-b', 'group-a']);
|
|
});
|
|
|
|
test('updateSavedTabSessionTabs removes the session when no tabs remain', async () => {
|
|
const store = {
|
|
savedTabSessions: [
|
|
{
|
|
id: 'session-a',
|
|
name: 'Session',
|
|
savedAt: '2026-05-22T08:00:00.000Z',
|
|
source: 'manual',
|
|
tabs: [{ url: 'https://a.test', title: 'A' }],
|
|
groups: [],
|
|
},
|
|
],
|
|
};
|
|
global.chrome = {
|
|
storage: {
|
|
local: {
|
|
async get(key) {
|
|
return { [key]: store[key] };
|
|
},
|
|
async set(next) {
|
|
Object.assign(store, next);
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const updated = await updateSavedTabSessionTabs('session-a', []);
|
|
assert.equal(updated.length, 0);
|
|
});
|
|
|
|
test('appendSavedTabSessionTabs appends new tabs, skips duplicate urls, and rebuilds groups', async () => {
|
|
const store = {
|
|
savedTabSessions: [
|
|
{
|
|
id: 'session-a',
|
|
name: 'Session',
|
|
savedAt: '2026-05-22T08:00:00.000Z',
|
|
source: 'manual',
|
|
tabs: [
|
|
{ url: 'https://a.test', title: 'A', groupKey: 'group-a', groupLabel: 'Group A' },
|
|
],
|
|
groups: [
|
|
{ key: 'group-a', label: 'Group A', manualGroupId: '', tabUrls: ['https://a.test'] },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
global.chrome = {
|
|
storage: {
|
|
local: {
|
|
async get(key) {
|
|
return { [key]: store[key] };
|
|
},
|
|
async set(next) {
|
|
Object.assign(store, next);
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = await appendSavedTabSessionTabs('session-a', [
|
|
{
|
|
url: 'extension://noogafoofpebimajpfpamcfhoaifemoa/suspended.html#ttl=Duplicate%20A&uri=https%3A%2F%2Fa.test',
|
|
title: 'Duplicate A',
|
|
groupKey: 'group-a',
|
|
groupLabel: 'Group A',
|
|
},
|
|
{ url: 'https://b.test', title: 'B', groupKey: 'group-b', groupLabel: 'Group B' },
|
|
]);
|
|
|
|
assert.equal(result.appendedCount, 1);
|
|
assert.equal(result.skippedDuplicateCount, 1);
|
|
assert.deepEqual(result.session.tabs.map(tab => tab.url), ['https://a.test', 'https://b.test']);
|
|
assert.deepEqual(result.session.groups.map(group => group.key), ['group-a', 'group-b']);
|
|
assert.equal(result.session.id, 'session-a');
|
|
assert.equal(result.session.name, 'Session');
|
|
assert.equal(result.session.savedAt, '2026-05-22T08:00:00.000Z');
|
|
assert.equal(result.session.source, 'manual');
|
|
});
|
|
|
|
test('appendSavedTabSessionTabs rejects missing target sessions and invalid tabs', async () => {
|
|
const store = {
|
|
savedTabSessions: [
|
|
{
|
|
id: 'session-a',
|
|
name: 'Session',
|
|
savedAt: '2026-05-22T08:00:00.000Z',
|
|
source: 'manual',
|
|
tabs: [{ url: 'https://a.test', title: 'A' }],
|
|
groups: [],
|
|
},
|
|
],
|
|
};
|
|
global.chrome = {
|
|
storage: {
|
|
local: {
|
|
async get(key) {
|
|
return { [key]: store[key] };
|
|
},
|
|
async set(next) {
|
|
Object.assign(store, next);
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
await assert.rejects(
|
|
() => appendSavedTabSessionTabs('missing', [{ url: 'https://b.test', title: 'B' }]),
|
|
/session not found/i
|
|
);
|
|
await assert.rejects(
|
|
() => appendSavedTabSessionTabs('session-a', [{ url: 'chrome://settings', title: 'Settings' }]),
|
|
/no restorable tabs selected/i
|
|
);
|
|
});
|
|
|
|
test('createSavedTabSessionFromTab derives name, source, savedAt, tabs, and groups from one dragged tab', () => {
|
|
const created = createSavedTabSessionFromTab({
|
|
sourceSession: {
|
|
id: 'session-a',
|
|
name: 'Original',
|
|
source: 'window',
|
|
},
|
|
tab: {
|
|
url: 'https://github.com/openai/openai-node/issues/123',
|
|
title: 'SDK issue - GitHub',
|
|
favIconUrl: 'https://github.com/favicon.ico',
|
|
groupKey: '__session_group__:bugs',
|
|
groupLabel: 'Bugs',
|
|
manualGroupId: 'bugs',
|
|
},
|
|
existingSessions: [],
|
|
now: '2026-05-22T09:30:00.000Z',
|
|
});
|
|
|
|
assert.equal(created.name, 'SDK issue');
|
|
assert.equal(created.source, 'window');
|
|
assert.equal(created.savedAt, '2026-05-22T09:30:00.000Z');
|
|
assert.equal(created.tabs.length, 1);
|
|
assert.deepEqual(created.groups, [
|
|
{
|
|
key: '__session_group__:bugs',
|
|
label: 'Bugs',
|
|
manualGroupId: 'bugs',
|
|
tabUrls: ['https://github.com/openai/openai-node/issues/123'],
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('createSavedTabSessionFromTab falls back to friendly domain and de-duplicates duplicate session names', () => {
|
|
const created = createSavedTabSessionFromTab({
|
|
sourceSession: {
|
|
id: 'session-a',
|
|
name: 'Original',
|
|
source: '',
|
|
},
|
|
tab: {
|
|
url: 'https://docs.github.com/en/actions',
|
|
title: '',
|
|
favIconUrl: '',
|
|
groupKey: '',
|
|
groupLabel: '',
|
|
manualGroupId: '',
|
|
},
|
|
existingSessions: [
|
|
{
|
|
id: 'session-b',
|
|
name: 'Docs',
|
|
savedAt: '2026-05-22T08:00:00.000Z',
|
|
source: 'manual',
|
|
tabs: [{ url: 'https://example.com', title: 'Example' }],
|
|
groups: [],
|
|
},
|
|
],
|
|
now: '2026-05-22T09:30:00.000Z',
|
|
});
|
|
|
|
assert.equal(created.name, 'Docs 2');
|
|
assert.equal(created.source, 'manual');
|
|
});
|