Merge pull request #1528 from atom/ks-spec-runner-ui

Tweak spec reporter styles
This commit is contained in:
Kevin Sawicki
2014-02-11 09:33:30 -08:00
2 changed files with 215 additions and 229 deletions

View File

@@ -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].<anonymous> (path:1:2)
for line, index in lines
prefixMatch = line.match(/at \[object Object\]\.<anonymous> \(([^\)]+)\)/)
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

View File

@@ -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;
}
}
}