build / task (16.x, npm, macOS-latest) (push) Canceled after 0s
build / task (16.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, npm, windows-latest) (push) Canceled after 0s
build / task (16.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (16.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (16.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (16.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (16.x, yarn, windows-latest) (push) Canceled after 0s
build / task (18.x, npm, macOS-latest) (push) Canceled after 0s
build / task (18.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, npm, windows-latest) (push) Canceled after 0s
build / task (18.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (18.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (18.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (18.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (18.x, yarn, windows-latest) (push) Canceled after 0s
build / task (20.x, npm, macOS-latest) (push) Canceled after 0s
build / task (20.x, npm, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, npm, windows-latest) (push) Canceled after 0s
build / task (20.x, pnpm, macOS-latest) (push) Canceled after 0s
build / task (20.x, pnpm, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, pnpm, windows-latest) (push) Canceled after 0s
build / task (20.x, yarn, macOS-latest) (push) Canceled after 0s
build / task (20.x, yarn, ubuntu-latest) (push) Canceled after 0s
build / task (20.x, yarn, windows-latest) (push) Canceled after 0s
159 lines
6.5 KiB
TypeScript
159 lines
6.5 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import { runInNewContext } from 'node:vm';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const sdkSource = readFileSync(resolve('src/components/rtsp-plugin-video/lib/videoPlayer.js'), 'utf8');
|
|
let sdkWindow: any;
|
|
let control: any;
|
|
let sent: any[];
|
|
|
|
function player(id = 'player-one', hwnd: number | string = 101) {
|
|
const instance = Object.create(sdkWindow.VideoPlayer.prototype);
|
|
instance.setting = {
|
|
videoId: id, hwnd, show: true, visible: true, channelList: [], division: 1, windowType: 0,
|
|
outContent: {}, language: 'zh', refreshTimer: 41, stopRefresh: false, request: {},
|
|
};
|
|
control.videoList[id] = instance;
|
|
if (typeof hwnd === 'number') control.hwndList[hwnd] = id;
|
|
return instance;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
sent = [];
|
|
sdkWindow = {
|
|
addEventListener: vi.fn(), removeEventListener: vi.fn(),
|
|
cancelAnimationFrame: vi.fn(), requestAnimationFrame: vi.fn(() => 42),
|
|
innerWidth: 1280, innerHeight: 720, outerWidth: 1280, devicePixelRatio: 1,
|
|
screen: { availLeft: 0 }, screenX: 0,
|
|
location: { hostname: 'localhost' }, document,
|
|
};
|
|
sdkWindow.parent = sdkWindow;
|
|
sdkWindow.top = sdkWindow;
|
|
runInNewContext(sdkSource, {
|
|
window: sdkWindow, document, navigator: { userAgent: 'Mozilla/5.0 Chrome/120.0 Windows NT 10.0' },
|
|
localStorage: {}, console: { log: vi.fn() }, setTimeout, clearTimeout, setInterval, clearInterval,
|
|
});
|
|
control = sdkWindow.dhPlayerControl;
|
|
control.videoWS = { readyState: 1, send: vi.fn((value) => sent.push(JSON.parse(value))), close: vi.fn() };
|
|
});
|
|
afterEach(() => {
|
|
vi.clearAllTimers();
|
|
vi.useRealTimers();
|
|
document.body.innerHTML = '';
|
|
});
|
|
|
|
describe('真实 SDK 源码的单实例销毁', () => {
|
|
it('原生窗口遮挡命令包含九个编号的精确矩形,隐藏后的编号不会留空洞', () => {
|
|
const instance = player();
|
|
instance.setting.shieldClass = ['video-window-label'];
|
|
const rects = Array.from({ length: 9 }, (_, index) => [260 + index % 3 * 300, 110 + Math.floor(index / 3) * 170, 44, 26]);
|
|
rects.forEach(([x, y, width, height]) => {
|
|
const label = document.createElement('span');
|
|
label.className = 'video-window-label';
|
|
label.getBoundingClientRect = () => ({ left: x, top: y, x, y, width, height, right: x + width, bottom: y + height, toJSON: () => ({}) });
|
|
document.body.appendChild(label);
|
|
});
|
|
instance.windowShield([]);
|
|
expect(sent.at(-1)).toMatchObject({ method: 'window.shield', info: { hwnd: 101, region: rects.flat() } });
|
|
document.body.innerHTML = '';
|
|
instance.windowShield([]);
|
|
expect(sent.at(-1).info.region).toEqual([]);
|
|
});
|
|
|
|
it('隐藏并销毁自身句柄,移除登记和刷新任务,保留共享连接与其他实例', () => {
|
|
const first = player();
|
|
const other = player('player-two', 202);
|
|
first.dispose();
|
|
expect(sent.map(message => message.method)).toEqual(['window.show', 'window.destroy']);
|
|
expect(sent.at(-1).info.hwnd).toBe(101);
|
|
expect(control.videoList['player-one']).toBeUndefined();
|
|
expect(control.hwndList[101]).toBeUndefined();
|
|
expect(control.videoList['player-two']).toBe(other);
|
|
expect(control.hwndList[202]).toBe('player-two');
|
|
expect(control.videoWS.close).not.toHaveBeenCalled();
|
|
expect(sdkWindow.cancelAnimationFrame).toHaveBeenCalledWith(41);
|
|
});
|
|
|
|
it('销毁是幂等的,晚到播放、显示与位置刷新不会再发送命令', () => {
|
|
const instance = player();
|
|
instance.dispose();
|
|
const count = sent.length;
|
|
instance.dispose();
|
|
instance.show();
|
|
instance.handleAdjust();
|
|
instance.send({ method: 'video.real', info: {} });
|
|
expect(sent).toHaveLength(count);
|
|
expect(sdkWindow.requestAnimationFrame).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('不会删除已登记到相同 ID 下的新实例', () => {
|
|
const old = player();
|
|
const replacement = player('player-one', 303);
|
|
old.dispose();
|
|
expect(control.videoList['player-one']).toBe(replacement);
|
|
expect(control.hwndList[303]).toBe('player-one');
|
|
expect(sent.at(-1).info.hwnd).toBe(101);
|
|
});
|
|
|
|
it('原有 destroy 仍可供内部重建使用,不会永久禁用实例', async () => {
|
|
const instance = player();
|
|
const destroying = instance.destroy();
|
|
const message = sent.at(-1);
|
|
control.callBackList[message.id].onSuccess({ code: 0 });
|
|
await destroying;
|
|
expect(instance.disposed).toBeUndefined();
|
|
expect(instance.setting.hwnd).toBe('');
|
|
});
|
|
|
|
it('创建请求发出后卸载,晚到的新句柄仍发送单窗口销毁', async () => {
|
|
const instance = player('late-create', '');
|
|
document.body.innerHTML = '<div id="late-create"></div>';
|
|
instance.create();
|
|
await vi.advanceTimersByTimeAsync(0);
|
|
const createMessage = sent.find(message => message.method === 'window.create');
|
|
expect(createMessage).toBeDefined();
|
|
const callback = control.callBackList[createMessage.id];
|
|
instance.dispose();
|
|
document.body.innerHTML = '';
|
|
callback.onSuccess({ data: { hwnd: 505 } });
|
|
expect(sent.at(-1)).toMatchObject({ method: 'window.destroy', info: { hwnd: 505, isRefresh: false } });
|
|
expect(control.videoList['late-create']).toBeUndefined();
|
|
});
|
|
|
|
it('创建回包时容器已被删除,同样回收句柄和 SDK 登记', async () => {
|
|
const instance = player('missing-dom', '');
|
|
document.body.innerHTML = '<div id="missing-dom"></div>';
|
|
instance.create();
|
|
await vi.advanceTimersByTimeAsync(0);
|
|
const message = sent.find(message => message.method === 'window.create');
|
|
document.body.innerHTML = '';
|
|
control.callBackList[message.id].onSuccess({ data: { hwnd: 606 } });
|
|
expect(sent.at(-1)).toMatchObject({ method: 'window.destroy', info: { hwnd: 606 } });
|
|
expect(instance.disposed).toBe(true);
|
|
expect(control.videoList['missing-dom']).toBeUndefined();
|
|
});
|
|
|
|
it('内部旧窗口销毁回包晚于 dispose 时,不会继续创建新窗口', async () => {
|
|
const instance = player('cancel-create');
|
|
document.body.innerHTML = '<div id="cancel-create"></div>';
|
|
instance.create();
|
|
const destroyMessage = sent.find(message => message.method === 'window.destroy');
|
|
const callback = control.callBackList[destroyMessage.id];
|
|
instance.dispose();
|
|
callback.onSuccess({ code: 0 });
|
|
await vi.advanceTimersByTimeAsync(0);
|
|
expect(sent.some(message => message.method === 'window.create')).toBe(false);
|
|
});
|
|
|
|
it('手动隐藏的实例不会在 SDK show 调用时重新显示', () => {
|
|
const instance = player();
|
|
instance.hide();
|
|
instance.setting.visible = false;
|
|
const count = sent.length;
|
|
instance.show();
|
|
expect(sent).toHaveLength(count);
|
|
});
|
|
});
|