import { describe, expect, it, vi } from 'vitest';
// SpriteAnimator пишет texture в Sprite — минимальный стаб.
function makeSprite() {
return { texture: null as unknown, destroyed: false } as unknown as import('pixi.js').Sprite;
}
function frames(n: number): import('pixi.js').Texture[] {
return Array.from({ length: n }, (_, i) => ({ idx: i }) as unknown as import('pixi.js').Texture);
}
import { SpriteAnimator } from '../SpriteAnimator';
describe('SpriteAnimator', () => {
it('конструктор без клипа: показывает стартовый шаг, время стоит', () => {
const sprite = makeSprite();
const a = new SpriteAnimator(sprite, { idle: { frames: frames(4) } }, undefined, { startStep: 2 });
expect(a.clip).toBe('idle');
expect(a.step).toBe(2);
expect(a.paused).toBe(true);
a.update(1);
expect(a.step).toBe(2); // заморожен
});
it('loop: шаги идут по fps, по кругу', () => {
const sprite = makeSprite();
const a = new SpriteAnimator(sprite, { walk: { frames: frames(3), fps: 10 } }, 'walk');
expect(a.step).toBe(0);
a.update(0.1); // 1 шаг при 10 fps
expect(a.step).toBe(1);
a.update(0.2); // ещё 2 — на конец
expect(a.step).toBe(0); // 3 % 3
});
it('тот же клип без restart продолжается; restart начинает с нуля', () => {
const sprite = makeSprite();
const a = new SpriteAnimator(sprite, { walk: { frames: frames(4), fps: 10 } }, 'walk');
a.update(0.15);
expect(a.step).toBe(1);
a.play('walk');
expect(a.step).toBe(1); // продолжение
a.play('walk', { restart: true });
expect(a.step).toBe(0);
});
it('once: доигрывает до последнего шага, paused, onFinish один раз', () => {
const sprite = makeSprite();
const finished: string[] = [];
const a = new SpriteAnimator(sprite, { hit: { frames: frames(3), fps: 10, loop: 'once' } }, 'hit');
a.onFinish = (c) => finished.push(c);
for (let i = 0; i < 10; i++) a.update(0.1); // 1 с — клип 0.3 с
expect(a.step).toBe(2);
expect(a.paused).toBe(true);
expect(finished).toEqual(['hit']);
});
it('большой dt проматывает промежуточные шаги (сообщается финальный)', () => {
const sprite = makeSprite();
const seen: number[] = [];
const a = new SpriteAnimator(sprite, { walk: { frames: frames(8), fps: 10 } }, 'walk');
a.onFrame = (s) => seen.push(s);
a.update(1); // 10 шагов за тик
expect(a.step).toBe(2); // 10 % 8
expect(seen).toEqual([2]); // промежуточные не долетели
});
it('randomPhase: одинаковый seed — одинаковая фаза (шаги совпадают)', () => {
const mk = (seed: number) =>
new SpriteAnimator(makeSprite(), { walk: { frames: frames(3) } }, undefined, { randomPhase: seed });
const a = mk(1234);
const b = mk(1234);
// Фаза приватна, но детерминизм виден через play + апдейты:
// time = phase, поэтому при одинаковых апдейтах шаги совпадают.
a.play('walk');
b.play('walk');
a.update(0.05);
b.update(0.05);
expect(a.step).toBe(b.step);
a.update(0.3);
b.update(0.3);
expect(a.step).toBe(b.step);
});
it('setClipFrames: кадры меняются на лету, шаг клэмпится', () => {
const sprite = makeSprite();
const a = new SpriteAnimator(sprite, { walk: { frames: frames(4), fps: 10 } }, 'walk');
a.update(0.3); // шаг 3
expect(a.step).toBe(3);
a.setClipFrames('walk', frames(2));
expect(a.step).toBe(1); // клэмп к 2-1
});
it('несуществующий клип — ошибка', () => {
const a = new SpriteAnimator(makeSprite(), { walk: { frames: frames(2) } });
expect(() => a.play('fly')).toThrow('fly');
});
it('destroyed спрайта пробрасывается (самозачистка из engine.fx)', () => {
const sprite = makeSprite();
const a = new SpriteAnimator(sprite, { walk: { frames: frames(2) } });
expect(a.destroyed).toBe(false);
sprite.destroyed = true;
expect(a.destroyed).toBe(true);
});
it('speedScale замедляет/ускоряет клип', () => {
const a = new SpriteAnimator(makeSprite(), { walk: { frames: frames(10), fps: 10 } }, 'walk', { speedScale: 0.5 });
a.update(0.1);
expect(a.step).toBe(0); // полшага — не дошёл
a.update(0.1);
expect(a.step).toBe(1);
});
it('пустой набор клипов допустим (без падений)', () => {
const a = new SpriteAnimator(makeSprite(), {});
expect(a.clip).toBeNull();
expect(() => a.update(0.1)).not.toThrow();
});
it('onFrame получает номер шага', () => {
const onFrame = vi.fn();
const a = new SpriteAnimator(makeSprite(), { walk: { frames: frames(2), fps: 10 } }, 'walk');
a.onFrame = onFrame;
a.update(0.1);
expect(onFrame).toHaveBeenCalledWith(1);
});
});