298 lines
8.8 KiB
TypeScript
298 lines
8.8 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { EffectsManager } from '../../../src/card-controller/effects/effects-manager';
|
|
import type { EffectComponent } from '../../../src/card-controller/effects/types';
|
|
import type { EffectName } from '../../../src/types';
|
|
import { flushPromises } from '../../test-utils';
|
|
|
|
// The source constructs each effect component with `new`, and a mock
|
|
// implementation must be callable with `new`, so it cannot be an arrow
|
|
// function.
|
|
vi.mock('../../../src/components/effects/fireworks', () => ({
|
|
AdvancedCameraCardEffectFireworks: vi.fn(function () {
|
|
return createMockEffectComponent();
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../../../src/components/effects/check', () => ({
|
|
AdvancedCameraCardEffectCheck: vi.fn(function () {
|
|
return createMockEffectComponent();
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../../../src/components/effects/ghost', () => ({
|
|
AdvancedCameraCardEffectGhost: vi.fn(function () {
|
|
return createMockEffectComponent();
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../../../src/components/effects/hearts', () => ({
|
|
AdvancedCameraCardEffectHearts: vi.fn(function () {
|
|
return createMockEffectComponent();
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../../../src/components/effects/shamrocks', () => ({
|
|
AdvancedCameraCardEffectShamrocks: vi.fn(function () {
|
|
return createMockEffectComponent();
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../../../src/components/effects/snow', () => ({
|
|
AdvancedCameraCardEffectSnow: vi.fn(function () {
|
|
return createMockEffectComponent();
|
|
}),
|
|
}));
|
|
|
|
const createMockEffectComponent = (): EffectComponent => {
|
|
const element = document.createElement('div') as unknown as EffectComponent;
|
|
element.fadeIn = true;
|
|
element.startFadeOut = vi.fn().mockResolvedValue(undefined);
|
|
return element;
|
|
};
|
|
|
|
// @vitest-environment jsdom
|
|
describe('EffectsManager', () => {
|
|
let container: HTMLElement;
|
|
let manager: EffectsManager;
|
|
|
|
beforeEach(() => {
|
|
container = document.createElement('div');
|
|
manager = new EffectsManager();
|
|
manager.setContainer(container);
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
describe('startEffect', () => {
|
|
it('should load and start snow effect', async () => {
|
|
expect(container.children.length).toBe(0);
|
|
|
|
await manager.startEffect('snow');
|
|
|
|
expect(container.children.length).toBe(1);
|
|
});
|
|
|
|
it('should load and start hearts effect', async () => {
|
|
expect(container.children.length).toBe(0);
|
|
|
|
await manager.startEffect('hearts');
|
|
|
|
expect(container.children.length).toBe(1);
|
|
});
|
|
|
|
it('should load and start shamrocks effect', async () => {
|
|
expect(container.children.length).toBe(0);
|
|
|
|
await manager.startEffect('shamrocks');
|
|
|
|
expect(container.children.length).toBe(1);
|
|
});
|
|
|
|
it('should load and start fireworks effect', async () => {
|
|
expect(container.children.length).toBe(0);
|
|
|
|
await manager.startEffect('fireworks');
|
|
|
|
expect(container.children.length).toBe(1);
|
|
});
|
|
|
|
it('should load and start ghost effect', async () => {
|
|
expect(container.children.length).toBe(0);
|
|
|
|
await manager.startEffect('ghost');
|
|
|
|
expect(container.children.length).toBe(1);
|
|
});
|
|
|
|
it('should load and start check effect', async () => {
|
|
expect(container.children.length).toBe(0);
|
|
|
|
await manager.startEffect('check');
|
|
|
|
expect(container.children.length).toBe(1);
|
|
});
|
|
|
|
it('should stop effect automatically after duration', async () => {
|
|
const effectPromise = manager.startEffect('snow', { duration: 1 });
|
|
|
|
// Wait for the effect to be added to the DOM.
|
|
await flushPromises();
|
|
|
|
expect(container.children.length).toBe(1);
|
|
|
|
vi.advanceTimersByTime(1000);
|
|
await effectPromise;
|
|
|
|
expect(container.children.length).toBe(0);
|
|
});
|
|
|
|
it('should clean up timer when stopped manually', async () => {
|
|
// Don't await startEffect as it waits for the duration
|
|
manager.startEffect('snow', { duration: 10 });
|
|
|
|
// Wait for the effect to be added to the DOM.
|
|
await flushPromises();
|
|
|
|
await manager.stopEffect('snow');
|
|
|
|
expect(container.children.length).toBe(0);
|
|
});
|
|
|
|
it('should set fadeIn to true by default', async () => {
|
|
await manager.startEffect('snow');
|
|
|
|
const effectElement = container.children[0] as EffectComponent;
|
|
expect(effectElement.fadeIn).toBe(true);
|
|
});
|
|
|
|
it('should set fadeIn from options', async () => {
|
|
await manager.startEffect('snow', { fadeIn: false });
|
|
|
|
const effectElement = container.children[0] as EffectComponent;
|
|
expect(effectElement.fadeIn).toBe(false);
|
|
});
|
|
|
|
it('should queue effects until a container is available', async () => {
|
|
manager.removeContainer();
|
|
|
|
await manager.startEffect('snow');
|
|
|
|
expect(container.children.length).toBe(0);
|
|
|
|
manager.setContainer(container);
|
|
await flushPromises();
|
|
|
|
expect(container.children.length).toBe(1);
|
|
});
|
|
|
|
it('should not start effect if already active', async () => {
|
|
await manager.startEffect('snow');
|
|
await manager.startEffect('snow');
|
|
|
|
// Should only have one child since the second call is ignored.
|
|
expect(container.children.length).toBe(1);
|
|
});
|
|
|
|
it('should allow starting different effects simultaneously', async () => {
|
|
await manager.startEffect('snow');
|
|
await manager.startEffect('hearts');
|
|
|
|
expect(container.children.length).toBe(2);
|
|
});
|
|
|
|
it('should return early if effect module cannot be loaded', async () => {
|
|
// Use an effect name that doesn't exist in the registry
|
|
await manager.startEffect('unknown' as EffectName);
|
|
|
|
expect(container.children.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('stopEffect', () => {
|
|
it('should stop an active effect', async () => {
|
|
await manager.startEffect('snow');
|
|
const effectElement = container.children[0] as EffectComponent;
|
|
|
|
await manager.stopEffect('snow');
|
|
|
|
expect(effectElement.startFadeOut).toHaveBeenCalled();
|
|
expect(container.children.length).toBe(0);
|
|
});
|
|
|
|
it('should do nothing if effect is not active', async () => {
|
|
expect(container.children.length).toBe(0);
|
|
|
|
await manager.stopEffect('snow');
|
|
|
|
expect(container.children.length).toBe(0);
|
|
});
|
|
|
|
it('should allow starting effect again after stopping', async () => {
|
|
await manager.startEffect('snow');
|
|
await manager.stopEffect('snow');
|
|
await manager.startEffect('snow');
|
|
|
|
expect(container.children.length).toBe(1);
|
|
});
|
|
|
|
it('should cancel effect when stopped during loading', async () => {
|
|
// Start the effect but don't await it - simulates the loading phase.
|
|
const startPromise = manager.startEffect('snow');
|
|
|
|
// Stop the effect immediately while it's still loading.
|
|
await manager.stopEffect('snow');
|
|
|
|
// Wait for start to complete.
|
|
await startPromise;
|
|
|
|
// The effect should not appear since it was stopped during loading.
|
|
expect(container.children.length).toBe(0);
|
|
});
|
|
|
|
it('should cancel queued effects', async () => {
|
|
manager.removeContainer();
|
|
await manager.startEffect('snow');
|
|
await manager.stopEffect('snow');
|
|
|
|
manager.setContainer(container);
|
|
await flushPromises();
|
|
|
|
expect(container.children.length).toBe(0);
|
|
});
|
|
|
|
it('should clear active effects and timers when container is removed', async () => {
|
|
manager.startEffect('snow', { duration: 10 });
|
|
await flushPromises();
|
|
|
|
expect(container.children.length).toBe(1);
|
|
|
|
manager.removeContainer();
|
|
|
|
expect(container.children.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('toggleEffect', () => {
|
|
it('should start effect when not active', async () => {
|
|
await manager.toggleEffect('snow');
|
|
|
|
expect(container.children.length).toBe(1);
|
|
});
|
|
|
|
it('should stop effect when active', async () => {
|
|
await manager.startEffect('snow');
|
|
const effectElement = container.children[0] as EffectComponent;
|
|
|
|
await manager.toggleEffect('snow');
|
|
|
|
expect(effectElement.startFadeOut).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should pass options when starting effect', async () => {
|
|
await manager.toggleEffect('snow', { fadeIn: false });
|
|
|
|
const effectElement = container.children[0] as EffectComponent;
|
|
expect(effectElement.fadeIn).toBe(false);
|
|
});
|
|
|
|
it('should cancel effect when toggled during loading', async () => {
|
|
// Start the effect but don't await it - simulates the loading phase.
|
|
const startPromise = manager.startEffect('snow');
|
|
|
|
// Toggle the effect immediately while it's still loading.
|
|
await manager.toggleEffect('snow');
|
|
|
|
// Wait for start to complete.
|
|
await startPromise;
|
|
|
|
// The effect should not appear since it was toggled (stopped) during loading.
|
|
expect(container.children.length).toBe(0);
|
|
});
|
|
});
|
|
});
|