Files
shaka-player/test/dash/mpd_utils_unit.js
T
Joey Parrish fbbd63d96b test: Late load tests, fix Chromecast test flake (#4115)
This change fixes tests on Chromecast by loading tests later in the process.  Test scripts are now dynamically inserted by boot.js, rather than loaded by Karma.  The bootstrapping code then awaits the completion of that before starting the Karma frameworks (Jasmine) to run the tests.

This also removes the use of goog.provide/goog.require in tests and test utils.  We don't need to load test utils or library sources dynamically in each test, and this gives us more explicit control over script loading and ordering.

Closes #4094
2022-04-11 15:47:48 -07:00

764 lines
26 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
describe('MpdUtils', () => {
const MpdUtils = shaka.dash.MpdUtils;
describe('fillUriTemplate', () => {
it('handles a single RepresentationID identifier', () => {
expect(
MpdUtils.fillUriTemplate(
'/example/$RepresentationID$.mp4',
'100', null, null, null).toString()).toBe('/example/100.mp4');
// RepresentationID cannot use a width specifier.
expect(
MpdUtils.fillUriTemplate(
'/example/$RepresentationID%01d$.mp4',
'100', null, null, null).toString()).toBe('/example/100.mp4');
expect(
MpdUtils.fillUriTemplate(
'/example/$RepresentationID$.mp4',
null, null, null, null).toString())
.toBe('/example/$RepresentationID$.mp4');
});
it('handles a single Number identifier', () => {
expect(
MpdUtils.fillUriTemplate(
'/example/$Number$.mp4',
null, 100, null, null).toString()).toBe('/example/100.mp4');
expect(
MpdUtils.fillUriTemplate(
'/example/$Number%05d$.mp4',
null, 100, null, null).toString()).toBe('/example/00100.mp4');
expect(
MpdUtils.fillUriTemplate(
'/example/$Number$.mp4',
null, null, null, null).toString())
.toBe('/example/$Number$.mp4');
});
it('handles a single Bandwidth identifier', () => {
expect(
MpdUtils.fillUriTemplate(
'/example/$Bandwidth$.mp4',
null, null, 100, null).toString()).toBe('/example/100.mp4');
expect(
MpdUtils.fillUriTemplate(
'/example/$Bandwidth%05d$.mp4',
null, null, 100, null).toString()).toBe('/example/00100.mp4');
expect(
MpdUtils.fillUriTemplate(
'/example/$Bandwidth$.mp4',
null, null, null, null).toString())
.toBe('/example/$Bandwidth$.mp4');
});
it('handles a single Time identifier', () => {
expect(
MpdUtils.fillUriTemplate(
'/example/$Time$.mp4',
null, null, null, 100).toString()).toBe('/example/100.mp4');
expect(
MpdUtils.fillUriTemplate(
'/example/$Time%05d$.mp4',
null, null, null, 100).toString()).toBe('/example/00100.mp4');
expect(
MpdUtils.fillUriTemplate(
'/example/$Time$.mp4',
null, null, null, null).toString())
.toBe('/example/$Time$.mp4');
});
it('handles rounding errors for calculated Times', () => {
expect(
MpdUtils.fillUriTemplate(
'/example/$Time$.mp4',
null, null, null, 100.0001).toString()).toBe('/example/100.mp4');
expect(
MpdUtils.fillUriTemplate(
'/example/$Time%05d$.mp4',
null, null, null, 99.9999).toString()).toBe('/example/00100.mp4');
});
it('handles multiple identifiers', () => {
expect(
MpdUtils.fillUriTemplate(
'/example/$RepresentationID$_$Number$_$Bandwidth$_$Time$.mp4',
'1', 2, 3, 4).toString()).toBe('/example/1_2_3_4.mp4');
// No spaces.
expect(
MpdUtils.fillUriTemplate(
'/example/$RepresentationID$$Number$$Bandwidth$$Time$.mp4',
'1', 2, 3, 4).toString()).toBe('/example/1234.mp4');
// Different order.
expect(
MpdUtils.fillUriTemplate(
'/example/$Bandwidth$_$Time$_$RepresentationID$_$Number$.mp4',
'1', 2, 3, 4).toString()).toBe('/example/3_4_1_2.mp4');
// Single width.
expect(
MpdUtils.fillUriTemplate(
'$RepresentationID$_$Number%01d$_$Bandwidth%01d$_$Time%01d$',
'1', 2, 3, 400).toString()).toBe('1_2_3_400');
// Different widths.
expect(
MpdUtils.fillUriTemplate(
'$RepresentationID$_$Number%02d$_$Bandwidth%02d$_$Time%02d$',
'1', 2, 3, 4).toString()).toBe('1_02_03_04');
// Double $$.
expect(
MpdUtils.fillUriTemplate(
'$$/$RepresentationID$$$$Number$$$$Bandwidth$$$$Time$$$.$$',
'1', 2, 3, 4).toString()).toBe('$/1$2$3$4$.$');
});
it('handles invalid identifiers', () => {
expect(
MpdUtils.fillUriTemplate(
'/example/$Garbage$.mp4',
'1', 2, 3, 4).toString()).toBe('/example/$Garbage$.mp4');
expect(
MpdUtils.fillUriTemplate(
'/example/$Time.mp4',
'1', 2, 3, 4).toString()).toBe('/example/$Time.mp4');
});
it('handles non-decimal format specifiers', () => {
expect(
MpdUtils.fillUriTemplate(
'/$Number%05x$_$Number%01X$_$Number%01u$_$Number%01o$.mp4',
'', 180, 0, 0).toString()).toBe('/000b4_B4_180_264.mp4');
});
});
describe('createTimeline', () => {
it('works in normal case', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(10, 10, 0),
createTimePoint(20, 10, 0),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 20},
{start: 20, end: 30},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles null start time', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(null, 10, 0),
createTimePoint(null, 10, 0),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 20},
{start: 20, end: 30},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles gaps', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(15, 10, 0),
];
const result = [
{start: 0, end: 15},
{start: 15, end: 25},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles overlap', () => {
const timePoints = [
createTimePoint(0, 15, 0),
createTimePoint(10, 10, 0),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 20},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles repetitions', () => {
const timePoints = [
createTimePoint(0, 10, 5),
createTimePoint(60, 10, 0),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 20},
{start: 20, end: 30},
{start: 30, end: 40},
{start: 40, end: 50},
{start: 50, end: 60},
{start: 60, end: 70},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles null repeat', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(10, 10, null),
createTimePoint(20, 10, 0),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 20},
{start: 20, end: 30},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles repetitions with gap', () => {
const timePoints = [
createTimePoint(0, 10, 2),
createTimePoint(35, 10, 0),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 20},
{start: 20, end: 35},
{start: 35, end: 45},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles negative repetitions', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(10, 10, -1),
createTimePoint(40, 10, 0),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 20},
{start: 20, end: 30},
{start: 30, end: 40},
{start: 40, end: 50},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles negative repetitions with uneven border', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(10, 10, -1),
createTimePoint(45, 5, 0),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 20},
{start: 20, end: 30},
{start: 30, end: 40},
{start: 40, end: 45},
{start: 45, end: 50},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles negative repetitions w/ bad next start time', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(10, 10, -1),
createTimePoint(5, 10, 0),
];
const result = [
{start: 0, end: 10},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles negative repetitions w/ null next start time', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(10, 10, -1),
createTimePoint(null, 10, 0),
];
const result = [
{start: 0, end: 10},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles negative repetitions at end', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(10, 5, -1),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 15},
{start: 15, end: 20},
{start: 20, end: 25},
];
checkTimePoints(timePoints, result, 1, 0, 25);
});
it('handles negative repetitions at end w/o Period length', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(10, 5, -1),
];
const result = [
{start: 0, end: 10},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('handles negative repetitions at end w/ bad Period length', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(10, 10, 0),
createTimePoint(25, 5, -1),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 20},
];
checkTimePoints(timePoints, result, 1, 0, 20);
});
it('ignores elements after null duration', () => {
const timePoints = [
createTimePoint(0, 10, 0),
createTimePoint(10, 10, 0),
createTimePoint(20, null, 0),
createTimePoint(30, 10, 0),
createTimePoint(40, 10, 0),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 20},
];
checkTimePoints(timePoints, result, 1, 0, Infinity);
});
it('adjust start with presentationTimeOffset', () => {
const timePoints = [
createTimePoint(10, 10, 0),
createTimePoint(20, 10, 0),
createTimePoint(30, 10, 0),
createTimePoint(40, 10, 0),
];
const result = [
{start: 0, end: 10},
{start: 10, end: 20},
{start: 20, end: 30},
{start: 30, end: 40},
];
checkTimePoints(timePoints, result, 1, 10, Infinity);
});
it('adjust start time w/ t missing', () => {
// No S@t is equivalent to t=0, which should use PTO to make negative.
// See https://github.com/shaka-project/shaka-player/issues/2590
const timePoints = [
createTimePoint(null, 10, 0),
createTimePoint(10, 10, 0),
];
const result = [
{start: -5, end: 5},
{start: 5, end: 15},
];
checkTimePoints(timePoints, result, 1, 5, Infinity);
});
/**
* Creates a new TimePoint.
*
* @param {?number} t
* @param {?number} d
* @param {?number} r
* @return {{t: ?number, d: ?number, r: ?number}}
*/
function createTimePoint(t, d, r) {
return {t: t, d: d, r: r};
}
/**
* Checks that the createTimeline works with the given timePoints and the
* given expected results.
*
* @param {!Array.<{t: ?number, d: ?number, r: ?number}>} points
* @param {!Array.<{start: number, end: number}>} expected
* @param {number} timescale
* @param {number} presentationTimeOffset
* @param {number} periodDuration
*/
function checkTimePoints(points, expected, timescale,
presentationTimeOffset, periodDuration) {
// Construct a SegmentTimeline Node object.
const xmlLines = ['<?xml version="1.0"?>', '<SegmentTimeline>'];
for (const p of points) {
xmlLines.push('<S' +
(p.t != null ? ' t="' + p.t + '"' : '') +
(p.d != null ? ' d="' + p.d + '"' : '') +
(p.r != null ? ' r="' + p.r + '"' : '') +
' />');
}
xmlLines.push('</SegmentTimeline>');
const parser = new DOMParser();
const xml =
parser.parseFromString(xmlLines.join('\n'), 'application/xml');
const segmentTimeline = xml.documentElement;
console.assert(segmentTimeline);
const timeline = MpdUtils.createTimeline(
segmentTimeline, timescale, presentationTimeOffset, periodDuration);
expect(timeline).toEqual(
expected.map((c) => jasmine.objectContaining(c)));
}
});
describe('processXlinks', () => {
const Error = shaka.util.Error;
/** @type {!shaka.test.FakeNetworkingEngine} */
let fakeNetEngine;
/** @type {shaka.extern.RetryParameters} */
let retry;
/** @type {!DOMParser} */
let parser;
/** @type {boolean} */
let failGracefully;
beforeEach(() => {
failGracefully = false;
retry = shaka.net.NetworkingEngine.defaultRetryParameters();
fakeNetEngine = new shaka.test.FakeNetworkingEngine();
parser = new DOMParser();
});
it('will replace elements and children', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink1" xlink:actuate="onLoad" />');
const xlinkXMLString = '<ToReplace variable="1"><Contents /></ToReplace>';
const desiredXMLString = inBaseContainer(
'<ToReplace variable="1"><Contents /></ToReplace>');
fakeNetEngine.setResponseText('https://xlink1', xlinkXMLString);
await testSucceeds(baseXMLString, desiredXMLString, 1);
});
it('preserves non-xlink attributes', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace otherVariable="q" xlink:href="https://xlink1" ' +
'xlink:actuate="onLoad" />');
const xlinkXMLString = '<ToReplace variable="1"><Contents /></ToReplace>';
const desiredXMLString = inBaseContainer(
'<ToReplace otherVariable="q" variable="1"><Contents /></ToReplace>');
fakeNetEngine.setResponseText('https://xlink1', xlinkXMLString);
await testSucceeds(baseXMLString, desiredXMLString, 1);
});
it('preserves text', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink1" xlink:actuate="onLoad" />');
const xlinkXMLString =
'<ToReplace variable="1">TEXT CONTAINED WITHIN</ToReplace>';
const desiredXMLString = inBaseContainer(
'<ToReplace variable="1">TEXT CONTAINED WITHIN</ToReplace>');
fakeNetEngine.setResponseText('https://xlink1', xlinkXMLString);
await testSucceeds(baseXMLString, desiredXMLString, 1);
});
it('supports multiple replacements', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink1" xlink:actuate="onLoad" />',
'<ToReplace xlink:href="https://xlink2" xlink:actuate="onLoad" />');
const xlinkXMLString1 = makeRecursiveXMLString(1, 'https://xlink3');
const xlinkXMLString2 =
'<ToReplace variable="2"><Contents /></ToReplace>';
const xlinkXMLString3 = '<ToReplace otherVariable="blue" />';
const desiredXMLString = inBaseContainer(
'<ToReplace xmlns="urn:mpeg:dash:schema:mpd:2011" ' +
'xmlns:xlink="http://www.w3.org/1999/xlink" variable="1">' +
'<ToReplace otherVariable="blue" /></ToReplace>',
'<ToReplace variable="2"><Contents /></ToReplace>');
fakeNetEngine
.setResponseText('https://xlink1', xlinkXMLString1)
.setResponseText('https://xlink2', xlinkXMLString2)
.setResponseText('https://xlink3', xlinkXMLString3);
await testSucceeds(baseXMLString, desiredXMLString, 3);
});
it('fails if loaded file is invalid xml', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink1" xlink:actuate="onLoad" />');
// Note this does not have a close angle bracket.
const xlinkXMLString = '<ToReplace></ToReplace';
const expectedError = new shaka.util.Error(
Error.Severity.CRITICAL, Error.Category.MANIFEST,
Error.Code.DASH_INVALID_XML, 'https://xlink1');
fakeNetEngine.setResponseText('https://xlink1', xlinkXMLString);
await testFails(baseXMLString, expectedError, 1);
});
it('fails if it recurses too many times', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink0" xlink:actuate="onLoad" />');
// Create a large but finite number of links, so this won't
// infinitely recurse if there isn't a depth limit.
for (let i = 0; i < 20; i++) {
const key = 'https://xlink' + i;
const value = makeRecursiveXMLString(0, 'https://xlink' + (i + 1));
fakeNetEngine.setResponseText(key, value);
}
const expectedError = new shaka.util.Error(
Error.Severity.CRITICAL, Error.Category.MANIFEST,
Error.Code.DASH_XLINK_DEPTH_LIMIT);
await testFails(baseXMLString, expectedError, 5);
});
it('preserves url parameters', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink1?parameter" ' +
'xlink:actuate="onLoad" />');
const xlinkXMLString = '<ToReplace variable="1"><Contents /></ToReplace>';
const desiredXMLString = inBaseContainer(
'<ToReplace variable="1"><Contents /></ToReplace>');
fakeNetEngine.setResponseText(
'https://xlink1?parameter', xlinkXMLString);
await testSucceeds(baseXMLString, desiredXMLString, 1);
});
it('replaces existing contents', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink1" xlink:actuate="onLoad">' +
'<Unwanted /></ToReplace>');
const xlinkXMLString = '<ToReplace variable="1"><Contents /></ToReplace>';
const desiredXMLString = inBaseContainer(
'<ToReplace variable="1"><Contents /></ToReplace>');
fakeNetEngine.setResponseText('https://xlink1', xlinkXMLString);
await testSucceeds(baseXMLString, desiredXMLString, 1);
});
it('handles relative links', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="xlink1" xlink:actuate="onLoad" />',
'<ToReplace xlink:href="xlink2" xlink:actuate="onLoad" />');
const xlinkXMLString1 = // This is loaded relative to base.
makeRecursiveXMLString(1, 'xlink3');
const xlinkXMLString2 = // This is loaded relative to base.
'<ToReplace variable="2"><Contents /></ToReplace>';
const xlinkXMLString3 = // This is loaded relative to string1.
'<ToReplace variable="3" />';
fakeNetEngine
.setResponseText('https://base/xlink1', xlinkXMLString1)
.setResponseText('https://base/xlink2', xlinkXMLString2)
.setResponseText('https://base/xlink3', xlinkXMLString3);
const desiredXMLString = inBaseContainer(
'<ToReplace xmlns="urn:mpeg:dash:schema:mpd:2011" ' +
'xmlns:xlink="http://www.w3.org/1999/xlink" variable="1">' +
'<ToReplace variable="3" /></ToReplace>',
'<ToReplace variable="2"><Contents /></ToReplace>');
await testSucceeds(baseXMLString, desiredXMLString, 3);
});
it('fails for actuate=onRequest', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink1" ' +
'xlink:actuate="onRequest" />');
const xlinkXMLString = '<ToReplace variable="1"><Contents /></ToReplace>';
const expectedError = new shaka.util.Error(
Error.Severity.CRITICAL, Error.Category.MANIFEST,
Error.Code.DASH_UNSUPPORTED_XLINK_ACTUATE);
fakeNetEngine.setResponseText('https://xlink1', xlinkXMLString);
await testFails(baseXMLString, expectedError, 0);
});
it('fails for no actuate', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink1" />');
const xlinkXMLString = '<ToReplace variable="1"><Contents /></ToReplace>';
const expectedError = new shaka.util.Error(
Error.Severity.CRITICAL, Error.Category.MANIFEST,
Error.Code.DASH_UNSUPPORTED_XLINK_ACTUATE);
fakeNetEngine.setResponseText('https://xlink1', xlinkXMLString);
await testFails(baseXMLString, expectedError, 0);
});
it('removes elements with resolve-to-zero', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="urn:mpeg:dash:resolve-to-zero:2013" />');
const desiredXMLString = inBaseContainer();
await testSucceeds(baseXMLString, desiredXMLString, 0);
});
it('needs the top-level to match the link\'s tagName', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink1" xlink:actuate="onLoad" />');
const xlinkXMLString = '<BadTagName</BadTagName>';
fakeNetEngine.setResponseText('https://xlink1', xlinkXMLString);
await testFails(baseXMLString, null, 1);
});
it('doesn\'t error when set to fail gracefully', async () => {
failGracefully = true;
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink1" xlink:actuate="onLoad">' +
'<DefaultContents />' +
'</ToReplace>');
const xlinkXMLString = '<BadTagName</BadTagName>';
const desiredXMLString = inBaseContainer(
'<ToReplace><DefaultContents /></ToReplace>');
fakeNetEngine.setResponseText('https://xlink1', xlinkXMLString);
await testSucceeds(baseXMLString, desiredXMLString, 1);
});
it('interrupts requests on abort', async () => {
const baseXMLString = inBaseContainer(
'<ToReplace xlink:href="https://xlink0" xlink:actuate="onLoad" />');
// Create a few links. This is few enough that it would succeed if we
// didn't abort it.
for (let i = 0; i < 4; i++) {
const key = 'https://xlink' + i;
const value = makeRecursiveXMLString(0, 'https://xlink' + (i + 1));
fakeNetEngine.setResponseText(key, value);
}
/** @type {!shaka.util.PublicPromise} */
const continuePromise = fakeNetEngine.delayNextRequest();
const xml = parser.parseFromString(baseXMLString, 'text/xml')
.documentElement;
/** @type {!shaka.extern.IAbortableOperation} */
const operation = MpdUtils.processXlinks(
xml, retry, failGracefully, 'https://base', fakeNetEngine);
const abort = async () => {
await shaka.test.Util.shortDelay();
// Only one request has been made so far.
expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
continuePromise.resolve();
// Abort the operation.
operation.abort();
};
// The operation was aborted.
const expected = shaka.test.Util.jasmineError(new shaka.util.Error(
Error.Severity.CRITICAL,
Error.Category.PLAYER,
Error.Code.OPERATION_ABORTED));
const p = expectAsync(operation.promise).toBeRejectedWith(expected);
await Promise.all([abort(), p]);
// Still only one request has been made.
expect(fakeNetEngine.request).toHaveBeenCalledTimes(1);
});
it('ignores SegmentTimeline children', async () => {
const baseXMLString = inBaseContainer(
'<SegmentTimeline>' +
' <ToReplace xlink:href="https://xlink1" ' +
' xlink:actuate="onRequest" />' +
'</SegmentTimeline>');
await testSucceeds(baseXMLString, baseXMLString, 0);
});
async function testSucceeds(
baseXMLString, desiredXMLString, desiredNetCalls) {
const desiredXML = parser.parseFromString(desiredXMLString, 'text/xml')
.documentElement;
const finalXML = await testRequest(baseXMLString);
expect(fakeNetEngine.request).toHaveBeenCalledTimes(desiredNetCalls);
expect(finalXML).toEqualElement(desiredXML);
}
async function testFails(baseXMLString, desiredError, desiredNetCalls) {
if (desiredError) {
await expectAsync(testRequest(baseXMLString))
.toBeRejectedWith(shaka.test.Util.jasmineError(desiredError));
} else {
await expectAsync(testRequest(baseXMLString)).toBeRejected();
}
expect(fakeNetEngine.request).toHaveBeenCalledTimes(desiredNetCalls);
}
/**
* Creates an XML string with an xlink link to another URL,
* for use in testing recursive chains of xlink links.
* @param {number} variable
* @param {string} link
* @return {string}
* @private
*/
function makeRecursiveXMLString(variable, link) {
const format =
'<ToReplace xmlns="urn:mpeg:dash:schema:mpd:2011" ' +
'xmlns:xlink="http://www.w3.org/1999/xlink" variable="%(let)s">' +
'<ToReplace xlink:href="%(link)s" xlink:actuate="onLoad" />' +
'</ToReplace>';
return sprintf(format, {'let': variable, 'link': link});
}
/**
* @param {string=} toReplaceOne
* @param {string=} toReplaceTwo
* @return {string}
* @private
*/
function inBaseContainer(toReplaceOne = '', toReplaceTwo = '') {
const format =
'<Container xmlns="urn:mpeg:dash:schema:mpd:2011" ' +
'xmlns:xlink="http://www.w3.org/1999/xlink">' +
'<Thing>' +
'%(toReplaceOne)s' +
'</Thing>' +
'%(toReplaceTwo)s' +
'</Container>';
return sprintf(format, {
toReplaceOne: toReplaceOne,
toReplaceTwo: toReplaceTwo});
}
function testRequest(baseXMLString) {
const xml = parser.parseFromString(baseXMLString, 'text/xml')
.documentElement;
return MpdUtils.processXlinks(xml, retry, failGracefully, 'https://base',
fakeNetEngine).promise;
}
});
});