docs: enterprise refactor plan with ralph specs

This commit is contained in:
debian
2026-03-04 16:17:03 -05:00
parent 4c92712d20
commit f8191133c8
204 changed files with 32722 additions and 422 deletions

View File

@@ -0,0 +1,136 @@
import { StateGraph } from '../../src/core/StateGraph';
import { IState, IAction } from '../../src/core/interfaces';
function makeState(id: string, url: string, visitCount = 0): IState {
return {
id,
url,
title: `Page ${id}`,
timestamp: 1000,
domSnapshot: '<body></body>',
visitCount,
};
}
function makeAction(id: string, stateId: string): IAction {
return {
id,
type: 'click',
selector: '#btn',
timestamp: 2000,
seed: 42,
stateId,
};
}
describe('StateGraph', () => {
describe('addState', () => {
it('adds a new state', () => {
const graph = new StateGraph();
const state = makeState('s1', 'http://localhost/');
graph.addState(state);
expect(graph.hasState('s1')).toBe(true);
});
it('does not duplicate states with the same id', () => {
const graph = new StateGraph();
graph.addState(makeState('s1', 'http://localhost/'));
graph.addState(makeState('s1', 'http://localhost/'));
expect(graph.getAllStates()).toHaveLength(1);
});
it('increments visitCount when adding existing state id', () => {
const graph = new StateGraph();
graph.addState(makeState('s1', 'http://localhost/', 0));
graph.addState(makeState('s1', 'http://localhost/', 0));
const state = graph.getState('s1')!;
expect(state.visitCount).toBe(1);
});
});
describe('hasState', () => {
it('returns false for unknown state', () => {
const graph = new StateGraph();
expect(graph.hasState('nonexistent')).toBe(false);
});
});
describe('recordTransition', () => {
it('records a transition between states', () => {
const graph = new StateGraph();
graph.addState(makeState('s1', '/'));
graph.addState(makeState('s2', '/about'));
const action = makeAction('a1', 's1');
graph.recordTransition('s1', action, 's2');
const transitions = graph.getTransitions();
expect(transitions).toHaveLength(1);
expect(transitions[0].fromId).toBe('s1');
expect(transitions[0].toId).toBe('s2');
expect(transitions[0].action.id).toBe('a1');
});
it('records multiple transitions', () => {
const graph = new StateGraph();
graph.addState(makeState('s1', '/'));
graph.addState(makeState('s2', '/a'));
graph.addState(makeState('s3', '/b'));
graph.recordTransition('s1', makeAction('a1', 's1'), 's2');
graph.recordTransition('s1', makeAction('a2', 's1'), 's3');
expect(graph.getTransitions()).toHaveLength(2);
});
});
describe('getUnvisited', () => {
it('returns states with visitCount === 0', () => {
const graph = new StateGraph();
graph.addState(makeState('s1', '/', 0));
graph.addState(makeState('s2', '/a', 1));
graph.addState(makeState('s3', '/b', 0));
const unvisited = graph.getUnvisited();
expect(unvisited.map((s) => s.id)).toEqual(['s1', 's3']);
});
it('returns empty when all states visited', () => {
const graph = new StateGraph();
graph.addState(makeState('s1', '/', 1));
expect(graph.getUnvisited()).toHaveLength(0);
});
});
describe('getNextToExplore', () => {
it('returns oldest unvisited state (BFS order)', () => {
const graph = new StateGraph();
graph.addState(makeState('s1', '/'));
graph.addState(makeState('s2', '/a'));
const next = graph.getNextToExplore();
expect(next?.id).toBe('s1');
});
it('returns null when no unvisited states remain', () => {
const graph = new StateGraph();
graph.addState(makeState('s1', '/', 1));
expect(graph.getNextToExplore()).toBeNull();
});
it('skips visited states', () => {
const graph = new StateGraph();
graph.addState(makeState('s1', '/'));
graph.addState(makeState('s2', '/a'));
graph.incrementVisit('s1');
const next = graph.getNextToExplore();
expect(next?.id).toBe('s2');
});
});
describe('toJSON', () => {
it('produces a serializable object', () => {
const graph = new StateGraph();
graph.addState(makeState('s1', '/'));
graph.recordTransition('s1', makeAction('a1', 's1'), 's1');
const json = graph.toJSON() as any;
expect(json.stateCount).toBe(1);
expect(json.transitionCount).toBe(1);
expect(JSON.parse(JSON.stringify(json))).toEqual(json);
});
});
});