summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/annotation-model/scripts/JSONtest.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/annotation-model/scripts/JSONtest.js')
-rw-r--r--testing/web-platform/tests/annotation-model/scripts/JSONtest.js803
1 files changed, 803 insertions, 0 deletions
diff --git a/testing/web-platform/tests/annotation-model/scripts/JSONtest.js b/testing/web-platform/tests/annotation-model/scripts/JSONtest.js
new file mode 100644
index 000000000..3ee49b86a
--- /dev/null
+++ b/testing/web-platform/tests/annotation-model/scripts/JSONtest.js
@@ -0,0 +1,803 @@
+/* globals add_completion_callback, Promise, showdown, done, assert_true, Ajv, on_event */
+
+/**
+ * Creates a JSONtest object. If the parameters are supplied
+ * it also loads a referenced testFile, processes that file, loads any
+ * referenced external assertions, and sets up event listeners to process the
+ * user's test data. The loading is done asynchronously via Promises. The test
+ * button's text is changed to Loading while it is processing, and to "Check
+ * JSON" once the data is loaded.
+ *
+ * @constructor
+ * @param {object} params
+ * @param {string} [params.test] - object containing JSON test definition
+ * @param {string} [params.testFile] - URI of a file with JSON test definition
+ * @param {string} params.runTest - IDREF of an element that when clicked will run the test
+ * @param {string} params.testInput - IDREF of an element that contains the JSON(-LD) to evaluate against the assertions in the test / testFile
+ * @event DOMContentLoaded Calls init once DOM is fully loaded
+ * @returns {object} Reference to the new object
+ */
+
+function JSONtest(params) {
+ 'use strict';
+
+ this.Assertions = []; // object that will contain the assertions to process
+ this.AssertionText = ""; // string that holds the titles of all the assertions in use
+ this.DescriptionText = "";
+ this.Base = null; // URI "base" for the test suite being run
+ this.TestDir = null; // URI "base" for the test case being run
+ this.Params = null; // paramaters passed in
+ this.Promise = null; // master Promise that resolves when intialization is complete
+ this.Properties = null; // testharness_properties from the opening window
+ this.SkipFailures = []; // list of assertionType values that should be skipped if their test would fail
+ this.Test = null; // test being run
+ this.AssertionCounter = 0;// keeps track of which assertion is being processed
+
+ this._assertionCache = [];// Array to put loaded assertions into
+ this._assertionText = []; // Array of text or nested arrays of assertions
+ this._loading = true;
+
+ showdown.extension('strip', function() {
+ return [
+ { type: 'output',
+ regex: /<p>/,
+ replace: ''
+ },
+ { type: 'output',
+ regex: /<\/p>$/,
+ replace: ''
+ }
+ ];
+ });
+
+
+ this.markdown = new showdown.Converter({ extensions: [ 'strip' ] }) ;
+
+ var pending = [] ;
+
+ // set up in case DOM finishes loading early
+ pending.push(new Promise(function(resolve) {
+ on_event(document, "DOMContentLoaded", function() {
+ resolve(true);
+ }.bind(this));
+ }.bind(this)));
+
+ // create an ajv object that will stay around so that caching
+ // of schema that are compiled just works
+ this.ajv = new Ajv({allErrors: true, validateSchema: false}) ;
+
+ // determine the base URI for the test collection. This is
+ // the top level folder in the test "document.location"
+
+ var l = document.location;
+ var p = l.pathname;
+ this.TestDir = p.substr(0, 1+p.lastIndexOf('/'));
+ this.Base = p.substr(0, 1+p.indexOf('/', 1));
+
+ // if we are under runner, then there are props in the parent window
+ //
+ // if "output" is set in that, then pause at the end of running so the output
+ // can be analyzed. @@@TODO@@@
+ if (window && window.opener && window.opener.testharness_properties) {
+ this.Properties = window.opener.testharness_properties;
+ }
+
+ this.Params = params;
+
+ // if there is a list of definitions in the params,
+ // include them
+ if (this.Params.schemaDefs) {
+ var defPromise = new Promise(function(resolve, reject) {
+ var promisedSchema = this.Params.schemaDefs.map(function(item) {
+ return this.loadDefinition(item);
+ }.bind(this));
+
+ // Once all the loadAssertion promises resolve...
+ Promise.all(promisedSchema)
+ .then(function (schemaContents) {
+ this.ajv.addSchema(schemaContents);
+ resolve(true);
+ }.bind(this))
+ .catch(function(err) {
+ reject(err);
+ }.bind(this));
+ }.bind(this));
+ // these schema need to load up too
+ pending.push(defPromise) ;
+ }
+
+ // start by loading the test (it might be inline, but
+ // loadTest deals with that
+ pending.push(this.loadTest(params)
+ .then(function(test) {
+ // if the test is NOT an object, turn it into one
+ if (typeof test === 'string') {
+ test = JSON.parse(test) ;
+ }
+
+ this.Test = test;
+
+ // Test should have information that we can put in the template
+
+ if (test.description) {
+ this.DescriptionText = test.description;
+ }
+
+ if (test.hasOwnProperty("skipFailures") && Array.isArray(test.skipFailures) ) {
+ this.SkipFailures = test.skipFailures;
+ }
+
+ if (test.content) {
+ // we have content
+ if (typeof test.content === "string") {
+ // the test content is a string - meaning it is a reference to a file of content
+ var cPromise = new Promise(function(resolve, reject) {
+ this.loadDefinition(test.content)
+ .then(function(content) {
+ if (typeof content === 'string') {
+ content = JSON.parse(content) ;
+ }
+ test.content = content;
+ resolve(true);
+ }.bind(this))
+ .catch(function(err) {
+ reject("Loading " + test.content + ": " + JSON.stringify(err));
+ });
+
+ }.bind(this));
+ pending.push(cPromise);
+ }
+ }
+
+ return new Promise(function(resolve, reject) {
+ if (test.assertions &&
+ typeof test.assertions === "object") {
+ // we have at least one assertion
+ // get the inline contents and the references to external files
+ var assertFiles = this._assertionRefs(test.assertions);
+
+ var promisedAsserts = assertFiles.map(function(item) {
+ return this.loadAssertion(item);
+ }.bind(this));
+
+ // Once all the loadAssertion promises resolve...
+ Promise.all(promisedAsserts)
+ .then(function (assertContents) {
+ // assertContents has assertions in document order
+
+ var typeMap = {
+ 'must' : "<b>[MANDATORY]</b> ",
+ 'may' : "<b>[OPTIONAL]</b> ",
+ 'should' : "<b>[RECOMMENDED]</b> "
+ };
+
+ var assertIdx = 0;
+
+ // populate the display of assertions that are being exercised
+ // returns the list of top level assertions to walk through
+
+ var buildList = function(assertions, level) {
+ if (level === undefined) {
+ level = 1;
+ }
+
+ // accumulate the assertions - but only when level is 0
+ var list = [] ;
+
+ var type = "";
+ if (assertions) {
+ if (typeof assertions === "object" && assertions.hasOwnProperty('assertions')) {
+ // this is a conditionObject
+ if (level === 0) {
+ list.push(assertContents[assertIdx]);
+ }
+ type = assertContents[assertIdx].hasOwnProperty('assertionType') ?
+ assertContents[assertIdx].assertionType : "must" ;
+
+ // ensure type defaults to must
+ if (!typeMap.hasOwnProperty(type)) {
+ type = "must";
+ }
+
+ this.AssertionText += "<li>" + typeMap[type] + this.markdown.makeHtml(assertContents[assertIdx++].title);
+ this.AssertionText += "<ol>";
+ buildList(assertions.assertions, level+1) ;
+ this.AssertionText += "</ol></li>\n";
+ } else {
+ // it is NOT a conditionObject - must be an array
+ assertions.forEach( function(assert) {
+ if (typeof assert === "object" && Array.isArray(assert)) {
+ this.AssertionText += "<ol>";
+ // it is a nested list - recurse
+ buildList(assert, level+1) ;
+ this.AssertionText += "</ol>\n";
+ } else if (typeof assert === "object" &&
+ !Array.isArray(assert) &&
+ assert.hasOwnProperty('assertions')) {
+ if (level === 0) {
+ list.push(assertContents[assertIdx]);
+ }
+ type = assertContents[assertIdx].hasOwnProperty('assertionType') ?
+ assertContents[assertIdx].assertionType : "must" ;
+
+ // ensure type defaults to must
+ if (!typeMap.hasOwnProperty(type)) {
+ type = "must";
+ }
+
+ // there is a condition object in the array
+ this.AssertionText += "<li>" + typeMap[type] + this.markdown.makeHtml(assertContents[assertIdx++].title);
+ this.AssertionText += "<ol>";
+ buildList(assert, level+1) ; // capture the children too
+ this.AssertionText += "</ol></li>\n";
+ } else {
+ if (level === 0) {
+ list.push(assertContents[assertIdx]);
+ }
+ type = assertContents[assertIdx].hasOwnProperty('assertionType') ?
+ assertContents[assertIdx].assertionType : "must" ;
+
+ // ensure type defaults to must
+ if (!typeMap.hasOwnProperty(type)) {
+ type = "must";
+ }
+
+ this.AssertionText += "<li>" + typeMap[type] + this.markdown.makeHtml(assertContents[assertIdx++].title) + "</li>\n";
+ }
+ }.bind(this));
+ }
+ }
+ return list;
+ }.bind(this);
+
+ // Assertions will ONLY contain the top level assertions
+ this.Assertions = buildList(test.assertions, 0);
+ resolve(true);
+ }.bind(this))
+ .catch(function(err) {
+ reject(err);
+ }.bind(this));
+ } else {
+ if (!test.assertions) {
+ reject("Test has no assertion property");
+ } else {
+ reject("Test assertion property is not an Array");
+ }
+ }
+ }.bind(this));
+ }.bind(this)));
+
+ this.Promise = new Promise(function(resolve, reject) {
+ // once the DOM and the test / assertions are loaded... set us up
+ Promise.all(pending)
+ .then(function() {
+ this.loading = false;
+ this.init();
+ resolve(this);
+ }.bind(this))
+ .catch(function(err) {
+ // loading the components failed somehow - report the errors and mark the test failed
+ test( function() {
+ assert_true(false, "Loading of test components failed: " +JSON.stringify(err)) ;
+ }, "Loading test components");
+ done() ;
+ reject("Loading of test components failed: "+JSON.stringify(err));
+ return ;
+ }.bind(this));
+ }.bind(this));
+
+ return this;
+}
+
+JSONtest.prototype = {
+
+ /**
+ * @listens click
+ */
+ init: function() {
+ 'use strict';
+ // set up a handler
+ var runButton = document.getElementById(this.Params.runTest) ;
+ var closeButton = document.getElementById(this.Params.closeWindow) ;
+ var testInput = document.getElementById(this.Params.testInput) ;
+ var assertion = document.getElementById("assertion") ;
+ var desc = document.getElementById("testDescription") ;
+
+ if (!this.loading) {
+ if (runButton) {
+ runButton.disabled = false;
+ runButton.value = "Check JSON";
+ }
+ if (desc) {
+ desc.innerHTML = this.DescriptionText;
+ }
+ if (assertion) {
+ assertion.innerHTML = "<ol>" + this.AssertionText + "</ol>\n";
+ }
+ } else {
+ window.alert("Loading did not finish before init handler was called!");
+ }
+
+ // @@@TODO@@@ implement the output showing handler
+ if (0 && this.Properties && this.Properties.output && closeButton) {
+ // set up a callback
+ add_completion_callback( function() {
+ var p = new Promise(function(resolve) {
+ closeButton.style.display = "inline";
+ closeButton.disabled = false;
+ on_event(closeButton, "click", function() {
+ resolve(true);
+ });
+ }.bind(this));
+ p.then();
+ }.bind(this));
+ }
+
+ if (runButton) {
+ on_event(runButton, "click", function() {
+ // user clicked
+ var content = testInput.value;
+ runButton.disabled = true;
+
+ // make sure content is an object
+ if (typeof content === "string") {
+ try {
+ content = JSON.parse(content) ;
+ } catch(err) {
+ // if the parsing failed, create a special test and mark it failed
+ test( function() {
+ assert_true(false, "Parse of JSON failed: " + err) ;
+ }, "Parsing submitted input");
+ // and just give up
+ done();
+ return ;
+ }
+ }
+
+ // iterate over all of the tests for this instance
+ this.runTests(this.Assertions, content);
+
+ // explicitly tell the test framework we are done
+ done();
+ }.bind(this));
+ }
+ },
+
+ // runTests - process tests
+ /**
+ * @param {object} assertions - List of assertions to process
+ * @param {string} content - JSON(-LD) to be evaluated
+ * @param {string} [testAction='continue'] - state of test processing (in parent when recursing)
+ * @param {integer} [level=0] - depth of recursion since assertion lists can nest
+ * @param {string} [compareWith='and'] - the way the results of the referenced assertions should be compared
+ * @returns {string} - the testAction resulting from evaluating all of the assertions
+ */
+ runTests: function(assertions, content, testAction, level, compareWith) {
+ 'use strict';
+
+ // level
+ if (level === undefined) {
+ level = 1;
+ }
+
+ // testAction
+ if (testAction === undefined) {
+ testAction = 'continue';
+ }
+
+ // compareWith
+ if (compareWith === undefined) {
+ compareWith = 'and';
+ }
+
+ var typeMap = {
+ 'must' : "",
+ 'may' : "INFORMATIONAL: ",
+ 'should' : "WARNING: "
+ };
+
+
+ // for each assertion (in order) load the external json schema if
+ // one is referenced, or use the inline schema if supplied
+ // validate content against the referenced schema
+
+ var theResults = [] ;
+
+ if (assertions) {
+
+ assertions.forEach( function(assert, num) {
+
+ var expected = assert.hasOwnProperty('expectedResult') ?
+ assert.expectedResult : 'valid' ;
+ var message = assert.hasOwnProperty('errorMessage') ?
+ assert.errorMessage : "Result was not " + expected;
+ var type = assert.hasOwnProperty('assertionType') ?
+ assert.assertionType.toLowerCase() : "must" ;
+ if (!typeMap.hasOwnProperty(type)) {
+ type = "must";
+ }
+
+ // first - what is the type of the assert
+ if (typeof assert === "object" && !Array.isArray(assert)) {
+ if (assert.hasOwnProperty("compareWith") && assert.hasOwnProperty("assertions") && Array.isArray(assert.assertions) ) {
+ // this is a comparisonObject
+ var r = this.runTests(assert.assertions, content, testAction, level+1, assert.compareWith);
+ // r is an object that contains, among other things, an array of results from the child assertions
+ testAction = r.action;
+
+ // evaluate the results against the compareWith setting
+ var result = true;
+ var data = r.results ;
+ var i;
+
+ if (assert.compareWith === "or") {
+ result = false;
+ for(i = 0; i < data.length; i++) {
+ if (data[i]) {
+ result = true;
+ }
+ }
+ } else {
+ for(i = 0; i < data.length; i++) {
+ if (!data[i]) {
+ result = false;
+ }
+ }
+ }
+
+ // create a test and push the result
+ test(function() {
+ var newAction = this.determineAction(assert, result) ;
+ // next time around we will use this action
+ testAction = newAction;
+
+ var err = ";";
+
+ if (testAction === 'abort') {
+ err += "; Aborting execution of remaining assertions;";
+ } else if (testAction === 'skip') {
+ err += "; Skipping execution of remaining assertions at level " + level + ";";
+ }
+
+ if (result === false) {
+ // test result was unexpected; use message
+ assert_true(result, message + err);
+ } else {
+ assert_true(result, err) ;
+ }
+ }.bind(this), "" + level + ":" + (num+1) + " " + assert.title);
+ // we are going to return out of this
+ return;
+ }
+ } else if (typeof assert === "object" && Array.isArray(assert)) {
+ // it is a nested list - recurse
+ var o = this.runTests(assert, content, testAction, level+1);
+ if (o.result && o.result === 'abort') {
+ // we are bailing out
+ testAction = 'abort';
+ }
+ }
+
+ if (testAction === 'abort') {
+ return {action: 'abort' };
+ }
+
+ var schemaName = "inline " + level + ":" + (num+1);
+
+ if (typeof assert === "string") {
+ // the assertion passed in is a file name; find it in the cache
+ if (this._assertionCache[assert]) {
+ assert = this._assertionCache[assert];
+ } else {
+ test( function() {
+ assert_true(false, "Reference to assertion " + assert + " at level " + level + ":" + (num+1) + " unresolved") ;
+ }, "Processing " + assert);
+ return ;
+ }
+ }
+
+ if (assert.assertionFile) {
+ schemaName = "external file " + assert.assertionFile + " " + level + ":" + (num+1);
+ }
+
+ var validate = null;
+
+ try {
+ validate = this.ajv.compile(assert);
+ }
+ catch(err) {
+ test( function() {
+ assert_true(false, "Compilation of schema " + level + ":" + (num+1) + " failed: " + err) ;
+ }, "Compiling " + schemaName);
+ return ;
+ }
+
+ if (testAction === 'continue') {
+ // a previous test told us to not run this test; skip it
+ // test(function() { }, "SKIPPED: " + assert.title);
+ // start an actual sub-test
+ var valid = validate(content) ;
+
+ var theResult = this.determineResult(assert, valid) ;
+
+ // remember the result
+ theResults.push(theResult);
+
+ var newAction = this.determineAction(assert, theResult) ;
+ // next time around we will use this action
+ testAction = newAction;
+
+ // only run the test if we are NOT skipping fails for some types
+ // or the result is expected
+ if ( theResult === true || !this.SkipFailures.includes(type) ) {
+ test(function() {
+ var err = ";";
+ if (validate.errors !== null && !assert.hasOwnProperty("errorMessage")) {
+ err = "; Errors: " + this.ajv.errorsText(validate.errors) + ";" ;
+ }
+ if (testAction === 'abort') {
+ err += "; Aborting execution of remaining assertions;";
+ } else if (testAction === 'skip') {
+ err += "; Skipping execution of remaining assertions at level " + level + ";";
+ }
+ if (theResult === false) {
+ // test result was unexpected; use message
+ assert_true(theResult, typeMap[type] + message + err);
+ } else {
+ assert_true(theResult, err) ;
+ }
+ }.bind(this), "" + level + ":" + (num+1) + " " + assert.title);
+ }
+ }
+ }.bind(this));
+ }
+
+ return { action: testAction, results: theResults} ;
+ },
+
+ determineResult: function(schema, valid) {
+ 'use strict';
+ var r = 'valid' ;
+ if (schema.hasOwnProperty('expectedResult')) {
+ r = schema.expectedResult;
+ }
+
+ if (r === 'valid' && valid || r === 'invalid' && !valid) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ determineAction: function(schema, result) {
+ 'use strict';
+ // mapping from results to actions
+ var mapping = {
+ 'failAndContinue' : 'continue',
+ 'failAndSkip' : 'skip',
+ 'failAndAbort' : 'abort',
+ 'passAndContinue': 'continue',
+ 'passAndSkip' : 'skip',
+ 'passAndAbort' : 'abort'
+ };
+
+ // if the result was as expected, then just keep going
+ if (result) {
+ return 'continue';
+ }
+
+ var a = 'failAndContinue';
+
+ if (schema.hasOwnProperty('onUnexpectedResult')) {
+ a = schema.onUnexpectedResult;
+ }
+
+ if (mapping[a]) {
+ return mapping[a];
+ } else {
+ return 'continue';
+ }
+ },
+
+ // loadAssertion - load an Assertion from an external JSON file
+ //
+ // returns a promise that resolves with the contents of the assertion file
+
+ loadAssertion: function(afile) {
+ 'use strict';
+ if (typeof(afile) === 'string') {
+ var theFile = this._parseURI(afile);
+ // it is a file reference - load it
+ return new Promise(function(resolve, reject) {
+ this._loadFile("GET", theFile, true)
+ .then(function(data) {
+ data.assertionFile = afile;
+ this._assertionCache[afile] = data;
+ resolve(data);
+ }.bind(this))
+ .catch(function(err) {
+ if (typeof err === "object") {
+ err.theFile = theFile;
+ }
+ reject(err);
+ });
+ }.bind(this));
+ }
+ else if (afile.hasOwnProperty("assertionFile")) {
+ // this object is referecing an external assertion
+ return new Promise(function(resolve, reject) {
+ var theFile = this._parseURI(afile.assertionFile);
+ this._loadFile("GET", theFile, true)
+ .then(function(external) {
+ // okay - we have an external object
+ Object.keys(afile).forEach(function(key) {
+ if (key !== 'assertionFile') {
+ external[key] = afile[key];
+ }
+ });
+ resolve(external);
+ }.bind(this))
+ .catch(function(err) {
+ if (typeof err === "object") {
+ err.theFile = theFile;
+ }
+ reject(err);
+ });
+ }.bind(this));
+ } else {
+ // it is already a loaded assertion - just use it
+ return new Promise(function(resolve) {
+ resolve(afile);
+ });
+ }
+ },
+
+ // loadDefinition - load a JSON Schema definition from an external JSON file
+ //
+ // returns a promise that resolves with the contents of the definition file
+
+ loadDefinition: function(dfile) {
+ 'use strict';
+ return new Promise(function(resolve, reject) {
+ this._loadFile("GET", this._parseURI(dfile), true)
+ .then(function(data) {
+ resolve(data);
+ }.bind(this))
+ .catch(function(err) {
+ reject(err);
+ });
+ }.bind(this));
+ },
+
+
+ // loadTest - load a test from an external JSON file
+ //
+ // returns a promise that resolves with the contents of the
+ // test
+
+ loadTest: function(params) {
+ 'use strict';
+
+ if (params.hasOwnProperty('testFile')) {
+ // the test is referred to by a file name
+ return this._loadFile("GET", params.testFile);
+ } // else
+ return new Promise(function(resolve, reject) {
+ if (params.hasOwnProperty('test')) {
+ resolve(params.test);
+ } else {
+ reject("Must supply a 'test' or 'testFile' parameter");
+ }
+ });
+ },
+
+ _parseURI: function(theURI) {
+ 'use strict';
+ // determine what the top level URI should be
+ if (theURI.indexOf('/') === -1) {
+ // no slash - it's relative to where we are
+ // so just use it
+ return this.TestDir + theURI;
+ } else if (theURI.indexOf('/') === 0 || theURI.indexOf('http:') === 0 || theURI.indexOf('https:') === 0) {
+ // it is an absolute URI so just use it
+ return theURI;
+ } else {
+ // it is relative and contains a slash.
+ // make it relative to the current test root
+ return this.Base + theURI;
+ }
+ },
+
+ /**
+ * return a list of all inline assertions or references
+ *
+ * @param {array} assertions list of assertions to examine
+ */
+
+ _assertionRefs: function(assertions) {
+ 'use strict';
+ var ret = [] ;
+
+ // when the reference is to an object that has an array of assertions in it (a conditionObject)
+ // then remember that one and loop over its embedded assertions
+ if (typeof(assertions) === "object" && !Array.isArray(assertions) && assertions.hasOwnProperty('assertions')) {
+ ret.push(assertions) ;
+ assertions = assertions.assertions;
+ }
+ if (typeof(assertions) === "object" && Array.isArray(assertions)) {
+ assertions.forEach( function(assert) {
+ // first - what is the type of the assert
+ if (typeof assert === "object" && Array.isArray(assert)) {
+ // it is a nested list - recurse
+ this._assertionRefs(assert).forEach( function(item) {
+ ret.push(item);
+ }.bind(this));
+ } else if (typeof assert === "object") {
+ ret.push(assert) ;
+ if (assert.hasOwnProperty("assertions")) {
+ // there are embedded assertions; get those too
+ ret.concat(this._assertionRefs(assert.assertions));
+ }
+ } else {
+ // it is a file name
+ ret.push(assert) ;
+ }
+ }.bind(this));
+ }
+ return ret;
+ },
+
+ // _loadFile - return a promise loading a file
+ //
+ _loadFile: function(method, url, parse) {
+ 'use strict';
+ if (parse === undefined) {
+ parse = true;
+ }
+
+ return new Promise(function (resolve, reject) {
+ if (document.location.search) {
+ var s = document.location.search;
+ s = s.replace(/^\?/, '');
+ if (url.indexOf('?') !== -1) {
+ url += "&" + s;
+ } else {
+ url += "?" + s;
+ }
+ }
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, url);
+ xhr.onload = function () {
+ if (this.status >= 200 && this.status < 300) {
+ var d = xhr.response;
+ if (parse) {
+ try {
+ d = JSON.parse(d);
+ resolve(d);
+ }
+ catch(err) {
+ reject({ status: this.status,
+ statusText: "Parsing of " + url + " failed: " + err }
+ );
+ }
+ } else {
+ resolve(d);
+ }
+ } else {
+ reject({
+ status: this.status,
+ statusText: xhr.statusText
+ });
+ }
+ };
+ xhr.onerror = function () {
+ reject({
+ status: this.status,
+ statusText: xhr.statusText
+ });
+ };
+ xhr.send();
+ });
+ },
+
+};