mirror of
https://github.com/adobe/brackets.git
synced 2024-11-20 09:53:00 +01:00
420 lines
16 KiB
JavaScript
420 lines
16 KiB
JavaScript
/*
|
|
* Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
|
|
/*global require, define, $, waitsForDone, beforeEach, afterEach, beforeFirst, afterLast, jasmine, brackets */
|
|
|
|
// Set the baseUrl to brackets/src
|
|
require.config({
|
|
baseUrl: "../src",
|
|
paths: {
|
|
"test" : "../test",
|
|
"perf" : "../test/perf",
|
|
"spec" : "../test/spec",
|
|
"text" : "thirdparty/text/text",
|
|
"i18n" : "thirdparty/i18n/i18n",
|
|
"lodash" : "thirdparty/lodash.custom.min"
|
|
}
|
|
});
|
|
|
|
define(function (require, exports, module) {
|
|
'use strict';
|
|
|
|
var _ = require("lodash");
|
|
|
|
// Utility dependency
|
|
var AppInit = require("utils/AppInit"),
|
|
CodeHintManager = require("editor/CodeHintManager"),
|
|
Global = require("utils/Global"),
|
|
SpecRunnerUtils = require("spec/SpecRunnerUtils"),
|
|
ExtensionLoader = require("utils/ExtensionLoader"),
|
|
Async = require("utils/Async"),
|
|
FileUtils = require("file/FileUtils"),
|
|
Menus = require("command/Menus"),
|
|
NativeFileSystem = require("file/NativeFileSystem").NativeFileSystem,
|
|
UrlParams = require("utils/UrlParams").UrlParams,
|
|
UnitTestReporter = require("test/UnitTestReporter").UnitTestReporter,
|
|
NodeConnection = require("utils/NodeConnection"),
|
|
BootstrapReporterView = require("test/BootstrapReporterView").BootstrapReporterView,
|
|
ColorUtils = require("utils/ColorUtils"),
|
|
NativeApp = require("utils/NativeApp");
|
|
|
|
// Load modules that self-register and just need to get included in the main project
|
|
require("document/ChangedDocumentTracker");
|
|
|
|
// TODO (#2155): These are used by extensions via brackets.getModule(), so tests that run those
|
|
// extensions need these to be required up front. We need a better solution for this eventually.
|
|
require("utils/ExtensionUtils");
|
|
|
|
// Load both top-level suites. Filtering is applied at the top-level as a filter to BootstrapReporter.
|
|
require("test/UnitTestSuite");
|
|
require("test/PerformanceTestSuite");
|
|
|
|
// Load JUnitXMLReporter
|
|
require("test/thirdparty/jasmine-reporters/jasmine.junit_reporter");
|
|
|
|
var selectedSuites,
|
|
params = new UrlParams(),
|
|
reporter,
|
|
_nodeConnectionDeferred = new $.Deferred(),
|
|
reporterView,
|
|
_writeResults = new $.Deferred(),
|
|
_writeResultsPromise = _writeResults.promise(),
|
|
resultsPath;
|
|
|
|
/**
|
|
* @const
|
|
* Amount of time to wait before automatically rejecting the connection
|
|
* deferred. If we hit this timeout, we'll never have a node connection
|
|
* for the installer in this run of Brackets.
|
|
*/
|
|
var NODE_CONNECTION_TIMEOUT = 30000; // 30 seconds - TODO: share with StaticServer?
|
|
|
|
// parse URL parameters
|
|
params.parse();
|
|
resultsPath = params.get("resultsPath");
|
|
|
|
function _loadExtensionTests() {
|
|
// augment jasmine to identify extension unit tests
|
|
var addSuite = jasmine.Runner.prototype.addSuite;
|
|
jasmine.Runner.prototype.addSuite = function (suite) {
|
|
suite.category = "extension";
|
|
addSuite.call(this, suite);
|
|
};
|
|
|
|
var bracketsPath = FileUtils.getNativeBracketsDirectoryPath(),
|
|
paths = ["default"];
|
|
|
|
// load dev and user extensions only when running the extension test suite
|
|
if (selectedSuites.indexOf("extension") >= 0) {
|
|
paths.push("dev");
|
|
paths.push(ExtensionLoader.getUserExtensionPath());
|
|
}
|
|
|
|
// This returns path to test folder, so convert to src
|
|
bracketsPath = bracketsPath.replace(/\/test$/, "/src");
|
|
|
|
return Async.doInParallel(paths, function (dir) {
|
|
var extensionPath = dir;
|
|
|
|
// If the item has "/" in it, assume it is a full path. Otherwise, load
|
|
// from our source path + "/extensions/".
|
|
if (dir.indexOf("/") === -1) {
|
|
extensionPath = bracketsPath + "/extensions/" + dir;
|
|
}
|
|
|
|
return ExtensionLoader.testAllExtensionsInNativeDirectory(extensionPath);
|
|
});
|
|
}
|
|
|
|
function _documentReadyHandler() {
|
|
if (brackets.app.showDeveloperTools) {
|
|
$("#show-dev-tools").click(function () {
|
|
brackets.app.showDeveloperTools();
|
|
});
|
|
} else {
|
|
$("#show-dev-tools").remove();
|
|
}
|
|
|
|
$("#reload").click(function () {
|
|
window.location.reload(true);
|
|
});
|
|
|
|
if (selectedSuites.length === 1) {
|
|
$("#" + (selectedSuites[0])).closest("li").toggleClass("active", true);
|
|
}
|
|
|
|
AppInit._dispatchReady(AppInit.APP_READY);
|
|
|
|
jasmine.getEnv().execute();
|
|
}
|
|
|
|
function writeResults(path, text) {
|
|
// check if the file already exists
|
|
brackets.fs.stat(path, function (err, stat) {
|
|
if (err === brackets.fs.ERR_NOT_FOUND) {
|
|
// file not found, write the new file with xml content
|
|
brackets.fs.writeFile(path, text, NativeFileSystem._FSEncodings.UTF8, function (err) {
|
|
if (err) {
|
|
_writeResults.reject();
|
|
} else {
|
|
_writeResults.resolve();
|
|
}
|
|
});
|
|
} else {
|
|
// file exists, do not overwrite
|
|
_writeResults.reject();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Listener for UnitTestReporter "runnerEnd" event. Attached only if
|
|
* "resultsPath" URL parameter exists. Does not overwrite existing file.
|
|
*
|
|
* @param {!$.Event} event
|
|
* @param {!UnitTestReporter} reporter
|
|
*/
|
|
function _runnerEndHandler(event, reporter) {
|
|
if (resultsPath && resultsPath.substr(-5) === ".json") {
|
|
writeResults(resultsPath, reporter.toJSON());
|
|
}
|
|
|
|
_writeResults.always(function () { window.close(); });
|
|
}
|
|
|
|
/**
|
|
* Patch JUnitXMLReporter to use brackets.fs and to consolidate all results
|
|
* into a single file.
|
|
*/
|
|
function _patchJUnitReporter() {
|
|
jasmine.JUnitXmlReporter.prototype.reportSpecResultsOriginal = jasmine.JUnitXmlReporter.prototype.reportSpecResults;
|
|
jasmine.JUnitXmlReporter.prototype.getNestedOutputOriginal = jasmine.JUnitXmlReporter.prototype.getNestedOutput;
|
|
|
|
jasmine.JUnitXmlReporter.prototype.reportSpecResults = function (spec) {
|
|
if (spec.results().skipped) {
|
|
return;
|
|
}
|
|
|
|
this.reportSpecResultsOriginal(spec);
|
|
};
|
|
|
|
jasmine.JUnitXmlReporter.prototype.getNestedOutput = function (suite) {
|
|
if (suite.results().totalCount === 0) {
|
|
return "";
|
|
}
|
|
|
|
return this.getNestedOutputOriginal(suite);
|
|
};
|
|
|
|
jasmine.JUnitXmlReporter.prototype.reportRunnerResults = function (runner) {
|
|
var suites = runner.suites(),
|
|
output = '<?xml version="1.0" encoding="UTF-8" ?>',
|
|
i;
|
|
|
|
output += "\n<testsuites>";
|
|
|
|
for (i = 0; i < suites.length; i++) {
|
|
var suite = suites[i];
|
|
if (!suite.parentSuite) {
|
|
output += this.getNestedOutput(suite);
|
|
}
|
|
}
|
|
|
|
output += "\n</testsuites>";
|
|
writeResults(resultsPath, output);
|
|
|
|
// When all done, make it known on JUnitXmlReporter
|
|
jasmine.JUnitXmlReporter.finished_at = (new Date()).getTime();
|
|
};
|
|
|
|
jasmine.JUnitXmlReporter.prototype.writeFile = function (path, filename, text) {
|
|
// do nothing
|
|
};
|
|
}
|
|
|
|
function _registerBeforeAfterHandlers() {
|
|
// Initiailize unit test preferences for each spec
|
|
beforeEach(function () {
|
|
// Unique key for unit testing
|
|
localStorage.setItem("preferencesKey", SpecRunnerUtils.TEST_PREFERENCES_KEY);
|
|
|
|
// Reset preferences from previous test runs
|
|
localStorage.removeItem("doLoadPreferences");
|
|
localStorage.removeItem(SpecRunnerUtils.TEST_PREFERENCES_KEY);
|
|
|
|
SpecRunnerUtils.runBeforeFirst();
|
|
});
|
|
|
|
// Revert unit test preferences after each spec
|
|
afterEach(function () {
|
|
// Clean up preferencesKey
|
|
localStorage.removeItem("preferencesKey");
|
|
|
|
SpecRunnerUtils.runAfterLast();
|
|
});
|
|
|
|
// Delete temp folder before running the first test
|
|
beforeFirst(function () {
|
|
SpecRunnerUtils.removeTempDirectory();
|
|
});
|
|
|
|
// Delete temp folder after running the last test
|
|
afterLast(function () {
|
|
SpecRunnerUtils.removeTempDirectory();
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
// Start up the node connection, which is held in the
|
|
// _nodeConnectionDeferred module variable. (Use
|
|
// _nodeConnectionDeferred.done() to access it.
|
|
|
|
// This is in SpecRunner rather than SpecRunnerUtils because the hope
|
|
// is to hook up jasmine-node tests in this test runner.
|
|
|
|
// TODO: duplicates code from StaticServer
|
|
// TODO: can this be done lazily?
|
|
|
|
var connectionTimeout = setTimeout(function () {
|
|
console.error("[SpecRunner] Timed out while trying to connect to node");
|
|
_nodeConnectionDeferred.reject();
|
|
}, NODE_CONNECTION_TIMEOUT);
|
|
|
|
var _nodeConnection = new NodeConnection();
|
|
_nodeConnection.connect(true).then(function () {
|
|
var domainPath = FileUtils.getNativeBracketsDirectoryPath() + "/" + FileUtils.getNativeModuleDirectoryPath(module) + "/../test/node/TestingDomain";
|
|
|
|
_nodeConnection.loadDomains(domainPath, true)
|
|
.then(
|
|
function () {
|
|
clearTimeout(connectionTimeout);
|
|
_nodeConnectionDeferred.resolve(_nodeConnection);
|
|
},
|
|
function () { // Failed to connect
|
|
console.error("[SpecRunner] Failed to connect to node", arguments);
|
|
clearTimeout(connectionTimeout);
|
|
_nodeConnectionDeferred.reject();
|
|
}
|
|
);
|
|
});
|
|
|
|
selectedSuites = (params.get("suite") || localStorage.getItem("SpecRunner.suite") || "unit").split(",");
|
|
|
|
// Create a top-level filter to show/hide performance and extensions tests
|
|
var runAll = (selectedSuites.indexOf("all") >= 0);
|
|
|
|
var topLevelFilter = function (spec) {
|
|
// special case "all" suite to run unit, perf, extension, and integration tests
|
|
if (runAll) {
|
|
return true;
|
|
}
|
|
|
|
var currentSuite = spec.suite,
|
|
category = spec.category;
|
|
|
|
if (!category) {
|
|
// find the category from the closest suite
|
|
while (currentSuite) {
|
|
if (currentSuite.category) {
|
|
category = currentSuite.category;
|
|
break;
|
|
}
|
|
|
|
currentSuite = currentSuite.parentSuite;
|
|
}
|
|
}
|
|
|
|
// if unit tests are selected, make sure there is no category in the heirarchy
|
|
// if not a unit test, make sure the category is selected
|
|
return (selectedSuites.indexOf("unit") >= 0 && category === undefined) ||
|
|
(selectedSuites.indexOf(category) >= 0);
|
|
};
|
|
|
|
/*
|
|
* TODO (jason-sanjose): extension unit tests should only load the
|
|
* extension and the extensions dependencies. We should not load
|
|
* unrelated extensions. Currently, this solution is all or nothing.
|
|
*/
|
|
|
|
// configure spawned test windows to load extensions
|
|
SpecRunnerUtils.setLoadExtensionsInTestWindow(selectedSuites.indexOf("extension") >= 0);
|
|
|
|
_loadExtensionTests(selectedSuites).done(function () {
|
|
var jasmineEnv = jasmine.getEnv();
|
|
jasmineEnv.updateInterval = 1000;
|
|
|
|
_registerBeforeAfterHandlers();
|
|
|
|
// Create the reporter, which is really a model class that just gathers
|
|
// spec and performance data.
|
|
reporter = new UnitTestReporter(jasmineEnv, topLevelFilter, params.get("spec"));
|
|
SpecRunnerUtils.setUnitTestReporter(reporter);
|
|
|
|
// Optionally emit JUnit XML file for automated runs
|
|
if (resultsPath) {
|
|
if (resultsPath.substr(-4) === ".xml") {
|
|
_patchJUnitReporter();
|
|
jasmineEnv.addReporter(new jasmine.JUnitXmlReporter(null, true, false));
|
|
}
|
|
|
|
// Close the window
|
|
$(reporter).on("runnerEnd", _runnerEndHandler);
|
|
} else {
|
|
_writeResults.resolve();
|
|
}
|
|
|
|
jasmineEnv.addReporter(reporter);
|
|
|
|
// Create the view that displays the data from the reporter. (Usually in
|
|
// Jasmine this is part of the reporter, but we separate them out so that
|
|
// we can more easily grab just the model data for output during automatic
|
|
// testing.)
|
|
reporterView = new BootstrapReporterView(document, reporter);
|
|
|
|
// remember the suite for the next unit test window launch
|
|
localStorage.setItem("SpecRunner.suite", selectedSuites);
|
|
|
|
$(window.document).ready(_documentReadyHandler);
|
|
});
|
|
|
|
|
|
// Prevent clicks on any link from navigating to a different page (which could lose unsaved
|
|
// changes). We can't use a simple .on("click", "a") because of http://bugs.jquery.com/ticket/3861:
|
|
// jQuery hides non-left clicks from such event handlers, yet middle-clicks still cause CEF to
|
|
// navigate. Also, a capture handler is more reliable than bubble.
|
|
window.document.body.addEventListener("click", function (e) {
|
|
// Check parents too, in case link has inline formatting tags
|
|
var node = e.target, url;
|
|
|
|
while (node) {
|
|
if (node.tagName === "A") {
|
|
url = node.getAttribute("href");
|
|
if (url && url.match(/^http/)) {
|
|
NativeApp.openURLInDefaultBrowser(url);
|
|
e.preventDefault();
|
|
}
|
|
break;
|
|
}
|
|
node = node.parentElement;
|
|
}
|
|
}, true);
|
|
}
|
|
|
|
/**
|
|
* Allows access to the deferred that manages the node connection for tests.
|
|
*
|
|
* @return {jQuery.Deferred} The deferred that manages the node connection
|
|
*/
|
|
function getNodeConnectionDeferred() {
|
|
return _nodeConnectionDeferred;
|
|
}
|
|
|
|
// this is used by SpecRunnerUtils
|
|
brackets.testing = {
|
|
getNodeConnectionDeferred: getNodeConnectionDeferred
|
|
};
|
|
|
|
init();
|
|
});
|