diff --git a/tests/contents/DASH_dynamic_UTCTimings/media/Manifest_with_http.mpd b/tests/contents/DASH_dynamic_UTCTimings/media/Manifest_with_http.mpd index 878334f3169..3b15d2de22c 100644 --- a/tests/contents/DASH_dynamic_UTCTimings/media/Manifest_with_http.mpd +++ b/tests/contents/DASH_dynamic_UTCTimings/media/Manifest_with_http.mpd @@ -1,8 +1,9 @@ + + schemeIdUri="urn:mpeg:dash:utc:http-iso:2014" + value="http://127.0.0.1:3000/time" /> Media Presentation Description from DASHI-IF live simulator diff --git a/tests/contents/DASH_dynamic_UTCTimings/urls.mjs b/tests/contents/DASH_dynamic_UTCTimings/urls.mjs index a1389b29c91..3fc77046c36 100644 --- a/tests/contents/DASH_dynamic_UTCTimings/urls.mjs +++ b/tests/contents/DASH_dynamic_UTCTimings/urls.mjs @@ -220,4 +220,9 @@ export default [ path: path.join(currentDirectory, "./media/V300/776759079.m4s"), contentType: "audio/mp4", }, + { + url: "/time", + data: "2019-05-25T13:49:08.014Z", + contentType: "text/plain", + }, ]; diff --git a/tests/integration/scenarios/dash_forced-subtitles.js b/tests/integration/scenarios/dash_forced-subtitles.js index 685264d3d7e..3b21fd079fd 100644 --- a/tests/integration/scenarios/dash_forced-subtitles.js +++ b/tests/integration/scenarios/dash_forced-subtitles.js @@ -3,14 +3,12 @@ import RxPlayer from "../../../src"; import { forcedSubtitles, } from "../../contents/DASH_static_SegmentTimeline"; -import XHRMock from "../../utils/request_mock"; import { waitForLoadedStateAfterLoadVideo, } from "../../utils/waitForPlayerState"; describe("DASH forced-subtitles content (SegmentTimeline)", function () { let player; - let xhrMock; async function loadContent() { player.loadVideo({ url: forcedSubtitles.url, @@ -44,12 +42,10 @@ describe("DASH forced-subtitles content (SegmentTimeline)", function () { beforeEach(() => { player = new RxPlayer(); player.setWantedBufferAhead(5); // We don't really care - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should use the forced text track linked to the default audio track by default", async function () { diff --git a/tests/integration/scenarios/dash_live.js b/tests/integration/scenarios/dash_live.js index 1b8029643a2..58efa1d1038 100644 --- a/tests/integration/scenarios/dash_live.js +++ b/tests/integration/scenarios/dash_live.js @@ -5,54 +5,70 @@ import { noTimeShiftBufferDepthManifestInfos, } from "../../contents/DASH_dynamic_SegmentTimeline"; import sleep from "../../utils/sleep.js"; -import XHRMock from "../../utils/request_mock"; describe("DASH live content (SegmentTimeline)", function () { let player; - let xhrMock; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should fetch and parse the Manifest", async function () { - xhrMock.lock(); + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + segmentLoaderLoaderCalledTimes++; + // Do nothing here + }; player.loadVideo({ url: manifestInfos.url, - transport: manifestInfos.transport }); + transport: manifestInfos.transport, + manifestLoader, + segmentLoader }); await sleep(1); - expect(xhrMock.getLockedXHR().length).to.equal(1); // Manifest request - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); // Manifest request + await sleep(50); expect(player.getPlayerState()).to.equal("LOADING"); expect(player.isLive()).to.equal(true); expect(player.getContentUrls()).to.eql([manifestInfos.url]); - expect(xhrMock.getLockedXHR().length).to.be.at.least(2); + expect(manifestLoaderCalledTimes).to.equal(1); + expect(segmentLoaderLoaderCalledTimes).to.be.at.least(2); }); describe("getAvailableAudioTracks", () => { it("should list the right audio languages", async function () { - xhrMock.lock(); - + let manifestLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + // Do nothing here + }; player.loadVideo({ url: manifestInfos.url, transport: manifestInfos.transport, + manifestLoader, + segmentLoader, }); expect(player.getAvailableAudioTracks()).to.eql([]); await sleep(1); - expect(player.getAvailableAudioTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); + await sleep(50); const audioTracks = player.getAvailableAudioTracks(); @@ -89,17 +105,26 @@ describe("DASH live content (SegmentTimeline)", function () { describe("getAvailableTextTracks", () => { it("should list the right text languages", async function () { - xhrMock.lock(); + let manifestLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + // Do nothing here + }; player.loadVideo({ url: manifestInfos.url, transport: manifestInfos.transport, + manifestLoader, + segmentLoader, }); expect(player.getAvailableTextTracks()).to.eql([]); await sleep(1); - expect(player.getAvailableTextTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); + await sleep(50); const textTracks = player.getAvailableTextTracks(); @@ -136,18 +161,26 @@ describe("DASH live content (SegmentTimeline)", function () { describe("getAvailableVideoTracks", () => { it("should list the right video tracks", async function () { - xhrMock.lock(); - + let manifestLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + // Do nothing here + }; player.loadVideo({ url: manifestInfos.url, transport:manifestInfos.transport, + manifestLoader, + segmentLoader, }); expect(player.getAvailableVideoTracks()).to.eql([]); - await sleep(1); expect(player.getAvailableVideoTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); + await sleep(50); const videoTracks = player.getAvailableVideoTracks(); @@ -195,14 +228,22 @@ describe("DASH live content (SegmentTimeline)", function () { describe("getMinimumPosition", () => { it("should return the last position minus the TimeShift window", async () => { - xhrMock.lock(); - + let manifestLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + // Do nothing here + }; player.loadVideo({ url: manifestInfos.url, - transport:manifestInfos.transport }); + transport:manifestInfos.transport, + manifestLoader, + segmentLoader }); - await sleep(1); - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); + await sleep(50); expect(player.getMinimumPosition()).to.be .closeTo(1527507768, 1); }); @@ -210,16 +251,24 @@ describe("DASH live content (SegmentTimeline)", function () { describe("getMaximumPosition", () => { it("should return the last playable position", async () => { - xhrMock.lock(); - + let manifestLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + // Do nothing here + }; player.loadVideo({ url: manifestInfos.url, transport:manifestInfos.transport, + manifestLoader, + segmentLoader, }); - await sleep(1); - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); + await sleep(50); expect(player.getMaximumPosition()).to.be .closeTo(1527508062, 1); }); @@ -228,52 +277,67 @@ describe("DASH live content (SegmentTimeline)", function () { describe("DASH live content with no timeShiftBufferDepth (SegmentTimeline)", function () { let player; - let xhrMock; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should fetch and parse the Manifest", async function () { - xhrMock.lock(); + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(noTimeShiftBufferDepthManifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + segmentLoaderLoaderCalledTimes++; + }; player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport: noTimeShiftBufferDepthManifestInfos.transport, + manifestLoader, + segmentLoader, }); await sleep(1); - expect(xhrMock.getLockedXHR().length).to.equal(1); // Manifest request - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); + await sleep(50); expect(player.getPlayerState()).to.equal("LOADING"); expect(player.isLive()).to.equal(true); expect(player.getContentUrls()) .to.eql([noTimeShiftBufferDepthManifestInfos.url]); - expect(xhrMock.getLockedXHR().length).to.be.at.least(2); + expect(segmentLoaderLoaderCalledTimes).to.be.at.least(2); }); describe("getAvailableAudioTracks", () => { it("should list the right audio languages", async function () { - xhrMock.lock(); - + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(noTimeShiftBufferDepthManifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + segmentLoaderLoaderCalledTimes++; + }; player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport: noTimeShiftBufferDepthManifestInfos.transport, + manifestLoader, + segmentLoader, }); expect(player.getAvailableAudioTracks()).to.eql([]); - await sleep(1); - expect(player.getAvailableAudioTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); + await sleep(50); const audioTracks = player.getAvailableAudioTracks(); @@ -305,22 +369,33 @@ describe("DASH live content with no timeShiftBufferDepth (SegmentTimeline)", fun } } } + expect(segmentLoaderLoaderCalledTimes).to.be.at.least(2); }); }); describe("getAvailableTextTracks", () => { it("should list the right text languages", async function () { - xhrMock.lock(); + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(noTimeShiftBufferDepthManifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + segmentLoaderLoaderCalledTimes++; + }; player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport: noTimeShiftBufferDepthManifestInfos.transport, + manifestLoader, + segmentLoader, }); expect(player.getAvailableTextTracks()).to.eql([]); - await sleep(1); expect(player.getAvailableTextTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); + await sleep(50); const textTracks = player.getAvailableTextTracks(); @@ -352,23 +427,33 @@ describe("DASH live content with no timeShiftBufferDepth (SegmentTimeline)", fun } } } + expect(segmentLoaderLoaderCalledTimes).to.be.at.least(2); }); }); describe("getAvailableVideoTracks", () => { it("should list the right video tracks", async function () { - xhrMock.lock(); - + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(noTimeShiftBufferDepthManifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + segmentLoaderLoaderCalledTimes++; + }; player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport:noTimeShiftBufferDepthManifestInfos.transport, + manifestLoader, + segmentLoader, }); expect(player.getAvailableVideoTracks()).to.eql([]); - await sleep(1); expect(player.getAvailableVideoTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); + await sleep(50); const videoTracks = player.getAvailableVideoTracks(); @@ -411,39 +496,60 @@ describe("DASH live content with no timeShiftBufferDepth (SegmentTimeline)", fun } } } + expect(segmentLoaderLoaderCalledTimes).to.be.at.least(2); }); }); describe("getMinimumPosition", () => { it("should return the time of the first segment declared", async () => { - xhrMock.lock(); - + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(noTimeShiftBufferDepthManifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + segmentLoaderLoaderCalledTimes++; + }; player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport:noTimeShiftBufferDepthManifestInfos.transport, + manifestLoader, + segmentLoader, }); - await sleep(1); - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); + await sleep(50); expect(player.getMinimumPosition()).to.equal(6); + expect(segmentLoaderLoaderCalledTimes).to.be.at.least(2); }); }); describe("getMaximumPosition", () => { it("should return the last playable position", async () => { - xhrMock.lock(); - + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(noTimeShiftBufferDepthManifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + segmentLoaderLoaderCalledTimes++; + }; player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport:noTimeShiftBufferDepthManifestInfos.transport, + manifestLoader, + segmentLoader, }); - await sleep(1); - await xhrMock.flush(); - await sleep(1); + expect(manifestLoaderCalledTimes).to.equal(1); + await sleep(50); expect(player.getMaximumPosition()).to.be .closeTo(1527508062, 1); + expect(segmentLoaderLoaderCalledTimes).to.be.at.least(2); }); }); }); diff --git a/tests/integration/scenarios/dash_live_SegmentTemplate.js b/tests/integration/scenarios/dash_live_SegmentTemplate.js index e565a694588..730f1577476 100644 --- a/tests/integration/scenarios/dash_live_SegmentTemplate.js +++ b/tests/integration/scenarios/dash_live_SegmentTemplate.js @@ -5,58 +5,70 @@ import { noTimeShiftBufferDepthManifestInfos, } from "../../contents/DASH_dynamic_SegmentTemplate"; import sleep from "../../utils/sleep.js"; -import XHRMock from "../../utils/request_mock"; +import { waitForLoadedStateAfterLoadVideo } from "../../utils/waitForPlayerState"; describe("DASH live content (SegmentTemplate)", function() { let player; - let xhrMock; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should fetch and parse the manifest", async function() { - xhrMock.lock(); - + let manifestLoaderCalledTimes = 0; + const requestedSegments = []; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = (info, callbacks) => { + requestedSegments.push(info.url); + callbacks.fallback(); + // do nothing else + }; player.loadVideo({ url: manifestInfos.url, transport: manifestInfos.transport, + manifestLoader, + segmentLoader, + requestConfig: { + segment: { + maxRetry: Infinity, + }, + }, }); - await sleep(1); - expect(xhrMock.getLockedXHR().length).to.equal(1); - await xhrMock.flush(); - await sleep(1); - + expect(manifestLoaderCalledTimes).to.equal(1); + expect(player.getPlayerState()).to.equal("LOADING"); + await sleep(100); + expect(manifestLoaderCalledTimes).to.equal(1); expect(player.getPlayerState()).to.equal("LOADING"); expect(player.isLive()).to.equal(true); expect(player.getContentUrls()).to.eql([manifestInfos.url]); - expect(xhrMock.getLockedXHR().length).to.be.at.least(2); + expect(requestedSegments.length).to.be.at.least(2); }); describe("getAvailableAudioTracks", () => { it("should list the right audio languages", async function () { - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport: manifestInfos.transport, + requestConfig: { + segment: { + maxRetry: Infinity, + }, + }, }); expect(player.getAvailableAudioTracks()).to.eql([]); - await sleep(1); - expect(player.getAvailableAudioTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); - + await sleep(100); const audioTracks = player.getAvailableAudioTracks(); const audioAdaptations = manifestInfos.periods[0].adaptations.audio; @@ -92,19 +104,18 @@ describe("DASH live content (SegmentTemplate)", function() { describe("getAvailableTextTracks", () => { it("should list the right text languages", async function () { - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport: manifestInfos.transport, + requestConfig: { + segment: { + maxRetry: Infinity, + }, + }, }); expect(player.getAvailableTextTracks()).to.eql([]); - await sleep(1); - expect(player.getAvailableTextTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); - + await sleep(100); const textTracks = player.getAvailableTextTracks(); const textAdaptations = manifestInfos.periods[0].adaptations.text; @@ -140,18 +151,17 @@ describe("DASH live content (SegmentTemplate)", function() { describe("getAvailableVideoTracks", () => { it("should list the right video tracks", async function () { - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport:manifestInfos.transport, + requestConfig: { + segment: { + maxRetry: Infinity, + }, + }, }); expect(player.getAvailableVideoTracks()).to.eql([]); - - await sleep(1); - expect(player.getAvailableVideoTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); + await sleep(100); const videoTracks = player.getAvailableVideoTracks(); @@ -199,32 +209,31 @@ describe("DASH live content (SegmentTemplate)", function() { describe("getMinimumPosition", () => { it("should return the last position minus the TimeShift window", async () => { - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport:manifestInfos.transport, + requestConfig: { + segment: { + maxRetry: Infinity, + }, + }, }); - await sleep(1); - await xhrMock.flush(); - await sleep(1); + expect(player.getMinimumPosition()).to.eql(null); + await sleep(100); expect(player.getMinimumPosition()).to.be.closeTo(1553521448, 1); }); }); describe("getMaximumPosition", () => { it("should return the last playable position", async () => { - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport:manifestInfos.transport, }); - await sleep(1); - await xhrMock.flush(); - await sleep(1); + expect(player.getMaximumPosition()).to.eql(null); + await sleep(100); expect(player.getMaximumPosition()).to.be.closeTo(1553521748, 1); }); }); @@ -232,55 +241,64 @@ describe("DASH live content (SegmentTemplate)", function() { describe("DASH live content without timeShiftBufferDepth (SegmentTemplate)", function() { let player; - let xhrMock; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should fetch and parse the manifest", async function() { - xhrMock.lock(); - + let manifestLoaderCalledTimes = 0; + const requestedSegments = []; + const manifestLoader = (man, callbacks) => { + expect(man.url).to.equal(noTimeShiftBufferDepthManifestInfos.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = (info, callbacks) => { + requestedSegments.push(info.url); + callbacks.fallback(); + // do nothing else + }; player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport: noTimeShiftBufferDepthManifestInfos.transport, + manifestLoader, + segmentLoader, + requestConfig: { + segment: { + maxRetry: Infinity, + }, + }, }); - await sleep(1); - expect(xhrMock.getLockedXHR().length).to.equal(1); - await xhrMock.flush(); - await sleep(1); - + expect(manifestLoaderCalledTimes).to.equal(1); expect(player.getPlayerState()).to.equal("LOADING"); - + await sleep(100); + expect(manifestLoaderCalledTimes).to.equal(1); expect(player.isLive()).to.equal(true); expect(player.getContentUrls()) .to.eql([noTimeShiftBufferDepthManifestInfos.url]); - - expect(xhrMock.getLockedXHR().length).to.be.at.least(2); + expect(requestedSegments.length).to.be.at.least(2); }); describe("getAvailableAudioTracks", () => { it("should list the right audio languages", async function () { - xhrMock.lock(); - player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport: noTimeShiftBufferDepthManifestInfos.transport, + requestConfig: { + segment: { + maxRetry: Infinity, + }, + }, }); expect(player.getAvailableAudioTracks()).to.eql([]); - await sleep(1); - expect(player.getAvailableAudioTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); - + await sleep(100); const audioTracks = player.getAvailableAudioTracks(); const audioAdaptations = manifestInfos.periods[0].adaptations.audio; @@ -316,19 +334,18 @@ describe("DASH live content without timeShiftBufferDepth (SegmentTemplate)", fun describe("getAvailableTextTracks", () => { it("should list the right text languages", async function () { - xhrMock.lock(); player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport: noTimeShiftBufferDepthManifestInfos.transport, + requestConfig: { + segment: { + maxRetry: Infinity, + }, + }, }); expect(player.getAvailableTextTracks()).to.eql([]); - - await sleep(1); - expect(player.getAvailableTextTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); - + await sleep(100); const textTracks = player.getAvailableTextTracks(); const textAdaptations = manifestInfos.periods[0].adaptations.text; @@ -364,19 +381,18 @@ describe("DASH live content without timeShiftBufferDepth (SegmentTemplate)", fun describe("getAvailableVideoTracks", () => { it("should list the right video tracks", async function () { - xhrMock.lock(); - player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport:noTimeShiftBufferDepthManifestInfos.transport, + requestConfig: { + segment: { + maxRetry: Infinity, + }, + }, }); expect(player.getAvailableVideoTracks()).to.eql([]); - await sleep(1); - expect(player.getAvailableVideoTracks()).to.eql([]); - await xhrMock.flush(); - await sleep(1); - + await sleep(100); const videoTracks = player.getAvailableVideoTracks(); const videoAdaptations = manifestInfos.periods[0].adaptations.video; @@ -423,32 +439,36 @@ describe("DASH live content without timeShiftBufferDepth (SegmentTemplate)", fun describe("getMinimumPosition", () => { it("should return the period start if one", async () => { - xhrMock.lock(); - player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport:noTimeShiftBufferDepthManifestInfos.transport, + requestConfig: { + segment: { + maxRetry: Infinity, + }, + }, }); - await sleep(1); - await xhrMock.flush(); - await sleep(1); + expect(player.getMinimumPosition()).to.eql(null); + await sleep(100); expect(player.getMinimumPosition()).to.equal(1553515200); }); }); describe("getMaximumPosition", () => { it("should return the last playable position", async () => { - xhrMock.lock(); - player.loadVideo({ url: noTimeShiftBufferDepthManifestInfos.url, transport:noTimeShiftBufferDepthManifestInfos.transport, + requestConfig: { + segment: { + maxRetry: Infinity, + }, + }, }); - await sleep(1); - await xhrMock.flush(); - await sleep(1); + expect(player.getMaximumPosition()).to.eql(null); + await sleep(100); expect(player.getMaximumPosition()).to.be .closeTo(1553521448, 3); }); diff --git a/tests/integration/scenarios/dash_live_multi_periods.js b/tests/integration/scenarios/dash_live_multi_periods.js index ad9c015e7cc..4ff594e78c0 100644 --- a/tests/integration/scenarios/dash_live_multi_periods.js +++ b/tests/integration/scenarios/dash_live_multi_periods.js @@ -1,5 +1,4 @@ import { expect } from "chai"; -import XHRMock from "../../utils/request_mock"; import RxPlayer from "../../../src"; import { manifestInfos } from "../../contents/DASH_dynamic_SegmentTemplate_Multi_Periods"; import sinon from "sinon"; @@ -24,54 +23,42 @@ const sleepWithoutSinonStub = (function() { describe("DASH live content multi-periods (SegmentTemplate)", function() { let player; - let xhrMock; let clock; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); clock = sinon.useFakeTimers((1567781280 + 500) * 1000); }); afterEach(() => { player.dispose(); - xhrMock.restore(); clock.restore(); }); - it("should return correct maximum position", async () => { - xhrMock.lock(); - + it("should return correct maximum and minimum positions", async () => { + let manifestLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + // Do nothing here + }; player.loadVideo({ url: manifestInfos.url, transport: manifestInfos.transport, + manifestLoader, + segmentLoader, }); - await sleepWithoutSinonStub(1); - expect(xhrMock.getLockedXHR().length).to.equal(1); - await xhrMock.flush(); - await sleepWithoutSinonStub(1); + await sleepWithoutSinonStub(50); const now = 1567781280 + 500; const maxPos = player.getMaximumPosition(); expect(maxPos).to.be.closeTo(now, 2); - }); - - it("should return correct minimum position", async () => { - xhrMock.lock(); - - player.loadVideo({ - url: manifestInfos.url, - transport: manifestInfos.transport, - }); - - await sleepWithoutSinonStub(1); - expect(xhrMock.getLockedXHR().length).to.equal(1); - await xhrMock.flush(); - await sleepWithoutSinonStub(1); - - const now = 1567781280 + 500; const minPos = player.getMinimumPosition(); expect(minPos).to.be.closeTo(now - manifestInfos.tsbd, 2); + expect(manifestLoaderCalledTimes).to.equal(1); }); }); diff --git a/tests/integration/scenarios/dash_live_utc_timings.js b/tests/integration/scenarios/dash_live_utc_timings.js index cd106e0a867..b50ebd428a0 100644 --- a/tests/integration/scenarios/dash_live_utc_timings.js +++ b/tests/integration/scenarios/dash_live_utc_timings.js @@ -7,34 +7,25 @@ import { WithoutTimings, } from "../../contents/DASH_dynamic_UTCTimings"; import sleep from "../../utils/sleep.js"; -import XHRMock from "../../utils/request_mock"; describe("DASH live - UTCTimings", () => { describe("DASH live content (SegmentTemplate + Direct UTCTiming)", function () { const { manifestInfos } = WithDirect; let player; - let xhrMock; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should calculate the right bounds", async () => { - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport:manifestInfos.transport, }); - - await sleep(100); - await xhrMock.flush(); await sleep(100); expect(player.getMinimumPosition()).to.be .closeTo(1553521448, 3); @@ -43,8 +34,6 @@ describe("DASH live - UTCTimings", () => { }); it("should consider `serverSyncInfos` if provided", async () => { - xhrMock.lock(); - const serverTimestamp = +new Date("2019-03-25T13:54:08.000Z"); player.loadVideo({ url: manifestInfos.url, transport:manifestInfos.transport, @@ -52,9 +41,6 @@ describe("DASH live - UTCTimings", () => { serverTimestamp, clientTime: performance.now(), } }); - - await sleep(100); - await xhrMock.flush(); await sleep(100); expect(player.getMinimumPosition()).to.be .closeTo(1553521748, 1); @@ -66,45 +52,32 @@ describe("DASH live - UTCTimings", () => { describe("DASH live content (SegmentTemplate + HTTP UTCTiming)", function () { const { manifestInfos } = WithHTTP; let player; - let xhrMock; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should fetch the clock and then calculate the right bounds", async () => { - xhrMock.respondTo("GET", - "https://time.akamai.com/?iso", - [ 200, - { "Content-Type": "text/plain"}, - "2019-03-25T13:49:08.014Z"]); - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport:manifestInfos.transport, + manifestLoader(arg, cbs) { + cbs.fallback(); + }, + segmentLoader() { + // noop + }, }); - - await sleep(100); - await xhrMock.flush(); // Manifest request - await sleep(100); - await xhrMock.flush(); // time request - await sleep(100); - await xhrMock.flush(); // Once for the init segment - await sleep(100); + await sleep(200); expect(player.getMinimumPosition()).to.be - .closeTo(1553521448, 3); + .closeTo(1558791848, 3); }); it("should consider `serverSyncInfos` if provided", async () => { - xhrMock.lock(); - const serverTimestamp = +new Date("2019-03-25T13:54:08.000Z"); player.loadVideo({ url: manifestInfos.url, @@ -115,8 +88,6 @@ describe("DASH live - UTCTimings", () => { }, }); - await sleep(100); - await xhrMock.flush(); await sleep(100); expect(player.getMinimumPosition()).to.be .closeTo(1553521748, 1); @@ -128,28 +99,20 @@ describe("DASH live - UTCTimings", () => { describe("DASH live content (SegmentTemplate + Without Timing)", function() { const { manifestInfos } = WithoutTimings; let player; - let xhrMock; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should calculate the right bounds", async () => { - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport:manifestInfos.transport, }); - - await sleep(100); - await xhrMock.flush(); await sleep(100); const timeShiftBufferDepth = 5 * 60; @@ -164,8 +127,6 @@ describe("DASH live - UTCTimings", () => { }); it("should consider `serverSyncInfos` if provided", async () => { - xhrMock.lock(); - const serverTimestamp = +new Date("2019-03-25T13:54:08.000Z"); player.loadVideo({ url: manifestInfos.url, @@ -176,8 +137,6 @@ describe("DASH live - UTCTimings", () => { }, }); - await sleep(100); - await xhrMock.flush(); await sleep(100); expect(player.getMinimumPosition()).to.be .closeTo(1553521748, 1); @@ -189,40 +148,27 @@ describe("DASH live - UTCTimings", () => { describe("DASH live content (SegmentTemplate + Direct & HTTP UTCTiming)", function () { const { manifestInfos } = WithDirectAndHTTP; let player; - let xhrMock; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should not fetch the clock but still calculate the right bounds", async () => { - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport:manifestInfos.transport, }); - await sleep(100); - await xhrMock.flush(); - await sleep(100); + await sleep(200); expect(player.getMinimumPosition()).to.be .closeTo(1553521448, 3); - - const requestsDone = xhrMock.getLockedXHR().map(r => r.url); - expect(requestsDone) - .not.to.include("https://time.akamai.com/?iso"); }); it("should consider `serverSyncInfos` if provided", async () => { - xhrMock.lock(); - const serverTimestamp = +new Date("2019-03-25T13:54:08.000Z"); player.loadVideo({ url: manifestInfos.url, @@ -233,8 +179,6 @@ describe("DASH live - UTCTimings", () => { }, }); - await sleep(100); - await xhrMock.flush(); await sleep(100); expect(player.getMinimumPosition()).to.be .closeTo(1553521748, 1); diff --git a/tests/integration/scenarios/dash_multi-track.js b/tests/integration/scenarios/dash_multi-track.js index be6b4dc2948..b10b411cb06 100644 --- a/tests/integration/scenarios/dash_multi-track.js +++ b/tests/integration/scenarios/dash_multi-track.js @@ -4,24 +4,20 @@ import { multiAdaptationSetsInfos, } from "../../contents/DASH_static_SegmentTimeline"; import sleep from "../../utils/sleep.js"; -import XHRMock from "../../utils/request_mock"; import waitForPlayerState, { waitForLoadedStateAfterLoadVideo, } from "../../utils/waitForPlayerState"; describe("DASH multi-track content (SegmentTimeline)", function () { let player; - let xhrMock; beforeEach(() => { player = new RxPlayer(); player.setWantedBufferAhead(5); // We don't really care - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); async function loadContent() { @@ -265,21 +261,29 @@ describe("DASH multi-track content (SegmentTimeline)", function () { it("should properly load the content with the right default tracks", async function () { this.timeout(3000); - xhrMock.lock(); - player.loadVideo({ url: multiAdaptationSetsInfos.url, - transport: multiAdaptationSetsInfos.transport }); - - await sleep(10); - - expect(xhrMock.getLockedXHR().length).to.equal(1); // Manifest request - await xhrMock.flush(); - await sleep(10); + let manifestLoaderCalledTimes = 0; + const requestedSegments = []; + const manifestLoader = (man, callbacks) => { + expect(multiAdaptationSetsInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = (info, callbacks) => { + requestedSegments.push(info.url); + callbacks.fallback(); + }; + player.loadVideo({ + url: multiAdaptationSetsInfos.url, + transport: multiAdaptationSetsInfos.transport, + manifestLoader, + segmentLoader, + }); + expect(manifestLoaderCalledTimes).to.equal(1); + expect(requestedSegments).to.be.empty; expect(player.getPlayerState()).to.equal("LOADING"); - await xhrMock.unlock(); - await sleep(1500); - - expect(player.getPlayerState()).to.equal("LOADED"); + await waitForLoadedStateAfterLoadVideo(player); + expect(requestedSegments.length).to.be.above(2); checkAudioTrack("de", "deu", false); checkNoTextTrack(); @@ -293,16 +297,26 @@ describe("DASH multi-track content (SegmentTimeline)", function () { }); it("should allow setting tracks BEFORE loading segments", async function () { + const requestedSegments = []; + const manifestLoader = (man, callbacks) => { + expect(multiAdaptationSetsInfos.url).to.equal(man.url); + callbacks.fallback(); + manifestRequestDone = true; + }; + const segmentLoader = (info, callbacks) => { + requestedSegments.push(info.url); + callbacks.fallback(); + }; player = new RxPlayer(); - let called = 0; + let eventCalled = 0; let manifestRequestDone = false; - let afterManifestResponse = false; player.addEventListener("newAvailablePeriods", () => { - called++; + eventCalled++; expect(manifestRequestDone).to.equal(true); - expect(afterManifestResponse).to.equal(called > 1); - expect(xhrMock.getLockedXHR()).to.have.lengthOf(0); + if (eventCalled === 1) { + expect(requestedSegments.length === 0).to.equal(true); + } updateTracks( [ { language: "fr", audioDescription: false }, @@ -318,33 +332,31 @@ describe("DASH multi-track content (SegmentTimeline)", function () { ] ); }); - xhrMock.lock(); - player.loadVideo({ url: multiAdaptationSetsInfos.url, - transport: multiAdaptationSetsInfos.transport }); - - await sleep(10); + player.loadVideo({ + url: multiAdaptationSetsInfos.url, + transport: multiAdaptationSetsInfos.transport, + manifestLoader, + segmentLoader, + }); - expect(xhrMock.getLockedXHR().length).to.equal(1); // Manifest request - manifestRequestDone = true; - await xhrMock.flush(); - afterManifestResponse = true; - await sleep(10); + await sleep(50); - expect(called).to.equal(1); + expect(eventCalled).to.equal(1); checkAudioTrack("fr", "fra", false); checkTextTrack("de", "deu", false); checkVideoTrack({ all: true, test: /avc1\.42C014/ }, true); - xhrMock.unlock(); - await waitForLoadedStateAfterLoadVideo(player); - expect(called).to.equal(1); + if (player.getPlayerState() !== "LOADED") { + await waitForLoadedStateAfterLoadVideo(player); + } + expect(eventCalled).to.equal(1); checkAudioTrack("fr", "fra", false); checkTextTrack("de", "deu", false); checkVideoTrack({ all: true, test: /avc1\.42C014/ }, true); await goToSecondPeriod(); - expect(called).to.equal(2); + expect(eventCalled).to.equal(2); checkAudioTrack("de", "deu", true); checkTextTrack("fr", "fra", true); checkVideoTrack({ all: false, test: /avc1\.640028/ }, undefined); diff --git a/tests/integration/scenarios/dash_static.js b/tests/integration/scenarios/dash_static.js index c7b60372e58..67f11df194f 100644 --- a/tests/integration/scenarios/dash_static.js +++ b/tests/integration/scenarios/dash_static.js @@ -1,7 +1,6 @@ import { expect } from "chai"; import { stub } from "sinon"; import RxPlayer from "../../../src"; -import XHRMock from "../../utils/request_mock"; import launchTestsForContent from "../utils/launch_tests_for_content.js"; import sleep from "../../utils/sleep.js"; import waitForPlayerState from "../../utils/waitForPlayerState"; @@ -54,7 +53,6 @@ describe("DASH content CENC wrong version in MPD", function () { (bytes[offset + 3])); } let player; - let xhrMock; let stubs = []; beforeEach(() => { }); @@ -63,10 +61,6 @@ describe("DASH content CENC wrong version in MPD", function () { player.dispose(); player = undefined; } - if (xhrMock !== undefined) { - xhrMock.restore(); - xhrMock = undefined; - } stubs.forEach(declaredStub => declaredStub.restore()); stubs = []; }); @@ -82,8 +76,6 @@ describe("DASH content CENC wrong version in MPD", function () { let foundCencV1 = false; let foundOtherCencVersion = false; player = new RxPlayer(); - xhrMock = new XHRMock(); - xhrMock.lock(); const generateRequestStub = stub(window.MediaKeySession.prototype, "generateRequest") .callsFake((_initDataType, initData) => { let offset = 0; @@ -144,11 +136,7 @@ describe("DASH content CENC wrong version in MPD", function () { }, }, ] }); - await sleep(50); - xhrMock.flush(); // MPD request await sleep(200); - xhrMock.flush(); // init segment requests - await sleep(50); expect(generateRequestStub.called).to.equal(true, "generateRequest was not called"); expect(foundCencV1).to.equal(true, @@ -171,97 +159,126 @@ describe("DASH non-linear content with a \"broken\" sidx", function() { } const player = new RxPlayer(); - player.setWantedBufferAhead(1); - const xhrMock = new XHRMock(); + const requestedManifests = []; + const requestedSegments = []; + const manifestLoader = (man, callbacks) => { + requestedManifests.push(man.url); + callbacks.fallback(); + }; + const segmentLoader = (info, callbacks) => { + requestedSegments.push(info); + callbacks.fallback(); + }; - // Play a first time without the option + player.setWantedBufferAhead(1); - player.loadVideo({ url: brokenSidxManifestInfos.url, - transport: brokenSidxManifestInfos.transport, - checkMediaSegmentIntegrity: true, - autoPlay: false }); + // Play a first time without the option + player.loadVideo({ + url: brokenSidxManifestInfos.url, + transport: brokenSidxManifestInfos.transport, + checkMediaSegmentIntegrity: true, + autoPlay: false, + manifestLoader, + segmentLoader, + }); await waitForPlayerState(player, "LOADED"); - xhrMock.lock(); + requestedSegments.length = 0; player.seekTo(player.getMaximumPosition() - 1); + await sleep(10); + player.setWantedBufferAhead(30); await sleep(50); - let lockedXhrs = xhrMock.getLockedXHR(); - expect(lockedXhrs.length).to.equal(2); - const foundTruncatedVideoSegment = lockedXhrs + expect(requestedSegments.length).to.be.at.least(2); + const foundTruncatedVideoSegment = requestedSegments .some(seg => { return isBrokenVideoSegment(seg.url) && - seg.requestHeadersSet.some(requestHeaderSet => - requestHeaderSet[0].toLowerCase() === "range" && - requestHeaderSet[1].toLowerCase() === "bytes=696053-746228"); + seg.byteRanges.some(byteRange => + byteRange[0] === 696053 && + byteRange[1] === 746228); }); expect(foundTruncatedVideoSegment) .to.equal(true, "should have requested the video segment with a bad range initially"); - xhrMock.unlock(); + + player.setWantedBufferAhead(1); // Play a second time with the option - player.loadVideo({ url: brokenSidxManifestInfos.url, - transport: brokenSidxManifestInfos.transport, - checkMediaSegmentIntegrity: true, - __priv_patchLastSegmentInSidx: true, - autoPlay: false }); + player.loadVideo({ + url: brokenSidxManifestInfos.url, + transport: brokenSidxManifestInfos.transport, + checkMediaSegmentIntegrity: true, + __priv_patchLastSegmentInSidx: true, + autoPlay: false, + manifestLoader, + segmentLoader, + }); await waitForPlayerState(player, "LOADED"); - xhrMock.lock(); + requestedSegments.length = 0; player.seekTo(player.getMaximumPosition() - 1); + await sleep(10); + player.setWantedBufferAhead(30); await sleep(50); - lockedXhrs = xhrMock.getLockedXHR(); - expect(lockedXhrs.length).to.equal(2); - const foundFixedVideoSegment = lockedXhrs + expect(requestedSegments.length).to.be.at.least(2); + const foundFixedVideoSegment = requestedSegments .some(seg => { return isBrokenVideoSegment(seg.url) && - seg.requestHeadersSet.some(requestHeaderSet => - requestHeaderSet[0].toLowerCase() === "range" && - requestHeaderSet[1].toLowerCase() === "bytes=696053-"); + seg.byteRanges.some(byteRange => + byteRange[0] === 696053 && + byteRange[1] === Infinity); }); expect(foundFixedVideoSegment) .to.equal(true, "should have fixed the video segment with a bad range"); - xhrMock.restore(); player.dispose(); }); }); describe("DASH non-linear content with number-based SegmentTimeline", function () { - let xhrMock; let player; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should correctly parse DASH number-based SegmentTimeline", async function () { - xhrMock.lock(); - - player.loadVideo({ url: numberBasedTimelineManifestInfos.url, - transport: numberBasedTimelineManifestInfos.transport }); + const requestedManifests = []; + const requestedSegments = []; + const manifestLoader = (man, callbacks) => { + requestedManifests.push(man.url); + callbacks.fallback(); + }; + const segmentLoader = (info, callbacks) => { + requestedSegments.push(info.url); + callbacks.fallback(); + }; + player.loadVideo({ + url: numberBasedTimelineManifestInfos.url, + transport: numberBasedTimelineManifestInfos.transport, + manifestLoader, + segmentLoader, + }); - // should only have the manifest for now - await sleep(1); - expect(xhrMock.getLockedXHR().length).to.equal(1); - expect(xhrMock.getLockedXHR()[0].url) + expect(requestedManifests).to.have.length(1); + expect(requestedSegments).to.be.empty; + expect(requestedManifests[0]) .to.equal(numberBasedTimelineManifestInfos.url); - - await xhrMock.flush(); // only wait for the manifest request - await sleep(10); - expect(player.getPlayerState()).to.equal("LOADING"); + await sleep(100); + // segment requests should be pending - expect(xhrMock.getLockedXHR().length).to.be.at.least(1); + expect(requestedSegments.length).to.be.at.least(1); + expect(requestedSegments).to + .include("http://127.0.0.1:3000/DASH_static_number_based_SegmentTimeline/media/video_190_avc-1.mp4"); + expect(requestedSegments).to + .include("http://127.0.0.1:3000/DASH_static_number_based_SegmentTimeline/media/audio_64_aaclc_fra-1.ts"); }); }); diff --git a/tests/integration/scenarios/discontinuities.js b/tests/integration/scenarios/discontinuities.js index 6901379ae16..4883ae42678 100644 --- a/tests/integration/scenarios/discontinuities.js +++ b/tests/integration/scenarios/discontinuities.js @@ -1,6 +1,5 @@ import { expect } from "chai"; import RxPlayer from "../../../src"; -import XHRMock from "../../utils/request_mock"; import sleep from "../../utils/sleep.js"; import { waitForLoadedStateAfterLoadVideo, @@ -15,17 +14,14 @@ import { } from "../../contents/DASH_static_SegmentTimeline"; let player; -let xhrMock; describe("discontinuities handling", () => { beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); describe("discontinuities between periods", () => { diff --git a/tests/integration/scenarios/end_number.js b/tests/integration/scenarios/end_number.js index c2c73af4362..19790c41952 100644 --- a/tests/integration/scenarios/end_number.js +++ b/tests/integration/scenarios/end_number.js @@ -1,5 +1,4 @@ import { expect } from "chai"; -import XHRMock from "../../utils/request_mock"; import { manifestInfosEndNumber as numberBasedManifestInfos, } from "../../contents/DASH_static_number_based_SegmentTimeline"; @@ -17,20 +16,16 @@ import {lockLowestBitrates} from "../../utils/bitrates"; let player; describe("end number", function () { - let xhrMock; beforeEach(() => { player = new RxPlayer({ stopAtEnd: false }); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should calculate the right duration according to endNumber on a number-based SegmentTemplate", async function () { this.timeout(3000); - xhrMock.lock(); lockLowestBitrates(player); player.setWantedBufferAhead(15); const { url, transport } = templateManifestinfos; @@ -40,9 +35,6 @@ describe("end number", function () { transport, autoPlay: false, }); - await sleep(50); - expect(xhrMock.getLockedXHR().length).to.equal(1); - await xhrMock.flush(); await sleep(500); expect(player.getMinimumPosition()).to.eql(0); expect(player.getMaximumPosition()).to.eql(120 + 30); @@ -50,40 +42,40 @@ describe("end number", function () { it("should not load segment later than the end number on a time-based SegmentTimeline", async function () { this.timeout(15000); - xhrMock.lock(); lockLowestBitrates(player); - player.setWantedBufferAhead(15); const { url, transport } = timeBasedManifestInfos; + const requestedSegments = []; + const segmentLoader = (info, callbacks) => { + requestedSegments.push(info.url); + callbacks.fallback(); + }; + player.setWantedBufferAhead(1); player.loadVideo({ url, transport, - autoPlay: true, + autoPlay: false, + segmentLoader, }); await sleep(50); - expect(xhrMock.getLockedXHR().length).to.equal(1); // Manifest - await xhrMock.flush(); - await sleep(50); expect(player.getMaximumPosition()).to.be.closeTo(20, 1); - expect(xhrMock.getLockedXHR().length).to.equal(4); // Init + media of audio - // + video - await xhrMock.flush(); - await waitForLoadedStateAfterLoadVideo(player); - - await sleep(50); - expect(xhrMock.getLockedXHR().length).to.equal(2); // next audio + video - xhrMock.flush(); + expect(requestedSegments.length).to.equal(4); // Init+media of audio+video + if (player.getPlayerState() !== "LOADED") { + await waitForLoadedStateAfterLoadVideo(player); + } player.seekTo(19); + player.play(); + await sleep(10); + player.setWantedBufferAhead(Infinity); + expect(requestedSegments.length).to.equal(6); // + last audio + video await sleep(50); - expect(xhrMock.getLockedXHR().length).to.equal(2); // last audio + video - xhrMock.flush(); + expect(requestedSegments.length).to.equal(6); // + last audio + video await waitForState(player, "ENDED", ["BUFFERING", "RELOADING", "PLAYING"]); expect(player.getPosition()).to.be.closeTo(20, 1); }); it("should calculate the right duration on a number-based SegmentTimeline", async function () { this.timeout(10000); - xhrMock.lock(); lockLowestBitrates(player); player.setWantedBufferAhead(15); const { url, transport } = numberBasedManifestInfos; @@ -93,10 +85,7 @@ describe("end number", function () { transport, autoPlay: true, }); - await sleep(50); - expect(xhrMock.getLockedXHR().length).to.equal(1); - await xhrMock.flush(); - await sleep(50); + await sleep(100); expect(player.getMaximumPosition()).to.be.closeTo(20, 1); }); }); diff --git a/tests/integration/scenarios/fast-switching.js b/tests/integration/scenarios/fast-switching.js index 4f6290bca9d..449beee91ec 100644 --- a/tests/integration/scenarios/fast-switching.js +++ b/tests/integration/scenarios/fast-switching.js @@ -1,7 +1,6 @@ import { expect } from "chai"; import { manifestInfos } from "../../contents/DASH_static_SegmentTimeline"; import RxPlayer from "../../../src"; -import XHRMock from "../../utils/request_mock"; import sleep from "../../utils/sleep.js"; import { waitForLoadedStateAfterLoadVideo } from "../../utils/waitForPlayerState"; import { @@ -10,17 +9,14 @@ import { } from "../../utils/bitrates"; let player; -let xhrMock; describe("Fast-switching", function () { beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); const { url, transport } = manifestInfos; diff --git a/tests/integration/scenarios/initial_playback.js b/tests/integration/scenarios/initial_playback.js index dc25d2c0fc1..acc5bce2e74 100644 --- a/tests/integration/scenarios/initial_playback.js +++ b/tests/integration/scenarios/initial_playback.js @@ -20,20 +20,16 @@ import { manifestInfos } from "../../contents/DASH_static_SegmentTimeline"; import sleep from "../../utils/sleep.js"; import waitForState, { waitForLoadedStateAfterLoadVideo} from "../../utils/waitForPlayerState"; -import XHRMock from "../../utils/request_mock"; describe("basic playback use cases: non-linear DASH SegmentTimeline", function () { let player; - let xhrMock; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); it("should begin playback on play", async function () { @@ -218,52 +214,61 @@ describe("basic playback use cases: non-linear DASH SegmentTimeline", function ( }); it("should download first segment when wanted buffer ahead is under first segment duration", async function () { - xhrMock.lock(); + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = (_, callbacks) => { + segmentLoaderLoaderCalledTimes++; + callbacks.fallback(); + }; player.setWantedBufferAhead(2); player.loadVideo({ transport: manifestInfos.transport, url: manifestInfos.url, + manifestLoader, + segmentLoader, }); - await sleep(10); - expect(xhrMock.getLockedXHR().length).to.equal(1); // Manifest - await xhrMock.flush(); - await sleep(10); - - // init segments first media segments - expect(xhrMock.getLockedXHR().length).to.equal(4); - await xhrMock.flush(); + expect(manifestLoaderCalledTimes).to.equal(1); await sleep(100); + expect(manifestLoaderCalledTimes).to.equal(1); - expect(xhrMock.getLockedXHR().length).to.equal(0); // nada + expect(segmentLoaderLoaderCalledTimes).to.equal(4); expect(player.getCurrentBufferGap()).to.be.above(4); expect(player.getCurrentBufferGap()).to.be.below(5); }); it("should download more than the first segment when wanted buffer ahead is over the first segment duration", async function () { - xhrMock.lock(); + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = (_, callbacks) => { + segmentLoaderLoaderCalledTimes++; + callbacks.fallback(); + }; player.setWantedBufferAhead(20); player.loadVideo({ transport: manifestInfos.transport, url: manifestInfos.url, + manifestLoader, + segmentLoader, }); - await sleep(1); - expect(xhrMock.getLockedXHR().length).to.equal(1); // Manifest - await xhrMock.flush(); - await sleep(1); - - // init segments first media segments - expect(xhrMock.getLockedXHR().length).to.equal(4); - await xhrMock.flush(); - await sleep(100); - - expect(xhrMock.getLockedXHR().length).to.equal(2); // next 2 - await xhrMock.flush(); + expect(manifestLoaderCalledTimes).to.equal(1); await sleep(100); + expect(manifestLoaderCalledTimes).to.equal(1); - expect(player.getCurrentBufferGap()).to.be.above(7); - expect(player.getCurrentBufferGap()).to.be.below(9); + expect(segmentLoaderLoaderCalledTimes).to.equal(12); + expect(player.getCurrentBufferGap()).to.be.above(18); + expect(player.getCurrentBufferGap()).to.be.below(30); }); it("should continue downloading when seek to wanted buffer ahead", async function() { @@ -372,21 +377,13 @@ describe("basic playback use cases: non-linear DASH SegmentTimeline", function ( await sleep(100); expect(player.getPlayerState()).to.equal("PLAYING"); - xhrMock.lock(); - player.seekTo(10); await waitForState(player, "SEEKING", ["PLAYING"]); expect(player.getCurrentBufferGap()).to.equal(0); - await sleep(100); - expect(player.getPlayerState()).to.equal("SEEKING"); - expect(player.getCurrentBufferGap()).to.equal(0); - - await xhrMock.flush(); - await sleep(100); + await waitForState(player, "PLAYING", ["SEEKING"]); expect(player.getCurrentBufferGap()).to.be.above(1); expect(player.getCurrentBufferGap()).to.be.below(10); - expect(player.getPlayerState()).to.equal("PLAYING"); }); it("should respect a set max buffer size", async function () { diff --git a/tests/integration/scenarios/loadVideo_options.js b/tests/integration/scenarios/loadVideo_options.js index 71b9d246c0b..414775fffe4 100644 --- a/tests/integration/scenarios/loadVideo_options.js +++ b/tests/integration/scenarios/loadVideo_options.js @@ -21,20 +21,16 @@ import sleep from "../../utils/sleep.js"; import /* waitForState, */ { waitForLoadedStateAfterLoadVideo, } from "../../utils/waitForPlayerState"; -import XHRMock from "../../utils/request_mock"; describe("loadVideo Options", () => { let player; - let xhrMock; beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); describe("url", () => { @@ -48,17 +44,28 @@ describe("loadVideo Options", () => { }); it("should request the URL if one is given", async () => { - xhrMock.lock(); + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = () => { + segmentLoaderLoaderCalledTimes++; + }; player.loadVideo({ url: manifestInfos.url, transport: "dash", autoPlay: true, + manifestLoader, + segmentLoader, }); await sleep(0); - expect(xhrMock.getLockedXHR().length).to.equal(1); - expect(xhrMock.getLockedXHR()[0].url).to.equal(manifestInfos.url); + expect(manifestLoaderCalledTimes).to.equal(1); + expect(segmentLoaderLoaderCalledTimes).to.equal(0); }); }); diff --git a/tests/integration/scenarios/video_thumbnail_loader.js b/tests/integration/scenarios/video_thumbnail_loader.js index 194852a8b6f..e49a19d1320 100644 --- a/tests/integration/scenarios/video_thumbnail_loader.js +++ b/tests/integration/scenarios/video_thumbnail_loader.js @@ -7,25 +7,21 @@ import { manifestInfos, trickModeInfos, } from "../../contents/DASH_static_SegmentTimeline"; -import XHRMock from "../../utils/request_mock"; import sleep from "../../utils/sleep"; import { waitForLoadedStateAfterLoadVideo } from "../../utils/waitForPlayerState"; describe("Video Thumbnail Loader", () => { let rxPlayer; let videoThumbnailLoader; - let xhrMock; const videoElement = document.createElement("video"); beforeEach(() => { rxPlayer = new RxPlayer(); videoThumbnailLoader = new VideoThumbnailLoader(videoElement, rxPlayer); - xhrMock = new XHRMock(); }); afterEach(() => { rxPlayer.dispose(); videoThumbnailLoader.dispose(); - xhrMock.restore(); }); it("should not work when no fetcher was imported", async function() { @@ -154,14 +150,32 @@ describe("Video Thumbnail Loader", () => { VideoThumbnailLoader.addLoader(DASH_LOADER); const wantedThumbnail1 = { time: 1, range: [0, 4] }; - rxPlayer.loadVideo({ url: trickModeInfos.url, transport: "dash" }); + const manifestLoader = (man, callbacks) => { + expect(trickModeInfos.url).to.equal(man.url); + callbacks.fallback(); + }; + const requestedSegments = []; + const segmentLoader = (info, callbacks) => { + requestedSegments.push(info.url); + callbacks.fallback(); + }; + rxPlayer.setWantedBufferAhead(1); + rxPlayer.loadVideo({ + url: trickModeInfos.url, + transport: "dash", + manifestLoader, + segmentLoader, + }); await waitForLoadedStateAfterLoadVideo(rxPlayer); let time; let error; + expect(requestedSegments).not.to.be.empty; try { + requestedSegments.length = 0; time = await videoThumbnailLoader.setTime(wantedThumbnail1.time); + expect(requestedSegments).to.be.empty; } catch (err) { error = err; } @@ -176,10 +190,6 @@ describe("Video Thumbnail Loader", () => { time = undefined; error = undefined; - // Lock, because it should work without needing to load again the thumbnail, - // as the thumbnail is already buffered. - xhrMock.lock(); - try { time = await videoThumbnailLoader.setTime(wantedThumbnail1.time); } catch (err) { @@ -198,11 +208,23 @@ describe("Video Thumbnail Loader", () => { VideoThumbnailLoader.addLoader(DASH_LOADER); const wantedThumbnail1 = { time: 1, range: [0, 4] }; - rxPlayer.loadVideo({ url: trickModeInfos.url, transport: "dash" }); + const manifestLoader = (man, callbacks) => { + expect(trickModeInfos.url).to.equal(man.url); + callbacks.fallback(); + }; + const requestedSegments = []; + const segmentLoader = (info, callbacks) => { + requestedSegments.push(info.url); + callbacks.fallback(); + }; + rxPlayer.setWantedBufferAhead(1); + rxPlayer.loadVideo({ + url: trickModeInfos.url, + transport: "dash", + segmentLoader, + manifestLoader, + }); await waitForLoadedStateAfterLoadVideo(rxPlayer); - - rxPlayer.setWantedBufferAhead(0); - xhrMock.lock(); videoThumbnailLoader.setTime(wantedThumbnail1.time); videoThumbnailLoader.setTime(wantedThumbnail1.time); videoThumbnailLoader.setTime(wantedThumbnail1.time); @@ -210,22 +232,11 @@ describe("Video Thumbnail Loader", () => { videoThumbnailLoader.setTime(wantedThumbnail1.time); await sleep(75); - const xhrs = xhrMock.getLockedXHR(); - - expect(xhrs.length).to.equal(1); - expect(xhrs[0].url) - .to.equal("http://127.0.0.1:3000/DASH_static_SegmentTimeline/media/dash/ateam-video=400000.dash"); - - await xhrMock.flush(1); - await sleep(75); - - const xhrs2 = xhrMock.getLockedXHR(); - expect(xhrs2.length).to.equal(1); - expect(xhrs2[0].url) - .to.equal("http://127.0.0.1:3000/DASH_static_SegmentTimeline/media/dash/ateam-video=400000-0.dash"); - - xhrMock.unlock(); - await sleep(75); + expect(requestedSegments).to.have.length(4); + expect(requestedSegments) + .to.include("http://127.0.0.1:3000/DASH_static_SegmentTimeline/media/dash/ateam-video=400000.dash"); + expect(requestedSegments) + .to.include("http://127.0.0.1:3000/DASH_static_SegmentTimeline/media/dash/ateam-video=400000-0.dash"); expect(videoElement.buffered.length).to.equal(1); expect(videoElement.buffered.start(0)) .to.be.closeTo(wantedThumbnail1.range[0], 0.01); @@ -237,28 +248,32 @@ describe("Video Thumbnail Loader", () => { VideoThumbnailLoader.addLoader(DASH_LOADER); const wantedThumbnail1 = { time: 1, range: [0, 8] }; - rxPlayer.loadVideo({ url: trickModeInfos.url, transport: "dash" }); + const requestedVideoSegments = []; + const segmentLoader = (info, callbacks) => { + if (info.trackType === "video") { + requestedVideoSegments.push(info.url); + } + callbacks.fallback(); + }; + rxPlayer.loadVideo({ + url: trickModeInfos.url, + transport: "dash", + segmentLoader, + }); await waitForLoadedStateAfterLoadVideo(rxPlayer); rxPlayer.setWantedBufferAhead(0); - xhrMock.lock(); videoThumbnailLoader.setTime(wantedThumbnail1.time); - await sleep(75); - await xhrMock.flush(1); // load and push init segment - await sleep(75); + await sleep(200); + const before = requestedVideoSegments.length; + expect(requestedVideoSegments) + .to.include("http://127.0.0.1:3000/DASH_static_SegmentTimeline/media/dash/ateam-video=400000-0.dash"); + expect(requestedVideoSegments) + .to.include("http://127.0.0.1:3000/DASH_static_SegmentTimeline/media/dash/ateam-video=400000-4004.dash"); videoThumbnailLoader.setTime(wantedThumbnail1.time + 2); await sleep(75); + expect(requestedVideoSegments.length).to.equal(before); - const xhrs = xhrMock.getLockedXHR(); - - expect(xhrs.length).to.equal(2); - expect(xhrs[0].url) - .to.equal("http://127.0.0.1:3000/DASH_static_SegmentTimeline/media/dash/ateam-video=400000-0.dash"); - expect(xhrs[1].url) - .to.equal("http://127.0.0.1:3000/DASH_static_SegmentTimeline/media/dash/ateam-video=400000-4004.dash"); - - xhrMock.unlock(); - await sleep(75); expect(videoElement.buffered.length).to.equal(1); expect(videoElement.buffered.start(0)) .to.be.closeTo(wantedThumbnail1.range[0], 0.01); diff --git a/tests/integration/utils/launch_tests_for_content.js b/tests/integration/utils/launch_tests_for_content.js index 55743074502..607d79eab14 100644 --- a/tests/integration/utils/launch_tests_for_content.js +++ b/tests/integration/utils/launch_tests_for_content.js @@ -3,7 +3,6 @@ import RxPlayer from "../../../src"; import sleep from "../../utils/sleep.js"; import waitForState, { waitForLoadedStateAfterLoadVideo } from "../../utils/waitForPlayerState"; import tryTestMultipleTimes from "../../utils/try_test_multiple_times"; -import XHRMock from "../../utils/request_mock"; import { lockLowestBitrates } from "../../utils/bitrates"; /** @@ -49,7 +48,6 @@ import { lockLowestBitrates } from "../../utils/bitrates"; */ export default function launchTestsForContent(manifestInfos) { let player; - let xhrMock; const { isLive, maximumPosition, @@ -62,32 +60,43 @@ export default function launchTestsForContent(manifestInfos) { describe("API tests", () => { beforeEach(() => { player = new RxPlayer(); - xhrMock = new XHRMock(); }); afterEach(() => { player.dispose(); - xhrMock.restore(); }); describe("loadVideo", () => { it("should fetch the manifest then the init segments", async function () { - xhrMock.lock(); + let manifestLoaderCalledTimes = 0; + const requestedSegments = []; + const manifestLoader = (man, callbacks) => { + expect(manifestInfos.url).to.equal(man.url); + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = (info, callbacks) => { + requestedSegments.push(info.url); + callbacks.fallback(); + }; // Lock the lowest bitrate to facilitate the test lockLowestBitrates(player); - player.loadVideo({ url: manifestInfos.url, transport }); + player.loadVideo({ + url: manifestInfos.url, + transport, + manifestLoader, + segmentLoader, + }); // should only have the manifest for now - await sleep(50); - expect(xhrMock.getLockedXHR().length).to.equal(1); - expect(xhrMock.getLockedXHR()[0].url).to.equal(manifestInfos.url); - - await xhrMock.flush(); // only wait for the manifest request - await sleep(50); + expect(manifestLoaderCalledTimes).to.equal(1); expect(player.getPlayerState()).to.equal("LOADING"); + const loadingTime = performance.now(); + await waitForLoadedStateAfterLoadVideo(player); + expect(performance.now() - loadingTime).to.be.at.most(1000); const firstPeriodAdaptationsInfos = periodsInfos[firstPeriodIndex] .adaptations; @@ -108,18 +117,15 @@ export default function launchTestsForContent(manifestInfos) { (audioRepresentationInfos && audioRepresentationInfos.index.init) && (videoRepresentationInfos && videoRepresentationInfos.index.init) ) { - expect(xhrMock.getLockedXHR().length) - .to.be.at.least(2, "should request two init segments"); - const requestsDone = xhrMock.getLockedXHR().map(({ url }) => url); - - const hasRequestedVideoInitSegment = requestsDone.some(r => { + expect(requestedSegments.length).to.be.at.least(2); + const hasRequestedVideoInitSegment = requestedSegments.some(r => { const relativeUrl = videoRepresentationInfos.index.init.url === null ? "" : videoRepresentationInfos.index.init.url; return r.endsWith(relativeUrl); }); - const hasRequestedAudioInitSegment = requestsDone.some(r => { + const hasRequestedAudioInitSegment = requestedSegments.some(r => { const relativeUrl = audioRepresentationInfos.index.init.url === null ? "" : @@ -131,13 +137,13 @@ export default function launchTestsForContent(manifestInfos) { } else if (!( audioRepresentationInfos && audioRepresentationInfos.index.init) ) { - expect(xhrMock.getLockedXHR().length).to.equal(1); - expect(xhrMock.getLockedXHR()[0].url).to - .equal(videoRepresentationInfos.index.init.url); + expect(requestedSegments.length).to.be.at.least(1); + expect(requestedSegments) + .to.include(videoRepresentationInfos.index.init.url); } else { - expect(xhrMock.getLockedXHR().length).to.equal(1); - expect(xhrMock.getLockedXHR()[0].url).to - .equal(audioRepresentationInfos.index.init.url); + expect(requestedSegments.length).to.be.at.least(1); + expect(requestedSegments) + .to.include(audioRepresentationInfos.index.init.url); } } }); @@ -147,28 +153,52 @@ export default function launchTestsForContent(manifestInfos) { const initialManifest = await ( (await fetch(manifestInfos.url)) .text()); - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, - transport, - initialManifest }); + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = (_, callbacks) => { + manifestLoaderCalledTimes++; + callbacks.fallback(); + }; + const segmentLoader = (_, callbacks) => { + segmentLoaderLoaderCalledTimes++; + callbacks.fallback(); + }; + player.loadVideo({ + url: manifestInfos.url, + transport, + initialManifest, + manifestLoader, + segmentLoader, + }); - await sleep(100); - expect(xhrMock.getLockedXHR().length).to.be.at.least(1); - expect(xhrMock.getLockedXHR()[0].url).not.to.equal(manifestInfos.url); + await waitForLoadedStateAfterLoadVideo(player); + expect(manifestLoaderCalledTimes).to.equal(0); + expect(segmentLoaderLoaderCalledTimes).to.be.at.least(1); }); it("should not do the initial manifest request if an `initialManifest` option is set as a document", async function () { const initialManifestStr = await ( (await fetch(manifestInfos.url)) .text()); const initialManifest = new DOMParser().parseFromString(initialManifestStr, "text/xml"); - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, - transport, - initialManifest }); + let manifestLoaderCalledTimes = 0; + let segmentLoaderLoaderCalledTimes = 0; + const manifestLoader = () => { + manifestLoaderCalledTimes++; + }; + const segmentLoader = () => { + segmentLoaderLoaderCalledTimes++; + }; + player.loadVideo({ + url: manifestInfos.url, + transport, + initialManifest, + manifestLoader, + segmentLoader, + }); await sleep(100); - expect(xhrMock.getLockedXHR().length).to.be.at.least(1); - expect(xhrMock.getLockedXHR()[0].url).not.to.equal(manifestInfos.url); + expect(manifestLoaderCalledTimes).to.equal(0); + expect(segmentLoaderLoaderCalledTimes).to.be.at.least(1); }); } }); @@ -1386,17 +1416,11 @@ export default function launchTestsForContent(manifestInfos) { describe("getAvailableAudioTracks", () => { it("should list the right audio languages", async function () { - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport, + segmentLoader: () => { /* do nothing */ }, }); - expect(player.getAvailableAudioTracks()).to.eql([]); - - await sleep(1); - expect(player.getAvailableAudioTracks()).to.eql([]); - await xhrMock.flush(); await sleep(50); const audioTracks = player.getAvailableAudioTracks(); @@ -1433,17 +1457,11 @@ export default function launchTestsForContent(manifestInfos) { describe("getAvailableTextTracks", () => { it("should list the right text languages", async function () { - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport, + segmentLoader: () => { /* do nothing */ }, }); - expect(player.getAvailableTextTracks()).to.eql([]); - - await sleep(1); - expect(player.getAvailableTextTracks()).to.eql([]); - await xhrMock.flush(); await sleep(50); const textTracks = player.getAvailableTextTracks(); @@ -1480,17 +1498,11 @@ export default function launchTestsForContent(manifestInfos) { describe("getAvailableVideoTracks", () => { it("should list the right video tracks", async function () { - xhrMock.lock(); - player.loadVideo({ url: manifestInfos.url, transport, + segmentLoader: () => { /* do nothing */ }, }); - expect(player.getAvailableVideoTracks()).to.eql([]); - - await sleep(1); - expect(player.getAvailableVideoTracks()).to.eql([]); - await xhrMock.flush(); await sleep(50); const videoTracks = player.getAvailableVideoTracks(); diff --git a/tests/utils/create_lock.js b/tests/utils/create_lock.js new file mode 100644 index 00000000000..8a3a0ea2e87 --- /dev/null +++ b/tests/utils/create_lock.js @@ -0,0 +1,48 @@ +/** + * Create Object allowing to create a "lock" which can be `await`ed, alongside + * `unlock`/`lock` functions allowing respectively to resolve the awaited + * promise or to create a new one. + * + * Optionally, a value can be given to `unlock` which will be communicated + * through the `Promise` returned by `awaitCurrentLock` as it resolves. + * + * @example + * ```js + * const lock = createLock(); + * lock.awaitCurrentLock().then(() => console.log("DONE 1")); + * + * // Lead to a logged `"DONE 1"` (asynchronous after this call, Promise + * // resolution is always in a microtask) + * lock.unlock(); + * + * // `"DONE 2"` will be logged (still asynchronously) as the lock is already + * // "unlocked" + * lock.awaitCurrentLock().then(() => console.log("DONE 2")); + * + * // Reset the lock, already-resolved Promise are not affected. + * lock.lock(); + * + * // This one won't log until `unlock` is called again + * lock.awaitCurrentLock().then(() => console.log("DONE 3")); + * ``` + * @returns {Object} + */ +export default function createLock() { + let unlockFn; + let waitForUnlock = new Promise((res) => { + unlockFn = res; + }); + return { + awaitCurrentLock() { + return waitForUnlock; + }, + unlock(val) { + unlockFn(val); + }, + lock() { + waitForUnlock = new Promise((res) => { + unlockFn = res; + }); + }, + }; +} diff --git a/tests/utils/request_mock/events/custom_event.js b/tests/utils/request_mock/events/custom_event.js deleted file mode 100644 index 5899accc19d..00000000000 --- a/tests/utils/request_mock/events/custom_event.js +++ /dev/null @@ -1,14 +0,0 @@ -import Event from "./event"; - -/** - * @param {string} type - * @param {Object} customData - * @param {Object} target - */ -export default function CustomEvent(type, customData, target) { - this.initEvent(type, false, false, target); - this.detail = customData.detail || null; -} - -CustomEvent.prototype = new Event(); -CustomEvent.prototype.constructor = CustomEvent; diff --git a/tests/utils/request_mock/events/event.js b/tests/utils/request_mock/events/event.js deleted file mode 100644 index 3c5f9896321..00000000000 --- a/tests/utils/request_mock/events/event.js +++ /dev/null @@ -1,21 +0,0 @@ -function Event(type, bubbles, cancelable, target) { - this.initEvent(type, bubbles, cancelable, target); -} - -Event.prototype = { - initEvent(type, bubbles, cancelable, target) { - this.type = type; - this.bubbles = bubbles; - this.cancelable = cancelable; - this.target = target; - this.currentTarget = target; - }, - - stopPropagation() {}, - - preventDefault() { - this.defaultPrevented = true; - }, -}; - -export default Event; diff --git a/tests/utils/request_mock/events/event_target.js b/tests/utils/request_mock/events/event_target.js deleted file mode 100644 index 60b0e95ac50..00000000000 --- a/tests/utils/request_mock/events/event_target.js +++ /dev/null @@ -1,93 +0,0 @@ -function flattenOptions(options) { - if (options !== Object(options)) { - return { capture: Boolean(options), - once: false, - passive: false }; - } - return { capture: Boolean(options.capture), - once: Boolean(options.once), - passive: Boolean(options.passive) }; -} - -function not(fn) { - return function () { - return !fn.apply(this, arguments); - }; -} - -function hasListenerFilter(listener, capture) { - return function (listenerSpec) { - return listenerSpec.capture === capture - && listenerSpec.listener === listener; - }; -} - -const EventTarget = { - // https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener - addEventListener(event, listener, providedOptions) { - const options = flattenOptions(providedOptions); - - if (listener == null) { - return; - } - - this._listeners = this._listeners || {}; - this._listeners[event] = this._listeners[event] || []; - - // 4. If context object’s associated list of event listener - // does not contain an event listener whose type is type, - // callback is callback, and capture is capture, then append - // a new event listener to it, whose type is type, callback is - // callback, capture is capture, passive is passive, and once is once. - if (!this._listeners[event].some(hasListenerFilter(listener, - options.capture))) - { - this._listeners[event].push({ listener: listener, - capture: options.capture, - once: options.once }); - } - }, - - // https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener - removeEventListener: function removeEventListener( - event, - listener, - providedOptions - ) { - if (!this._listeners || !this._listeners[event]) { - return; - } - - const options = flattenOptions(providedOptions); - - this._listeners[event] = this._listeners[event] - .filter(not(hasListenerFilter(listener, options.capture))); - }, - - dispatchEvent: function dispatchEvent(event) { - if (!this._listeners || !this._listeners[event.type]) { - return Boolean(event.defaultPrevented); - } - - const type = event.type; - const listeners = this._listeners[type]; - - // Remove listeners, that should be dispatched once - // before running dispatch loop to avoid nested dispatch issues - this._listeners[type] = listeners.filter(function (listenerSpec) { - return !listenerSpec.once; - }); - listeners.forEach((listenerSpec) => { - const { listener } = listenerSpec; - if (typeof listener === "function") { - listener.call(this, event); - } else { - listener.handleEvent(event); - } - }); - - return Boolean(event.defaultPrevented); - }, -}; - -export default EventTarget; diff --git a/tests/utils/request_mock/events/index.js b/tests/utils/request_mock/events/index.js deleted file mode 100644 index 1c6e0473eea..00000000000 --- a/tests/utils/request_mock/events/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import Event from "./event"; -import ProgressEvent from "./progress_event"; -import CustomEvent from "./custom_event"; -import EventTarget from "./event_target"; - -export { - Event, - ProgressEvent, - CustomEvent, - EventTarget, -}; diff --git a/tests/utils/request_mock/events/progress_event.js b/tests/utils/request_mock/events/progress_event.js deleted file mode 100644 index 3050ce9d7d2..00000000000 --- a/tests/utils/request_mock/events/progress_event.js +++ /dev/null @@ -1,12 +0,0 @@ -import Event from "./event"; - -function ProgressEvent(type, progressEventRaw, target) { - this.initEvent(type, false, false, target); - this.loaded = typeof progressEventRaw.loaded === "number" ? progressEventRaw.loaded : null; - this.total = typeof progressEventRaw.total === "number" ? progressEventRaw.total : null; - this.lengthComputable = !!progressEventRaw.total; -} - -ProgressEvent.prototype = new Event(); -ProgressEvent.prototype.constructor = ProgressEvent; -export default ProgressEvent; diff --git a/tests/utils/request_mock/fake_xhr.js b/tests/utils/request_mock/fake_xhr.js deleted file mode 100644 index cee5231931c..00000000000 --- a/tests/utils/request_mock/fake_xhr.js +++ /dev/null @@ -1,766 +0,0 @@ -import * as Events from "./events"; - -export const originalXHR = window.XMLHttpRequest; - -const XHR_STATES = { UNSENT: 0, - OPENED: 1, - HEADERS_RECEIVED: 2, - LOADING: 3, - DONE: 4 }; - -const STATUS_TEXTS = { 100: "Continue", - 101: "Switching Protocols", - 200: "OK", - 201: "Created", - 202: "Accepted", - 203: "Non-Authoritative Information", - 204: "No Content", - 205: "Reset Content", - 206: "Partial Content", - 207: "Multi-Status", - 300: "Multiple Choice", - 301: "Moved Permanently", - 302: "Found", - 303: "See Other", - 304: "Not Modified", - 305: "Use Proxy", - 307: "Temporary Redirect", - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Request Entity Too Large", - 414: "Request-URI Too Long", - 415: "Unsupported Media Type", - 416: "Requested Range Not Satisfiable", - 417: "Expectation Failed", - 422: "Unprocessable Entity", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported" }; - -// https://fetch.spec.whatwg.org/#forbidden-header-name -const FORBIDDEN_HEADERS = [ "Accept-Charset", - "Accept-Encoding", - "Access-Control-Request-Headers", - "Access-Control-Request-Method", - "Connection", - "Content-Length", - "Content-Transfer-Encoding", - "Cookie", - "Cookie2", - "DNT", - "Date", - "Expect", - "Host", - "Keep-Alive", - "Origin", - "Referer", - "TE", - "Trailer", - "Transfer-Encoding", - "Upgrade", - "User-Agent", - "Via" ]; - -function EventTargetHandler() { - const self = this; - const events = [ "loadstart", - "progress", - "abort", - "error", - "load", - "timeout", - "loadend" ]; - - function addEventListener(eventName) { - self.addEventListener(eventName, function (event) { - const listener = self["on" + eventName]; - - if (listener && typeof listener === "function") { - listener.call(this, event); - } - }); - } - - events.forEach(addEventListener); -} - -EventTargetHandler.prototype = Events.EventTarget; - -/** - * XHR implementation for mocking usages. - * Heavily inspired from nise. - */ -export function FakeXMLHttpRequest() { - EventTargetHandler.call(this); - this.readyState = XHR_STATES.UNSENT; - this.requestHeaders = {}; - this.requestBody = null; - this.status = 0; - this.statusText = ""; - this.upload = new EventTargetHandler(); - this.responseType = ""; - this.response = ""; - this.timeout = 0; - this.withCredentials = false; - - // Callback which will be called when xhr.send is called - this._onSend = null; - - // Return true if the current request should be mocked - this._shouldMock = () => false; - - // Promise which resolve when the request is done or was aborted - this._finished = new Promise((res) => { - this.__finishPromise = res; - }); - if (typeof FakeXMLHttpRequest._onCreate === "function") { - FakeXMLHttpRequest._onCreate(this); - } -} - -Object.assign(FakeXMLHttpRequest.prototype, Events.EventTarget, { - async: true, - - open(...args) { - const [ method, url, async, username, password ] = args; - this.method = method; - this.url = url; - this.async = typeof async === "boolean" ? async : true; - this.username = username; - this.password = password; - clearResponse(this); - this.requestHeaders = {}; - this.sendFlag = false; - - if (!this._shouldMock(this, args)) { - resetOnOpen(this, args); - return; - } - updateReadyState(this, FakeXMLHttpRequest.OPENED); - }, - - /** - * Add a header to the current request. - * @see https://xhr.spec.whatwg.org/#dom-xmlhttprequest-setrequestheader - * @param {string} name - * @param {string} value - */ - setRequestHeader(name, value) { - if (typeof this._onSetRequestHeader === "function") { - this._onSetRequestHeader(name, value); - } - if (typeof value !== "string") { - throw new TypeError("By RFC7230, section 3.2.4, header values should " + - " be strings. Got " + typeof value); - } - if (this.readyState !== XHR_STATES.OPENED) { - throw new Error("INVALID_STATE_ERR"); - } - if (this.sendFlag) { - throw new Error("INVALID_STATE_ERR"); - } - - // https://fetch.spec.whatwg.org/#forbidden-header-name - if (FORBIDDEN_HEADERS.includes(name) || /^(Sec-|Proxy-)/i.test(name)) { - throw new Error(`Refused to set unsafe header "${name}"`); - } - - const normalizedValue = normalizeHeaderValue(value); - const existingName = getHeaderName(this.requestHeaders, name); - if (existingName != null) { - this.requestHeaders[existingName] += ", " + normalizedValue; - } else { - this.requestHeaders[name] = normalizedValue; - } - }, - - // Currently treats ALL data as a DOMString (i.e. no Document) - send(data) { - if (this.readyState !== XHR_STATES.OPENED) { - throw new Error("INVALID_STATE_ERR"); - } - if (this.sendFlag) { - throw new Error("INVALID_STATE_ERR"); - } - - if (!/^(head)$/i.test(this.method)) { - const contentType = getHeaderName(this.requestHeaders, "Content-Type"); - if (this.requestHeaders[contentType]) { - const value = this.requestHeaders[contentType].split(";"); - this.requestHeaders[contentType] = value[0] + ";charset=utf-8"; - } - - this.requestBody = data; - } - - this.errorFlag = false; - this.sendFlag = this.async; - clearResponse(this); - updateReadyState(this, FakeXMLHttpRequest.OPENED); - - if (typeof this._onSend === "function") { - this._onSend(this); - } - - - // Only listen if setInterval and Date are a stubbed. - if (typeof setInterval.clock === "object" && typeof Date.clock === "object") { - const initiatedTime = Date.now(); - const self = this; - - // Listen to any possible tick by fake timers and check to see if timeout - // has been exceeded. It's important to note that timeout can be changed - // while a request is in flight, so we must check anytime the end user - // forces a clock tick to make sure timeout hasn't changed. - // https://xhr.spec.whatwg.org/#dfnReturnLink-2 - const clearIntervalId = setInterval(function () { - // Check if the readyState has been reset or is done. If this is the - // case, there should be no timeout. This will also prevent aborted - // requests and fakeServerWithClock from triggering unnecessary - // responses. - if (self.readyState === FakeXMLHttpRequest.UNSENT || - self.readyState === FakeXMLHttpRequest.DONE) - { - clearInterval(clearIntervalId); - } else if (typeof self.timeout === "number" && self.timeout > 0) { - if (Date.now() >= (initiatedTime + self.timeout)) { - triggerTimeout(self); - clearInterval(clearIntervalId); - } - } - }, 1); - } - - this.dispatchEvent(new Events.Event("loadstart", false, false, this)); - }, - - /** - * Abort the given request (do not send it). - */ - abort() { - this.aborted = true; - requestErrorSteps(this); - updateReadyState(this, FakeXMLHttpRequest.UNSENT); - }, - - error() { - clearResponse(this); - this.errorFlag = true; - this.requestHeaders = {}; - this.responseHeaders = {}; - - updateReadyState(this, FakeXMLHttpRequest.DONE); - }, - - /** - * @param {string} header - * @returns {string|null} - */ - getResponseHeader(header) { - if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { - return null; - } - - if (/^Set-Cookie2?$/i.test(header)) { - return null; - } - - const realHeader = getHeaderName(this.responseHeaders, header); - return this.responseHeaders[realHeader] || null; - }, - - /** - * @returns {string} - */ - getAllResponseHeaders() { - if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) { - return ""; - } - - const responseHeaders = this.responseHeaders; - return Object.keys(responseHeaders).reduce(function (prev, header) { - const value = responseHeaders[header]; - return prev + (header + ": " + value + "\r\n"); - }, ""); - }, - - /** - * Manually respond (/!\ only for mocked requests) - * @param {number} status - * @param {Object} headers - * @param {*} body - */ - respond(status, headers, body) { - setStatus(this, status); - setResponseHeaders(this, headers || {}); - setResponseBody(this, body || ""); - }, - - uploadProgress(progressEventRaw) { - this.upload.dispatchEvent(new Events.ProgressEvent("progress", progressEventRaw, this.upload)); - }, - - downloadProgress(progressEventRaw) { - this.dispatchEvent(new Events.ProgressEvent("progress", progressEventRaw, this)); - }, - - uploadError(error) { - this.upload.dispatchEvent(new Events.CustomEvent("error", {detail: error})); - }, - - overrideMimeType(type) { - if (this.readyState >= FakeXMLHttpRequest.LOADING) { - throw new Error("INVALID_STATE_ERR"); - } - this.overriddenMimeType = type; - }, -}); - -Object.assign(FakeXMLHttpRequest, XHR_STATES); -Object.assign(FakeXMLHttpRequest.prototype, XHR_STATES); - -export default function useFakeXMLHttpRequest() { - FakeXMLHttpRequest.restore = function restore() { - window.XMLHttpRequest = originalXHR; - delete FakeXMLHttpRequest.restore; - }; - window.XMLHttpRequest = FakeXMLHttpRequest; - return FakeXMLHttpRequest; -} - -// ================= PRIVATE METHODS ===================== - -/** - * @param {FakeXMLHttpRequest} fakeXhr - */ -function triggerTimeout(fakeXhr) { - fakeXhr.timedOut = true; - requestErrorSteps(fakeXhr); -} - -/** - * Set the response headers according to the key and values of the given - * object. - * @throws Error - Throws if the request is not opened. - * @param {FakeXMLHttpRequest} fakeXhr - * @param {Object} headers - */ -function setResponseHeaders(fakeXhr, headers) { - if (fakeXhr.readyState !== FakeXMLHttpRequest.OPENED) { - throw new Error("INVALID_STATE_ERR - " + fakeXhr.readyState); - } - - const responseHeaders = fakeXhr.responseHeaders = {}; - - Object.keys(headers).forEach(function (header) { - responseHeaders[header] = headers[header]; - }); - - if (fakeXhr.async) { - updateReadyState(fakeXhr, FakeXMLHttpRequest.HEADERS_RECEIVED); - } else { - fakeXhr.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED; - } -} - -/** - * Set corresponding status and statusText on the XHR. - * @throws Error - Throws if the request is not opened. - * @param {FakeXMLHttpRequest} fakeXhr - * @param {number} status - */ -function setStatus(fakeXhr, status) { - if (fakeXhr.readyState !== FakeXMLHttpRequest.OPENED) { - throw new Error("INVALID_STATE_ERR - " + fakeXhr.readyState); - } - fakeXhr.status = status; - fakeXhr.statusText = STATUS_TEXTS[status]; -} - -/** - * @param {string} value - * @returns {string} - */ -function normalizeHeaderValue(value) { - // https://fetch.spec.whatwg.org/#concept-header-value-normalize - return value.replace(/(^[\x09\x0A\x0D\x20]+)|([\x09\x0A\x0D\x20]+$)/g, ""); -} - -/** - * Retrieve corresponding header from the list of headers. - * `null` if not found. - * @param {Object} headers - * @param {string} header - * @returns {Object|null} - */ -function getHeaderName(headers, header) { - const foundHeader = Object.keys(headers).filter(function (h) { - return h.toLowerCase() === header.toLowerCase(); - }); - return foundHeader[0] || null; -} - -/** - * Convert text given into an XML Document. - * Return `null` if an error was encountered. - * @param {string} text - * @returns {Document|null} - */ -function toXML(text) { - if (text === "" || typeof DOMParser === "undefined") { - return null; - } - try { - const parser = new DOMParser(); - let parsererrorNS = ""; - - try { - const parsererrors = parser - .parseFromString("INVALID", "text/xml") - .getElementsByTagName("parsererror"); - if (parsererrors.length) { - parsererrorNS = parsererrors[0].namespaceURI; - } - } catch (e) { - // passing invalid XML makes IE11 throw - // so no namespace needs to be determined - } - - let result; - try { - result = parser.parseFromString(text, "text/xml"); - } catch (err) { - return null; - } - - return result.getElementsByTagNameNS(parsererrorNS, "parsererror") - .length ? null : result; - } catch (e) {} - return null; -} - -/** - * Update the ready state and send corresponding events. - * @param {FakeXMLHttpRequest} fakeXhr - * @param {string} state - */ -function updateReadyState(fakeXhr, state) { - fakeXhr.readyState = state; - - if (state === FakeXMLHttpRequest.UNSENT) { - fakeXhr.__finishPromise(); - return; - } - - const readyStateChangeEvent = new Events.Event("readystatechange", - false, - false, - fakeXhr); - let evtName, progress; - - if (typeof fakeXhr.onreadystatechange === "function") { - try { - fakeXhr.onreadystatechange(readyStateChangeEvent); - } catch (e) { - fakeXhr.logError("Fake XHR onreadystatechange handler", e); - } - } - - if (fakeXhr.readyState === FakeXMLHttpRequest.DONE) { - if (fakeXhr.timedOut || fakeXhr.aborted || fakeXhr.status === 0) { - progress = { loaded: 0, total: 0 }; - if (fakeXhr.timedOut) { - evtName = "timeout"; - } else if (fakeXhr.aborted) { - evtName = "abort"; - } else { - evtName = "error"; - } - } else { - progress = { loaded: 100, total: 100 }; - evtName = "load"; - } - - fakeXhr.upload.dispatchEvent(new Events.ProgressEvent("progress", - progress, - fakeXhr)); - fakeXhr.upload.dispatchEvent(new Events.ProgressEvent(evtName, - progress, - fakeXhr)); - fakeXhr.upload.dispatchEvent(new Events.ProgressEvent("loadend", - progress, - fakeXhr)); - - fakeXhr.dispatchEvent(new Events.ProgressEvent("progress", progress, fakeXhr)); - fakeXhr.dispatchEvent(new Events.ProgressEvent(evtName, progress, fakeXhr)); - fakeXhr.dispatchEvent(readyStateChangeEvent); - fakeXhr.dispatchEvent(new Events.ProgressEvent("loadend", progress, fakeXhr)); - fakeXhr.__finishPromise(); - } else { - fakeXhr.dispatchEvent(readyStateChangeEvent); - } -} - -/** - * @param {FakeXMLHttpRequest} fakeXhr - * @param {*} body - */ -function setResponseBody(fakeXhr, body) { - if (fakeXhr.readyState === XHR_STATES.DONE) { - throw new Error("Request done"); - } - if (fakeXhr.async && fakeXhr.readyState !== XHR_STATES.HEADERS_RECEIVED) { - throw new Error("No headers received"); - } - verifyResponseBodyType(body, fakeXhr.responseType); - const contentType = fakeXhr.overriddenMimeType || - fakeXhr.getResponseHeader("Content-Type"); - - const isTextResponse = fakeXhr.responseType === "" || - fakeXhr.responseType === "text"; - clearResponse(fakeXhr); - - if (fakeXhr.async) { - const chunkSize = fakeXhr.chunkSize || 10; - let index = 0; - - do { - updateReadyState(fakeXhr, FakeXMLHttpRequest.LOADING); - if (isTextResponse) { - const newSubstr = body.substring(index, index + chunkSize); - fakeXhr.responseText = fakeXhr.response += newSubstr; - } - index += chunkSize; - } while (index < body.length); - } - - fakeXhr.response = convertResponseBody(fakeXhr.responseType, - contentType, - body); - if (isTextResponse) { - fakeXhr.responseText = fakeXhr.response; - } - - if (fakeXhr.responseType === "document") { - fakeXhr.responseXML = fakeXhr.response; - } else if (fakeXhr.responseType === "" && isXmlContentType(contentType)) { - fakeXhr.responseXML = toXML(fakeXhr.responseText); - } - updateReadyState(fakeXhr, FakeXMLHttpRequest.DONE); -} - -/** - * @param {FakeXMLHttpRequest} fakeXhr - * @param {Array} xhrArgs - */ -function resetOnOpen(fakeXhr, xhrArgs) { - const realXHR = new originalXHR(); - const methods = [ "open", - "abort", - "getResponseHeader", - "getAllResponseHeaders", - "addEventListener", - "overrideMimeType", - "removeEventListener" ]; - - methods.forEach(function (method) { - fakeXhr[method] = function (...args) { - return realXHR[method](...args); - }; - }); - - fakeXhr.setRequestHeader = function setRequestHeader(name, value, ...args) { - if (typeof fakeXhr._onSetRequestHeader === "function") { - fakeXhr._onSetRequestHeader(name, value); - } - return realXHR.setRequestHeader(name, value, ...args); - }; - - fakeXhr.send = function send(data) { - if (typeof fakeXhr._onSend === "function") { - fakeXhr._onSend(data); - } - }; - - fakeXhr.actuallySend = function actuallySend(data) { - if (realXHR.responseType !== fakeXhr.responseType) { - realXHR.responseType = fakeXhr.responseType; - } - return realXHR.send(data); - }; - - function setXHRAttrs(args) { - args.forEach(function (attr) { - fakeXhr[attr] = realXHR[attr]; - }); - } - - function onReadyStateChangeBefore() { - fakeXhr.readyState = realXHR.readyState; - if (realXHR.readyState >= XHR_STATES.HEADERS_RECEIVED) { - setXHRAttrs(["status", "statusText"]); - } - if (realXHR.readyState >= FakeXMLHttpRequest.LOADING) { - setXHRAttrs(["response"]); - if (realXHR.responseType === "" || realXHR.responseType === "text") { - setXHRAttrs(["responseText"]); - } - } - if ( - realXHR.readyState === FakeXMLHttpRequest.DONE && - (realXHR.responseType === "" || realXHR.responseType === "document") - ) { - setXHRAttrs(["responseXML"]); - } - } - - function onReadyStateChangeAfter() { - if (fakeXhr.onreadystatechange) { - fakeXhr.onreadystatechange.call(fakeXhr, { target: fakeXhr, - currentTarget: fakeXhr }); - } - } - - realXHR.addEventListener("readystatechange", onReadyStateChangeBefore); - Object.keys(fakeXhr._listeners).forEach(function (event) { - fakeXhr._listeners[event].forEach(function (handler) { - realXHR.addEventListener(event, - handler.listener, - { capture: handler.capture, - once: handler.once }); - }); - }); - - function onLoadEndAfter() { - fakeXhr.__finishPromise(); - } - realXHR.addEventListener("readystatechange", onReadyStateChangeAfter); - realXHR.addEventListener("loadend", onLoadEndAfter); - realXHR.open(...xhrArgs); -} - -/** - * Check that the response's body set can be converted to the given - * responseType. - * Throw if not. - * @param {*} body - * @param {string|undefined} responseType - */ -function verifyResponseBodyType(body, responseType) { - let error = null; - if (responseType === "arraybuffer") { - if (typeof body !== "string" && !(body instanceof ArrayBuffer)) { - error = new Error("Attempted to respond to fake XMLHttpRequest with " + - body + ", which is not a string or ArrayBuffer."); - error.name = "InvalidBodyException"; - } - } else if (typeof body !== "string") { - error = new Error("Attempted to respond to fake XMLHttpRequest with " + - body + ", which is not a string."); - error.name = "InvalidBodyException"; - } - - if (error) { - throw error; - } -} - -/** - * @param {ArrayBuffer|string} body - * @param {string|undefined} encoding - * @returns {ArrayBuffer} - */ -function convertToArrayBuffer(body, encoding) { - if (body instanceof ArrayBuffer) { - return body; - } - return new TextEncoder(encoding || "utf-8").encode(body).buffer; -} - -/** - * @param {string|undefined} contentType - * @returns {boolean} - */ -function isXmlContentType(contentType) { - return !contentType || - /(text\/xml)|(application\/xml)|(\+xml)/.test(contentType); -} - -/** - * Convert the response's body into the given responseType. - * @param {string} responseType - * @param {string} contentType - * @returns {string|ArrayBuffer|Document|Blob|null|Object} - */ -function convertResponseBody(responseType, contentType, body) { - if (responseType === "" || responseType === "text") { - return body; - } else if (responseType === "arraybuffer") { - return convertToArrayBuffer(body); - } else if (responseType === "json") { - try { - return JSON.parse(body); - } catch (e) { - // Return parsing failure as null - return null; - } - } else if (responseType === "blob") { - const blobOptions = {}; - if (contentType) { - blobOptions.type = contentType; - } - return new Blob([convertToArrayBuffer(body)], blobOptions); - } else if (responseType === "document") { - if (isXmlContentType(contentType)) { - return toXML(body); - } - return null; - } - throw new Error("Invalid responseType " + responseType); -} - -/** - * Clear the response properties of the given XHR. - * @param {XMLHttpRequest} xhr - */ -function clearResponse(xhr) { - if (xhr.responseType === "" || xhr.responseType === "text") { - xhr.response = xhr.responseText = ""; - } else { - xhr.response = xhr.responseText = null; - } - xhr.responseXML = null; -} - -/** - * Steps to follow when there is an error, according to: - * https://xhr.spec.whatwg.org/#request-error-steps - * @param {FakeXMLHttpRequest} fakeXhr - */ -function requestErrorSteps(fakeXhr) { - clearResponse(fakeXhr); - fakeXhr.errorFlag = true; - fakeXhr.requestHeaders = {}; - fakeXhr.responseHeaders = {}; - - if (fakeXhr.readyState !== XHR_STATES.UNSENT && fakeXhr.sendFlag - && fakeXhr.readyState !== XHR_STATES.DONE) - { - updateReadyState(fakeXhr, XHR_STATES.DONE); - fakeXhr.sendFlag = false; - } -} diff --git a/tests/utils/request_mock/index.js b/tests/utils/request_mock/index.js deleted file mode 100644 index a63bea377bc..00000000000 --- a/tests/utils/request_mock/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import XHRMock from "./xhr_mock"; -export default XHRMock; diff --git a/tests/utils/request_mock/xhr_mock.js b/tests/utils/request_mock/xhr_mock.js deleted file mode 100644 index a4f7a0eff6c..00000000000 --- a/tests/utils/request_mock/xhr_mock.js +++ /dev/null @@ -1,224 +0,0 @@ -import useFakeXMLHttpRequest from "./fake_xhr"; - -/** - * Mock and lock XHR from being sent. - * Provide methods to obtain Promise when every locked XMLHttpRequest are - * finished. - * - * @example - * ```js - * const xhrMock = new XHRMock(); - * - * // Mock specific request - * xhrMock.respondTo( - * "GET", - * "https://time.akamai.com/?iso", - * [ 200, { "Content-Type": "text/plain"}, "2019-03-25T12:49:08.014Z"] - * ); - * xhrMock.lock(); // lock every request from being sent - * - * performRequests(); - * - * xhrMock.flush().then(() => { - * // every requests have finished / been handled - * }); - * ``` - * @class XHRMock - */ -export default class XHRMock { - constructor() { - this.isLocked = false; - - this._rules = []; - this._sendingQueue = []; - - this._fakeXHR = useFakeXMLHttpRequest(); - const self = this; - this._fakeXHR._onCreate = (xhr) => { - function shouldMockRequest(args) { - if (args.length < 2) { - return false; - } - - const [ method, url ] = args; - if (url === "" || url == null) { - return false; - } - - return !!self.__getRuleFor(url, method); - } - xhr._shouldMock = (...args) => shouldMockRequest(args); - - /** - * Contains tuple of all request headers, name and value, passed to the - * `requestHeadersSet` XHR method. - */ - const requestHeadersSet = []; - - xhr._onSetRequestHeader = (name, value) => { - requestHeadersSet.push([name, value]); - }; - - xhr._onSend = (data) => { - if (!xhr.async) { // We sadly cannot manage those without breaking stuff - /* eslint-disable-next-line no-console */ - console.warn("XHRMock: A non-async XMLHttpRequest was sent.", - "Sending immediately."); - - this.__xhrSend(xhr, data); - } else if (!this.isLocked) { - this.__xhrSend(xhr, data); - } else { - this._sendingQueue.push({ xhr, data, requestHeadersSet }); - } - }; - }; - } - - /** - * Mock response to a given request. - * - * @example - * ```js - * // with method and URL - * respond("GET", "http://a.com", [ 200, { - * "Content-Type": "text/plain", - * }, "data" ]); - * - * // with only URL == for every methods - * respond("http://data.org", [ 200, { - * "Content-Type": "text/plain", - * }, new Uint8Array([]) ]); - * ``` - */ - respondTo(...args) { - let response, url, method; - if (args.length === 2) { - url = args[0]; - response = args[1]; - method = null; - } else if (args.length === 3) { - method = args[0]; - url = args[1]; - response = args[2]; - } else { - throw new Error("XHRMock: wrong number of arguments"); - } - this._rules.push({ method, url, response }); - } - - /** - * Returns an array describing the currently locked requests. - * Each element in this array link to a locked request. They are in - * chronological order (from the oldest to the newest) and have the following - * keys: - * - ``xhr`` {``Object``} - Corresponding XMLHttpRequest (technically, it is - * not an instance of a regular XMLHttpRequest but rather our own - * implementation). - * - ``method`` (``string``): method used when opening the XMLHttpRequest. - * - ``url`` (``string``): URL used when opening the XMLHttpRequest. - * - ``requestHeadersSet`` (``Array``): Array containing all request headers - * set through the `setRequestHeader` method of that XHR, by chronological - * order (from earliest to latest). - * For each header set trough this method, you will get a tuple under - * the following form `[headerName: string, headerValue: string]`. - * - * @returns {Array.} - */ - getLockedXHR() { - return this._sendingQueue.map(req => ({ - xhr: req.xhr, - requestHeadersSet: req.requestHeadersSet, - method: req.xhr.method, - url: req.xhr.url })); - } - - /** - * Lock every request from being sent until unlocked - */ - lock() { - this.isLocked = true; - } - - /** - * Remove the lock and resolve the promise once every locked requests have - * finished (either aborted, on error or finished succesfully). - * @returns {Promise} - */ - unlock() { - this.isLocked = false; - return this.flush(); - } - - /** - * Perform every locked requests and resolve once every one of them have - * finished (either aborted, on error or finished succesfully). - * Keep the lock on, if one. - * @param {number} count - * @returns {Promise} - */ - flush(nbrOfRequests) { - const len = this._sendingQueue.length; - const proms = []; - const nbrOfRequestsToFlush = nbrOfRequests !== undefined ? - Math.min(len, nbrOfRequests) : len; - const nbrOfRequestThatStays = len - nbrOfRequestsToFlush; - while (this._sendingQueue.length > nbrOfRequestThatStays) { - const { xhr, data } = this._sendingQueue.shift(); - this.__xhrSend(xhr, data); - proms.push(xhr._finished); - } - return Promise.all(proms); - } - - /** - * Remove side-effect performed by the mock. - * /!\ The mock will be unusable after that. - */ - restore() { - this.flush(); - this._fakeXHR.restore(); - } - - /** - * Send the request given in args. - * @private - * @param {FakeXMLHttpRequest} fakeXhr - * @param {*} data - */ - __xhrSend(fakeXhr, data) { - if (fakeXhr.aborted || fakeXhr.readyState === 4) { - return; - } - - const rule = this.__getRuleFor(fakeXhr.url, fakeXhr.method); - if (!rule) { - fakeXhr.actuallySend(data); - return; - } - - const { response } = rule; - fakeXhr.respond(response[0], response[1], response[2]); - } - - /** - * If the request has been mocked, return the corresponding "rule". - * Else, return `null`. - * @private - * @param {string} url - * @param {string} method - * @returns {Object|null} - */ - __getRuleFor(url, method) { - const rules = this._rules.slice(); - for (let i = rules.length - 1; i >= 0; i--) { - const rule = rules[i]; - if (rule.url === url) { - if (rule.method == null || rule.method === method) { - return rule; - } - } - } - return null; - } -}