mirror of https://github.com/garrytan/gstack.git
96 lines
3.6 KiB
TypeScript
96 lines
3.6 KiB
TypeScript
import { describe, test, expect, beforeEach } from 'bun:test';
|
|
import type { Page } from 'playwright';
|
|
import {
|
|
__testInternals,
|
|
undoModification,
|
|
} from '../src/cdp-inspector';
|
|
|
|
// Regression tests for the modificationHistory cap (D6 / smoking gun #2).
|
|
// Pre-cap, the module-scoped array grew unbounded across the session. Cap is
|
|
// 200 entries, oldest evicted on push past the cap. undoModification reports
|
|
// "evicted at the cap" in the error message so a user who asks for a
|
|
// no-longer-available index understands what happened (instead of seeing the
|
|
// pre-cap "No modification at index 500" with no context).
|
|
|
|
const { pushModification, MOD_HISTORY_CAP, getRawHistory, getTotalPushed, resetForTest } = __testInternals;
|
|
|
|
function fakeMod(id: number) {
|
|
return {
|
|
selector: `#node-${id}`,
|
|
property: 'color',
|
|
oldValue: 'red',
|
|
newValue: 'blue',
|
|
source: 'inline' as const,
|
|
timestamp: id,
|
|
method: 'setProperty' as 'setProperty',
|
|
};
|
|
}
|
|
|
|
beforeEach(() => {
|
|
resetForTest();
|
|
});
|
|
|
|
describe('modificationHistory cap', () => {
|
|
test('1. push under cap keeps every entry', () => {
|
|
for (let i = 0; i < 50; i++) pushModification(fakeMod(i));
|
|
expect(getRawHistory().length).toBe(50);
|
|
expect(getTotalPushed()).toBe(50);
|
|
expect(getRawHistory()[0].timestamp).toBe(0);
|
|
expect(getRawHistory()[49].timestamp).toBe(49);
|
|
});
|
|
|
|
test('2. push exactly cap keeps every entry', () => {
|
|
for (let i = 0; i < MOD_HISTORY_CAP; i++) pushModification(fakeMod(i));
|
|
expect(getRawHistory().length).toBe(MOD_HISTORY_CAP);
|
|
expect(getTotalPushed()).toBe(MOD_HISTORY_CAP);
|
|
expect(getRawHistory()[0].timestamp).toBe(0);
|
|
});
|
|
|
|
test('3. push past cap evicts oldest, keeps length at cap', () => {
|
|
const total = MOD_HISTORY_CAP + 50;
|
|
for (let i = 0; i < total; i++) pushModification(fakeMod(i));
|
|
expect(getRawHistory().length).toBe(MOD_HISTORY_CAP);
|
|
expect(getTotalPushed()).toBe(total);
|
|
// Oldest 50 dropped — entry that was #0 is gone; new oldest is #50.
|
|
expect(getRawHistory()[0].timestamp).toBe(50);
|
|
expect(getRawHistory()[MOD_HISTORY_CAP - 1].timestamp).toBe(total - 1);
|
|
});
|
|
|
|
test('4. resetForTest clears both buffer and totalPushed', () => {
|
|
for (let i = 0; i < 10; i++) pushModification(fakeMod(i));
|
|
resetForTest();
|
|
expect(getRawHistory().length).toBe(0);
|
|
expect(getTotalPushed()).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('undoModification eviction-aware error', () => {
|
|
// Stub Page: undoModification throws before any await when idx is out of
|
|
// range, so the stub never actually gets called.
|
|
const stubPage = {} as unknown as Page;
|
|
|
|
test('5. out-of-range BEFORE any eviction → no evicted note', async () => {
|
|
for (let i = 0; i < 5; i++) pushModification(fakeMod(i));
|
|
await expect(undoModification(stubPage, 99)).rejects.toThrow(
|
|
'No modification at index 99. History has 5 entries.',
|
|
);
|
|
});
|
|
|
|
test('6. out-of-range AFTER eviction → message names the evicted count', async () => {
|
|
const total = MOD_HISTORY_CAP + 73;
|
|
for (let i = 0; i < total; i++) pushModification(fakeMod(i));
|
|
// 273 pushed, 200 in buffer, 73 evicted. Ask for idx=400 (above buffer).
|
|
await expect(undoModification(stubPage, 400)).rejects.toThrow(
|
|
`No modification at index 400. History has ${MOD_HISTORY_CAP} entries ` +
|
|
`(most recent ${MOD_HISTORY_CAP} only — 73 earlier entries evicted at the cap).`,
|
|
);
|
|
});
|
|
|
|
test('7. negative explicit index throws cleanly (no NaN propagation)', async () => {
|
|
for (let i = 0; i < 10; i++) pushModification(fakeMod(i));
|
|
await expect(undoModification(stubPage, -1)).rejects.toThrow(
|
|
'No modification at index -1.',
|
|
);
|
|
});
|
|
});
|