Use Translator Tester code from shared repo for Scaffold
This commit is contained in:
parent
8f90cfbcfd
commit
4d497afea0
8 changed files with 14 additions and 1612 deletions
|
@ -269,7 +269,7 @@ var Scaffold = new function() {
|
|||
}
|
||||
|
||||
//Strip JSON metadata
|
||||
var code = yield translator.getCode();
|
||||
var code = yield _translatorProvider.getCodeForTranslator(translator);
|
||||
var lastUpdatedIndex = code.indexOf('"lastUpdated"');
|
||||
var header = code.substr(0, lastUpdatedIndex + 50);
|
||||
var m = /^\s*{[\S\s]*?}\s*?[\r\n]+/.exec(header);
|
||||
|
@ -747,10 +747,6 @@ var Scaffold = new function() {
|
|||
translator[props[i]] = metadata[props[i]];
|
||||
}
|
||||
|
||||
translator.getCode = function () {
|
||||
return Zotero.Promise.resolve(this.code);
|
||||
};
|
||||
|
||||
if(!translator.configOptions) translator.configOptions = {};
|
||||
if(!translator.displayOptions) translator.displayOptions = {};
|
||||
if(!translator.browserSupport) translator.browserSupport = "g";
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
title="Scaffold"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script src="chrome://zotero/content/include.js"/>
|
||||
<script src="chrome://zotero/content/tools/testTranslators/translatorTester.js"/>
|
||||
<script src="chrome://zotero/content/xpcom/translate/testTranslators/translatorTester.js"/>
|
||||
<script src="translators.js"/>
|
||||
<script src="scaffold.js"/>
|
||||
|
||||
|
|
|
@ -107,6 +107,17 @@ var Scaffold_Translators = {
|
|||
return translator ? translator.translator : false;
|
||||
}.bind(this),
|
||||
|
||||
getCodeForTranslator: async function (translator) {
|
||||
if (translator.code) return translator.code;
|
||||
return Zotero.File.getContentsAsync(translator.path).then(function(code) {
|
||||
if (translator.cacheCode) {
|
||||
// See Translator.init() for cache rules
|
||||
translator.code = code;
|
||||
}
|
||||
return code;
|
||||
});
|
||||
}.bind(this),
|
||||
|
||||
getAllForType: async function (type) {
|
||||
if (!this._translators.size) {
|
||||
await this.load();
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
body {
|
||||
font-family: Helvetica, sans;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-color: black;
|
||||
border-width: 0 0 1px 1px;
|
||||
border-style: solid;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border-color: black;
|
||||
border-width: 1px 1px 0 0;
|
||||
border-style: solid;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.th-translator {
|
||||
}
|
||||
|
||||
.th-status {
|
||||
width: 100px;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.th-pending, .th-supported, .th-succeeded, .th-failed, .th-mismatch {
|
||||
width: 75px;
|
||||
max-width: 75px;
|
||||
}
|
||||
|
||||
.th-issues {
|
||||
}
|
||||
|
||||
.status-succeeded, .supported-yes {
|
||||
background-color: #90ff90;
|
||||
}
|
||||
|
||||
.status-failed, .supported-no {
|
||||
background-color: #ff9090;
|
||||
}
|
||||
|
||||
.status-mismatch {
|
||||
background-color: #FFB;
|
||||
}
|
||||
|
||||
.status-untested {
|
||||
background-color: #ececec;
|
||||
}
|
||||
|
||||
.status-pending, .status-running {
|
||||
background-color: #9FF;
|
||||
}
|
||||
|
||||
.status-partial-failure {
|
||||
background-color: rgb(249, 180, 98);
|
||||
}
|
||||
|
||||
tr.output-displayed > td {
|
||||
background-color: #b4d5ff !important;
|
||||
}
|
||||
|
||||
#translator-box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 25%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 5px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#output-box {
|
||||
position: absolute;
|
||||
top: 75%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 5px;
|
||||
white-space: pre;
|
||||
overflow: scroll;
|
||||
font-family: Monaco, Courier, monospace;
|
||||
font-size: 10px;
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2011 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
-->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<script type="text/javascript" src="testTranslators.js"></script>
|
||||
<script type="text/javascript" src="chrome://zotero/content/include.js"></script>
|
||||
<script type="text/javascript" src="translatorTester.js"></script>
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="testTranslators.css" />
|
||||
<title>Zotero Translator Tester</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
|
@ -1,653 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2011 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
const NUM_CONCURRENT_TESTS = 6;
|
||||
const TABLE_COLUMNS = ["Translator", "Supported", "Status", "Pending", "Succeeded", "Failed", "Mismatch", "Issues"];
|
||||
// Not using const to prevent const collisions in connectors
|
||||
var TRANSLATOR_TYPES = ["Web", "Import", "Export", "Search"];
|
||||
var translatorTables = {},
|
||||
translatorTestViews = {},
|
||||
translatorTestViewsToRun = {},
|
||||
translatorTestStats = {},
|
||||
translatorBox,
|
||||
outputBox,
|
||||
allOutputView,
|
||||
currentOutputView,
|
||||
seleniumOutput = {},
|
||||
viewerMode = true;
|
||||
|
||||
/**
|
||||
* Fetches issue information from GitHub
|
||||
*/
|
||||
var Issues = new function() {
|
||||
var _executeWhenRetrieved = [];
|
||||
var githubInfo;
|
||||
|
||||
/**
|
||||
* Gets issues for a specific translator
|
||||
* @param {String} translatorLabel Gets issues starting with translatorLabel
|
||||
* @param {Function} callback Function to call when issue information is available
|
||||
*/
|
||||
this.getFor = function(translatorLabel, callback) {
|
||||
translatorLabel = translatorLabel.toLowerCase();
|
||||
|
||||
var whenRetrieved = function() {
|
||||
var issues = [];
|
||||
for(var i=0; i<githubInfo.length; i++) {
|
||||
var issue = githubInfo[i];
|
||||
if(issue.title.substr(0, translatorLabel.length).toLowerCase() === translatorLabel) {
|
||||
issues.push(issue);
|
||||
}
|
||||
}
|
||||
callback(issues);
|
||||
};
|
||||
|
||||
if(githubInfo) {
|
||||
whenRetrieved();
|
||||
} else {
|
||||
_executeWhenRetrieved.push(whenRetrieved);
|
||||
}
|
||||
};
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", "https://api.github.com/repos/zotero/translators/issues?per_page=100", true);
|
||||
req.onreadystatechange = function(e) {
|
||||
if(req.readyState != 4) return;
|
||||
|
||||
githubInfo = JSON.parse(req.responseText);
|
||||
for(var i=0; i<_executeWhenRetrieved.length; i++) {
|
||||
_executeWhenRetrieved[i]();
|
||||
}
|
||||
_executeWhenRetrieved = [];
|
||||
};
|
||||
req.send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles adding debug output to the output box
|
||||
* @param {HTMLElement} el An element to add class="selected" to when this outputView is displayed
|
||||
*/
|
||||
var OutputView = function(el) {
|
||||
this._output = [];
|
||||
this._el = el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this output is currently displayed in the output box
|
||||
* @param {Boolean} isDisplayed
|
||||
*/
|
||||
OutputView.prototype.setDisplayed = function(isDisplayed) {
|
||||
this.isDisplayed = isDisplayed;
|
||||
if(this.isDisplayed) outputBox.textContent = this._output.join("\n");
|
||||
if(this._el) this._el.className = (isDisplayed ? "output-displayed" : "output-hidden");
|
||||
currentOutputView = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds output to the output view
|
||||
*/
|
||||
OutputView.prototype.addOutput = function(msg, level) {
|
||||
this._output.push(msg);
|
||||
if(this.isDisplayed) outputBox.textContent = this._output.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets output to the output view
|
||||
*/
|
||||
OutputView.prototype.getOutput = function() {
|
||||
return this._output.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates a set of tests for a specific translator and type
|
||||
* @constructor
|
||||
*/
|
||||
var TranslatorTestView = function() {
|
||||
var row = this._row = document.createElement("tr");
|
||||
|
||||
// Translator
|
||||
this._label = document.createElement("td");
|
||||
row.appendChild(this._label);
|
||||
|
||||
// Supported
|
||||
this._supported = document.createElement("td");
|
||||
row.appendChild(this._supported);
|
||||
|
||||
// Status
|
||||
this._status = document.createElement("td");
|
||||
row.appendChild(this._status);
|
||||
|
||||
// Pending
|
||||
this._pending = document.createElement("td");
|
||||
row.appendChild(this._pending);
|
||||
|
||||
// Succeeded
|
||||
this._succeeded = document.createElement("td");
|
||||
row.appendChild(this._succeeded);
|
||||
|
||||
// Failed
|
||||
this._failed = document.createElement("td");
|
||||
row.appendChild(this._failed);
|
||||
|
||||
// Mismatch
|
||||
this._unknown = document.createElement("td");
|
||||
row.appendChild(this._unknown);
|
||||
|
||||
// Issues
|
||||
this._issues = document.createElement("td");
|
||||
row.appendChild(this._issues);
|
||||
|
||||
// create output view and debug function
|
||||
var outputView = this._outputView = new OutputView(row);
|
||||
this._debug = function(obj, msg, level) {
|
||||
outputView.addOutput(msg, level);
|
||||
allOutputView.addOutput(msg, level);
|
||||
|
||||
const translatorID = obj.translator.translatorID;
|
||||
if (!seleniumOutput[translatorID]) {
|
||||
seleniumOutput[translatorID] = { label: obj.translator.label, message: "" };
|
||||
}
|
||||
seleniumOutput[translatorID].message += msg + "\n";
|
||||
}
|
||||
|
||||
// put click handler on row to allow display of debug output
|
||||
row.addEventListener("click", function(e) {
|
||||
// don't run deselect click event handler
|
||||
e.stopPropagation();
|
||||
|
||||
currentOutputView.setDisplayed(false);
|
||||
outputView.setDisplayed(true);
|
||||
}, false);
|
||||
|
||||
// create translator tester and update status based on what it knows
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label and retrieves corresponding GitHub issues
|
||||
*/
|
||||
TranslatorTestView.prototype.setLabel = function(label) {
|
||||
this._label.appendChild(document.createTextNode(label));
|
||||
var issuesNode = this._issues;
|
||||
Issues.getFor(label, function(issues) {
|
||||
for(var i=0; i<issues.length; i++) {
|
||||
var issue = issues[i];
|
||||
var div = document.createElement("div"),
|
||||
a = document.createElement("a");
|
||||
|
||||
var date = issue.updated_at;
|
||||
date = new Date(Date.UTC(date.substr(0, 4), date.substr(5, 2)-1, date.substr(8, 2),
|
||||
date.substr(11, 2), date.substr(14, 2), date.substr(17, 2)));
|
||||
if("toLocaleFormat" in date) {
|
||||
date = date.toLocaleFormat("%x");
|
||||
} else {
|
||||
date = date.getFullYear()+"-"+date.getMonth()+"-"+date.getDate();
|
||||
}
|
||||
|
||||
a.textContent = issue.title+" (#"+issue.number+"; "+date+")";
|
||||
a.setAttribute("href", issue.html_url);
|
||||
a.setAttribute("target", "_blank");
|
||||
div.appendChild(a);
|
||||
issuesNode.appendChild(div);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes TranslatorTestView given a translator and its type
|
||||
*/
|
||||
TranslatorTestView.prototype.initWithTranslatorAndType = function(translator, type) {
|
||||
this.setLabel(translator.label);
|
||||
|
||||
this._translatorTester = new Zotero_TranslatorTester(translator, type, this._debug);
|
||||
this.canRun = !!this._translatorTester.tests.length;
|
||||
this.updateStatus(this._translatorTester);
|
||||
|
||||
this._type = type;
|
||||
translatorTestViews[type].push(this);
|
||||
translatorTables[this._type].appendChild(this._row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes TranslatorTestView given a JSON-ified translatorTester
|
||||
*/
|
||||
TranslatorTestView.prototype.unserialize = function(serializedData) {
|
||||
this._outputView.addOutput(serializedData.output);
|
||||
this.setLabel(serializedData.label);
|
||||
|
||||
this._type = serializedData.type;
|
||||
translatorTestViews[serializedData.type].push(this);
|
||||
|
||||
this.canRun = false;
|
||||
this.updateStatus(serializedData);
|
||||
translatorTables[this._type].appendChild(this._row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes TranslatorTestView given a JSON-ified translatorTester
|
||||
*/
|
||||
TranslatorTestView.prototype.serialize = function(serializedData) {
|
||||
return this._translatorTester.serialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the displayed status of a translator
|
||||
*/
|
||||
TranslatorTestView.prototype.updateStatus = function(obj, status) {
|
||||
while(this._status.hasChildNodes()) {
|
||||
this._status.removeChild(this._status.firstChild);
|
||||
}
|
||||
|
||||
this._supported.textContent = obj.isSupported ? "Yes" : "No";
|
||||
this._supported.className = obj.isSupported ? "supported-yes" : "supported-no";
|
||||
|
||||
var pending = typeof obj.pending === "object" ? obj.pending.length : obj.pending;
|
||||
var succeeded = typeof obj.succeeded === "object" ? obj.succeeded.length : obj.succeeded;
|
||||
var failed = typeof obj.failed === "object" ? obj.failed.length : obj.failed;
|
||||
var unknown = typeof obj.unknown === "object" ? obj.unknown.length : obj.unknown;
|
||||
|
||||
if(pending || succeeded || failed || unknown) {
|
||||
if(pending) {
|
||||
if(this.isRunning) {
|
||||
this._status.className = "status-running";
|
||||
this._status.textContent = "Running";
|
||||
} else if(status && status === "pending") {
|
||||
this._status.className = "status-pending";
|
||||
this._status.textContent = "Pending";
|
||||
} else if(this.canRun) {
|
||||
// show link to start
|
||||
var me = this;
|
||||
var a = document.createElement("a");
|
||||
a.href = "#";
|
||||
a.addEventListener("click", function(e) {
|
||||
e.preventDefault();
|
||||
me.runTests();
|
||||
}, false);
|
||||
a.textContent = "Run";
|
||||
this._status.appendChild(a);
|
||||
} else {
|
||||
this._status.textContent = "Not Run";
|
||||
}
|
||||
} else if((succeeded || unknown) && failed) {
|
||||
this._status.className = "status-partial-failure";
|
||||
this._status.textContent = "Partial Failure";
|
||||
} else if(failed) {
|
||||
this._status.className = "status-failed";
|
||||
this._status.textContent = "Failure";
|
||||
} else if(unknown) {
|
||||
this._status.className = "status-mismatch";
|
||||
this._status.textContent = "Data Mismatch";
|
||||
} else {
|
||||
this._status.className = "status-succeeded";
|
||||
this._status.textContent = "Success";
|
||||
}
|
||||
} else {
|
||||
this._status.className = "status-untested";
|
||||
this._status.textContent = "Untested";
|
||||
}
|
||||
|
||||
this._pending.textContent = pending;
|
||||
this._succeeded.textContent = succeeded;
|
||||
this._failed.textContent = failed;
|
||||
this._unknown.textContent = unknown;
|
||||
|
||||
if(this._type) translatorTestStats[this._type].update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs test for this translator
|
||||
*/
|
||||
TranslatorTestView.prototype.runTests = function(doneCallback) {
|
||||
if(this.isRunning) return;
|
||||
this.isRunning = true;
|
||||
|
||||
// show as running
|
||||
this.updateStatus(this._translatorTester);
|
||||
|
||||
// set up callback
|
||||
var me = this;
|
||||
var newCallback = function(obj, test, status, message) {
|
||||
me.updateStatus(obj);
|
||||
if(obj.pending.length === 0 && doneCallback) {
|
||||
doneCallback();
|
||||
}
|
||||
};
|
||||
|
||||
this._translatorTester.runTests(newCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets overall stats for translators
|
||||
*/
|
||||
var TranslatorTestStats = function(translatorType) {
|
||||
this.translatorType = translatorType
|
||||
this.node = document.createElement("p");
|
||||
};
|
||||
|
||||
TranslatorTestStats.prototype.update = function() {
|
||||
var types = {
|
||||
"Success":0,
|
||||
"Data Mismatch":0,
|
||||
"Partial Failure":0,
|
||||
"Failure":0,
|
||||
"Untested":0,
|
||||
"Running":0,
|
||||
"Pending":0,
|
||||
"Not Run":0
|
||||
};
|
||||
|
||||
var testViews = translatorTestViews[this.translatorType];
|
||||
for(var i in testViews) {
|
||||
var status = testViews[i]._status ? testViews[i]._status.textContent : "Not Run";
|
||||
if(status in types) {
|
||||
types[status] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
var typeInfo = [];
|
||||
for(var i in types) {
|
||||
if(types[i]) {
|
||||
typeInfo.push(i+": "+types[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.node.textContent = typeInfo.join(" | ");
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when loaded
|
||||
*/
|
||||
function load(event) {
|
||||
try {
|
||||
viewerMode = !Zotero;
|
||||
} catch(e) {};
|
||||
|
||||
if(!viewerMode && (window.chrome || window.safari)) {
|
||||
// initialize injection
|
||||
Zotero.initInject();
|
||||
// make sure that connector is online
|
||||
Zotero.Connector.checkIsOnline(function (status) {
|
||||
if (status || Zotero.allowRepoTranslatorTester) {
|
||||
init();
|
||||
} else {
|
||||
document.body.textContent = "To avoid excessive repo requests, the translator tester may only be used when Zotero Standalone is running.";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds translator display and retrieves translators
|
||||
*/
|
||||
async function init() {
|
||||
// create translator box
|
||||
translatorBox = document.createElement("div");
|
||||
translatorBox.id = "translator-box";
|
||||
document.body.appendChild(translatorBox);
|
||||
|
||||
// create output box
|
||||
outputBox = document.createElement("div");
|
||||
outputBox.id = "output-box";
|
||||
document.body.appendChild(outputBox);
|
||||
|
||||
// set click handler for translator box to display all output, so that when the user clicks
|
||||
// outside of a translator, it will revert to this state
|
||||
translatorBox.addEventListener("click", function(e) {
|
||||
currentOutputView.setDisplayed(false);
|
||||
allOutputView.setDisplayed(true);
|
||||
}, false);
|
||||
|
||||
// create output view for all output and display
|
||||
allOutputView = new OutputView();
|
||||
allOutputView.setDisplayed(true);
|
||||
|
||||
await Promise.all(TRANSLATOR_TYPES.map(async displayType => {
|
||||
let translatorType = displayType.toLowerCase();
|
||||
|
||||
translatorTestViews[translatorType] = [];
|
||||
|
||||
// create header
|
||||
var h1 = document.createElement("h1");
|
||||
h1.appendChild(document.createTextNode(displayType+" Translators "));
|
||||
|
||||
if(!viewerMode) {
|
||||
// create "run all"
|
||||
var runAll = document.createElement("a");
|
||||
runAll.href = "#";
|
||||
runAll.appendChild(document.createTextNode("(Run)"));
|
||||
runAll.addEventListener("click", new function() {
|
||||
var type = translatorType;
|
||||
return function(e) {
|
||||
e.preventDefault();
|
||||
runTranslatorTests(type);
|
||||
}
|
||||
}, false);
|
||||
h1.appendChild(runAll);
|
||||
}
|
||||
|
||||
translatorBox.appendChild(h1);
|
||||
|
||||
// create table
|
||||
var translatorTable = document.createElement("table");
|
||||
translatorTables[translatorType] = translatorTable;
|
||||
|
||||
translatorTestStats[translatorType] = new TranslatorTestStats(translatorType);
|
||||
translatorBox.appendChild(translatorTestStats[translatorType].node);
|
||||
|
||||
// add headings to table
|
||||
var headings = document.createElement("tr");
|
||||
for(var j in TABLE_COLUMNS) {
|
||||
var th = document.createElement("th");
|
||||
th.className = "th-"+TABLE_COLUMNS[j].toLowerCase();
|
||||
th.appendChild(document.createTextNode(TABLE_COLUMNS[j]));
|
||||
headings.appendChild(th);
|
||||
}
|
||||
|
||||
// append to document
|
||||
translatorTable.appendChild(headings);
|
||||
translatorBox.appendChild(translatorTable);
|
||||
|
||||
// get translators, with code for unsupported translators
|
||||
if(!viewerMode) {
|
||||
let translators = await Zotero.Translators.getAllForType(translatorType, true);
|
||||
haveTranslators(translators, translatorType);
|
||||
}
|
||||
}));
|
||||
|
||||
if(viewerMode) {
|
||||
// if no Zotero object, try to unserialize data
|
||||
var req = new XMLHttpRequest();
|
||||
var loc = "testResults.json";
|
||||
if(window.location.hash) {
|
||||
var hashVars = {};
|
||||
var hashVarsSplit = window.location.hash.substr(1).split("&");
|
||||
for(var i=0; i<hashVarsSplit.length; i++) {
|
||||
var myVar = hashVarsSplit[i];
|
||||
var index = myVar.indexOf("=");
|
||||
hashVars[myVar.substr(0, index)] = myVar.substr(index+1);
|
||||
}
|
||||
|
||||
if(hashVars["browser"] && /^[a-z]+$/.test(hashVars["browser"])
|
||||
&& hashVars["version"] && /^[0-9a-zA-Z\-._]/.test(hashVars["version"])) {
|
||||
loc = "testResults-"+hashVars["browser"]+"-"+hashVars["version"]+".json";
|
||||
}
|
||||
if(hashVars["date"] && /^[0-9\-]+$/.test(hashVars["date"])) {
|
||||
loc = hashVars["date"]+"/"+loc;
|
||||
}
|
||||
}
|
||||
req.open("GET", loc, true);
|
||||
req.overrideMimeType("text/plain");
|
||||
req.onreadystatechange = function(e) {
|
||||
if(req.readyState != 4) return;
|
||||
|
||||
if(req.status === 200 && req.responseText) { // success; unserialize
|
||||
var data = JSON.parse(req.responseText);
|
||||
for(var i=0, n=data.results.length; i<n; i++) {
|
||||
var translatorTestView = new TranslatorTestView();
|
||||
translatorTestView.unserialize(data.results[i]);
|
||||
}
|
||||
} else {
|
||||
jsonNotFound("XMLHttpRequest returned "+req.status);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
req.send();
|
||||
} catch(e) {
|
||||
jsonNotFound(e.toString());
|
||||
}
|
||||
} else {
|
||||
// create "serialize" link at bottom
|
||||
var lastP = document.createElement("p");
|
||||
var serialize = document.createElement("a");
|
||||
serialize.href = "#";
|
||||
serialize.appendChild(document.createTextNode("Serialize Results"));
|
||||
serialize.addEventListener("click", serializeToDownload, false);
|
||||
lastP.appendChild(serialize);
|
||||
translatorBox.appendChild(lastP);
|
||||
|
||||
// Run translators specified in the hash params if any
|
||||
runURLSpecifiedTranslators();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates no JSON file could be found.
|
||||
*/
|
||||
function jsonNotFound(str) {
|
||||
var body = document.body;
|
||||
while(body.hasChildNodes()) body.removeChild(body.firstChild);
|
||||
body.textContent = "testResults.json could not be loaded ("+str+").";
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after translators are returned from main script
|
||||
*/
|
||||
function haveTranslators(translators, type) {
|
||||
translatorTestViewsToRun[type] = [];
|
||||
|
||||
translators = translators.sort(function(a, b) {
|
||||
return a.label.localeCompare(b.label);
|
||||
});
|
||||
|
||||
var promises = [];
|
||||
for(var i in translators) {
|
||||
promises.push(Zotero.Translators.getCodeForTranslator(translators[i]));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(function(codes) {
|
||||
for(var i in translators) {
|
||||
// Make sure translator code is cached on the object
|
||||
translators[i].code = codes[i];
|
||||
var translatorTestView = new TranslatorTestView();
|
||||
translatorTestView.initWithTranslatorAndType(translators[i], type);
|
||||
if(translatorTestView.canRun) {
|
||||
translatorTestViewsToRun[type].push(translatorTestView);
|
||||
}
|
||||
}
|
||||
|
||||
translatorTestStats[type].update();
|
||||
var ev = document.createEvent('HTMLEvents');
|
||||
ev.initEvent('ZoteroHaveTranslators-'+type, true, true);
|
||||
document.dispatchEvent(ev);
|
||||
});
|
||||
}
|
||||
|
||||
async function runURLSpecifiedTranslators() {
|
||||
const href = document.location.href;
|
||||
let hashParams = href.split('#')[1];
|
||||
if (!hashParams) return;
|
||||
|
||||
let translatorIDs = new Set(hashParams.split('translators=')[1].split(',').map(decodeURI));
|
||||
let translatorTestViews = [];
|
||||
for (let type in translatorTestViewsToRun) {
|
||||
for (const translatorTestView of translatorTestViewsToRun[type]) {
|
||||
if (translatorIDs.has(translatorTestView._translatorTester.translator.translatorID)) {
|
||||
translatorTestViews.push(translatorTestView);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const translatorTestView of translatorTestViews) {
|
||||
await new Promise((resolve) => {
|
||||
translatorTestView.runTests(resolve);
|
||||
});
|
||||
}
|
||||
var elem = document.createElement('p');
|
||||
elem.setAttribute('id', 'translator-tests-complete');
|
||||
document.body.appendChild(elem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin running all translator tests of a given type
|
||||
*/
|
||||
function runTranslatorTests(type, callback) {
|
||||
for(var i in translatorTestViewsToRun[type]) {
|
||||
var testView = translatorTestViewsToRun[type][i];
|
||||
testView.updateStatus(testView._translatorTester, "pending");
|
||||
}
|
||||
for(var i=0; i<NUM_CONCURRENT_TESTS; i++) {
|
||||
initTests(type, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run translator tests recursively, after translatorTestViews has been populated
|
||||
*/
|
||||
function initTests(type, callback, runCallbackIfComplete) {
|
||||
if(translatorTestViewsToRun[type].length) {
|
||||
if(translatorTestViewsToRun[type].length === 1) runCallbackIfComplete = true;
|
||||
var translatorTestView = translatorTestViewsToRun[type].shift();
|
||||
translatorTestView.runTests(function() { initTests(type, callback, runCallbackIfComplete) });
|
||||
} else if(callback && runCallbackIfComplete) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes translator tests to JSON
|
||||
*/
|
||||
function serializeToJSON() {
|
||||
var serializedData = {"browser":Zotero.browser, "version":Zotero.version, "results":[]};
|
||||
for(var i in translatorTestViews) {
|
||||
var n = translatorTestViews[i].length;
|
||||
for(var j=0; j<n; j++) {
|
||||
serializedData.results.push(translatorTestViews[i][j].serialize());
|
||||
}
|
||||
}
|
||||
return serializedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes all run translator tests
|
||||
*/
|
||||
function serializeToDownload(e) {
|
||||
var serializedData = serializeToJSON();
|
||||
document.location.href = "data:application/octet-stream,"+encodeURIComponent(JSON.stringify(serializedData, null, "\t"));
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
window.addEventListener("load", load, false);
|
|
@ -1,829 +0,0 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2009 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
// Timeout for test to complete
|
||||
var TEST_RUN_TIMEOUT = 15000;
|
||||
var EXPORTED_SYMBOLS = ["Zotero_TranslatorTesters"];
|
||||
|
||||
// For debugging specific translators by label
|
||||
var includeTranslators = [];
|
||||
|
||||
if (typeof window != "undefined") {
|
||||
window.Zotero = window.Zotero;
|
||||
} else if (typeof global != 'undefined') {
|
||||
global.Zotero = global.Zotero;
|
||||
} else if (typeof this != 'undefined') {
|
||||
this.Zotero = this.Zotero;
|
||||
}
|
||||
|
||||
var Zotero_TranslatorTesters = new function() {
|
||||
const TEST_TYPES = ["web", "import", "export", "search"];
|
||||
var collectedResults = {};
|
||||
|
||||
/**
|
||||
* Runs all tests
|
||||
*/
|
||||
this.runAllTests = function (numConcurrentTests, skipTranslators, writeDataCallback) {
|
||||
var id = Math.random() * (100000000 - 1) + 1;
|
||||
|
||||
if (!(typeof process === 'object' && process + '' === '[object process]')){
|
||||
waitForDialog();
|
||||
|
||||
if(!Zotero) {
|
||||
Zotero = Components.classes["@zotero.org/Zotero;1"]
|
||||
.getService(Components.interfaces.nsISupports).wrappedJSObject;
|
||||
}
|
||||
}
|
||||
|
||||
var testers = [];
|
||||
var waitingForTranslators = TEST_TYPES.length;
|
||||
for(var i=0; i<TEST_TYPES.length; i++) {
|
||||
Zotero.Translators.getAllForType(TEST_TYPES[i], true).
|
||||
then(new function() {
|
||||
var type = TEST_TYPES[i];
|
||||
return function(translators) {
|
||||
try {
|
||||
for(var i=0; i<translators.length; i++) {
|
||||
if (includeTranslators.length
|
||||
&& !includeTranslators.some(x => translators[i].label.includes(x))) continue;
|
||||
if (skipTranslators && skipTranslators[translators[i].translatorID]) continue;
|
||||
testers.push(new Zotero_TranslatorTester(translators[i], type));
|
||||
};
|
||||
|
||||
if(!(--waitingForTranslators)) {
|
||||
runTesters(testers, numConcurrentTests, id, writeDataCallback);
|
||||
}
|
||||
} catch(e) {
|
||||
Zotero.debug(e);
|
||||
Zotero.logError(e);
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs a specific set of tests
|
||||
*/
|
||||
function runTesters(testers, numConcurrentTests, id, writeDataCallback) {
|
||||
var testersRunning = 0;
|
||||
var results = []
|
||||
|
||||
var testerDoneCallback = function(tester) {
|
||||
try {
|
||||
if(tester.pending.length) return;
|
||||
|
||||
Zotero.debug("Done testing "+tester.translator.label);
|
||||
|
||||
// Done translating, so serialize test results
|
||||
testersRunning--;
|
||||
let results = tester.serialize();
|
||||
let last = !testers.length && !testersRunning;
|
||||
collectData(id, results, last, writeDataCallback);
|
||||
|
||||
if(testers.length) {
|
||||
// Run next tester if one is available
|
||||
runNextTester();
|
||||
}
|
||||
} catch(e) {
|
||||
Zotero.debug(e);
|
||||
Zotero.logError(e);
|
||||
}
|
||||
};
|
||||
|
||||
var runNextTester = function() {
|
||||
if (!testers.length) {
|
||||
return;
|
||||
}
|
||||
testersRunning++;
|
||||
Zotero.debug("Testing "+testers[0].translator.label);
|
||||
testers.shift().runTests(testerDoneCallback);
|
||||
};
|
||||
|
||||
for(var i=0; i<numConcurrentTests; i++) {
|
||||
runNextTester();
|
||||
};
|
||||
}
|
||||
|
||||
function waitForDialog() {
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
var loadobserver = function (ev) {
|
||||
ev.originalTarget.removeEventListener("load", loadobserver, false);
|
||||
if (ev.target.location == "chrome://global/content/commonDialog.xul") {
|
||||
let win = ev.target.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindow);
|
||||
Zotero.debug("Closing rogue dialog box!\n\n" + win.document.documentElement.textContent, 2);
|
||||
win.document.documentElement.getButton('accept').click();
|
||||
}
|
||||
};
|
||||
var winobserver = {
|
||||
observe: function (subject, topic, data) {
|
||||
if (topic != "domwindowopened") return;
|
||||
var win = subject.QueryInterface(Components.interfaces.nsIDOMWindow);
|
||||
win.addEventListener("load", loadobserver, false);
|
||||
}
|
||||
};
|
||||
Services.ww.registerNotification(winobserver);
|
||||
}
|
||||
|
||||
function collectData(id, results, last, writeDataCallback) {
|
||||
if (!collectedResults[id]) {
|
||||
collectedResults[id] = [];
|
||||
}
|
||||
collectedResults[id].push(results);
|
||||
|
||||
//
|
||||
// TODO: Only do the below every x collections, or if last == true
|
||||
//
|
||||
// Sort results
|
||||
if ("getLocaleCollation" in Zotero) {
|
||||
let collation = Zotero.getLocaleCollation();
|
||||
var strcmp = function (a, b) {
|
||||
return collation.compareString(1, a, b);
|
||||
};
|
||||
}
|
||||
else {
|
||||
var strcmp = function (a, b) {
|
||||
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||
};
|
||||
}
|
||||
collectedResults[id].sort(function (a, b) {
|
||||
if (a.type !== b.type) {
|
||||
return TEST_TYPES.indexOf(a.type) - TEST_TYPES.indexOf(b.type);
|
||||
}
|
||||
return strcmp(a.label, b.label);
|
||||
});
|
||||
|
||||
writeDataCallback(collectedResults[id], last);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A tool to run unit tests for a given translator
|
||||
*
|
||||
* @property {Array} tests All tests for this translator
|
||||
* @property {Array} pending All tests for this translator
|
||||
* @property {Array} succeeded All tests for this translator
|
||||
* @property {Array} failed All tests for this translator
|
||||
* @property {Array} unknown All tests for this translator
|
||||
* @constructor
|
||||
* @param {Zotero.Translator[]} translator The translator for which to run tests
|
||||
* @param {String} type The type of tests to run (web, import, export, or search)
|
||||
* @param {Function} [debugCallback] A function to call to write debug output. If not present,
|
||||
* Zotero.debug will be used.
|
||||
* @param {Object} [translatorProvider] Used by Scaffold to override Zotero.Translators
|
||||
*/
|
||||
var Zotero_TranslatorTester = function(translator, type, debugCallback, translatorProvider) {
|
||||
this.type = type;
|
||||
this.translator = translator;
|
||||
this.output = "";
|
||||
this.isSupported = this.translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER;
|
||||
this.translator.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
|
||||
this.translatorProvider = translatorProvider;
|
||||
|
||||
this.tests = [];
|
||||
this.pending = [];
|
||||
this.succeeded = [];
|
||||
this.failed = [];
|
||||
this.unknown = [];
|
||||
|
||||
var me = this;
|
||||
this._debug = function(obj, a, b) {
|
||||
me.output += me.output ? "\n"+a : a;
|
||||
if(debugCallback) {
|
||||
debugCallback(me, a, b);
|
||||
} else {
|
||||
Zotero.debug(a, b);
|
||||
}
|
||||
};
|
||||
|
||||
var code = translator.code;
|
||||
var testStart = code.indexOf("/** BEGIN TEST CASES **/");
|
||||
var testEnd = code.indexOf("/** END TEST CASES **/");
|
||||
if (testStart !== -1 && testEnd !== -1) {
|
||||
var test = code.substring(testStart + 24, testEnd)
|
||||
.replace(/^[\s\r\n]*var testCases = /, '')
|
||||
.replace(/;[\s\r\n]*$/, '');
|
||||
try {
|
||||
var testObject = JSON.parse(test);
|
||||
} catch (e) {
|
||||
Zotero.logError(e+" parsing tests for "+translator.label);
|
||||
return;
|
||||
}
|
||||
|
||||
for(var i=0, n=testObject.length; i<n; i++) {
|
||||
if(testObject[i].type === type) {
|
||||
this.tests.push(testObject[i]);
|
||||
this.pending.push(testObject[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Zotero_TranslatorTester.DEFER_DELAY = 5000; // Delay for deferred tests
|
||||
|
||||
/**
|
||||
* Removes document objects, which contain cyclic references, and other fields to be ignored from items
|
||||
* @param {Object} Item, in the format returned by Zotero.Item.serialize()
|
||||
*/
|
||||
Zotero_TranslatorTester._sanitizeItem = function(item, testItem, keepValidFields) {
|
||||
// remove cyclic references
|
||||
if(item.attachments && item.attachments.length) {
|
||||
// don't actually test URI equality
|
||||
for (var i=0; i<item.attachments.length; i++) {
|
||||
var attachment = item.attachments[i];
|
||||
if(attachment.document) {
|
||||
delete attachment.document;
|
||||
// Mirror connector/server itemDone() behavior from translate.js
|
||||
attachment.mimeType = 'text/html';
|
||||
}
|
||||
|
||||
if(attachment.url) {
|
||||
delete attachment.url;
|
||||
}
|
||||
|
||||
if(attachment.complete) {
|
||||
delete attachment.complete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to convert to JSON and back to get rid of undesirable undeletable elements; this may fail
|
||||
try {
|
||||
item = JSON.parse(JSON.stringify(item));
|
||||
} catch(e) {};
|
||||
|
||||
// remove fields that don't exist or aren't valid for this item type, and normalize base fields
|
||||
// to fields specific to this item
|
||||
var fieldID, itemFieldID,
|
||||
typeID = Zotero.ItemTypes.getID(item.itemType);
|
||||
const skipFields = ["note", "notes", "itemID", "attachments", "tags", "seeAlso",
|
||||
"itemType", "complete", "creators"];
|
||||
for(var field in item) {
|
||||
if(skipFields.indexOf(field) !== -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if((!item[field] && (!testItem || item[field] !== false))
|
||||
|| !(fieldID = Zotero.ItemFields.getID(field))) {
|
||||
delete item[field];
|
||||
continue;
|
||||
}
|
||||
|
||||
if(itemFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, fieldID)) {
|
||||
var value = item[field];
|
||||
delete item[field];
|
||||
item[Zotero.ItemFields.getName(itemFieldID)] = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!Zotero.ItemFields.isValidForType(fieldID, typeID)) {
|
||||
delete item[field];
|
||||
}
|
||||
}
|
||||
|
||||
// remove fields to be ignored
|
||||
if(!keepValidFields && "accessDate" in item) delete item.accessDate;
|
||||
|
||||
// Sort tags
|
||||
if (item.tags && Array.isArray(item.tags)) {
|
||||
// Normalize tags -- necessary until tests are updated for 5.0
|
||||
if (testItem) {
|
||||
item.tags = Zotero.Translate.Base.prototype._cleanTags(item.tags);
|
||||
}
|
||||
item.tags.sort((a, b) => {
|
||||
if (a.tag < b.tag) return -1;
|
||||
if (b.tag < a.tag) return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
return item;
|
||||
};
|
||||
/**
|
||||
* Serializes translator tester results to JSON
|
||||
*/
|
||||
Zotero_TranslatorTester.prototype.serialize = function() {
|
||||
return {
|
||||
"translatorID":this.translator.translatorID,
|
||||
"type":this.type,
|
||||
"output":this.output,
|
||||
"label":this.translator.label,
|
||||
"isSupported":this.isSupported,
|
||||
"pending":this.pending,
|
||||
"failed":this.failed,
|
||||
"succeeded":this.succeeded,
|
||||
"unknown":this.unknown
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets tests for this translatorTester
|
||||
*/
|
||||
Zotero_TranslatorTester.prototype.setTests = function(tests) {
|
||||
this.tests = tests.slice(0);
|
||||
this.pending = tests.slice(0);
|
||||
this.succeeded = [];
|
||||
this.failed = [];
|
||||
this.unknown = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes tests for this translator
|
||||
* @param {Function} testDoneCallback A callback to be executed each time a test is complete
|
||||
*/
|
||||
Zotero_TranslatorTester.prototype.runTests = function(testDoneCallback, recursiveRun) {
|
||||
if(!recursiveRun) {
|
||||
var w = (this.pending.length === 1) ? "test" : "tests";
|
||||
this._debug(this, "TranslatorTester: Running "+this.pending.length+" "+w+" for "+this.translator.label);
|
||||
}
|
||||
|
||||
if(!this.pending.length) {
|
||||
// always call testDoneCallback once if there are no tests
|
||||
if(!recursiveRun && testDoneCallback) testDoneCallback(this, null, "unknown", "No tests present\n");
|
||||
return;
|
||||
}
|
||||
|
||||
this._runTestsRecursively(testDoneCallback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes tests for this translator, without checks or a debug message
|
||||
* @param {Function} testDoneCallback A callback to be executed each time a test is complete
|
||||
*/
|
||||
Zotero_TranslatorTester.prototype._runTestsRecursively = function(testDoneCallback) {
|
||||
var test = this.pending.shift();
|
||||
var testNumber = this.tests.length-this.pending.length;
|
||||
var me = this;
|
||||
|
||||
this._debug(this, "TranslatorTester: Running "+this.translator.label+" Test "+testNumber);
|
||||
|
||||
var executedCallback = false;
|
||||
var callback = function(obj, test, status, message) {
|
||||
if(executedCallback) return;
|
||||
executedCallback = true;
|
||||
|
||||
me._debug(this, "TranslatorTester: "+me.translator.label+" Test "+testNumber+": "+status+" ("+message+")");
|
||||
me[status].push(test);
|
||||
test.message = message;
|
||||
if(testDoneCallback) testDoneCallback(me, test, status, message);
|
||||
me.runTests(testDoneCallback, true);
|
||||
};
|
||||
|
||||
if(this.type === "web") {
|
||||
this.fetchPageAndRunTest(test, callback);
|
||||
} else {
|
||||
(Zotero.setTimeout ? Zotero : window).setTimeout(function() {
|
||||
me.runTest(test, null, callback);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
(Zotero.setTimeout ? Zotero : window).setTimeout(function() {
|
||||
callback(me, test, "failed", "Test timed out after "+TEST_RUN_TIMEOUT/1000+" seconds");
|
||||
}, TEST_RUN_TIMEOUT);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the page for a given test and runs it
|
||||
*
|
||||
* This function is only applicable in Firefox; it is overridden in translator_global.js in Chrome
|
||||
* and Safari.
|
||||
*
|
||||
* @param {Object} test - Test to execute
|
||||
* @param {Function} testDoneCallback - A callback to be executed when test is complete
|
||||
*/
|
||||
Zotero_TranslatorTester.prototype.fetchPageAndRunTest = function (test, testDoneCallback) {
|
||||
// Scaffold
|
||||
if (Zotero.isFx) {
|
||||
let browser = Zotero.HTTP.loadDocuments(
|
||||
test.url,
|
||||
(doc) => {
|
||||
if (test.defer) {
|
||||
Zotero.debug("Waiting " + (Zotero_TranslatorTester.DEFER_DELAY / 1000)
|
||||
+ " second(s) for page content to settle");
|
||||
}
|
||||
setTimeout(() => {
|
||||
// Use cookies from document in translator HTTP requests
|
||||
this._cookieSandbox = new Zotero.CookieSandbox(null, test.url, doc.cookie);
|
||||
|
||||
this.runTest(test, doc, function (obj, test, status, message) {
|
||||
Zotero.Browser.deleteHiddenBrowser(browser);
|
||||
testDoneCallback(obj, test, status, message);
|
||||
});
|
||||
}, test.defer ? Zotero_TranslatorTester.DEFER_DELAY : 0);
|
||||
},
|
||||
null,
|
||||
(e) => {
|
||||
Zotero.Browser.deleteHiddenBrowser(browser);
|
||||
testDoneCallback(this, test, "failed", "Translation failed to initialize: " + e);
|
||||
},
|
||||
true
|
||||
);
|
||||
browser.docShell.allowMetaRedirects = true;
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof process === 'object' && process + '' === '[object process]'){
|
||||
this._cookieSandbox = require('request').jar();
|
||||
}
|
||||
Zotero.HTTP.processDocuments(
|
||||
test.url,
|
||||
(doc) => {
|
||||
this.runTest(test, doc, function (obj, test, status, message) {
|
||||
testDoneCallback(obj, test, status, message);
|
||||
});
|
||||
},
|
||||
{
|
||||
cookieSandbox: this._cookieSandbox
|
||||
}
|
||||
)
|
||||
.catch(function (e) {
|
||||
testDoneCallback(this, test, "failed", "Translation failed to initialize: " + e);
|
||||
}.bind(this))
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes a test for a translator, given the document to test upon
|
||||
* @param {Object} test Test to execute
|
||||
* @param {Document} data DOM document to test against
|
||||
* @param {Function} testDoneCallback A callback to be executed when test is complete
|
||||
*/
|
||||
Zotero_TranslatorTester.prototype.runTest = function(test, doc, testDoneCallback) {
|
||||
this._debug(this, "TranslatorTester: Translating"+(test.url ? " "+test.url : ""));
|
||||
|
||||
var me = this;
|
||||
var translate = Zotero.Translate.newInstance(this.type);
|
||||
if (this.translatorProvider) {
|
||||
translate.setTranslatorProvider(this.translatorProvider);
|
||||
}
|
||||
if(this.type === "web") {
|
||||
translate.setDocument(doc);
|
||||
} else if(this.type === "import") {
|
||||
translate.setString(test.input);
|
||||
} else if(this.type === "search") {
|
||||
translate.setSearch(test.input);
|
||||
}
|
||||
if (translate.setCookieSandbox && this._cookieSandbox) {
|
||||
translate.setCookieSandbox(this._cookieSandbox);
|
||||
}
|
||||
|
||||
translate.setHandler("translators", function(obj, translators) {
|
||||
me._runTestTranslate(translate, translators, test, testDoneCallback);
|
||||
});
|
||||
translate.setHandler("debug", this._debug);
|
||||
var errorReturned;
|
||||
translate.setHandler("error", function(obj, err) {
|
||||
errorReturned = err;
|
||||
});
|
||||
translate.setHandler("done", function(obj, returnValue) {
|
||||
me._checkResult(test, obj, returnValue, errorReturned, testDoneCallback);
|
||||
});
|
||||
var selectCalled = false;
|
||||
translate.setHandler("select", function(obj, items, callback) {
|
||||
if(test.items !== "multiple" && test.items.length <= 1) {
|
||||
testDoneCallback(me, test, "failed", "Zotero.selectItems() called, but only one item defined in test");
|
||||
callback({});
|
||||
return;
|
||||
} else if(selectCalled) {
|
||||
testDoneCallback(me, test, "failed", "Zotero.selectItems() called multiple times");
|
||||
callback({});
|
||||
return;
|
||||
}
|
||||
|
||||
selectCalled = true;
|
||||
var newItems = {};
|
||||
var haveItems = false;
|
||||
for(var i in items) {
|
||||
if(items[i] && typeof(items[i]) == "object" && items[i].title !== undefined) {
|
||||
newItems[i] = items[i].title;
|
||||
} else {
|
||||
newItems[i] = items[i];
|
||||
}
|
||||
haveItems = true;
|
||||
|
||||
// only save one item if "items":"multiple" (as opposed to an array of items)
|
||||
if(test.items === "multiple") break;
|
||||
}
|
||||
|
||||
if(!haveItems) {
|
||||
testDoneCallback(me, test, "failed", "No items defined");
|
||||
callback({});
|
||||
}
|
||||
|
||||
callback(newItems);
|
||||
});
|
||||
translate.capitalizeTitles = false;
|
||||
|
||||
// internal hack to call detect on this translator
|
||||
translate._potentialTranslators = [this.translator];
|
||||
translate._foundTranslators = [];
|
||||
translate._currentState = "detect";
|
||||
translate._detect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs translation for a translator, given a document to test against
|
||||
*/
|
||||
Zotero_TranslatorTester.prototype._runTestTranslate = function(translate, translators, test, testDoneCallback) {
|
||||
if(!translators.length) {
|
||||
testDoneCallback(this, test, "failed", "Detection failed");
|
||||
return;
|
||||
} else if(this.type === "web" && translators[0].itemType !== Zotero.Translator.RUN_MODE_ZOTERO_SERVER
|
||||
&& (translators[0].itemType !== "multiple" && test.items.length > 1 ||
|
||||
test.items.length === 1 && translators[0].itemType !== test.items[0].itemType)) {
|
||||
// this handles "items":"multiple" too, since the string has length 8
|
||||
testDoneCallback(this, test, "failed", "Detection returned wrong item type");
|
||||
return;
|
||||
}
|
||||
|
||||
translate.setTranslator(this.translator);
|
||||
translate.translate({
|
||||
libraryID: false
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the results of translation match what is expected by the test
|
||||
* @param {Object} test Test that was executed
|
||||
* @param {Zotero.Translate} translate The Zotero.Translate instance
|
||||
* @param {Boolean} returnValue Whether translation completed successfully
|
||||
* @param {Error} error Error code, if one was specified
|
||||
* @param {Function} testDoneCallback A callback to be executed when test is complete
|
||||
*/
|
||||
Zotero_TranslatorTester.prototype._checkResult = function(test, translate, returnValue, error, testDoneCallback) {
|
||||
if(error) {
|
||||
var errorString = "Translation failed: "+error.toString();
|
||||
if(typeof error === "object") {
|
||||
for(var i in error) {
|
||||
if(typeof(error[i]) != "object") {
|
||||
errorString += "\n"+i+' => '+error[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
testDoneCallback(this, test, "failed", errorString);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!returnValue) {
|
||||
testDoneCallback(this, test, "failed", "Translation failed; examine debug output for errors");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!translate.newItems.length) {
|
||||
testDoneCallback(this, test, "failed", "Translation failed: no items returned");
|
||||
return;
|
||||
}
|
||||
|
||||
if(test.items !== "multiple") {
|
||||
if(translate.newItems.length !== test.items.length) {
|
||||
testDoneCallback(this, test, "unknown", "Expected "+test.items.length+" items; got "+translate.newItems.length);
|
||||
return;
|
||||
}
|
||||
|
||||
for(var i=0, n=test.items.length; i<n; i++) {
|
||||
var testItem = Zotero_TranslatorTester._sanitizeItem(test.items[i], true);
|
||||
var translatedItem = Zotero_TranslatorTester._sanitizeItem(translate.newItems[i]);
|
||||
|
||||
if(!Zotero_TranslatorTester._compare(testItem, translatedItem)) {
|
||||
// Show diff
|
||||
this._debug(this, "TranslatorTester: Data mismatch detected:");
|
||||
this._debug(this, Zotero_TranslatorTester._generateDiff(testItem, translatedItem));
|
||||
|
||||
// Save items. This makes it easier to correct tests automatically.
|
||||
var m = translate.newItems.length;
|
||||
test.itemsReturned = new Array(m);
|
||||
for(var j=0; j<m; j++) {
|
||||
test.itemsReturned[j] = Zotero_TranslatorTester._sanitizeItem(translate.newItems[i]);
|
||||
}
|
||||
|
||||
testDoneCallback(this, test, "unknown", "Item "+i+" does not match");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testDoneCallback(this, test, "succeeded", "Test succeeded");
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new test for a document
|
||||
* @param {Document} doc DOM document to test against
|
||||
* @param {Function} testReadyCallback A callback to be passed test (as object) when complete
|
||||
*/
|
||||
Zotero_TranslatorTester.prototype.newTest = function(doc, testReadyCallback) {
|
||||
// keeps track of whether select was called
|
||||
var multipleMode = false;
|
||||
|
||||
var me = this;
|
||||
var translate = Zotero.Translate.newInstance(this.type);
|
||||
if (this.translatorProvider) {
|
||||
translate.setTranslatorProvider(this.translatorProvider);
|
||||
}
|
||||
translate.setDocument(doc);
|
||||
// Use cookies from document
|
||||
if (doc.cookie) {
|
||||
translate.setCookieSandbox(new Zotero.CookieSandbox(
|
||||
null,
|
||||
doc.location.href,
|
||||
doc.cookie
|
||||
));
|
||||
}
|
||||
translate.setTranslator(this.translator);
|
||||
translate.setHandler("debug", this._debug);
|
||||
translate.setHandler("select", function(obj, items, callback) {
|
||||
multipleMode = true;
|
||||
|
||||
var newItems = {};
|
||||
for(var i in items) {
|
||||
if(items[i] && typeof(items[i]) == "object" && items[i].title !== undefined) {
|
||||
newItems[i] = items[i].title;
|
||||
} else {
|
||||
newItems[i] = items[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
callback(newItems);
|
||||
});
|
||||
translate.setHandler("done", function(obj, returnValue) { me._createTest(obj, multipleMode, returnValue, testReadyCallback) });
|
||||
translate.capitalizeTitles = false;
|
||||
translate.translate({
|
||||
libraryID: false
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new test for a document
|
||||
* @param {Zotero.Translate} translate The Zotero.Translate instance
|
||||
* @param {Function} testDoneCallback A callback to be passed test (as object) when complete
|
||||
*/
|
||||
Zotero_TranslatorTester.prototype._createTest = function(translate, multipleMode, returnValue, testReadyCallback) {
|
||||
if(!returnValue) {
|
||||
testReadyCallback(returnValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!translate.newItems.length) {
|
||||
testReadyCallback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(multipleMode) {
|
||||
var items = "multiple";
|
||||
} else {
|
||||
for(var i=0, n=translate.newItems.length; i<n; i++) {
|
||||
Zotero_TranslatorTester._sanitizeItem(translate.newItems[i]);
|
||||
}
|
||||
var items = translate.newItems;
|
||||
}
|
||||
|
||||
testReadyCallback(this, {"type":this.type, "url":translate.document.location.href,
|
||||
"items":items});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compare items or sets thereof
|
||||
*/
|
||||
Zotero_TranslatorTester._compare = function(a, b) {
|
||||
// If a is false, comparisons always succeed. This allows us to explicitly set that
|
||||
// certain properties are allowed.
|
||||
if(a === false) return true;
|
||||
|
||||
if(((typeof a === "object" && a !== null) || typeof a === "function")
|
||||
&& ((typeof a === "object" && b !== null) || typeof b === "function")) {
|
||||
if((Object.prototype.toString.apply(a) === "[object Array]")
|
||||
!== (Object.prototype.toString.apply(b) === "[object Array]")) {
|
||||
return false;
|
||||
}
|
||||
for(var key in a) {
|
||||
if(!a.hasOwnProperty(key)) continue;
|
||||
if(a[key] !== false && !b.hasOwnProperty(key)) return false;
|
||||
if(!Zotero_TranslatorTester._compare(a[key], b[key])) return false;
|
||||
}
|
||||
for(var key in b) {
|
||||
if(!b.hasOwnProperty(key)) continue;
|
||||
if(!a.hasOwnProperty(key)) return false;
|
||||
}
|
||||
return true;
|
||||
} else if(typeof a === "string" && typeof b === "string") {
|
||||
// Ignore whitespace mismatches on strings
|
||||
return a === b || Zotero.Utilities.trimInternal(a) === Zotero.Utilities.trimInternal(b);
|
||||
}
|
||||
return a === b;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a diff of items
|
||||
*/
|
||||
Zotero_TranslatorTester._generateDiff = new function() {
|
||||
function show(a, action, prefix, indent) {
|
||||
if((typeof a === "object" && a !== null) || typeof a === "function") {
|
||||
var isArray = Object.prototype.toString.apply(a) === "[object Array]",
|
||||
startBrace = (isArray ? "[" : "{"),
|
||||
endBrace = (isArray ? "]" : "}"),
|
||||
changes = "",
|
||||
haveKeys = false;
|
||||
|
||||
for(var key in a) {
|
||||
if(!a.hasOwnProperty(key)) continue;
|
||||
|
||||
haveKeys = true;
|
||||
changes += show(a[key], action,
|
||||
isArray ? "" : JSON.stringify(key)+": ", indent+" ");
|
||||
}
|
||||
|
||||
if(haveKeys) {
|
||||
return action+" "+indent+prefix+startBrace+"\n"+
|
||||
changes+action+" "+indent+endBrace+"\n";
|
||||
}
|
||||
return action+" "+indent+prefix+startBrace+endBrace+"\n";
|
||||
}
|
||||
|
||||
return action+" "+indent+prefix+JSON.stringify(a)+"\n";
|
||||
}
|
||||
|
||||
function compare(a, b, prefix, indent) {
|
||||
if(!prefix) prefix = "";
|
||||
if(!indent) indent = "";
|
||||
|
||||
if(((typeof a === "object" && a !== null) || typeof a === "function")
|
||||
&& ((typeof b === "object" && b !== null) || typeof b === "function")) {
|
||||
var aIsArray = Object.prototype.toString.apply(a) === "[object Array]",
|
||||
bIsArray = Object.prototype.toString.apply(b) === "[object Array]";
|
||||
if(aIsArray === bIsArray) {
|
||||
var startBrace = (aIsArray ? "[" : "{"),
|
||||
endBrace = (aIsArray ? "]" : "}"),
|
||||
changes = "",
|
||||
haveKeys = false;
|
||||
|
||||
for(var key in a) {
|
||||
if(!a.hasOwnProperty(key)) continue;
|
||||
|
||||
haveKeys = true;
|
||||
var keyPrefix = aIsArray ? "" : JSON.stringify(key)+": ";
|
||||
if(b.hasOwnProperty(key)) {
|
||||
changes += compare(a[key], b[key], keyPrefix, indent+" ");
|
||||
} else {
|
||||
changes += show(a[key], "-", keyPrefix, indent+" ");
|
||||
}
|
||||
}
|
||||
for(var key in b) {
|
||||
if(!b.hasOwnProperty(key)) continue;
|
||||
|
||||
haveKeys = true;
|
||||
if(!a.hasOwnProperty(key)) {
|
||||
var keyPrefix = aIsArray ? "" : JSON.stringify(key)+": ";
|
||||
changes += show(b[key], "+", keyPrefix, indent+" ");
|
||||
}
|
||||
}
|
||||
|
||||
if(haveKeys) {
|
||||
return " "+indent+prefix+startBrace+"\n"+
|
||||
changes+" "+indent+(aIsArray ? "]" : "}")+"\n";
|
||||
}
|
||||
return " "+indent+prefix+startBrace+endBrace+"\n";
|
||||
}
|
||||
}
|
||||
|
||||
if(a === b) {
|
||||
return show(a, " ", prefix, indent);
|
||||
}
|
||||
return show(a, "-", prefix, indent)+show(b, "+", prefix, indent);
|
||||
}
|
||||
|
||||
return function(a, b) {
|
||||
// Remove last newline
|
||||
var txt = compare(a, b);
|
||||
return txt.substr(0, txt.length-1);
|
||||
};
|
||||
};
|
||||
|
||||
if (typeof process === 'object' && process + '' === '[object process]'){
|
||||
module.exports = {
|
||||
Tester: Zotero_TranslatorTesters,
|
||||
TranslatorTester: Zotero_TranslatorTester
|
||||
};
|
||||
}
|
|
@ -1 +1 @@
|
|||
Subproject commit 4eeca1b5a698bb4cf5f1ce9db554a05b82be6bcd
|
||||
Subproject commit 0024058720198f5967a50a57d33eb714b1c321c2
|
Loading…
Reference in a new issue