mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-04 05:40:58 +00:00
* fix(acp-bridge): preserve original timestamp when replaying session history History replay re-emits each persisted record with its original epoch-ms time nested in update._meta, but BridgeClient.sessionUpdate published the frame without lifting it to the envelope. EventBus.publish then stamped envelope _meta.serverTimestamp with publish-time Date.now(), which the client's extractServerTimestamp picks up at higher priority than the nested original — so a resumed session rendered every historical message at the resume moment instead of when it was sent. Lift update._meta.timestamp (or serverTimestamp) to the envelope serverTimestamp so EventBus preserves it. Live updates without such a timestamp keep the Date.now() fallback unchanged. * feat(web-shell): show each history message's time on hover Carry each transcript block's wall-clock time (serverTimestamp ?? clientReceivedAt) onto every message and reveal it as a CSS-only hover tooltip in the message list. Same-day messages show HH:mm:ss; older ones show yyyy-MM-dd HH:mm:ss (local time, zero-padded).
82 lines
2.9 KiB
TypeScript
82 lines
2.9 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { act, type ReactNode } from 'react';
|
|
import { createRoot, type Root } from 'react-dom/client';
|
|
import { MessageTimestamp, formatTimestamp } from './MessageTimestamp';
|
|
|
|
(
|
|
globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
|
).IS_REACT_ACT_ENVIRONMENT = true;
|
|
|
|
const mounted: Array<{ root: Root; container: HTMLElement }> = [];
|
|
|
|
afterEach(() => {
|
|
for (const { root, container } of mounted.splice(0)) {
|
|
act(() => root.unmount());
|
|
container.remove();
|
|
}
|
|
});
|
|
|
|
function render(node: ReactNode): HTMLElement {
|
|
const container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
const root = createRoot(container);
|
|
act(() => root.render(node));
|
|
mounted.push({ root, container });
|
|
return container;
|
|
}
|
|
|
|
describe('formatTimestamp', () => {
|
|
// Built from local-time parts so expectations are timezone independent
|
|
// (month is 0-based: 5 = June).
|
|
const now = new Date(2026, 5, 13, 12, 0, 0);
|
|
|
|
it('shows only HH:mm:ss for a same-day timestamp', () => {
|
|
const ts = new Date(2026, 5, 13, 9, 8, 7).getTime();
|
|
expect(formatTimestamp(ts, now)).toBe('09:08:07');
|
|
});
|
|
|
|
it('shows full yyyy-MM-dd HH:mm:ss for an earlier day in the same year', () => {
|
|
const ts = new Date(2026, 0, 2, 9, 8, 7).getTime();
|
|
expect(formatTimestamp(ts, now)).toBe('2026-01-02 09:08:07');
|
|
});
|
|
|
|
it('shows full yyyy-MM-dd HH:mm:ss for a previous year', () => {
|
|
// Same month/day as `now` but last year — must not be read as "today".
|
|
const ts = new Date(2025, 5, 13, 9, 8, 7).getTime();
|
|
expect(formatTimestamp(ts, now)).toBe('2025-06-13 09:08:07');
|
|
});
|
|
});
|
|
|
|
describe('MessageTimestamp', () => {
|
|
it('reveals the wall-clock time as a hover tooltip when a timestamp is set', () => {
|
|
const ts = new Date(2026, 5, 13, 9, 8, 7).getTime();
|
|
const container = render(
|
|
<MessageTimestamp timestamp={ts}>
|
|
<div>body</div>
|
|
</MessageTimestamp>,
|
|
);
|
|
|
|
const tip = container.querySelector('span[aria-hidden="true"]');
|
|
expect(tip).not.toBeNull();
|
|
// Every variant ends in HH:mm:ss; the leading parts depend on the real
|
|
// "now", so assert the shape rather than an exact string here.
|
|
expect(tip?.textContent).toMatch(/\d{2}:\d{2}:\d{2}$/);
|
|
expect(container.textContent).toContain('body');
|
|
});
|
|
|
|
it('renders children unchanged with no tooltip when timestamp is undefined', () => {
|
|
const container = render(
|
|
<MessageTimestamp>
|
|
<div data-testid="child">body</div>
|
|
</MessageTimestamp>,
|
|
);
|
|
|
|
expect(container.querySelector('span[aria-hidden="true"]')).toBeNull();
|
|
// No wrapper element is introduced: the child stays a direct child of the
|
|
// mount container, so message spacing/structure is untouched.
|
|
const child = container.querySelector('[data-testid="child"]');
|
|
expect(child).not.toBeNull();
|
|
expect(child?.parentElement).toBe(container);
|
|
});
|
|
});
|