86 lines
3.5 KiB
TypeScript
86 lines
3.5 KiB
TypeScript
import { beforeEach, describe, expect, test } from 'bun:test';
|
|
|
|
import { MessageFreshnessDetector } from './messageFreshness';
|
|
|
|
import type { Message } from '@opencode-ai/sdk/v2';
|
|
|
|
const makeAssistantMessage = (id: string, created: number): Message =>
|
|
({
|
|
id,
|
|
role: 'assistant',
|
|
sessionID: 'session-a',
|
|
time: { created },
|
|
}) as unknown as Message;
|
|
|
|
describe('MessageFreshnessDetector.shouldAnimateMessage', () => {
|
|
let detector: MessageFreshnessDetector;
|
|
|
|
beforeEach(() => {
|
|
detector = MessageFreshnessDetector.getInstance();
|
|
detector.clearAll();
|
|
});
|
|
|
|
test('fresh message animates once and is recorded as seen', () => {
|
|
detector.recordSessionStart('session-a');
|
|
const message = makeAssistantMessage('msg-fresh', Date.now());
|
|
|
|
expect(detector.shouldAnimateMessage(message, 'session-a')).toBe(true);
|
|
expect(detector.hasBeenAnimated('msg-fresh')).toBe(true);
|
|
});
|
|
|
|
test('regression #2124: fresh message does not re-animate when returning to the session', () => {
|
|
detector.recordSessionStart('session-a');
|
|
const message = makeAssistantMessage('msg-fresh', Date.now());
|
|
|
|
// First visit: the message is fresh and animates.
|
|
expect(detector.shouldAnimateMessage(message, 'session-a')).toBe(true);
|
|
|
|
// User switches away and back; ChatViewport remounts and re-evaluates
|
|
// before recordSessionStart runs again, so the old session start time
|
|
// is still in effect. The message must not animate a second time.
|
|
expect(detector.shouldAnimateMessage(message, 'session-a')).toBe(false);
|
|
});
|
|
|
|
test('stale history message never animates and is recorded as seen', () => {
|
|
detector.recordSessionStart('session-a');
|
|
const message = makeAssistantMessage('msg-old', Date.now() - 60_000);
|
|
|
|
expect(detector.shouldAnimateMessage(message, 'session-a')).toBe(false);
|
|
expect(detector.hasBeenAnimated('msg-old')).toBe(true);
|
|
expect(detector.shouldAnimateMessage(message, 'session-a')).toBe(false);
|
|
});
|
|
|
|
test('message evaluated without a recorded session start does not animate and is recorded', () => {
|
|
const message = makeAssistantMessage('msg-no-session', Date.now());
|
|
|
|
expect(detector.shouldAnimateMessage(message, 'session-a')).toBe(false);
|
|
expect(detector.hasBeenAnimated('msg-no-session')).toBe(true);
|
|
|
|
// Recording the session start afterwards must not resurrect the animation.
|
|
detector.recordSessionStart('session-a');
|
|
expect(detector.shouldAnimateMessage(message, 'session-a')).toBe(false);
|
|
});
|
|
|
|
test('non-assistant messages never animate', () => {
|
|
detector.recordSessionStart('session-a');
|
|
const message = {
|
|
id: 'msg-user',
|
|
role: 'user',
|
|
sessionID: 'session-a',
|
|
time: { created: Date.now() },
|
|
} as unknown as Message;
|
|
|
|
expect(detector.shouldAnimateMessage(message, 'session-a')).toBe(false);
|
|
});
|
|
|
|
test('a new fresh message still animates after older fresh messages were seen', () => {
|
|
detector.recordSessionStart('session-a');
|
|
const first = makeAssistantMessage('msg-first', Date.now());
|
|
const second = makeAssistantMessage('msg-second', Date.now());
|
|
|
|
expect(detector.shouldAnimateMessage(first, 'session-a')).toBe(true);
|
|
expect(detector.shouldAnimateMessage(second, 'session-a')).toBe(true);
|
|
expect(detector.shouldAnimateMessage(second, 'session-a')).toBe(false);
|
|
});
|
|
});
|