diff --git a/spec/atom-reporter.coffee b/spec/atom-reporter.coffee index b542e20e5..717217c65 100644 --- a/spec/atom-reporter.coffee +++ b/spec/atom-reporter.coffee @@ -1,34 +1,49 @@ -{View, $, $$} = require '../src/space-pen-extensions' +path = require 'path' _ = require 'underscore-plus' {convertStackTrace} = require 'coffeestack' +{View, $, $$} = require '../src/space-pen-extensions' sourceMaps = {} -formatStackTrace = (stackTrace) -> +formatStackTrace = (message='', stackTrace) -> return stackTrace unless stackTrace jasminePattern = /^\s*at\s+.*\(?.*\/jasmine(-[^\/]*)?\.js:\d+:\d+\)?\s*$/ + firstJasmineLinePattern = /^\s*at \/.*\/jasmine(-[^\/]*)?\.js:\d+:\d+\)?\s*$/ convertedLines = [] for line in stackTrace.split('\n') convertedLines.push(line) unless jasminePattern.test(line) + break if firstJasmineLinePattern.test(line) - convertStackTrace(convertedLines.join('\n'), sourceMaps) + stackTrace = convertStackTrace(convertedLines.join('\n'), sourceMaps) + lines = stackTrace.split('\n') + + # Remove first line of stack when it is the same as the error message + errorMatch = lines[0]?.match(/^Error: (.*)/) + lines.shift() if message.trim() is errorMatch?[1]?.trim() + + # Remove prefix of lines matching: at [object Object]. (path:1:2) + for line, index in lines + prefixMatch = line.match(/at \[object Object\]\. \(([^\)]+)\)/) + lines[index] = "at #{prefixMatch[1]}" if prefixMatch + + lines = lines.map (line) -> line.trim() + lines.join('\n') module.exports = class AtomReporter extends View @content: -> - @div id: 'HTMLReporter', class: 'jasmine_reporter', => - @div outlet: 'specPopup', class: "spec-popup" + @div class: 'spec-reporter', => @div outlet: "suites" - @div outlet: 'coreArea', => - @div outlet: 'coreHeader', class: 'symbolHeader' - @ul outlet: 'coreSummary', class: 'symbolSummary list-unstyled' - @div outlet: 'bundledArea', => - @div outlet: 'bundledHeader', class: 'symbolHeader' - @ul outlet: 'bundledSummary', class: 'symbolSummary list-unstyled' - @div outlet: 'userArea', => - @div outlet: 'userHeader', class: 'symbolHeader' - @ul outlet: 'userSummary', class: 'symbolSummary list-unstyled' - @div outlet: "status", class: 'status', => + @div outlet: 'coreArea', class: 'symbol-area', => + @div outlet: 'coreHeader', class: 'symbol-header' + @ul outlet: 'coreSummary', class: 'symbol-summary list-unstyled' + @div outlet: 'bundledArea', class: 'symbol-area', => + @div outlet: 'bundledHeader', class: 'symbol-header' + @ul outlet: 'bundledSummary', class: 'symbol-summary list-unstyled' + @div outlet: 'userArea', class: 'symbol-area', => + @div outlet: 'userHeader', class: 'symbol-header' + @ul outlet: 'userSummary', class: 'symbol-summary list-unstyled' + @div outlet: "status", class: 'status alert alert-success', => @div outlet: "time", class: 'time' @div outlet: "specCount", class: 'spec-count' @div outlet: "message", class: 'message' @@ -45,7 +60,7 @@ class AtomReporter extends View reportRunnerStarting: (runner) -> @handleEvents() - @startedAt = new Date() + @startedAt = Date.now() specs = runner.specs() @totalSpecCount = specs.length @addSpecs(specs) @@ -53,57 +68,28 @@ class AtomReporter extends View reportRunnerResults: (runner) -> @updateSpecCounts() - if @failedCount == 0 - @message.text "Success!" + if @failedCount is 1 + @message.text "#{@failedCount} failure" else - @message.text "Game Over" + @message.text "#{@failedCount} failures" reportSuiteResults: (suite) -> reportSpecResults: (spec) -> @completeSpecCount++ - spec.endedAt = new Date().getTime() + spec.endedAt = Date.now() @specComplete(spec) @updateStatusView(spec) reportSpecStarting: (spec) -> @specStarted(spec) - specFilter: (spec) -> - globalFocusPriority = jasmine.getEnv().focusPriority - parent = spec.parentSuite ? spec.suite - - if !globalFocusPriority - true - else if spec.focusPriority >= globalFocusPriority - true - else if not parent - false - else - @specFilter(parent) - handleEvents: -> - $(document).on "mouseover", ".spec-summary", ({currentTarget}) => - element = $(currentTarget) - description = element.data("description") - return unless description - - clearTimeout @timeoutId if @timeoutId? - @specPopup.show() - spec = _.find(window.timedSpecs, ({fullName}) -> description is fullName) - description = "#{description} #{spec.time}ms" if spec - @specPopup.text description - {left, top} = element.offset() - left += 20 - top += 20 - @specPopup.offset({left, top}) - @timeoutId = setTimeout((=> @specPopup.hide()), 3000) - $(document).on "click", ".spec-toggle", ({currentTarget}) => element = $(currentTarget) specFailures = element.parent().find('.spec-failures') specFailures.toggle() - if specFailures.is(":visible") then element.text "\uf03d" else element.html "\uf03f" + element.toggleClass('folded') false updateSpecCounts: -> @@ -115,7 +101,7 @@ class AtomReporter extends View updateStatusView: (spec) -> if @failedCount > 0 - @status.addClass('failed') unless @status.hasClass('failed') + @status.addClass('alert-danger').removeClass('alert-success') @updateSpecCounts() @@ -123,7 +109,7 @@ class AtomReporter extends View rootSuite = rootSuite.parentSuite while rootSuite.parentSuite @message.text rootSuite.description - time = "#{Math.round((spec.endedAt - @startedAt.getTime()) / 10)}" + time = "#{Math.round((spec.endedAt - @startedAt) / 10)}" time = "0#{time}" if time.length < 3 @time[0].textContent = "#{time[0...-2]}.#{time[-2..]}s" @@ -145,15 +131,22 @@ class AtomReporter extends View @userSummary.append symbol if coreSpecs > 0 - @coreHeader.text("Core Specs (#{coreSpecs}):") + @coreHeader.text("Core Specs (#{coreSpecs})") else @coreArea.hide() if bundledPackageSpecs > 0 - @bundledHeader.text("Bundled Package Specs (#{bundledPackageSpecs}):") + @bundledHeader.text("Bundled Package Specs (#{bundledPackageSpecs})") else @bundledArea.hide() if userPackageSpecs > 0 - @userHeader.text("User Package Specs (#{userPackageSpecs}):") + if coreSpecs is 0 and bundledPackageSpecs is 0 + # Package specs being run, show a more descriptive label + {specDirectory} = specs[0] + packageFolderName = path.basename(path.dirname(specDirectory)) + packageName = _.undasherize(_.uncamelcase(packageFolderName)) + @userHeader.text("#{packageName} Specs") + else + @userHeader.text("User Package Specs (#{userPackageSpecs})") else @userArea.hide() @@ -163,7 +156,7 @@ class AtomReporter extends View specComplete: (spec) -> specSummaryElement = $("#spec-summary-#{spec.id}") specSummaryElement.removeClass('pending') - specSummaryElement.data("description", spec.getFullName()) + specSummaryElement.setTooltip(title: spec.getFullName(), container: '.spec-reporter') results = spec.results() if results.skipped @@ -184,11 +177,9 @@ class SuiteResultView extends View @div class: 'suite', => @div outlet: 'description', class: 'description' - suite: null - initialize: (@suite) -> @attr('id', "suite-view-#{@suite.id}") - @description.html @suite.description + @description.text(@suite.description) attach: -> (@parentSuiteView() or $('.results')).append this @@ -205,20 +196,22 @@ class SuiteResultView extends View class SpecResultView extends View @content: -> @div class: 'spec', => - @div "\uf03d", class: 'spec-toggle' + @div class: 'spec-toggle' @div outlet: 'description', class: 'description' @div outlet: 'specFailures', class: 'spec-failures' - spec: null initialize: (@spec) -> @addClass("spec-view-#{@spec.id}") - @description.text @spec.description + + description = @spec.description + description = "it #{description}" if description.indexOf('it ') isnt 0 + @description.text(description) for result in @spec.results().getItems() when not result.passed() - stackTrace = formatStackTrace(result.trace.stack) + stackTrace = formatStackTrace(result.message, result.trace.stack) @specFailures.append $$ -> - @div result.message, class: 'resultMessage fail' - @div stackTrace, class: 'stackTrace' if stackTrace + @div result.message, class: 'result-message fail' + @pre stackTrace, class: 'stack-trace padded' if stackTrace attach: -> @parentSuiteView().append this diff --git a/static/jasmine.less b/static/jasmine.less index 7c2084807..aced4f449 100644 --- a/static/jasmine.less +++ b/static/jasmine.less @@ -1,171 +1,164 @@ -@import "octicon-mixins.less"; +@import "octicon-mixins"; -@font-face { .octicon-font(); } +#jasmine_content { + position: fixed; + right: 100%; +} body { - background-color: #ddd; + background-color: #fff; padding: 0; } -.spec-popup { - position: absolute; - background-color: #ddd; - border: 2px solid black; - padding: 5px; - font-size: 13px; - display: none; -} - -.list-unstyled { - list-style: none; -} - -#HTMLReporter { font-size: 11px; font-family: Monaco, Consolas, monospace; line-height: 1.6em; color: #333333; } -#HTMLReporter #jasmine_content { position: fixed; right: 100%; } - -#HTMLReporter .symbolHeader { - background-color: #222; - color: #c2c2c2; - font-size: 12px; - margin: 0; - padding: 5px 2px 2px 2px; -} - -#HTMLReporter .symbolSummary { - background-color: #222; - overflow: hidden; - margin: 0; -} - -#HTMLReporter .symbolSummary li { - float: left; - line-height: 10px; - height: 10px; - width: 10px; - font-size: 10px; -} - -#HTMLReporter .symbolSummary li.passed { color: #63AD75 } -#HTMLReporter .symbolSummary li.failed { color: #FF3F05 } -#HTMLReporter .symbolSummary li.skipped { color: #444 } -#HTMLReporter .symbolSummary li.pending { color: #111 } - -#HTMLReporter .symbolSummary li:before { content: "\02022"; } - -#HTMLReporter .symbolSummary li:hover { - color: white; -} - -#HTMLReporter .status { - font-size: 20px; - color: #222; - background-color: #E5FFC0; - line-height: 2em; - padding: 2px; - border: 2px solid #222; - border-left: 0; - border-right: 0; - text-align: center; -} - -#HTMLReporter .status.failed { - color: white; - background-color: rgba(204,51,63,1.0); -} - -#HTMLReporter .status .spec-count { - float: left; -} - -#HTMLReporter .status .message { -} - -#HTMLReporter .status .time { - float: right; -} - -#HTMLReporter .results .suite + .suite, #HTMLReporter .results .spec + .spec { - border-radius: 0; -} - -#HTMLReporter .results .suite, #HTMLReporter .results .spec { - border: 2px solid #222; - border-radius: 7px 0 0 0; - border-right: none; - border-bottom: none; - padding: 5px; - padding-right: 0; - padding-bottom: 0; -} - -#HTMLReporter .results .suite:first-child { - border-radius: 0; - border: 2px solid #6A4A3C; - border-top: 0; - border-left: 0; - border-right: 0; -} - -#HTMLReporter .results .suite { - border: 2px solid #6A4A3C; - border-radius: 7px 0 0 0; - border-right: none; - border-bottom: none; - padding: 5px; - padding-right: 0; - padding-bottom: 0; - background-color:rgba(204,51,63,0.33); -} - -#HTMLReporter .results .spec { - padding: 10px; - background-color:rgba(204,51,63,1.0); -} - -#HTMLReporter .results .suite > .suite, #HTMLReporter .results .suite > .spec { - margin-left: 5px -} - -#HTMLReporter .results .description { - color: white; - font-size: 15px; - padding-bottom: 10px -} - -#HTMLReporter .results .spec .spec-toggle { - font-family: Octicons Regular; - color: white; - font-size: 20px; - float: right; - cursor: pointer; - text-shadow: 3px 3px #222; - opacity: 0; -} - -#HTMLReporter .results .spec .spec-toggle:hover { - text-shadow: none; - padding-top: 3px; -} - -#HTMLReporter .results .spec:hover .spec-toggle { - opacity: 1; -} - -#HTMLReporter .resultMessage { - padding-top: 5px; - color: #fff; - font-size: 15px -} - -#HTMLReporter .stackTrace { - font-size: 12px; - padding: 5px; - margin: 5px 0 0 0; - border-radius: 2px; - line-height: 18px; - color: #666666; - border: 1px solid #ddd; - background: white; - white-space: pre; - overflow: auto; +.spec-reporter { + font-size: 11px; + line-height: 1.6em; + color: #333; + + .list-unstyled { + list-style: none; + } + + .symbol-header { + font-size: 18px; + font-weight: bold; + padding-bottom: 10px; + } + + .symbol-area { + padding: 10px; + } + + .symbol-summary { + overflow: hidden; + margin: 0; + + li { + font-family: Monaco, Consolas, monospace; + float: left; + line-height: 10px; + height: 10px; + width: 10px; + font-size: 10px; + + &.passed { + color: #5cb85c; + } + + &.failed { + color: #d9534f; + } + + &.skipped { + color: #ddd; + } + + &.pending { + color: #eee; + } + + &:before { + content: "\02022"; + } + } + } + + .status { + font-size: 20px; + line-height: 2em; + padding: 5px; + border-radius: 0; + text-align: center; + + .spec-count { + float: left; + } + + .time { + float: right; + } + } + + .results { + padding: 10px; + + .description { + font-size: 16px; + padding: 5px 0 5px 0; + } + + > .suite { + > .description { + font-size: 18px; + font-weight: bold; + } + + margin-bottom: 20px; + } + + .spec { + margin-top: 5px; + padding: 0 10px 10px 10px; + border-left: 3px solid #d9534f; + + .spec-toggle { + .octicon(fold); + float: right; + cursor: pointer; + opacity: 0; + color: #999; + + &.folded { + .octicon(unfold); + } + } + + .spec-toggle:hover { + color: #333; + } + + &:hover .spec-toggle { + opacity: 1; + } + } + + .suite > .suite, + .suite > .spec { + margin-left: 10px; + } + } + + .result-message { + font-size: 16px; + font-weight: bold; + color: #d9534f; + padding: 5px 0 5px 0; + } + + .stack-trace { + font-size: 12px; + margin: 5px 0 0 0; + border-radius: 2px; + line-height: 18px; + color: #666; + border: 1px solid #ddd; + overflow: auto; + } + + .tooltip { + .tooltip-inner { + border: 1px solid #ccc; + background: #fff; + color: #666; + max-width: 400px; + } + + &.in { + opacity: 1; + } + + .tooltip-arrow { + visibility: hidden; + } + } }