mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-08-21 06:33:35 +00:00
150 lines
4.5 KiB
TypeScript
150 lines
4.5 KiB
TypeScript
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
|
|
|
import { buildProjectSessionOverview } from '@/hooks/useProjectSessionOverview';
|
|
import {
|
|
createChatProjectionState,
|
|
type ChatMessageNode,
|
|
} from '@/lib/projector/chat';
|
|
import {
|
|
createHumanControlProjectionState,
|
|
type HumanControlProjectionState,
|
|
} from '@/lib/projector/control';
|
|
import type { ProjectEventStoreSnapshot } from '@/store/projectEventStore';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
function message(
|
|
runId: string,
|
|
sequence: number,
|
|
createdAt = new Date(sequence * 1_000).toISOString()
|
|
): ChatMessageNode {
|
|
return {
|
|
id: `${runId}:${sequence}`,
|
|
eventId: `${runId}:${sequence}`,
|
|
projectId: 'project-1',
|
|
runId,
|
|
createdAt,
|
|
runSequence: sequence,
|
|
cloudCursor: null,
|
|
eventType: 'message.completed',
|
|
legacyStep: null,
|
|
kind: 'message',
|
|
role: 'assistant',
|
|
content: runId,
|
|
status: 'complete',
|
|
};
|
|
}
|
|
|
|
function snapshot(): ProjectEventStoreSnapshot {
|
|
const nodes = [message('run-complete', 4), message('run-live', 1)];
|
|
return {
|
|
view: {
|
|
projectId: 'project-1',
|
|
mode: 'live',
|
|
seenEventIds: {},
|
|
currentCursor: 0,
|
|
eventsTruncated: false,
|
|
lastSyncedAt: null,
|
|
needsResync: false,
|
|
resyncReason: null,
|
|
resyncTargetCursor: null,
|
|
runs: {
|
|
'run-complete': {
|
|
runId: 'run-complete',
|
|
status: 'completed',
|
|
lastSequence: 4,
|
|
runVersion: 4,
|
|
updatedAt: new Date(4_000).toISOString(),
|
|
origin: 'local',
|
|
resumeBlockedReason: null,
|
|
},
|
|
'run-live': {
|
|
runId: 'run-live',
|
|
status: 'running',
|
|
lastSequence: 1,
|
|
runVersion: 1,
|
|
updatedAt: new Date(1_000).toISOString(),
|
|
origin: 'local',
|
|
resumeBlockedReason: null,
|
|
},
|
|
},
|
|
legacySteps: [],
|
|
unknownEvents: [],
|
|
},
|
|
chat: {
|
|
...createChatProjectionState('project-1'),
|
|
nodes,
|
|
nodeById: Object.fromEntries(nodes.map((node) => [node.id, node])),
|
|
seenEventIds: Object.fromEntries(
|
|
nodes.map((node) => [node.eventId, true as const])
|
|
),
|
|
},
|
|
control: createHumanControlProjectionState(
|
|
'project-1'
|
|
) as HumanControlProjectionState,
|
|
revision: 1,
|
|
hasHydratedSnapshot: true,
|
|
overflowed: false,
|
|
lastEffects: [],
|
|
};
|
|
}
|
|
|
|
describe('buildProjectSessionOverview', () => {
|
|
it('keeps an active durable Run current even when history is newer', () => {
|
|
const overview = buildProjectSessionOverview(snapshot());
|
|
|
|
expect(overview.currentRun?.runId).toBe('run-live');
|
|
expect(overview.historicalRuns.map((run) => run.runId)).toContain(
|
|
'run-complete'
|
|
);
|
|
});
|
|
|
|
it('groups semantic nodes by Run without consulting ChatStore', () => {
|
|
const overview = buildProjectSessionOverview(snapshot());
|
|
|
|
expect(overview.runs).toHaveLength(2);
|
|
expect(
|
|
overview.runs.find((run) => run.runId === 'run-complete')?.nodes
|
|
).toMatchObject([{ content: 'run-complete' }]);
|
|
});
|
|
|
|
it('keeps canonical Run sequence order when timestamps are reversed', () => {
|
|
const input = snapshot();
|
|
const nodes = [
|
|
message('run-live', 2, new Date(1_000).toISOString()),
|
|
message('run-live', 1, new Date(2_000).toISOString()),
|
|
];
|
|
input.chat.nodes = nodes;
|
|
input.chat.nodeById = Object.fromEntries(
|
|
nodes.map((node) => [node.id, node])
|
|
);
|
|
input.chat.seenEventIds = Object.fromEntries(
|
|
nodes.map((node) => [node.eventId, true as const])
|
|
);
|
|
|
|
const overview = buildProjectSessionOverview(input);
|
|
|
|
expect(overview.currentRun?.nodes.map((node) => node.runSequence)).toEqual([
|
|
1, 2,
|
|
]);
|
|
});
|
|
|
|
it('returns an empty view before durable hydration has a snapshot', () => {
|
|
expect(buildProjectSessionOverview(null)).toEqual({
|
|
currentRun: null,
|
|
historicalRuns: [],
|
|
runs: [],
|
|
});
|
|
});
|
|
});
|