takt/src/__tests__/loop-detector.test.ts
2026-02-10 07:07:40 +09:00

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);
});
});
});