81 lines
3.5 KiB
TypeScript
81 lines
3.5 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import { sortByTimestampStable, type LogEntry } from '../src/lib/utils/log-entry';
|
|
|
|
function entry(partial: Partial<LogEntry> & { id: number }): LogEntry {
|
|
return { text: `line-${partial.id}`, ...partial };
|
|
}
|
|
|
|
describe('sortByTimestampStable', () => {
|
|
test('interleaves two containers by ISO timestamp, oldest first', () => {
|
|
const input: LogEntry[] = [
|
|
entry({ id: 0, containerId: 'A', timestamp: '2026-08-13T10:00:03Z' }),
|
|
entry({ id: 1, containerId: 'A', timestamp: '2026-08-13T10:00:05Z' }),
|
|
entry({ id: 2, containerId: 'B', timestamp: '2026-08-13T10:00:01Z' }),
|
|
entry({ id: 3, containerId: 'B', timestamp: '2026-08-13T10:00:04Z' })
|
|
];
|
|
const out = sortByTimestampStable(input);
|
|
expect(out.map((e) => e.timestamp)).toEqual([
|
|
'2026-08-13T10:00:01Z',
|
|
'2026-08-13T10:00:03Z',
|
|
'2026-08-13T10:00:04Z',
|
|
'2026-08-13T10:00:05Z'
|
|
]);
|
|
});
|
|
|
|
test('is order-independent: same result whichever container arrives first', () => {
|
|
const a: LogEntry[] = [
|
|
entry({ id: 10, containerId: 'A', timestamp: '2026-08-13T10:00:03Z' }),
|
|
entry({ id: 11, containerId: 'B', timestamp: '2026-08-13T10:00:01Z' })
|
|
];
|
|
// same logical lines, reversed arrival order (ids reflect arrival)
|
|
const b: LogEntry[] = [
|
|
entry({ id: 20, containerId: 'B', timestamp: '2026-08-13T10:00:01Z' }),
|
|
entry({ id: 21, containerId: 'A', timestamp: '2026-08-13T10:00:03Z' })
|
|
];
|
|
expect(sortByTimestampStable(a).map((e) => e.timestamp))
|
|
.toEqual(sortByTimestampStable(b).map((e) => e.timestamp));
|
|
// the reported bug: clicking cycles order — this proves the sort is deterministic
|
|
expect(sortByTimestampStable(a).map((e) => e.containerId)).toEqual(['B', 'A']);
|
|
expect(sortByTimestampStable(b).map((e) => e.containerId)).toEqual(['B', 'A']);
|
|
});
|
|
|
|
test('continuation line (no timestamp) stays glued under its parent from the same container', () => {
|
|
const input: LogEntry[] = [
|
|
entry({ id: 0, containerId: 'A', timestamp: '2026-08-13T10:00:05Z', text: 'stacktrace header' }),
|
|
entry({ id: 1, containerId: 'A', text: ' at frame 1' }), // continuation, no ts
|
|
entry({ id: 2, containerId: 'A', text: ' at frame 2' }), // continuation, no ts
|
|
entry({ id: 3, containerId: 'B', timestamp: '2026-08-13T10:00:02Z', text: 'earlier B line' })
|
|
];
|
|
const out = sortByTimestampStable(input);
|
|
// B (10:00:02) sorts before A's block; the two continuation lines inherit
|
|
// A's 10:00:05 and remain directly under the header, in id order.
|
|
expect(out.map((e) => e.text)).toEqual([
|
|
'earlier B line',
|
|
'stacktrace header',
|
|
' at frame 1',
|
|
' at frame 2'
|
|
]);
|
|
});
|
|
|
|
test('equal timestamps fall back to id (stable, arrival order preserved)', () => {
|
|
const ts = '2026-08-13T10:00:00Z';
|
|
const input: LogEntry[] = [
|
|
entry({ id: 5, containerId: 'A', timestamp: ts }),
|
|
entry({ id: 2, containerId: 'B', timestamp: ts }),
|
|
entry({ id: 9, containerId: 'A', timestamp: ts })
|
|
];
|
|
expect(sortByTimestampStable(input).map((e) => e.id)).toEqual([2, 5, 9]);
|
|
});
|
|
|
|
test('entries with no timestamp at all keep arrival order (id) and sort before timestamped ones', () => {
|
|
const input: LogEntry[] = [
|
|
entry({ id: 3, containerId: 'A', timestamp: '2026-08-13T10:00:01Z' }),
|
|
entry({ id: 1, containerId: 'C', text: 'no ts at all' }),
|
|
entry({ id: 2, containerId: 'D', text: 'also no ts' })
|
|
];
|
|
const out = sortByTimestampStable(input);
|
|
// '' (no ts) < any ISO string, and id breaks the '' vs '' tie
|
|
expect(out.map((e) => e.id)).toEqual([1, 2, 3]);
|
|
});
|
|
});
|