优化目录结构
This commit is contained in:
172
client/test/unit/inputdevice.test.js
Normal file
172
client/test/unit/inputdevice.test.js
Normal file
@@ -0,0 +1,172 @@
|
||||
import {
|
||||
FourCC,
|
||||
Mouse,
|
||||
Keyboard,
|
||||
Touchscreen,
|
||||
Gamepad,
|
||||
KeyboardState,
|
||||
MouseState,
|
||||
TouchscreenState,
|
||||
GamepadState,
|
||||
StateEvent,
|
||||
InputEvent,
|
||||
TextEvent
|
||||
} from "../../src/input/inputdevice.js";
|
||||
|
||||
describe(`FourCC`, () => {
|
||||
test('toInt32', () => {
|
||||
const number = new FourCC('A', 'A', 'A', 'A').toInt32();
|
||||
expect(number).toBe(0x41414141);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`MouseState`, () => {
|
||||
describe(`with MouseEvent`, () => {
|
||||
let event;
|
||||
beforeEach(() => {
|
||||
event = new MouseEvent('click', { buttons:1, clientX:0, clientY:0});
|
||||
});
|
||||
test('format', () => {
|
||||
const format = new MouseState(event).format;
|
||||
expect(format).toBe(0x4d4f5553);
|
||||
});
|
||||
test('buffer', () => {
|
||||
const state = new MouseState(event);
|
||||
expect(state.buffer.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
describe(`with WheelEvent`, () => {
|
||||
let event;
|
||||
beforeEach(() => {
|
||||
event = new WheelEvent('wheel', { deltaX:0, deltaY:0 });
|
||||
});
|
||||
test('format', () => {
|
||||
const format = new MouseState(event).format;
|
||||
expect(format).toBe(0x4d4f5553);
|
||||
});
|
||||
test('buffer', () => {
|
||||
const state = new MouseState(event);
|
||||
expect(state.buffer.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`KeyboardState`, () => {
|
||||
let event;
|
||||
beforeEach(() => {
|
||||
event = new KeyboardEvent('keydown', { code: 'KeyA' });
|
||||
});
|
||||
test('format', () => {
|
||||
const format = new KeyboardState(event).format;
|
||||
expect(format).toBe(0x4b455953);
|
||||
});
|
||||
test('buffer', () => {
|
||||
const state = new KeyboardState(event);
|
||||
expect(state.buffer.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`TouchscreenState`, () => {
|
||||
let event;
|
||||
beforeEach(() => {
|
||||
event = new TouchEvent("touchstart", {
|
||||
changedTouches: [{ // InputInit
|
||||
identifier: 0,
|
||||
target: null,
|
||||
clientX: 0,
|
||||
clientY: 0,
|
||||
screenX: 0,
|
||||
screenY: 0,
|
||||
pageX: 0,
|
||||
pageY: 0,
|
||||
radiusX: 0,
|
||||
radiusY: 0,
|
||||
rotationAngle: 0,
|
||||
force: 0,
|
||||
altitudeAngle: 0,
|
||||
azimuthAngle:0,
|
||||
touchType: "direct"
|
||||
}]
|
||||
});
|
||||
});
|
||||
test('format', () => {
|
||||
const format = new TouchscreenState(event, null, Date.now()).format;
|
||||
expect(format).toBe(0x54534352);
|
||||
});
|
||||
test('buffer', () => {
|
||||
const state = new TouchscreenState(event, null, Date.now());
|
||||
expect(state.buffer.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`GamepadState`, () => {
|
||||
let event;
|
||||
beforeEach(() => {
|
||||
event = {
|
||||
type: 'gamepadupdated',
|
||||
gamepad : {
|
||||
id: 1,
|
||||
buttons: Array(16).fill({ pressed: false, value: 1 }),
|
||||
axes:[0, 0, 0, 0]
|
||||
}};
|
||||
});
|
||||
test('format', () => {
|
||||
const format = new GamepadState(event).format;
|
||||
expect(format).toBe(0x47504144);
|
||||
});
|
||||
test('buffer', () => {
|
||||
const state = new GamepadState(event);
|
||||
expect(state.buffer.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`StateEvent`, () => {
|
||||
let state;
|
||||
beforeEach(() => {
|
||||
const event = new KeyboardEvent('keydown', { code: 'KeyA' });
|
||||
state = new KeyboardState(event);
|
||||
});
|
||||
test('buffer', () => {
|
||||
const stateEvent = StateEvent.fromState(state, 0, Date.now());
|
||||
expect(new Int32Array(stateEvent.buffer.slice(0, 4))[0]).toBe(StateEvent.format);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`TextEvent`, () => {
|
||||
test('buffer', () => {
|
||||
const event = new KeyboardEvent('keydown', { code: 'KeyA', key: "a"});
|
||||
const textEvent = TextEvent.create(0, event, Date.now());
|
||||
expect(new Int32Array(textEvent.buffer.slice(0, 4))[0]).toBe(TextEvent.format);
|
||||
const offset = InputEvent.size;
|
||||
// 'a' is 97
|
||||
expect(new Uint32Array(textEvent.buffer.slice(offset, offset+4))[0]).toBe(97);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`Mouse`, () => {
|
||||
test('alignedSizeInBytes', () => {
|
||||
let device = new Mouse("Mouse", "Mouse", 1, null, null);
|
||||
expect(device).toBeInstanceOf(Mouse);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`Keyboard`, () => {
|
||||
test('alignedSizeInBytes', () => {
|
||||
let device = new Keyboard("Keyboard", "Keyboard", 1, null, null);
|
||||
expect(device).toBeInstanceOf(Keyboard);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`Touchscreen`, () => {
|
||||
test('alignedSizeInBytes', () => {
|
||||
let device = new Touchscreen("Touchscreen", "Touchscreen", 1, null, null);
|
||||
expect(device).toBeInstanceOf(Touchscreen);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`Gamepad`, () => {
|
||||
test('alignedSizeInBytes', () => {
|
||||
let device = new Gamepad("Gamepad", "Gamepad", 1, null, null);
|
||||
expect(device).toBeInstanceOf(Gamepad);
|
||||
});
|
||||
});
|
||||
132
client/test/unit/inputremoting.test.js
Normal file
132
client/test/unit/inputremoting.test.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
InputDevice,
|
||||
MouseState,
|
||||
KeyboardState,
|
||||
TouchscreenState,
|
||||
GamepadState
|
||||
} from "../../src/input/inputdevice.js";
|
||||
|
||||
import {
|
||||
MessageType,
|
||||
NewDeviceMsg,
|
||||
NewEventsMsg,
|
||||
RemoveDeviceMsg,
|
||||
InputRemoting,
|
||||
} from "../../src/input/inputremoting.js";
|
||||
|
||||
import {
|
||||
Sender,
|
||||
Observer
|
||||
} from "../../src/core/sender.js";
|
||||
|
||||
import {DOMRect} from "../helpers/domrect.js";
|
||||
|
||||
describe(`InputRemoting`, () => {
|
||||
let sender = null;
|
||||
let inputRemoting = null;
|
||||
let observer = null;
|
||||
beforeEach(async () => {
|
||||
document.getBoundingClientRect = function(){ return new DOMRect(0,0,0,0); };
|
||||
sender = new Sender(document);
|
||||
inputRemoting = new InputRemoting(sender);
|
||||
let dc = null;
|
||||
observer = new Observer(dc);
|
||||
});
|
||||
test('startSending', () => {
|
||||
expect.assertions(0);
|
||||
inputRemoting.startSending();
|
||||
});
|
||||
test('stopSending', () => {
|
||||
expect.assertions(0);
|
||||
inputRemoting.startSending();
|
||||
inputRemoting.stopSending();
|
||||
});
|
||||
test('subscribe', () => {
|
||||
expect.assertions(0);
|
||||
inputRemoting.subscribe(observer);
|
||||
});
|
||||
});
|
||||
|
||||
test('create NewDeviceMsg', () => {
|
||||
const device = new InputDevice("Keyboard", "Keyboard", 0, null, null);
|
||||
const msg = NewDeviceMsg.create(device);
|
||||
expect(msg.participant_id).toBe(0);
|
||||
expect(msg.type).toBe(MessageType.NewDevice);
|
||||
expect(msg.data).toBeInstanceOf(ArrayBuffer);
|
||||
expect(msg.data.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
describe('create NewEventMsg', () => {
|
||||
test('using MouseState', () => {
|
||||
const event = new MouseEvent('click', { buttons:0, clientX:0, clientY:0} );
|
||||
const state = new MouseState(event);
|
||||
const msg = NewEventsMsg.create(state);
|
||||
expect(msg.participant_id).toBe(0);
|
||||
expect(msg.type).toBe(MessageType.NewEvents);
|
||||
expect(msg.data).toBeInstanceOf(ArrayBuffer);
|
||||
expect(msg.data.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
test('using KeyboardState', () => {
|
||||
const event = new KeyboardEvent("keydown", { code: 'KeyA' });
|
||||
const state = new KeyboardState(event);
|
||||
const msg = NewEventsMsg.create(state);
|
||||
expect(msg.participant_id).toBe(0);
|
||||
expect(msg.type).toBe(MessageType.NewEvents);
|
||||
expect(msg.data).toBeInstanceOf(ArrayBuffer);
|
||||
expect(msg.data.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
test('using TouchscreenState', () => {
|
||||
const event = new TouchEvent("touchstart", {
|
||||
changedTouches: [{ // InputInit
|
||||
identifier: 0,
|
||||
target: null,
|
||||
clientX: 0,
|
||||
clientY: 0,
|
||||
screenX: 0,
|
||||
screenY: 0,
|
||||
pageX: 0,
|
||||
pageY: 0,
|
||||
radiusX: 0,
|
||||
radiusY: 0,
|
||||
rotationAngle: 0,
|
||||
force: 0,
|
||||
altitudeAngle: 0,
|
||||
azimuthAngle:0,
|
||||
touchType: "direct"
|
||||
}]
|
||||
});
|
||||
const state = new TouchscreenState(event, null, Date.now());
|
||||
expect(state.touchData).not.toBeNull();
|
||||
expect(state.touchData).toHaveLength(1);
|
||||
const msg = NewEventsMsg.create(state.touchData[0]);
|
||||
expect(msg.participant_id).toBe(0);
|
||||
expect(msg.type).toBe(MessageType.NewEvents);
|
||||
expect(msg.data).toBeInstanceOf(ArrayBuffer);
|
||||
expect(msg.data.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
test('using GamepadState', () => {
|
||||
const event = {
|
||||
type: 'gamepadupdated',
|
||||
gamepad : {
|
||||
id: 1,
|
||||
buttons: Array(16).fill({ pressed: false, value: 1 }),
|
||||
axes:[1, 1, 1, 1]
|
||||
}};
|
||||
const state = new GamepadState(event);
|
||||
const msg = NewEventsMsg.create(state);
|
||||
expect(msg.participant_id).toBe(0);
|
||||
expect(msg.type).toBe(MessageType.NewEvents);
|
||||
expect(msg.data).toBeInstanceOf(ArrayBuffer);
|
||||
expect(msg.data.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('create RemoveDeviceMsg', () => {
|
||||
const device = new InputDevice("Keyboard", "Keyboard", 0, null, null);
|
||||
const msg = RemoveDeviceMsg.create(device);
|
||||
expect(msg.participant_id).toBe(0);
|
||||
expect(msg.type).toBe(MessageType.RemoveDevice);
|
||||
expect(msg.data).toBeInstanceOf(ArrayBuffer);
|
||||
expect(msg.data.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
123
client/test/unit/meeting-recorder.test.js
Normal file
123
client/test/unit/meeting-recorder.test.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import { MeetingRecorder } from '../../public/call/media/meeting-recorder.js';
|
||||
|
||||
class MediaStreamMock {
|
||||
constructor(tracks = []) {
|
||||
this.tracks = tracks;
|
||||
}
|
||||
|
||||
getTracks() {
|
||||
return this.tracks;
|
||||
}
|
||||
|
||||
getAudioTracks() {
|
||||
return this.tracks.filter(track => track.kind === 'audio');
|
||||
}
|
||||
|
||||
getVideoTracks() {
|
||||
return this.tracks.filter(track => track.kind === 'video');
|
||||
}
|
||||
}
|
||||
|
||||
class MediaRecorderMock {
|
||||
static isTypeSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
constructor(stream, options) {
|
||||
this.stream = stream;
|
||||
this.mimeType = options.mimeType;
|
||||
this.state = 'inactive';
|
||||
}
|
||||
|
||||
start() {
|
||||
this.state = 'recording';
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.state = 'inactive';
|
||||
this.ondataavailable({ data: new Blob(['recording'], { type: this.mimeType }) });
|
||||
this.onstop();
|
||||
}
|
||||
}
|
||||
|
||||
function createTrack(kind) {
|
||||
return {
|
||||
kind,
|
||||
readyState: 'live',
|
||||
stop: jest.fn(),
|
||||
clone: jest.fn(() => createTrack(kind))
|
||||
};
|
||||
}
|
||||
|
||||
function createWindowMock({ mediaRecorder = MediaRecorderMock } = {}) {
|
||||
return {
|
||||
MediaRecorder: mediaRecorder,
|
||||
MediaStream: MediaStreamMock,
|
||||
URL: {
|
||||
createObjectURL: jest.fn(() => 'blob:recording'),
|
||||
revokeObjectURL: jest.fn()
|
||||
},
|
||||
requestAnimationFrame: jest.fn(() => 1),
|
||||
cancelAnimationFrame: jest.fn(),
|
||||
setTimeout: jest.fn((callback) => {
|
||||
callback();
|
||||
return 1;
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
describe('MeetingRecorder', () => {
|
||||
beforeEach(() => {
|
||||
HTMLCanvasElement.prototype.getContext = jest.fn(() => ({
|
||||
fillStyle: '',
|
||||
font: '',
|
||||
textAlign: '',
|
||||
textBaseline: '',
|
||||
fillRect: jest.fn(),
|
||||
fillText: jest.fn(),
|
||||
drawImage: jest.fn()
|
||||
}));
|
||||
HTMLCanvasElement.prototype.captureStream = jest.fn(() => new MediaStreamMock([createTrack('video')]));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
test('starts and stops recording a meeting file', async () => {
|
||||
const windowRef = createWindowMock();
|
||||
const recorder = new MeetingRecorder({ documentRef: document, windowRef });
|
||||
const localVideo = document.createElement('video');
|
||||
localVideo.id = 'localVideo';
|
||||
localVideo.srcObject = new MediaStreamMock([createTrack('video')]);
|
||||
Object.defineProperty(localVideo, 'readyState', { value: 2 });
|
||||
Object.defineProperty(localVideo, 'videoWidth', { value: 640 });
|
||||
Object.defineProperty(localVideo, 'videoHeight', { value: 360 });
|
||||
localVideo.getBoundingClientRect = () => ({ width: 320, height: 180 });
|
||||
document.body.appendChild(localVideo);
|
||||
|
||||
await recorder.start({
|
||||
localStream: new MediaStreamMock([createTrack('audio')]),
|
||||
connectionId: '123-456-789'
|
||||
});
|
||||
|
||||
expect(recorder.isRecording()).toBe(true);
|
||||
|
||||
const result = await recorder.stop();
|
||||
|
||||
expect(result.filename).toContain('meeting-recording-123-456-789');
|
||||
expect(result.mimeType).toBe('video/mp4;codecs=avc1.42E01E,mp4a.40.2');
|
||||
expect(result.filename).toMatch(/\.mp4$/);
|
||||
expect(windowRef.URL.createObjectURL).not.toHaveBeenCalled();
|
||||
expect(recorder.isRecording()).toBe(false);
|
||||
});
|
||||
|
||||
test('reports unsupported browsers', async () => {
|
||||
HTMLCanvasElement.prototype.captureStream = undefined;
|
||||
const windowRef = createWindowMock({ mediaRecorder: undefined });
|
||||
const recorder = new MeetingRecorder({ documentRef: document, windowRef });
|
||||
|
||||
await expect(recorder.start()).rejects.toThrow('当前浏览器不支持会议录制');
|
||||
});
|
||||
});
|
||||
67
client/test/unit/memoryhelper.test.js
Normal file
67
client/test/unit/memoryhelper.test.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
MemoryHelper
|
||||
} from "../../src/utils/memoryhelper.js";
|
||||
|
||||
describe(`MemoryHelper.writeSingleBit`, () => {
|
||||
test('turn on with offset 0', () => {
|
||||
let bytes = new ArrayBuffer(3);
|
||||
MemoryHelper.writeSingleBit(bytes, 0, false);
|
||||
|
||||
// check 00 00 00
|
||||
const view = new Uint8Array(bytes);
|
||||
expect(view[0]).toBe(0);
|
||||
expect(view[1]).toBe(0);
|
||||
expect(view[2]).toBe(0);
|
||||
});
|
||||
test('turn off with offset 0', () => {
|
||||
let bytes = new ArrayBuffer(3);
|
||||
MemoryHelper.writeSingleBit(bytes, 0, true);
|
||||
|
||||
// check 00 00 01
|
||||
const view = new Uint8Array(bytes);
|
||||
expect(view[0]).toBe(1);
|
||||
expect(view[1]).toBe(0);
|
||||
expect(view[2]).toBe(0);
|
||||
|
||||
MemoryHelper.writeSingleBit(bytes, 0, false);
|
||||
|
||||
// check 00 00 00
|
||||
expect(view[0]).toBe(0);
|
||||
expect(view[1]).toBe(0);
|
||||
expect(view[2]).toBe(0);
|
||||
});
|
||||
test('turn on with offset 32', () => {
|
||||
let bytes = new ArrayBuffer(3);
|
||||
MemoryHelper.writeSingleBit(bytes, 8, true);
|
||||
|
||||
// check 00 01 00
|
||||
const view = new Uint8Array(bytes);
|
||||
expect(view[0]).toBe(0);
|
||||
expect(view[1]).toBe(1);
|
||||
expect(view[2]).toBe(0);
|
||||
|
||||
MemoryHelper.writeSingleBit(bytes, 0, true);
|
||||
|
||||
// check 00 01 01
|
||||
expect(view[0]).toBe(1);
|
||||
expect(view[1]).toBe(1);
|
||||
expect(view[2]).toBe(0);
|
||||
});
|
||||
test('turn on with offset 15', () => {
|
||||
let bytes = new ArrayBuffer(3);
|
||||
MemoryHelper.writeSingleBit(bytes, 15, true);
|
||||
|
||||
// check 00 80 00
|
||||
const view = new Uint8Array(bytes);
|
||||
expect(view[0]).toBe(0);
|
||||
expect(view[1]).toBe(128);
|
||||
expect(view[2]).toBe(0);
|
||||
|
||||
MemoryHelper.writeSingleBit(bytes, 15, false);
|
||||
|
||||
// check 00 00 00
|
||||
expect(view[0]).toBe(0);
|
||||
expect(view[1]).toBe(0);
|
||||
expect(view[2]).toBe(0);
|
||||
});
|
||||
});
|
||||
250
client/test/unit/peerconnection.test.js
Normal file
250
client/test/unit/peerconnection.test.js
Normal file
@@ -0,0 +1,250 @@
|
||||
import Peer from "../../src/core/peer.js";
|
||||
import { waitFor, sleep, getUniqueId, getRTCConfiguration } from "../helpers/testutils.js";
|
||||
|
||||
|
||||
describe(`peer connection test`, () => {
|
||||
const connectionId = "12345";
|
||||
|
||||
test(`constructor`, () => {
|
||||
const peer = new Peer(connectionId, true);
|
||||
expect(peer).not.toBeNull();
|
||||
|
||||
const rtcPeer = peer.pc;
|
||||
expect(rtcPeer).not.toBeNull();
|
||||
expect(rtcPeer.ontrack).not.toBeNull();
|
||||
expect(rtcPeer.onicecandidate).not.toBeNull();
|
||||
expect(rtcPeer.onnegotiationneeded).not.toBeNull();
|
||||
expect(rtcPeer.onsignalingstatechange).not.toBeNull();
|
||||
expect(rtcPeer.oniceconnectionstatechange).not.toBeNull();
|
||||
expect(rtcPeer.onicegatheringstatechange).not.toBeNull();
|
||||
});
|
||||
|
||||
test(`close peer`, async () => {
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config);
|
||||
expect(peer).not.toBeNull();
|
||||
|
||||
peer.close();
|
||||
expect(peer.connectionId).toBeNull();
|
||||
expect(peer.pc).toBeNull();
|
||||
});
|
||||
|
||||
test(`transceiver direction is sendrecv if using addtrack`, () => {
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config);
|
||||
expect(peer).not.toBeNull();
|
||||
|
||||
const track = { id: getUniqueId(), kind: "audio" };
|
||||
const sender = peer.addTrack(connectionId, track);
|
||||
const transceiver = peer.getTransceivers(connectionId).find(t => t.sender == sender);
|
||||
expect(transceiver.direction).toBe("sendrecv");
|
||||
});
|
||||
|
||||
test(`fire trackevent when addtrack`, async () => {
|
||||
let trackEvent;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('trackevent', (e) => trackEvent = e.detail);
|
||||
|
||||
const track = { id: getUniqueId(), kind: "audio" };
|
||||
peer.addTrack(connectionId, track);
|
||||
await waitFor(() => trackEvent != null);
|
||||
expect(trackEvent.track).toBe(track);
|
||||
});
|
||||
|
||||
test(`fire trackevent when on got offer description include track`, async () => {
|
||||
let trackEvent;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('trackevent', (e) => trackEvent = e.detail);
|
||||
|
||||
const testDesc = { type: "offer", sdp: "newtracksdp" };
|
||||
peer.onGotDescription(connectionId, testDesc);
|
||||
await waitFor(() => trackEvent != null);
|
||||
expect(trackEvent.track).not.toBeNull();
|
||||
});
|
||||
|
||||
test(`fire sendoffer when addtrack`, async () => {
|
||||
let offer;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('sendoffer', (e) => offer = e.detail);
|
||||
|
||||
const track = { id: getUniqueId(), kind: "audio" };
|
||||
peer.addTrack(connectionId, track);
|
||||
await waitFor(() => offer != null);
|
||||
expect(offer.connectionId).toBe(connectionId);
|
||||
});
|
||||
|
||||
test(`fire sendoffer when addTransceiver`, async () => {
|
||||
let offer;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('sendoffer', (e) => offer = e.detail);
|
||||
|
||||
peer.addTransceiver(connectionId, "video");
|
||||
await waitFor(() => offer != null);
|
||||
expect(offer.connectionId).toBe(connectionId);
|
||||
});
|
||||
|
||||
test(`fire sendoffer when createDataChannel`, async () => {
|
||||
let offer;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('sendoffer', (e) => offer = e.detail);
|
||||
|
||||
peer.createDataChannel(connectionId, "testChannel");
|
||||
await waitFor(() => offer != null);
|
||||
expect(offer.connectionId).toBe(connectionId);
|
||||
});
|
||||
|
||||
test(`re-fire sendoffer if get answer not yet`, async () => {
|
||||
let sendOfferCount = 0;
|
||||
let offer;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config, 100);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('sendoffer', (e) => {
|
||||
offer = e.detail;
|
||||
sendOfferCount++;
|
||||
});
|
||||
|
||||
const track = { id: getUniqueId(), kind: "audio" };
|
||||
peer.addTrack(connectionId, track);
|
||||
await waitFor(() => sendOfferCount > 2);
|
||||
expect(offer.connectionId).toBe(connectionId);
|
||||
expect(sendOfferCount).toBeGreaterThan(2);
|
||||
});
|
||||
|
||||
test(`fire sendanswer when on got offer description in polite`, async () => {
|
||||
let answer;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('sendanswer', (e) => answer = e.detail);
|
||||
|
||||
const testDesc = { type: "offer", sdp: "newtracksdp" };
|
||||
peer.onGotDescription(connectionId, testDesc);
|
||||
await waitFor(() => answer != null);
|
||||
expect(answer.connectionId).toBe(connectionId);
|
||||
});
|
||||
|
||||
test(`fire sendanswer when on got offer description in polite that have offer`, async () => {
|
||||
let offer;
|
||||
let answer;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('sendoffer', (e) => offer = e.detail);
|
||||
peer.addEventListener('sendanswer', (e) => answer = e.detail);
|
||||
|
||||
const track = { id: getUniqueId(), kind: "audio" };
|
||||
peer.addTrack(connectionId, track);
|
||||
await waitFor(() => offer != null);
|
||||
expect(offer.connectionId).toBe(connectionId);
|
||||
|
||||
const testDesc = { type: "offer", sdp: "newtracksdp" };
|
||||
peer.onGotDescription(connectionId, testDesc);
|
||||
await waitFor(() => answer != null);
|
||||
expect(answer.connectionId).toBe(connectionId);
|
||||
});
|
||||
|
||||
test(`fire sendanswer when on got offer description in impolite that don't have offer`, async () => {
|
||||
let answer;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, false, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('sendanswer', (e) => answer = e.detail);
|
||||
|
||||
const testDesc = { type: "offer", sdp: "newtracksdp" };
|
||||
peer.onGotDescription(connectionId, testDesc);
|
||||
await waitFor(() => answer != null);
|
||||
expect(answer.connectionId).toBe(connectionId);
|
||||
});
|
||||
|
||||
test(`don't fire sendanswer when on got offer description in impolite that have offer`, async () => {
|
||||
let offer;
|
||||
let answer;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, false, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('sendoffer', (e) => offer = e.detail);
|
||||
peer.addEventListener('sendanswer', (e) => answer = e.detail);
|
||||
|
||||
const track = { id: getUniqueId(), kind: "audio" };
|
||||
peer.addTrack(connectionId, track);
|
||||
await waitFor(() => offer != null);
|
||||
expect(offer.connectionId).toBe(connectionId);
|
||||
|
||||
const testDesc = { type: "offer", sdp: "newtracksdp" };
|
||||
peer.onGotDescription(connectionId, testDesc);
|
||||
await sleep(100);
|
||||
expect(answer).toBeUndefined();
|
||||
});
|
||||
|
||||
test(`fire nagotiated when on got answer description that have offer`, async () => {
|
||||
let offer;
|
||||
let negotiated = false;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('sendoffer', (e) => offer = e.detail);
|
||||
peer.pc.addEventListener('negotiated', () => negotiated = true);
|
||||
|
||||
const track = { id: getUniqueId(), kind: "audio" };
|
||||
peer.addTrack(connectionId, track);
|
||||
await waitFor(() => offer != null);
|
||||
expect(offer.connectionId).toBe(connectionId);
|
||||
|
||||
const answerDesc = { type: "answer", sdp: "newtracksdp" };
|
||||
peer.onGotDescription(connectionId, answerDesc);
|
||||
await waitFor(() => negotiated);
|
||||
expect(negotiated).toBeTruthy();
|
||||
});
|
||||
|
||||
test(`fire sendcandidate when on addTransceiver`, async () => {
|
||||
let candidate;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, true, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('sendcandidate', (e) => candidate = e.detail);
|
||||
|
||||
peer.addTransceiver(connectionId, { id: getUniqueId(), kind: "video" });
|
||||
await waitFor(() => candidate != null);
|
||||
expect(candidate.connectionId).toBe(connectionId);
|
||||
});
|
||||
|
||||
test(`accept candidate when on got candidate that have remote description`, async () => {
|
||||
let answer;
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, false, config);
|
||||
expect(peer).not.toBeNull();
|
||||
peer.addEventListener('sendanswer', (e) => answer = e.detail);
|
||||
|
||||
const testDesc = { type: "offer", sdp: "newtracksdp" };
|
||||
peer.onGotDescription(connectionId, testDesc);
|
||||
await waitFor(() => answer != null);
|
||||
expect(answer.connectionId).toBe(connectionId);
|
||||
|
||||
const testCandidate = { candidate: getUniqueId(), sdpMLineIndex: 0, sdpMid: 0 };
|
||||
peer.onGotCandidate(connectionId, testCandidate);
|
||||
await waitFor(() => peer.pc.candidates.length > 0);
|
||||
expect(peer.pc.candidates.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test(`don't accept candidate when on got candidate that don't have remote description`, async () => {
|
||||
const config = getRTCConfiguration();
|
||||
const peer = new Peer(connectionId, false, config);
|
||||
expect(peer).not.toBeNull();
|
||||
|
||||
const testCandidate = { candidate: getUniqueId(), sdpMLineIndex: 0, sdpMid: 0 };
|
||||
peer.onGotCandidate(connectionId, testCandidate);
|
||||
await sleep(100);
|
||||
expect(peer.pc.candidates.length).toBe(0);
|
||||
});
|
||||
});
|
||||
45
client/test/unit/pointercorrect.test.js
Normal file
45
client/test/unit/pointercorrect.test.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
LetterBoxType,
|
||||
PointerCorrector
|
||||
} from "../../src/input/pointercorrect.js";
|
||||
|
||||
import {DOMRect} from "../helpers/domrect.js";
|
||||
import {DOMHTMLVideoElement} from "../helpers/domvideoelement.js";
|
||||
|
||||
describe(`PointerCorrector.map`, () => {
|
||||
test('letterboxType', () => {
|
||||
const rect = new DOMRect(10, 10, 200, 200);
|
||||
const element = new DOMHTMLVideoElement(rect);
|
||||
let corrector = new PointerCorrector(50, 100, element);
|
||||
expect(corrector.letterBoxType).toBe(LetterBoxType.Vertical);
|
||||
corrector.reset(100, 50, element);
|
||||
expect(corrector.letterBoxType).toBe(LetterBoxType.Horizontal);
|
||||
});
|
||||
test('letterboxSize', () => {
|
||||
const rect = new DOMRect(0, 0, 100, 100);
|
||||
const element = new DOMHTMLVideoElement(rect);
|
||||
let corrector = new PointerCorrector(50, 100, element);
|
||||
expect(corrector.letterBoxSize).toBe(25);
|
||||
});
|
||||
test('contentRect', () => {
|
||||
const rect = new DOMRect(0, 0, 100, 100);
|
||||
const element = new DOMHTMLVideoElement(rect);
|
||||
let corrector = new PointerCorrector(50, 100, element);
|
||||
expect(corrector.contentRect.x).toBe(25);
|
||||
expect(corrector.contentRect.y).toBe(0);
|
||||
expect(corrector.contentRect.width).toBe(50);
|
||||
expect(corrector.contentRect.height).toBe(100);
|
||||
});
|
||||
test('mapping', () => {
|
||||
const rect = new DOMRect(10, 10, 200, 200);
|
||||
const element = new DOMHTMLVideoElement(rect);
|
||||
const videoWidth = 100;
|
||||
const videoHeight = 100;
|
||||
let corrector = new PointerCorrector(videoWidth, videoHeight, element);
|
||||
const position = [10, 10];
|
||||
const newPosition = corrector.map(position);
|
||||
expect(newPosition[0]).toBe(0);
|
||||
expect(newPosition[1]).toBe(100);
|
||||
});
|
||||
|
||||
});
|
||||
187
client/test/unit/renderstreaming.test.js
Normal file
187
client/test/unit/renderstreaming.test.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import { MockSignaling, reset } from "../mocks/mocksignaling.js";
|
||||
import { waitFor, getUniqueId, getRTCConfiguration } from "../helpers/testutils.js";
|
||||
import { RenderStreaming } from "../../src/core/renderstreaming.js";
|
||||
|
||||
describe.each([
|
||||
{ mode: "private" },
|
||||
{ mode: "public" }
|
||||
])('renderstreaming test', ({ mode }) => {
|
||||
const connectionId1 = "12345";
|
||||
|
||||
test(`createConnection in ${mode} mode`, async () => {
|
||||
reset(mode == "private");
|
||||
const config = getRTCConfiguration();
|
||||
const renderstreaming = new RenderStreaming(new MockSignaling(), config);
|
||||
await renderstreaming.start();
|
||||
|
||||
let isConnect = false;
|
||||
renderstreaming.onConnect = () => isConnect = true;
|
||||
await renderstreaming.createConnection(connectionId1);
|
||||
await waitFor(() => isConnect);
|
||||
expect(isConnect).toBe(true);
|
||||
|
||||
await renderstreaming.stop();
|
||||
});
|
||||
|
||||
test(`addTrack in ${mode} mode`, async () => {
|
||||
reset(mode == "private");
|
||||
const config = getRTCConfiguration();
|
||||
const renderstreaming = new RenderStreaming(new MockSignaling(), config);
|
||||
await renderstreaming.start();
|
||||
|
||||
let isConnect = false;
|
||||
renderstreaming.onConnect = () => isConnect = true;
|
||||
await renderstreaming.createConnection(connectionId1);
|
||||
await waitFor(() => isConnect);
|
||||
expect(isConnect).toBe(true);
|
||||
expect(renderstreaming.getTransceivers(connectionId1).length).toBe(0);
|
||||
|
||||
const track = { id: getUniqueId(), kind: "audio" };
|
||||
renderstreaming.addTrack(track);
|
||||
expect(renderstreaming.getTransceivers(connectionId1).length).toBe(1);
|
||||
|
||||
let isDisconnect = false;
|
||||
renderstreaming.onDisconnect = () => isDisconnect = true;
|
||||
await renderstreaming.deleteConnection();
|
||||
await waitFor(() => isDisconnect);
|
||||
expect(isDisconnect).toBe(true);
|
||||
|
||||
await renderstreaming.stop();
|
||||
});
|
||||
|
||||
test(`createChannel in ${mode} mode`, async () => {
|
||||
reset(mode == "private");
|
||||
const config = getRTCConfiguration();
|
||||
const renderstreaming = new RenderStreaming(new MockSignaling(), config);
|
||||
await renderstreaming.start();
|
||||
|
||||
let isConnect = false;
|
||||
renderstreaming.onConnect = () => isConnect = true;
|
||||
await renderstreaming.createConnection(connectionId1);
|
||||
await waitFor(() => isConnect);
|
||||
expect(isConnect).toBe(true);
|
||||
expect(renderstreaming.getTransceivers(connectionId1).length).toBe(0);
|
||||
|
||||
const label = "testlabel";
|
||||
const channel = renderstreaming.createDataChannel(label);
|
||||
expect(channel.label).toBe(label);
|
||||
|
||||
let isDisconnect = false;
|
||||
renderstreaming.onDisconnect = () => isDisconnect = true;
|
||||
await renderstreaming.deleteConnection();
|
||||
await waitFor(() => isDisconnect);
|
||||
expect(isDisconnect).toBe(true);
|
||||
|
||||
await renderstreaming.stop();
|
||||
});
|
||||
|
||||
test(`onTrackEvent in ${mode} mode`, async () => {
|
||||
reset(mode == "private");
|
||||
|
||||
const config = getRTCConfiguration();
|
||||
const renderstreaming1 = new RenderStreaming(new MockSignaling(), config);
|
||||
const renderstreaming2 = new RenderStreaming(new MockSignaling(), config);
|
||||
await renderstreaming1.start();
|
||||
await renderstreaming2.start();
|
||||
|
||||
let isConnect1 = false;
|
||||
renderstreaming1.onConnect = () => isConnect1 = true;
|
||||
let isConnect2 = false;
|
||||
renderstreaming2.onConnect = () => isConnect2 = true;
|
||||
|
||||
await renderstreaming1.createConnection(connectionId1);
|
||||
await renderstreaming2.createConnection(connectionId1);
|
||||
await waitFor(() => isConnect1 && isConnect2);
|
||||
expect(isConnect1).toBe(true);
|
||||
expect(isConnect2).toBe(true);
|
||||
|
||||
let isGotOffer1 = false;
|
||||
let isOnTrack1 = false;
|
||||
let isGotAnswer2 = false;
|
||||
renderstreaming1.onGotOffer = () => { isGotOffer1 = true; };
|
||||
renderstreaming1.onTrackEvent = () => { isOnTrack1 = true; };
|
||||
renderstreaming2.onGotAnswer = () => { isGotAnswer2 = true; };
|
||||
|
||||
expect(renderstreaming1.getTransceivers(connectionId1).length).toBe(0);
|
||||
|
||||
const track = { id: getUniqueId(), kind: "audio" };
|
||||
renderstreaming2.addTrack(track);
|
||||
expect(renderstreaming2.getTransceivers(connectionId1).length).toBe(1);
|
||||
await waitFor(() => isGotOffer1);
|
||||
expect(isGotOffer1).toBe(true);
|
||||
|
||||
await waitFor(() => isOnTrack1);
|
||||
expect(isOnTrack1).toBe(true);
|
||||
expect(renderstreaming1.getTransceivers(connectionId1).length).toBe(1);
|
||||
|
||||
await waitFor(() => isGotAnswer2);
|
||||
expect(isGotAnswer2).toBe(true);
|
||||
|
||||
let isDisconnect1 = false;
|
||||
renderstreaming1.onDisconnect = () => isDisconnect1 = true;
|
||||
let isDisconnect2 = false;
|
||||
renderstreaming2.onDisconnect = () => isDisconnect2 = true;
|
||||
|
||||
await renderstreaming1.deleteConnection();
|
||||
await renderstreaming2.deleteConnection();
|
||||
await waitFor(() => isDisconnect1 && isDisconnect2);
|
||||
expect(isDisconnect1).toBe(true);
|
||||
expect(isDisconnect2).toBe(true);
|
||||
|
||||
await renderstreaming1.stop();
|
||||
await renderstreaming2.stop();
|
||||
});
|
||||
|
||||
test(`onAddDataChannel in ${mode} mode`, async () => {
|
||||
reset(mode == "private");
|
||||
|
||||
const config = getRTCConfiguration();
|
||||
const renderstreaming1 = new RenderStreaming(new MockSignaling(), config);
|
||||
const renderstreaming2 = new RenderStreaming(new MockSignaling(), config);
|
||||
await renderstreaming1.start();
|
||||
await renderstreaming2.start();
|
||||
|
||||
let isConnect1 = false;
|
||||
renderstreaming1.onConnect = () => isConnect1 = true;
|
||||
let isConnect2 = false;
|
||||
renderstreaming2.onConnect = () => isConnect2 = true;
|
||||
|
||||
await renderstreaming1.createConnection(connectionId1);
|
||||
await renderstreaming2.createConnection(connectionId1);
|
||||
await waitFor(() => isConnect1 && isConnect2);
|
||||
expect(isConnect1).toBe(true);
|
||||
expect(isConnect2).toBe(true);
|
||||
|
||||
let isGotOffer1 = false;
|
||||
let isAddChannel1 = false;
|
||||
let isGotAnswer2 = false;
|
||||
renderstreaming1.onGotOffer = () => { isGotOffer1 = true; };
|
||||
renderstreaming1.onAddChannel = () => { isAddChannel1 = true; };
|
||||
renderstreaming2.onGotAnswer = () => { isGotAnswer2 = true; };
|
||||
|
||||
renderstreaming2.createDataChannel("testchannel");
|
||||
await waitFor(() => isGotOffer1);
|
||||
expect(isGotOffer1).toBe(true);
|
||||
|
||||
await waitFor(() => isAddChannel1);
|
||||
expect(isAddChannel1).toBe(true);
|
||||
|
||||
await waitFor(() => isGotAnswer2);
|
||||
expect(isGotAnswer2).toBe(true);
|
||||
|
||||
let isDisconnect1 = false;
|
||||
renderstreaming1.onDisconnect = () => isDisconnect1 = true;
|
||||
let isDisconnect2 = false;
|
||||
renderstreaming2.onDisconnect = () => isDisconnect2 = true;
|
||||
|
||||
await renderstreaming1.deleteConnection();
|
||||
await renderstreaming2.deleteConnection();
|
||||
await waitFor(() => isDisconnect1 && isDisconnect2);
|
||||
expect(isDisconnect1).toBe(true);
|
||||
expect(isDisconnect2).toBe(true);
|
||||
|
||||
await renderstreaming1.stop();
|
||||
await renderstreaming2.stop();
|
||||
});
|
||||
|
||||
});
|
||||
143
client/test/unit/sender.test.js
Normal file
143
client/test/unit/sender.test.js
Normal file
@@ -0,0 +1,143 @@
|
||||
import {
|
||||
InputRemoting,
|
||||
} from "../../src/input/inputremoting.js";
|
||||
|
||||
import {
|
||||
Sender,
|
||||
Observer
|
||||
} from "../../src/core/sender.js";
|
||||
|
||||
import {jest} from '@jest/globals';
|
||||
import {DOMRect} from "../helpers/domrect.js";
|
||||
|
||||
// mock
|
||||
|
||||
class RTCDataChannel {
|
||||
get readyState() {
|
||||
return "open";
|
||||
}
|
||||
/* eslint-disable no-unused-vars */
|
||||
send(message) {
|
||||
}
|
||||
}
|
||||
|
||||
describe(`Sender`, () => {
|
||||
let inputRemoting = null;
|
||||
let sender = null;
|
||||
let observer = null;
|
||||
let events = {};
|
||||
let dc = null;
|
||||
beforeEach(async () => {
|
||||
// Empty our events before each test case
|
||||
events = {};
|
||||
|
||||
// Define the addEventListener method with a Jest mock function
|
||||
document.addEventListener = jest.fn((event, callback) => {
|
||||
events[event] = callback;
|
||||
});
|
||||
|
||||
document.removeEventListener = jest.fn((event, callback) => {
|
||||
delete events[event];
|
||||
});
|
||||
document.getBoundingClientRect = function(){ return new DOMRect(0,0,0,0); };
|
||||
sender = new Sender(document);
|
||||
inputRemoting = new InputRemoting(sender);
|
||||
dc = new RTCDataChannel();
|
||||
observer = new Observer(dc);
|
||||
});
|
||||
test('devices', () => {
|
||||
sender.addMouse();
|
||||
expect(sender.devices.length).toBe(1);
|
||||
sender.addKeyboard();
|
||||
expect(sender.devices.length).toBe(2);
|
||||
});
|
||||
test('send messages while called startSending', () => {
|
||||
jest.spyOn(dc, 'send');
|
||||
sender.addMouse();
|
||||
sender.addKeyboard();
|
||||
inputRemoting.subscribe(observer);
|
||||
inputRemoting.startSending();
|
||||
expect(dc.send).toHaveBeenCalled();
|
||||
});
|
||||
describe('mouse', () => {
|
||||
test('click', () => {
|
||||
jest.spyOn(dc, 'send');
|
||||
sender.addMouse();
|
||||
inputRemoting.subscribe(observer);
|
||||
inputRemoting.startSending();
|
||||
events.click(
|
||||
new MouseEvent('click', { buttons:1, clientX:0, clientY:0} ));
|
||||
expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer));
|
||||
});
|
||||
test('mousemove', () => {
|
||||
jest.spyOn(dc, 'send');
|
||||
sender.addMouse();
|
||||
inputRemoting.subscribe(observer);
|
||||
inputRemoting.startSending();
|
||||
events.mousemove(
|
||||
new MouseEvent('mousemove', { buttons:1, deltaX:0, deltaY:0 }));
|
||||
expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer));
|
||||
});
|
||||
test('wheel', () => {
|
||||
jest.spyOn(dc, 'send');
|
||||
sender.addMouse();
|
||||
inputRemoting.subscribe(observer);
|
||||
inputRemoting.startSending();
|
||||
events.wheel(
|
||||
new WheelEvent('wheel', { wheelDelta:0, deltaX:0, deltaY:0 }));
|
||||
expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer));
|
||||
});
|
||||
});
|
||||
describe('keyboard', () => {
|
||||
test('keydown', () => {
|
||||
jest.spyOn(dc, 'send');
|
||||
sender.addKeyboard();
|
||||
inputRemoting.subscribe(observer);
|
||||
inputRemoting.startSending();
|
||||
events.keydown(
|
||||
new KeyboardEvent('keydown', { code: 'KeyA' }));
|
||||
expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer));
|
||||
});
|
||||
test('keydown repeat', () => {
|
||||
jest.spyOn(dc, 'send');
|
||||
sender.addKeyboard();
|
||||
inputRemoting.subscribe(observer);
|
||||
inputRemoting.startSending();
|
||||
events.keydown(
|
||||
new KeyboardEvent('keydown', { code: 'KeyA', repeat: true }));
|
||||
expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer));
|
||||
});
|
||||
});
|
||||
describe('touchscreen', () => {
|
||||
test('touchstart', () => {
|
||||
jest.spyOn(dc, 'send');
|
||||
sender.addTouchscreen();
|
||||
inputRemoting.subscribe(observer);
|
||||
inputRemoting.startSending();
|
||||
events.touchstart(
|
||||
new TouchEvent("touchstart", {
|
||||
changedTouches: [{ // InputInit
|
||||
identifier: 0,
|
||||
target: null,
|
||||
clientX: 0,
|
||||
clientY: 0,
|
||||
screenX: 0,
|
||||
screenY: 0,
|
||||
pageX: 0,
|
||||
pageY: 0,
|
||||
radiusX: 0,
|
||||
radiusY: 0,
|
||||
rotationAngle: 0,
|
||||
force: 0,
|
||||
altitudeAngle: 0,
|
||||
azimuthAngle:0,
|
||||
touchType: "direct"
|
||||
}]
|
||||
}));
|
||||
expect(dc.send).toHaveBeenCalledWith(expect.any(ArrayBuffer));
|
||||
});
|
||||
});
|
||||
describe('gamepad', () => {
|
||||
//todo
|
||||
});
|
||||
});
|
||||
484
client/test/unit/signaling.test.js
Normal file
484
client/test/unit/signaling.test.js
Normal file
@@ -0,0 +1,484 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import * as Path from 'path';
|
||||
import { setup, teardown } from 'jest-dev-server';
|
||||
import { Signaling, WebSocketSignaling } from "../../src/core/signaling.js";
|
||||
import { MockSignaling, reset } from "../mocks/mocksignaling.js";
|
||||
import { waitFor, sleep, serverExeName } from "../helpers/testutils.js";
|
||||
|
||||
const portNumber = 8081;
|
||||
jest.setTimeout(10000);
|
||||
|
||||
describe.each([
|
||||
{ mode: "mock" },
|
||||
{ mode: "http" },
|
||||
{ mode: "websocket" },
|
||||
])('signaling test in public mode', ({ mode }) => {
|
||||
let signaling1;
|
||||
let signaling2;
|
||||
const connectionId1 = "12345";
|
||||
const connectionId2 = "67890";
|
||||
const testsdp = "test sdp";
|
||||
const testcandidate = "test candidate";
|
||||
|
||||
beforeAll(async () => {
|
||||
if (mode == "mock") {
|
||||
reset(false);
|
||||
signaling1 = new MockSignaling(1);
|
||||
signaling2 = new MockSignaling(1);
|
||||
} else {
|
||||
const path = Path.resolve(`../bin~/${serverExeName()}`);
|
||||
let cmd = `${path} -p ${portNumber}`;
|
||||
if (mode == "http") {
|
||||
cmd += " -t http";
|
||||
}
|
||||
|
||||
await setup({ command: cmd, port: portNumber, usedPortAction: 'error' });
|
||||
|
||||
if (mode == "http") {
|
||||
signaling1 = new Signaling(1);
|
||||
signaling2 = new Signaling(1);
|
||||
}
|
||||
|
||||
if (mode == "websocket") {
|
||||
signaling1 = new WebSocketSignaling(1);
|
||||
signaling2 = new WebSocketSignaling(1);
|
||||
}
|
||||
}
|
||||
|
||||
await signaling1.start();
|
||||
await signaling2.start();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await signaling1.stop();
|
||||
await signaling2.stop();
|
||||
signaling1 = null;
|
||||
signaling2 = null;
|
||||
|
||||
if (mode == "mock") {
|
||||
return;
|
||||
}
|
||||
|
||||
await teardown();
|
||||
// work around for linux, waitng kill server process
|
||||
await sleep(1000);
|
||||
});
|
||||
|
||||
test(`onConnect using ${mode}`, async () => {
|
||||
const signaling1Spy = jest.spyOn(signaling1, 'dispatchEvent');
|
||||
let connectRes;
|
||||
let disconnectRes;
|
||||
signaling1.addEventListener('connect', (e) => connectRes = e.detail);
|
||||
signaling1.addEventListener('disconnect', (e) => disconnectRes = e.detail);
|
||||
|
||||
await signaling1.createConnection(connectionId1);
|
||||
await waitFor(() => connectRes != null);
|
||||
expect(connectRes.connectionId).toBe(connectionId1);
|
||||
expect(connectRes.polite).toBe(true);
|
||||
|
||||
await signaling1.deleteConnection(connectionId1);
|
||||
await waitFor(() => disconnectRes != null);
|
||||
expect(disconnectRes.connectionId).toBe(connectionId1);
|
||||
|
||||
const disconnectCalledCount = signaling1Spy.mock.calls.map(x => x[0].type).filter(x => x == "disconnect").length;
|
||||
expect(disconnectCalledCount).toBe(1);
|
||||
|
||||
signaling1Spy.mockRestore();
|
||||
});
|
||||
|
||||
test(`onOffer using ${mode}`, async () => {
|
||||
let connectRes1;
|
||||
let disconnectRes1;
|
||||
signaling1.addEventListener('connect', (e) => connectRes1 = e.detail);
|
||||
signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail);
|
||||
|
||||
let connectRes2;
|
||||
let disconnectRes2;
|
||||
let offerRes2;
|
||||
signaling2.addEventListener('connect', (e) => connectRes2 = e.detail);
|
||||
signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail);
|
||||
signaling2.addEventListener('offer', (e) => offerRes2 = e.detail);
|
||||
|
||||
await signaling1.createConnection(connectionId1);
|
||||
await signaling2.createConnection(connectionId2);
|
||||
await waitFor(() => connectRes1 != null && connectRes2 != null);
|
||||
expect(connectRes1.connectionId).toBe(connectionId1);
|
||||
expect(connectRes2.connectionId).toBe(connectionId2);
|
||||
|
||||
await signaling1.sendOffer(connectionId1, testsdp);
|
||||
await waitFor(() => offerRes2 != null);
|
||||
expect(offerRes2.connectionId).toBe(connectionId1);
|
||||
expect(offerRes2.polite).toBe(false);
|
||||
|
||||
await signaling1.deleteConnection(connectionId1);
|
||||
await waitFor(() => disconnectRes1 != null);
|
||||
expect(disconnectRes1.connectionId).toBe(connectionId1);
|
||||
await signaling2.deleteConnection(connectionId2);
|
||||
await waitFor(() => disconnectRes2 != null);
|
||||
expect(disconnectRes2.connectionId).toBe(connectionId2);
|
||||
});
|
||||
|
||||
test(`onAnswer using ${mode}`, async () => {
|
||||
let connectRes1;
|
||||
let disconnectRes1;
|
||||
let answerRes1;
|
||||
signaling1.addEventListener('connect', (e) => connectRes1 = e.detail);
|
||||
signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail);
|
||||
signaling1.addEventListener('answer', (e) => answerRes1 = e.detail);
|
||||
|
||||
let connectRes2;
|
||||
let disconnectRes2;
|
||||
let offerRes2;
|
||||
signaling2.addEventListener('connect', (e) => connectRes2 = e.detail);
|
||||
signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail);
|
||||
signaling2.addEventListener('offer', (e) => offerRes2 = e.detail);
|
||||
|
||||
await signaling1.createConnection(connectionId1);
|
||||
await signaling2.createConnection(connectionId2);
|
||||
await waitFor(() => connectRes1 != null && connectRes2 != null);
|
||||
|
||||
await signaling1.sendOffer(connectionId1, testsdp);
|
||||
await waitFor(() => offerRes2 != null);
|
||||
expect(offerRes2.connectionId).toBe(connectionId1);
|
||||
expect(offerRes2.sdp).toBe(testsdp);
|
||||
|
||||
signaling2.sendAnswer(connectionId1, testsdp);
|
||||
await waitFor(() => answerRes1 != null);
|
||||
expect(answerRes1.connectionId).toBe(connectionId1);
|
||||
expect(answerRes1.sdp).toBe(testsdp);
|
||||
|
||||
await signaling1.deleteConnection(connectionId1);
|
||||
await waitFor(() => disconnectRes1 != null);
|
||||
await signaling2.deleteConnection(connectionId2);
|
||||
await waitFor(() => disconnectRes2 != null);
|
||||
});
|
||||
|
||||
test(`onCandidate using ${mode}`, async () => {
|
||||
let connectRes1;
|
||||
let disconnectRes1;
|
||||
let answerRes1;
|
||||
let candidateRes1;
|
||||
signaling1.addEventListener('connect', (e) => connectRes1 = e.detail);
|
||||
signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail);
|
||||
signaling1.addEventListener('answer', (e) => answerRes1 = e.detail);
|
||||
signaling1.addEventListener('candidate', (e) => candidateRes1 = e.detail);
|
||||
|
||||
let connectRes2;
|
||||
let disconnectRes2;
|
||||
let offerRes2;
|
||||
let candidateRes2;
|
||||
signaling2.addEventListener('connect', (e) => connectRes2 = e.detail);
|
||||
signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail);
|
||||
signaling2.addEventListener('offer', (e) => offerRes2 = e.detail);
|
||||
signaling2.addEventListener('candidate', (e) => candidateRes2 = e.detail);
|
||||
|
||||
await signaling1.createConnection(connectionId1);
|
||||
await signaling2.createConnection(connectionId2);
|
||||
await waitFor(() => connectRes1 != null && connectRes2 != null);
|
||||
|
||||
await signaling1.sendOffer(connectionId1, testsdp);
|
||||
await waitFor(() => offerRes2 != null);
|
||||
expect(offerRes2.connectionId).toBe(connectionId1);
|
||||
expect(offerRes2.sdp).toBe(testsdp);
|
||||
|
||||
signaling2.sendAnswer(connectionId1, testsdp);
|
||||
await waitFor(() => answerRes1 != null);
|
||||
expect(answerRes1.connectionId).toBe(connectionId1);
|
||||
expect(answerRes1.sdp).toBe(testsdp);
|
||||
|
||||
await signaling2.sendCandidate(connectionId1, testcandidate, 1, 1);
|
||||
await waitFor(() => candidateRes1 != null);
|
||||
expect(candidateRes1.connectionId).toBe(connectionId1);
|
||||
expect(candidateRes1.candidate).toBe(testcandidate);
|
||||
expect(candidateRes1.sdpMid).toBe(1);
|
||||
expect(candidateRes1.sdpMLineIndex).toBe(1);
|
||||
|
||||
await signaling1.sendCandidate(connectionId1, testcandidate, 1, 1);
|
||||
await waitFor(() => candidateRes2 != null);
|
||||
expect(candidateRes2.connectionId).toBe(connectionId1);
|
||||
expect(candidateRes2.candidate).toBe(testcandidate);
|
||||
expect(candidateRes2.sdpMid).toBe(1);
|
||||
expect(candidateRes2.sdpMLineIndex).toBe(1);
|
||||
|
||||
await signaling1.deleteConnection(connectionId1);
|
||||
await waitFor(() => disconnectRes1 != null);
|
||||
await signaling2.deleteConnection(connectionId2);
|
||||
await waitFor(() => disconnectRes2 != null);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
{ mode: "mock" },
|
||||
{ mode: "http" },
|
||||
{ mode: "websocket" },
|
||||
])('signaling test in private mode', ({ mode }) => {
|
||||
let signaling1;
|
||||
let signaling2;
|
||||
const connectionId = "12345";
|
||||
const testsdp = "test sdp";
|
||||
const testcandidate = "test candidate";
|
||||
|
||||
beforeAll(async () => {
|
||||
if (mode == "mock") {
|
||||
reset(true);
|
||||
signaling1 = new MockSignaling(1);
|
||||
signaling2 = new MockSignaling(1);
|
||||
return;
|
||||
}
|
||||
|
||||
const path = Path.resolve(`../bin~/${serverExeName()}`);
|
||||
let cmd = `${path} -p ${portNumber} -m private`;
|
||||
if (mode == "http") {
|
||||
cmd += " -t http";
|
||||
}
|
||||
|
||||
await setup({ command: cmd, port: portNumber, usedPortAction: 'error' });
|
||||
|
||||
if (mode == "http") {
|
||||
signaling1 = new Signaling(1);
|
||||
signaling2 = new Signaling(1);
|
||||
}
|
||||
|
||||
if (mode == "websocket") {
|
||||
signaling1 = new WebSocketSignaling(1);
|
||||
signaling2 = new WebSocketSignaling(1);
|
||||
}
|
||||
|
||||
await signaling1.start();
|
||||
await signaling2.start();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await signaling1.stop();
|
||||
await signaling2.stop();
|
||||
signaling1 = null;
|
||||
signaling2 = null;
|
||||
|
||||
if (mode == "mock") {
|
||||
return;
|
||||
}
|
||||
|
||||
await teardown();
|
||||
// work around for linux, waitng kill server process
|
||||
await sleep(1000);
|
||||
});
|
||||
|
||||
test(`onConnect using ${mode}`, async () => {
|
||||
let connectRes1;
|
||||
let disconnectRes1;
|
||||
signaling1.addEventListener('connect', (e) => connectRes1 = e.detail);
|
||||
signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail);
|
||||
|
||||
let connectRes2;
|
||||
let disconnectRes2;
|
||||
signaling2.addEventListener('connect', (e) => connectRes2 = e.detail);
|
||||
signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail);
|
||||
|
||||
await signaling1.createConnection(connectionId);
|
||||
await waitFor(() => connectRes1 != null);
|
||||
expect(connectRes1.connectionId).toBe(connectionId);
|
||||
expect(connectRes1.polite).toBe(false);
|
||||
|
||||
await signaling2.createConnection(connectionId);
|
||||
await waitFor(() => connectRes2 != null);
|
||||
expect(connectRes2.connectionId).toBe(connectionId);
|
||||
expect(connectRes2.polite).toBe(true);
|
||||
|
||||
await sleep(signaling1.interval * 2);
|
||||
|
||||
await signaling1.deleteConnection(connectionId);
|
||||
await waitFor(() => disconnectRes1 != null && disconnectRes2 != null);
|
||||
expect(disconnectRes1.connectionId).toBe(connectionId);
|
||||
expect(disconnectRes2.connectionId).toBe(connectionId);
|
||||
|
||||
disconnectRes2 = null;
|
||||
await signaling2.deleteConnection(connectionId);
|
||||
await waitFor(() => disconnectRes2 != null);
|
||||
});
|
||||
|
||||
test(`onOffer using ${mode}`, async () => {
|
||||
let connectRes1;
|
||||
let disconnectRes1;
|
||||
signaling1.addEventListener('connect', (e) => connectRes1 = e.detail);
|
||||
signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail);
|
||||
|
||||
let connectRes2;
|
||||
let disconnectRes2;
|
||||
let offerRes2;
|
||||
signaling2.addEventListener('connect', (e) => connectRes2 = e.detail);
|
||||
signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail);
|
||||
signaling2.addEventListener('offer', (e) => offerRes2 = e.detail);
|
||||
|
||||
await signaling1.createConnection(connectionId);
|
||||
await waitFor(() => connectRes1 != null);
|
||||
expect(connectRes1.connectionId).toBe(connectionId);
|
||||
|
||||
signaling1.sendOffer(connectionId, testsdp);
|
||||
await sleep(signaling1.interval * 2);
|
||||
// Do not receive offer other signaling if not connected same sendoffer connectionId in private mode
|
||||
expect(offerRes2).toBeUndefined();
|
||||
|
||||
await signaling2.createConnection(connectionId);
|
||||
await waitFor(() => connectRes2 != null);
|
||||
expect(connectRes2.connectionId).toBe(connectionId);
|
||||
|
||||
await signaling1.sendOffer(connectionId, testsdp);
|
||||
await waitFor(() => offerRes2 != null);
|
||||
expect(offerRes2.connectionId).toBe(connectionId);
|
||||
expect(offerRes2.polite).toBe(true);
|
||||
|
||||
await signaling1.deleteConnection(connectionId);
|
||||
await waitFor(() => disconnectRes1 != null && disconnectRes2 != null);
|
||||
expect(disconnectRes1.connectionId).toBe(connectionId);
|
||||
expect(disconnectRes2.connectionId).toBe(connectionId);
|
||||
|
||||
disconnectRes2 = null;
|
||||
await signaling2.deleteConnection(connectionId);
|
||||
await waitFor(() => disconnectRes2 != null);
|
||||
});
|
||||
|
||||
test(`onAnswer using ${mode}`, async () => {
|
||||
let connectRes1;
|
||||
let disconnectRes1;
|
||||
let answerRes1;
|
||||
signaling1.addEventListener('connect', (e) => connectRes1 = e.detail);
|
||||
signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail);
|
||||
signaling1.addEventListener('answer', (e) => answerRes1 = e.detail);
|
||||
|
||||
let connectRes2;
|
||||
let disconnectRes2;
|
||||
let offerRes2;
|
||||
signaling2.addEventListener('connect', (e) => connectRes2 = e.detail);
|
||||
signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail);
|
||||
signaling2.addEventListener('offer', (e) => offerRes2 = e.detail);
|
||||
|
||||
await signaling1.createConnection(connectionId);
|
||||
await signaling2.createConnection(connectionId);
|
||||
await waitFor(() => connectRes1 != null && connectRes2 != null);
|
||||
|
||||
await signaling1.sendOffer(connectionId, testsdp);
|
||||
await waitFor(() => offerRes2 != null);
|
||||
expect(offerRes2.connectionId).toBe(connectionId);
|
||||
expect(offerRes2.sdp).toBe(testsdp);
|
||||
|
||||
await signaling2.sendAnswer(connectionId, testsdp);
|
||||
await waitFor(() => answerRes1 != null);
|
||||
expect(answerRes1.connectionId).toBe(connectionId);
|
||||
expect(answerRes1.sdp).toBe(testsdp);
|
||||
|
||||
await signaling1.deleteConnection(connectionId);
|
||||
await waitFor(() => disconnectRes1 != null && disconnectRes2 != null);
|
||||
expect(disconnectRes1.connectionId).toBe(connectionId);
|
||||
expect(disconnectRes2.connectionId).toBe(connectionId);
|
||||
|
||||
disconnectRes2 = null;
|
||||
await signaling2.deleteConnection(connectionId);
|
||||
await waitFor(() => disconnectRes2 != null);
|
||||
});
|
||||
|
||||
test(`onCandidate using ${mode}`, async () => {
|
||||
let connectRes1;
|
||||
let disconnectRes1;
|
||||
let answerRes1;
|
||||
let candidateRes1;
|
||||
signaling1.addEventListener('connect', (e) => connectRes1 = e.detail);
|
||||
signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail);
|
||||
signaling1.addEventListener('answer', (e) => answerRes1 = e.detail);
|
||||
signaling1.addEventListener('candidate', (e) => candidateRes1 = e.detail);
|
||||
|
||||
let connectRes2;
|
||||
let disconnectRes2;
|
||||
let offerRes2;
|
||||
let candidateRes2;
|
||||
signaling2.addEventListener('connect', (e) => connectRes2 = e.detail);
|
||||
signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail);
|
||||
signaling2.addEventListener('offer', (e) => offerRes2 = e.detail);
|
||||
signaling2.addEventListener('candidate', (e) => candidateRes2 = e.detail);
|
||||
|
||||
await signaling1.createConnection(connectionId);
|
||||
await signaling2.createConnection(connectionId);
|
||||
await waitFor(() => connectRes1 != null && connectRes2 != null);
|
||||
|
||||
await signaling1.sendOffer(connectionId, testsdp);
|
||||
await waitFor(() => offerRes2 != null);
|
||||
expect(offerRes2.connectionId).toBe(connectionId);
|
||||
expect(offerRes2.sdp).toBe(testsdp);
|
||||
|
||||
await signaling2.sendAnswer(connectionId, testsdp);
|
||||
await waitFor(() => answerRes1 != null);
|
||||
expect(answerRes1.connectionId).toBe(connectionId);
|
||||
expect(answerRes1.sdp).toBe(testsdp);
|
||||
|
||||
await signaling2.sendCandidate(connectionId, testcandidate, 1, 1);
|
||||
await waitFor(() => candidateRes1 != null);
|
||||
expect(candidateRes1.connectionId).toBe(connectionId);
|
||||
expect(candidateRes1.candidate).toBe(testcandidate);
|
||||
expect(candidateRes1.sdpMLineIndex).toBe(1);
|
||||
expect(candidateRes1.sdpMid).toBe(1);
|
||||
|
||||
await signaling1.sendCandidate(connectionId, testcandidate, 1, 1);
|
||||
await waitFor(() => candidateRes2 != null);
|
||||
expect(candidateRes2.connectionId).toBe(connectionId);
|
||||
expect(candidateRes2.candidate).toBe(testcandidate);
|
||||
expect(candidateRes2.sdpMLineIndex).toBe(1);
|
||||
expect(candidateRes2.sdpMid).toBe(1);
|
||||
|
||||
await signaling1.deleteConnection(connectionId);
|
||||
await waitFor(() => disconnectRes1 != null && disconnectRes2 != null);
|
||||
expect(disconnectRes1.connectionId).toBe(connectionId);
|
||||
expect(disconnectRes2.connectionId).toBe(connectionId);
|
||||
|
||||
disconnectRes2 = null;
|
||||
await signaling2.deleteConnection(connectionId);
|
||||
await waitFor(() => disconnectRes2 != null);
|
||||
});
|
||||
|
||||
test(`notReceiveOwnOfferAnswer using ${mode}`, async () => {
|
||||
let connectRes1;
|
||||
let disconnectRes1;
|
||||
let offerRes1;
|
||||
let answerRes1;
|
||||
signaling1.addEventListener('connect', (e) => connectRes1 = e.detail);
|
||||
signaling1.addEventListener('disconnect', (e) => disconnectRes1 = e.detail);
|
||||
|
||||
let connectRes2;
|
||||
let disconnectRes2;
|
||||
let offerRes2;
|
||||
let answerRes2;
|
||||
signaling2.addEventListener('connect', (e) => connectRes2 = e.detail);
|
||||
signaling2.addEventListener('disconnect', (e) => disconnectRes2 = e.detail);
|
||||
|
||||
await signaling1.createConnection(connectionId);
|
||||
await signaling2.createConnection(connectionId);
|
||||
await waitFor(() => connectRes1 != null && connectRes2 != null);
|
||||
|
||||
signaling1.addEventListener('offer', (e) => offerRes1 = e.detail);
|
||||
signaling2.addEventListener('offer', (e) => offerRes2 = e.detail);
|
||||
await signaling1.sendOffer(connectionId, testsdp);
|
||||
await waitFor(() => offerRes2 != null);
|
||||
await sleep(signaling1.interval * 2);
|
||||
expect(offerRes1).toBeUndefined();
|
||||
expect(offerRes2).not.toBeUndefined();
|
||||
expect(offerRes2.connectionId).toBe(connectionId);
|
||||
expect(offerRes2.sdp).toBe(testsdp);
|
||||
|
||||
signaling1.addEventListener('answer', (e) => answerRes1 = e.detail);
|
||||
signaling2.addEventListener('answer', (e) => answerRes2 = e.detail);
|
||||
await signaling2.sendAnswer(connectionId, testsdp);
|
||||
await waitFor(() => answerRes1 != null);
|
||||
await sleep(signaling2.interval * 2);
|
||||
expect(answerRes1).not.toBeUndefined();
|
||||
expect(answerRes1.connectionId).toBe(connectionId);
|
||||
expect(answerRes1.sdp).toBe(testsdp);
|
||||
expect(answerRes2).toBeUndefined();
|
||||
|
||||
await signaling1.deleteConnection(connectionId);
|
||||
await waitFor(() => disconnectRes1 != null && disconnectRes2 != null);
|
||||
expect(disconnectRes1.connectionId).toBe(connectionId);
|
||||
expect(disconnectRes2.connectionId).toBe(connectionId);
|
||||
|
||||
disconnectRes2 = null;
|
||||
await signaling2.deleteConnection(connectionId);
|
||||
await waitFor(() => disconnectRes2 != null);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user