test(ui): assert #2903 records through the real snapshot builder
Replace the fake enabled-gate helper's store fixture with buildSessionMessageRecordsSnapshot so the regression covers the same record shape ChatContainer renders. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
This commit is contained in:
committed by
Cursor Agent
co-authored by
Serhii Dziupin
parent
903638db94
commit
0a07bc7e03
+40
-22
@@ -21,6 +21,8 @@ import { fileURLToPath } from 'node:url';
|
|||||||
import type { Message, Part } from '@opencode-ai/sdk/v2/client';
|
import type { Message, Part } from '@opencode-ai/sdk/v2/client';
|
||||||
|
|
||||||
import { getSessionMaterializationStatus, materializeSessionSnapshots } from '@/sync/materialization';
|
import { getSessionMaterializationStatus, materializeSessionSnapshots } from '@/sync/materialization';
|
||||||
|
import { buildSessionMessageRecordsSnapshot } from '@/sync/sync-context';
|
||||||
|
import { INITIAL_STATE } from '@/sync/types';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const appSource = readFileSync(join(__dirname, '..', '..', '..', 'App.tsx'), 'utf-8');
|
const appSource = readFileSync(join(__dirname, '..', '..', '..', 'App.tsx'), 'utf-8');
|
||||||
@@ -63,47 +65,66 @@ const buildFourteenMessageSnapshot = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mirrors the cold-start branch of useSessionMessageRecords when
|
* Cold-start `useSessionMessageRecords` when `enabled === false` and no prior
|
||||||
* `options.enabled === false` and no prior snapshot exists for the session.
|
* snapshot exists for the session: getSnapshot returns EMPTY records even
|
||||||
|
* though the store already holds a renderable transcript.
|
||||||
*/
|
*/
|
||||||
const readRecordsThroughEnabledGate = (
|
const readRecordsThroughEnabledGate = (
|
||||||
storeMessages: Message[] | undefined,
|
storeMessages: ReturnType<typeof buildSessionMessageRecordsSnapshot>['list'],
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
): Message[] => {
|
) => {
|
||||||
if (enabled === false) {
|
if (enabled === false) {
|
||||||
// Cold iframe: snapshotRef is empty / wrong session → EMPTY records.
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return storeMessages ?? [];
|
return storeMessages;
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('issue #2903 busy embedded subagent status-line-only', () => {
|
describe('issue #2903 busy embedded subagent status-line-only', () => {
|
||||||
test('materialized 14-message subagent is renderable', () => {
|
test('materialized 14-message subagent is renderable and snapshottable', () => {
|
||||||
const snapshot = buildFourteenMessageSnapshot();
|
const materialized = buildFourteenMessageSnapshot();
|
||||||
expect(snapshot.message[SESSION_ID]).toHaveLength(14);
|
expect(materialized.message[SESSION_ID]).toHaveLength(14);
|
||||||
expect(getSessionMaterializationStatus(snapshot, SESSION_ID)).toEqual({
|
expect(getSessionMaterializationStatus(materialized, SESSION_ID)).toEqual({
|
||||||
hasMessages: true,
|
hasMessages: true,
|
||||||
renderable: true,
|
renderable: true,
|
||||||
missingPartMessageIDs: [],
|
missingPartMessageIDs: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const records = buildSessionMessageRecordsSnapshot(
|
||||||
|
{ ...INITIAL_STATE, message: materialized.message, part: materialized.part },
|
||||||
|
SESSION_ID,
|
||||||
|
);
|
||||||
|
expect(records.list).toHaveLength(14);
|
||||||
|
expect(records.list.map((record) => record.info.id)).toEqual(
|
||||||
|
materialized.message[SESSION_ID].map((message) => message.id),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('inactive enabled:false hides a fully-renderable session (0 records)', () => {
|
test('inactive enabled:false hides a fully-renderable session (0 records)', () => {
|
||||||
const snapshot = buildFourteenMessageSnapshot();
|
const materialized = buildFourteenMessageSnapshot();
|
||||||
expect(getSessionMaterializationStatus(snapshot, SESSION_ID).renderable).toBe(true);
|
const records = buildSessionMessageRecordsSnapshot(
|
||||||
expect(readRecordsThroughEnabledGate(snapshot.message[SESSION_ID], false)).toHaveLength(0);
|
{ ...INITIAL_STATE, message: materialized.message, part: materialized.part },
|
||||||
|
SESSION_ID,
|
||||||
|
);
|
||||||
|
expect(getSessionMaterializationStatus(materialized, SESSION_ID).renderable).toBe(true);
|
||||||
|
expect(records.list).toHaveLength(14);
|
||||||
|
expect(readRecordsThroughEnabledGate(records.list, false)).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('enabled:true reveals all 14 materialized records', () => {
|
test('enabled:true reveals all 14 materialized records', () => {
|
||||||
const snapshot = buildFourteenMessageSnapshot();
|
const materialized = buildFourteenMessageSnapshot();
|
||||||
expect(readRecordsThroughEnabledGate(snapshot.message[SESSION_ID], true)).toHaveLength(14);
|
const records = buildSessionMessageRecordsSnapshot(
|
||||||
|
{ ...INITIAL_STATE, message: materialized.message, part: materialized.part },
|
||||||
|
SESSION_ID,
|
||||||
|
);
|
||||||
|
expect(readRecordsThroughEnabledGate(records.list, true)).toHaveLength(14);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('sync gate still returns empty on cold disabled reads', () => {
|
test('sync gate still returns empty on cold disabled reads', () => {
|
||||||
// Mutation check: the real hook still has the enabled===false early return
|
const hookStart = syncContextSource.indexOf('export function useSessionMessageRecords(');
|
||||||
// that produced the bug when ChatContainer passed enabled: active.
|
const hookBody = syncContextSource.slice(hookStart, hookStart + 1800);
|
||||||
expect(syncContextSource).toContain('if (options?.enabled === false)');
|
expect(hookBody).toContain('if (options?.enabled === false)');
|
||||||
expect(syncContextSource).toContain('EMPTY_SESSION_MESSAGE_RECORDS');
|
expect(hookBody).toContain('EMPTY_SESSION_MESSAGE_RECORDS');
|
||||||
|
expect(hookBody).toContain('snapshotRef.current.sessionID === sessionID ? snapshotRef.current.list');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('embedded session-chat keeps message history enabled while visibility gates active', () => {
|
test('embedded session-chat keeps message history enabled while visibility gates active', () => {
|
||||||
@@ -118,9 +139,6 @@ describe('issue #2903 busy embedded subagent status-line-only', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('empty+busy branch skips empty state so StatusRowContainer can stand alone', () => {
|
test('empty+busy branch skips empty state so StatusRowContainer can stand alone', () => {
|
||||||
// Busy + zero messages skips ChatEmptyState and falls through to the full
|
|
||||||
// ChatViewport, whose transcript always includes StatusRowContainer — the
|
|
||||||
// "one status line, no history" symptom when records stay empty.
|
|
||||||
expect(chatContainerSource).toContain('if (sessionMessages.length === 0 && !sessionIsWorking)');
|
expect(chatContainerSource).toContain('if (sessionMessages.length === 0 && !sessionIsWorking)');
|
||||||
expect(chatContainerSource).toContain('<ChatEmptyState');
|
expect(chatContainerSource).toContain('<ChatEmptyState');
|
||||||
expect(chatContainerSource).toContain('<StatusRowContainer />');
|
expect(chatContainerSource).toContain('<StatusRowContainer />');
|
||||||
|
|||||||
Reference in New Issue
Block a user