Files
openchamber/packages/ui/src/stores/useGlobalSessionsStore.test.ts
T
bashrusakhandLeonid Skorobogatyy a25fc4c25a fix(sync): reflect share status from global store after cancel (#1709)
* fix(sync): reflect share status from global store after cancel

Fix #1551: unshareSession() called updateLiveSession() which silently
fails when the child store doesn't exist. The sidebar rendered from the
child store first, showing stale share data. Now overlays the global
session's share field at merge points.

* fix(sync): extract shared mergeLiveSessionWithGlobalSession helper

Extracted the share-field overlay into a single shared helper in
useGlobalSessionsStore.ts. All 3 merge sites now use the helper
instead of duplicating the overlay logic.

* test(sync): add unit tests for mergeLiveSessionWithGlobalSession helper

---------

Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
2026-06-23 22:18:44 +03:00

109 lines
4.9 KiB
TypeScript

import { beforeEach, describe, expect, test } from 'bun:test';
import type { Session } from '@opencode-ai/sdk/v2';
import { resolveGlobalSessionDirectory, mergeLiveSessionWithGlobalSession, useGlobalSessionsStore } from './useGlobalSessionsStore';
type SessionExtra = Partial<Session> & {
directory?: string | null;
project?: { worktree?: string | null } | null;
};
const buildSession = (shareUrl: string, extra: SessionExtra = {}): Session => ({
id: 'ses_1',
title: 'Shared session',
time: { created: 1, updated: 2 },
share: { url: shareUrl },
...extra,
} as Session);
describe('useGlobalSessionsStore', () => {
beforeEach(() => {
useGlobalSessionsStore.setState({
activeSessions: [],
archivedSessions: [],
sessionsByDirectory: new Map(),
hasLoaded: false,
status: 'idle',
});
});
test('updates an existing session when the share URL changes', () => {
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/a'));
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/b'));
expect(useGlobalSessionsStore.getState().activeSessions[0]?.share?.url).toBe('https://share.example/b');
});
test('preserves directory metadata when a live update omits it', () => {
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/a', { directory: '/repo/app' }));
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/b', {
time: { created: 1, updated: 3 },
}));
const session = useGlobalSessionsStore.getState().activeSessions[0];
expect(resolveGlobalSessionDirectory(session)).toBe('/repo/app');
expect(useGlobalSessionsStore.getState().sessionsByDirectory.get('/repo/app')?.[0]?.id).toBe('ses_1');
});
test('preserves raw directory metadata when a live update only has project worktree', () => {
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/a', { directory: '/repo/app' }));
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/b', {
project: { worktree: '/repo/app' },
time: { created: 1, updated: 3 },
}));
const session = useGlobalSessionsStore.getState().activeSessions[0] as Session & { directory?: string | null };
expect(session.directory).toBe('/repo/app');
expect(resolveGlobalSessionDirectory(session)).toBe('/repo/app');
});
test('trusts explicit incoming raw directory metadata', () => {
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/a', { directory: '/repo/app' }));
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/b', {
directory: '/repo/app-worktree',
time: { created: 1, updated: 3 },
}));
expect(resolveGlobalSessionDirectory(useGlobalSessionsStore.getState().activeSessions[0])).toBe('/repo/app-worktree');
expect(useGlobalSessionsStore.getState().sessionsByDirectory.get('/repo/app')).toBe(undefined);
expect(useGlobalSessionsStore.getState().sessionsByDirectory.get('/repo/app-worktree')?.[0]?.id).toBe('ses_1');
});
test('preserves directory metadata when moving a session to archived', () => {
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/a', { directory: '/repo/app' }));
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/b', {
time: { created: 1, updated: 3, archived: 4 },
}));
expect(useGlobalSessionsStore.getState().activeSessions).toEqual([]);
expect(resolveGlobalSessionDirectory(useGlobalSessionsStore.getState().archivedSessions[0])).toBe('/repo/app');
});
});
describe('mergeLiveSessionWithGlobalSession', () => {
test('preserves global share over live share', () => {
const live = buildSession('https://live.example/s', { time: { created: 1, updated: 5 } });
const global = buildSession('https://global.example/s', { time: { created: 1, updated: 3 } });
const merged = mergeLiveSessionWithGlobalSession(live, global);
expect(merged.share?.url).toBe('https://global.example/s');
expect(merged.time?.updated).toBe(5);
});
test('preserves directory from global when live omits it', () => {
const live = buildSession('https://live.example/s', { time: { created: 1, updated: 5 } });
const global = buildSession('https://global.example/s', { directory: '/repo/app' });
const merged = mergeLiveSessionWithGlobalSession(live, global);
expect(resolveGlobalSessionDirectory(merged)).toBe('/repo/app');
});
test('live directory takes precedence over global when present', () => {
const live = buildSession('https://live.example/s', { directory: '/repo/worktree' });
const global = buildSession('https://global.example/s', { directory: '/repo/app' });
const merged = mergeLiveSessionWithGlobalSession(live, global);
expect(resolveGlobalSessionDirectory(merged)).toBe('/repo/worktree');
});
});