Permalink
Browse files

Click in Jasmine failure stack traces to jump to the corresponding so…

…urce

code in the parent Brackets window
  • Loading branch information...
1 parent 46665f3 commit 222c35acf773bcee103acd107511e0ce2701248e @peterflynn peterflynn committed Feb 20, 2013
Showing with 101 additions and 3 deletions.
  1. +8 −0 test/BootstrapReporterView.css
  2. +93 −3 test/BootstrapReporterView.js
@@ -31,3 +31,11 @@ pre {
font-size: 11px;
}
}
+
+.link-to-source {
+ /* Behavioral style - indicates that clicking opens a file in the parent Brackets window */
+}
+/* De-emphasize source links to Jasmine framework code (vs. links to test spec & Brackets core code) */
+.testframework-link {
+ opacity: 0.45;
+}
@@ -21,12 +21,13 @@
*
*/
-/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50, forin: true */
+/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50, regexp: true, forin: true */
/*global jasmine, $, define, document, require */
define(function (require, exports, module) {
'use strict';
- var UrlParams = require("utils/UrlParams").UrlParams,
+ var UrlParams = require("utils/UrlParams").UrlParams,
+ StringUtils = require("utils/StringUtils"),
SpecRunnerUtils = require("spec/SpecRunnerUtils");
var BootstrapReporterView = function (doc, reporter) {
@@ -238,9 +239,17 @@ define(function (require, exports, module) {
// print failure details
if (specData.messages) {
specData.messages.forEach(function (message) {
- $resultDisplay.append($('<pre/>').text(message));
+ // Render with clickable links if parent Brackets window available; plain text otherwise
+ if (window.opener) {
+ var htmlMessage = self._linkerizeStack(message);
+ $resultDisplay.append($('<pre/>').html(htmlMessage));
+ } else {
+ $resultDisplay.append($('<pre/>').text(message));
+ }
});
}
+
+ $resultDisplay.on("click", ".link-to-source", this._handleSourceLinkClick.bind(this));
this.$resultsContainer.append($resultDisplay);
}
@@ -268,6 +277,87 @@ define(function (require, exports, module) {
}
};
+
+ var _codeRefRegExp = /file:\/\/.*?:(\d+):(\d+)/g; // matches file:// followed by two :-prefixed numbers, all on the same line
+
+ /**
+ * Given a plaintext stack trace, returns an HTML version where all source file references are .link-to-source links
+ * @param {!string} text
+ * @return {!string} HTML
+ */
+ BootstrapReporterView.prototype._linkerizeStack = function (text) {
+ var html = "",
+ indexAfterLastMatch = 0, // index into 'text'
+ plainText,
+ match;
+
+ // We'll style links to Jasmine code less prominently (vs. test spec code / core Brackets code)
+ function isTestFrameworkCode() {
+ return match[0].indexOf("/jasmine-core/") !== -1;
+ }
+
+ while ((match = _codeRefRegExp.exec(text)) !== null) {
+ // Add any plain text before the link
+ plainText = text.substring(indexAfterLastMatch, match.index);
+ html += StringUtils.htmlEscape(plainText);
+
+ // Create a clickable link for the file
+ var line = match[1], ch = match[2];
+ var cssClasses = "link-to-source";
+ if (isTestFrameworkCode()) {
+ cssClasses += " testframework-link";
+ }
+ var linkPrefix = "<a href='#' class='" + cssClasses + "' data-line='" + line + "' data-ch='" + ch + "'>";
+ html += linkPrefix + StringUtils.htmlEscape(match[0]) + "</a>";
+
+ indexAfterLastMatch = match.index + match[0].length;
+ }
+
+ // Add any trailing plain text after last link
+ plainText = text.substring(indexAfterLastMatch);
+ html += StringUtils.htmlEscape(plainText);
+
+ return html;
+ };
+
+ /** Handles links generated by _linkerizeStack(), opening the source file in our parent Brackets window */
+ BootstrapReporterView.prototype._handleSourceLinkClick = function (event) {
+ var CommandManager = window.opener.brackets.getModule("command/CommandManager"),
+ Commands = window.opener.brackets.getModule("command/Commands"),
+ EditorManager = window.opener.brackets.getModule("editor/EditorManager"),
+ ProjectManager = window.opener.brackets.getModule("project/ProjectManager"),
+ FileUtils = window.opener.brackets.getModule("file/FileUtils");
+
+ var uri = $(event.target).text(),
+ lineData = $(event.target).attr("data-line"),
+ chData = $(event.target).attr("data-ch"),
+ lineNum = parseInt(lineData, 10) - 1,
+ chNum = parseInt(chData, 10) - 1;
+
+ // Remove file:// prefix and :line:ch suffix, then convert that "clean" URI to a native path
+ var path = uri.substring("file://".length, uri.length - lineData.length - chData.length - 2);
+ path = FileUtils.convertToNativePath(path);
+
+ // Convert from symlinked path to real path - otherwise Brackets will think they are two separate files.
+ // Note: we assume the current project open in our parent Brackets window is the Brackets source
+ var bracketsRoot = FileUtils.getNativeBracketsDirectoryPath();
+ if (bracketsRoot.substr(bracketsRoot.length - 4) === "/src") {
+ var symlinkPrefix = bracketsRoot.substring(0, bracketsRoot.length - 3); // include trailing "/"
+ if (path.indexOf(symlinkPrefix) === 0) {
+ var realPrefix = ProjectManager.getProjectRoot().fullPath;
+ path = realPrefix + path.substring(symlinkPrefix.length);
+ }
+ }
+
+ // Open file in parent Brackets window & jump cursor to indicated pos
+ // TODO: can we bring the Brackets window to the front?
+ CommandManager.execute(Commands.FILE_OPEN, {fullPath: path})
+ .done(function (doc) {
+ EditorManager.getCurrentFullEditor().setCursorPos(lineNum, chNum, true);
+ });
+ };
+
+
BootstrapReporterView.prototype.log = function (str) {
};

0 comments on commit 222c35a

Please sign in to comment.