121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
/**
|
|
* Unit tests for LoopDetector
|
|
*
|
|
* Tests consecutive same-movement detection and configurable actions.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { LoopDetector } from '../core/piece/engine/loop-detector.js';
|
|
|
|
describe('LoopDetector', () => {
|
|
describe('with default config', () => {
|
|
let detector: LoopDetector;
|
|
|
|
beforeEach(() => {
|
|
detector = new LoopDetector();
|
|
});
|
|
|
|
it('should not detect loop for different movements', () => {
|
|
const result1 = detector.check('step-a');
|
|
const result2 = detector.check('step-b');
|
|
const result3 = detector.check('step-a');
|
|
expect(result1.isLoop).toBe(false);
|
|
expect(result2.isLoop).toBe(false);
|
|
expect(result3.isLoop).toBe(false);
|
|
});
|
|
|
|
it('should not detect loop below threshold (10 consecutive)', () => {
|
|
for (let i = 0; i < 10; i++) {
|
|
const result = detector.check('step-a');
|
|
expect(result.isLoop).toBe(false);
|
|
}
|
|
});
|
|
|
|
it('should detect loop at 11th consecutive execution (default threshold 10)', () => {
|
|
for (let i = 0; i < 10; i++) {
|
|
detector.check('step-a');
|
|
}
|
|
const result = detector.check('step-a');
|
|
expect(result.isLoop).toBe(true);
|
|
expect(result.count).toBe(11);
|
|
expect(result.shouldWarn).toBe(true);
|
|
expect(result.shouldAbort).toBe(false);
|
|
});
|
|
|
|
it('should reset consecutive count when movement changes', () => {
|
|
for (let i = 0; i < 8; i++) {
|
|
detector.check('step-a');
|
|
}
|
|
detector.check('step-b');
|
|
const result = detector.check('step-a');
|
|
expect(result.isLoop).toBe(false);
|
|
expect(result.count).toBe(1);
|
|
});
|
|
|
|
it('should track consecutive count correctly', () => {
|
|
detector.check('step-a');
|
|
expect(detector.getConsecutiveCount()).toBe(1);
|
|
detector.check('step-a');
|
|
expect(detector.getConsecutiveCount()).toBe(2);
|
|
detector.check('step-b');
|
|
expect(detector.getConsecutiveCount()).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('with abort action', () => {
|
|
it('should set shouldAbort when action is abort', () => {
|
|
const detector = new LoopDetector({ maxConsecutiveSameStep: 3, action: 'abort' });
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
detector.check('step-a');
|
|
}
|
|
const result = detector.check('step-a');
|
|
expect(result.isLoop).toBe(true);
|
|
expect(result.shouldAbort).toBe(true);
|
|
expect(result.shouldWarn).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('with ignore action', () => {
|
|
it('should not warn or abort when action is ignore', () => {
|
|
const detector = new LoopDetector({ maxConsecutiveSameStep: 3, action: 'ignore' });
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
detector.check('step-a');
|
|
}
|
|
const result = detector.check('step-a');
|
|
expect(result.isLoop).toBe(true);
|
|
expect(result.shouldAbort).toBe(false);
|
|
expect(result.shouldWarn).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('with custom threshold', () => {
|
|
it('should detect loop at custom threshold + 1', () => {
|
|
const detector = new LoopDetector({ maxConsecutiveSameStep: 2 });
|
|
|
|
detector.check('step-a');
|
|
detector.check('step-a');
|
|
const result = detector.check('step-a');
|
|
expect(result.isLoop).toBe(true);
|
|
expect(result.count).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe('reset', () => {
|
|
it('should clear all state', () => {
|
|
const detector = new LoopDetector({ maxConsecutiveSameStep: 2 });
|
|
|
|
detector.check('step-a');
|
|
detector.check('step-a');
|
|
detector.reset();
|
|
|
|
expect(detector.getConsecutiveCount()).toBe(0);
|
|
|
|
const result = detector.check('step-a');
|
|
expect(result.isLoop).toBe(false);
|
|
expect(result.count).toBe(1);
|
|
});
|
|
});
|
|
});
|