docs: enterprise refactor plan with ralph specs
This commit is contained in:
136
tests/core/StateGraph.test.ts
Normal file
136
tests/core/StateGraph.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user