Files
2026-07-13 12:34:04 +08:00

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');
});