Files
ComfyUI-Lora-Manager/tests/frontend/components/modelCard.videoQueue.test.js
2025-10-06 07:45:51 +08:00

127 lines
4.2 KiB
JavaScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// Ensure globals are defined before importing module under test
const ORIGINAL_REQUEST_ANIMATION_FRAME = global.requestAnimationFrame;
class MockIntersectionObserver {
static instances = [];
constructor(callback, options) {
this.callback = callback;
this.options = options;
this.observed = new Set();
MockIntersectionObserver.instances.push(this);
}
observe(element) {
this.observed.add(element);
}
unobserve(element) {
this.observed.delete(element);
}
disconnect() {
this.observed.clear();
}
trigger(entries) {
this.callback(entries, this);
}
}
describe('ModelCard video lazy loading queue', () => {
let configureModelCardVideo;
let loadSpy;
let pauseSpy;
let playSpy;
beforeEach(async () => {
vi.useFakeTimers();
vi.setSystemTime(0);
MockIntersectionObserver.instances = [];
global.IntersectionObserver = MockIntersectionObserver;
global.requestAnimationFrame = (callback) => setTimeout(callback, 0);
({ configureModelCardVideo } = await import('../../../static/js/components/shared/ModelCard.js'));
loadSpy = vi.spyOn(HTMLMediaElement.prototype, 'load').mockImplementation(function () {
this.dataset.loadCalls = `${parseInt(this.dataset.loadCalls || '0', 10) + 1}`;
this.dataset.loadCallTime = `${Date.now()}`;
});
pauseSpy = vi.spyOn(HTMLMediaElement.prototype, 'pause').mockImplementation(() => {});
playSpy = vi.spyOn(HTMLMediaElement.prototype, 'play').mockImplementation(() => Promise.resolve());
});
afterEach(() => {
loadSpy.mockRestore();
pauseSpy.mockRestore();
playSpy.mockRestore();
vi.useRealTimers();
delete global.IntersectionObserver;
if (ORIGINAL_REQUEST_ANIMATION_FRAME) {
global.requestAnimationFrame = ORIGINAL_REQUEST_ANIMATION_FRAME;
} else {
delete global.requestAnimationFrame;
}
});
it('throttles large batches of intersecting videos', async () => {
const container = document.createElement('div');
document.body.appendChild(container);
const videoCount = 10;
const videos = [];
for (let index = 0; index < videoCount; index += 1) {
const preview = document.createElement('div');
preview.className = 'card-preview';
const video = document.createElement('video');
video.dataset.src = `video-${index}.mp4`;
const source = document.createElement('source');
source.dataset.src = `video-${index}.mp4`;
video.appendChild(source);
preview.appendChild(video);
container.appendChild(preview);
configureModelCardVideo(video, false);
videos.push(video);
}
const observer = MockIntersectionObserver.instances.at(-1);
observer.trigger(videos.map((video) => ({ target: video, isIntersecting: true })));
// Drain any immediate timers for the initial batch
await vi.runOnlyPendingTimersAsync();
// Advance timers to drain remaining batches at the paced interval
while (videos.some((video) => video.dataset.loaded !== 'true')) {
await vi.advanceTimersByTimeAsync(120);
await vi.runOnlyPendingTimersAsync();
}
const allLoaded = videos.every((video) => video.dataset.loaded === 'true');
expect(allLoaded).toBe(true);
const loadTimes = videos.map((video) => Number.parseInt(video.dataset.loadCallTime || '0', 10));
const uniqueIntervals = new Set(loadTimes);
expect(uniqueIntervals.size).toBeGreaterThan(1);
const loadsPerInterval = loadTimes.reduce((accumulator, time) => {
const nextAccumulator = accumulator;
nextAccumulator[time] = (nextAccumulator[time] || 0) + 1;
return nextAccumulator;
}, {});
const maxLoadsInInterval = Math.max(...Object.values(loadsPerInterval));
expect(maxLoadsInInterval).toBeLessThanOrEqual(2);
document.body.removeChild(container);
});
});