using afFancordion
** A Fancordion Skin for Twitter [Bootstrap]`http://getbootstrap.com/`.
**
** To use the vanilla 'Bootstrap' skin, set the 'skinType' field on [FancordionRunner]`afFancordion::FancordionRunner`:
**
** syntax: fantom
**
** using afFancordion
** using afFancordionBootstrap
**
** ** My Bootstrap Fixture
** class BootstrapFixture : FixtureTest {
** override FancordionRunner fancordionRunner() {
** FancordionRunner() {
** it.skinType = BootstrapSkin#
** }
** }
**
** ...
** }
**
** To use the themed version of Bootstrap, set the 'gimmeSomeSkin' field instead:
**
** syntax: fantom
** it.gimmeSomeSkin = |->FancordionSkin| { BootstrapSkin(true) }
**
class BootstrapSkin : FancordionSkin {
private Bool inPanelHeading
private Int nextErrId := 1
private Str[] modals := Str[,]
private Bool useTheme
** The CSS classes used for tables. Set to enhance your tables:
**
** syntax: fantom
** skin.tableCss = "table table-striped table-bordered table-hover table-condensed"
**
** Defaults to just 'table'.
** See [Bootstrap Tables]`http://getbootstrap.com/css/#tables` for details.
Str tableCss := "table"
** Creates a Bootstrap skin.
** Set 'useTheme' to 'true' to use the *themed* version of Bootstrap.
** The *themed* version uses colour gradients on buttons and other components.
new make(Bool useTheme := false) {
this.useTheme = useTheme
}
// ---- Setup / Tear Down -------------------
@NoDoc
override Void tearDown() {
super.tearDown
}
// ---- HTML Methods ------------------------
@NoDoc
override This head() {
super.head
write("""<meta name="viewport" content="width=device-width, initial-scale=1" />\n""")
// copy the bootstrap files over
addCss (bootstrapCssUri.get)
if (useTheme)
addCss (`fan://afFancordionBootstrap/doc/skins/bootstrap/css/bootstrap-theme.min.css`.get)
addCss (`fan://afFancordionBootstrap/doc/skins/bootstrap/css/fancordion-bs.css`.get)
addScript (`fan://afFancordionBootstrap/doc/skins/bootstrap/js/jquery-1.11.3.min.js`.get)
addScript (`fan://afFancordionBootstrap/doc/skins/bootstrap/js/bootstrap.min.js`.get)
copyFile (`fan://afFancordionBootstrap/doc/skins/bootstrap/fonts/glyphicons-halflings-regular.eot`.get, `fonts/`)
copyFile (`fan://afFancordionBootstrap/doc/skins/bootstrap/fonts/glyphicons-halflings-regular.ttf`.get, `fonts/`)
copyFile (`fan://afFancordionBootstrap/doc/skins/bootstrap/fonts/glyphicons-halflings-regular.woff`.get, `fonts/`)
copyFile (`fan://afFancordionBootstrap/doc/skins/bootstrap/fonts/glyphicons-halflings-regular.woff2`.get,`fonts/`)
return this
}
** Returns the location of the Bootstrap CSS file.
**
** Defaults to '`fan://afFancordionBootstrap/res/bootstrap/css/bootstrap.min.css`' but is overridden by Bootswatch skins.
virtual Uri bootstrapCssUri() {
`fan://afFancordionBootstrap/doc/skins/bootstrap/css/bootstrap.min.css`
}
@NoDoc
override This body() {
write("""<body>\n""")
write("""<div class="container">\n""")
write("""<div class="row">\n""")
write("""<div class="col-xs-12">\n""")
breadcrumbs
return this
}
@NoDoc
override This breadcrumbs() {
idx := 0
breadcrumbPaths := breadcrumbPaths
write("""<ol class="breadcrumb">""")
breadcrumbPaths.each |text, href| {
if (++idx == breadcrumbPaths.size)
write("""<li class="active">${text}</li>""")
else
write("<li>").a(href, text).write("</li>")
}
write("""</ol>""")
return this
}
@NoDoc
override This section() {
write("""<div class="panel panel-info">\n""")
write("""<div class="panel-heading">\n""")
inPanelHeading = true
return this
}
@NoDoc
override This sectionEnd() {
write("""</div>\n""")
write("""</div>\n""")
return this
}
@NoDoc
override This heading(Int level, Str title, Str? anchorId) {
id := (anchorId == null) ? Str.defVal : " id=\"${anchorId.toXml}\""
return inPanelHeading
? write("""<h${level}${id} class="panel-title">\n""")
: write("""<h${level}${id}>""")
}
@NoDoc
override This headingEnd(Int level) {
write("""</h${level}>\n""")
if (inPanelHeading) {
write("""</div>\n""")
write("""<div class="panel-body">\n""")
inPanelHeading = false
}
return this
}
@NoDoc
override This table(Str? cssClass := null) {
inTable = true
return write("""<table class="${tableCss} ${cssClass}">\n""")
}
@NoDoc
override This footer() {
ver := Pod.find("afFancordion").version
now := DateTime.now(1sec).toLocale("D MMM YYYY, k:mmaa zzzz 'Time'")
dur := DateTime.now(null) - fixtureMeta.StartTime
write("""<footer class="clearfix">\n""")
write("""<hr/>\n""")
write("""<div class="pull-right small text-right">\n""")
write("""\tResults generated by <b><a href="http://www.fantomfactory.org/pods/afFancordion">Fancordion v${ver}</a></b>\n""")
write("""\t<div class="testTime">in ${dur.toLocale} on ${now}</div>\n""")
write("""</div>\n""")
write("""</footer>\n""")
return this
}
@NoDoc
override This bodyEnd() {
footer
scriptUrls.each { script(it) }
write("""</div>\n""")
write("""</div>\n""")
write("""</div>\n""")
modals.each { write(it) }
write("""</body>\n""")
return this
}
// ---- Test Results --------------------------------------------------------------------------
private Str cmdHtml(Str level, Str body) {
if (inTable)
return """<${cmdElem} class="cmd ${level}">${body}</${cmdElem}>"""
if (inPre)
return """<${cmdElem} class="cmd bg-${level}">${body}</${cmdElem}>"""
return """<${cmdElem} class="cmd alert alert-${level}">${body}</${cmdElem}>"""
}
** Called to render an ignored command.
@NoDoc
override This cmdIgnored(Str text) {
return write(cmdHtml("active", text.toXml))
}
** Called to render a command success.
@NoDoc
override This cmdSuccess(Str text, Bool escape := true) {
body := escape ? text.toXml : text
return write(cmdHtml("success", body))
}
** Called to render a command failure.
@NoDoc
override This cmdFailure(Str expected, Obj? actual, Bool escape := true) {
text := escape ? expected.toXml : expected
body := """<del class="expected">${text}</del> <span class="actual">${firstLine(actual?.toStr).toXml}</span>"""
return write(cmdHtml("danger", body))
}
** Called to render a command error.
@NoDoc
override This cmdErr(Str cmdUrl, Str cmdText, Err err) {
modalId := "modal-${nextErrId++}"
body := """<del class="expected">${cmdText.toXml}</del> <samp class="actual">${err.typeof.name.toXml}: ${firstLine(err.msg).toXml}</samp>"""
write(cmdHtml("danger", body))
write(""" <button type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#${modalId}">Show Stack Trace</button>""")
modal :=
"""
<!-- Modal -->
<div class="modal fade" id="${modalId}" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
<h4 class="modal-title">${err.typeof.name.toXml}: ${firstLine(err.msg).toXml}</h4>
</div>
<div class="modal-body">
<p>Err thrown while evaluating command: <code>${cmdUrl.toXml}</code></p>
<pre>${err.traceToStr.toXml}</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
"""
modals.add(modal)
return this
}
private Str firstLine(Str? txt) {
txt?.splitLines?.exclude { it.trim.isEmpty }?.first ?: Str.defVal
}
}