Files
openchamber/packages/ui/src/components/session/sidebar/sessions/sessionNodeItemUtils.test.ts
T

161 lines
5.6 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import type { Session } from '@opencode-ai/sdk/v2';
import { getRuntimeKey } from '@/lib/runtime-switch';
import { getPinnedSessionKey } from '@/stores/useSessionPinnedStore';
import { computeNodeStructureKey, nodeHasPinnedMembershipChange, selectFolderRootNodes, selectQuestionBadgeSessionScopes } from './sessionNodeItemUtils';
import type { SessionNode } from '../types';
const session = (id: string, title: string): Session => ({
id,
title,
time: { created: 1, updated: 1 },
} as Session);
const rootWithChild = (childSession: Session): SessionNode => ({
session: session('root', 'Root'),
children: [{ session: childSession, children: [], worktree: null }],
worktree: null,
});
describe('computeNodeStructureKey', () => {
test('stays stable across grouping rebuilds that reuse session objects', () => {
const child = session('child', 'Child');
expect(computeNodeStructureKey(rootWithChild(child))).toBe(computeNodeStructureKey(rootWithChild(child)));
});
test('changes when a descendant session object changes', () => {
const previous = session('child', 'Before');
const next = { ...previous, title: 'After' };
expect(computeNodeStructureKey(rootWithChild(previous))).not.toBe(computeNodeStructureKey(rootWithChild(next)));
});
});
describe('selectQuestionBadgeSessionScopes', () => {
const withDirectory = (node: SessionNode, directory: string | null): SessionNode => ({
...node,
session: { ...node.session, directory } as Session,
});
test('rolls up the hidden subtree by owning directory when a parent is collapsed', () => {
const grandchild = withDirectory({ session: session('grandchild', 'Grandchild'), children: [], worktree: null }, '/worktrees/feature');
const child = withDirectory({ session: session('child', 'Child'), children: [grandchild], worktree: null }, '/worktrees/feature');
const root = withDirectory({ session: session('root', 'Root'), children: [child], worktree: null }, '/repo');
expect(selectQuestionBadgeSessionScopes(root, false, '/repo')).toEqual([
{ directory: '/repo', sessionIDs: ['root'] },
{ directory: '/worktrees/feature', sessionIDs: ['child', 'grandchild'] },
]);
});
test('keeps expanded rows accurate to their own session only', () => {
const child = withDirectory({ session: session('child', 'Child'), children: [], worktree: null }, '/worktrees/feature');
const root = withDirectory({ session: session('root', 'Root'), children: [child], worktree: null }, '/repo');
expect(selectQuestionBadgeSessionScopes(root, true, '/repo')).toEqual([
{ directory: '/repo', sessionIDs: ['root'] },
]);
});
test('falls back to the group directory when the session has none', () => {
const root: SessionNode = { session: session('root', 'Root'), children: [], worktree: null };
expect(selectQuestionBadgeSessionScopes(root, false, '/fallback')).toEqual([
{ directory: '/fallback', sessionIDs: ['root'] },
]);
});
});
describe('nodeHasPinnedMembershipChange', () => {
test('detects composite pin changes using the group directory fallback', () => {
const node: SessionNode = {
session: session('root', 'Root'),
children: [],
worktree: null,
};
const pinnedKey = getPinnedSessionKey(getRuntimeKey(), '/repo', 'root');
expect(pinnedKey).not.toBeNull();
expect(nodeHasPinnedMembershipChange(
node,
node,
new Set(),
new Set([pinnedKey!]),
'/repo',
'/repo',
)).toBe(true);
});
test('ignores pin changes for the same session id in another directory', () => {
const node: SessionNode = {
session: session('root', 'Root'),
children: [],
worktree: null,
};
const pinnedKey = getPinnedSessionKey(getRuntimeKey(), '/other-repo', 'root');
expect(pinnedKey).not.toBeNull();
expect(nodeHasPinnedMembershipChange(
node,
node,
new Set(),
new Set([pinnedKey!]),
'/repo',
'/repo',
)).toBe(false);
});
});
describe('selectFolderRootNodes', () => {
test('does not render assigned descendants again beside their assigned parent tree', () => {
const grandchild: SessionNode = {
session: { ...session('grandchild', 'Grandchild'), parentID: 'child' } as Session,
children: [],
worktree: null,
};
const child: SessionNode = {
session: { ...session('child', 'Child'), parentID: 'root' } as Session,
children: [grandchild],
worktree: null,
};
const root: SessionNode = {
session: session('root', 'Root'),
children: [child],
worktree: null,
};
const nodes = new Map([
['root', root],
['child', child],
['grandchild', grandchild],
]);
expect(selectFolderRootNodes(['root', 'child', 'grandchild'], nodes)).toEqual([root]);
});
test('keeps a child as a folder root when none of its ancestors are assigned', () => {
const child: SessionNode = {
session: { ...session('child', 'Child'), parentID: 'root' } as Session,
children: [],
worktree: null,
};
const root: SessionNode = {
session: session('root', 'Root'),
children: [child],
worktree: null,
};
expect(selectFolderRootNodes(['child'], new Map([['root', root], ['child', child]]))).toEqual([child]);
});
test('keeps a child when an assigned ancestor is not available in the group', () => {
const child: SessionNode = {
session: { ...session('child', 'Child'), parentID: 'missing-root' } as Session,
children: [],
worktree: null,
};
expect(selectFolderRootNodes(['missing-root', 'child'], new Map([['child', child]]))).toEqual([child]);
});
});