diff options
Diffstat (limited to 'src/mongo/gotools/vendor/src/github.com/smartystreets/goconvey/web/client/resources/js/goconvey.js')
-rw-r--r-- | src/mongo/gotools/vendor/src/github.com/smartystreets/goconvey/web/client/resources/js/goconvey.js | 1322 |
1 files changed, 1322 insertions, 0 deletions
diff --git a/src/mongo/gotools/vendor/src/github.com/smartystreets/goconvey/web/client/resources/js/goconvey.js b/src/mongo/gotools/vendor/src/github.com/smartystreets/goconvey/web/client/resources/js/goconvey.js new file mode 100644 index 00000000000..3bc12c5a7cd --- /dev/null +++ b/src/mongo/gotools/vendor/src/github.com/smartystreets/goconvey/web/client/resources/js/goconvey.js @@ -0,0 +1,1322 @@ +$(init); + +$(window).load(function() +{ + // Things may shift after all the elements (images/fonts) are loaded + // In Chrome, calling reframe() doesn't work (maybe a quirk); we need to trigger resize + $(window).resize(); +}); + +function init() +{ + log("Welcome to GoConvey!"); + log("Initializing interface"); + convey.overall = emptyOverall(); + loadTheme(); + $('body').show(); + initPoller(); + wireup(); + latest(); +} + +function loadTheme(thmID) +{ + var defaultTheme = "dark"; + var linkTagId = "themeRef"; + + if (!thmID) + thmID = get('theme') || defaultTheme; + + log("Initializing theme: " + thmID); + + if (!convey.config.themes[thmID]) + { + replacement = Object.keys(convey.config.themes)[0] || defaultTheme; + log("NOTICE: Could not find '" + thmID + "' theme; defaulting to '" + replacement + "'"); + thmID = replacement; + } + + convey.theme = thmID; + save('theme', convey.theme); + + var linkTag = $('#'+linkTagId); + var fullPath = convey.config.themePath + + convey.config.themes[convey.theme].filename; + + if (linkTag.length === 0) + { + $('head').append('<link rel="stylesheet" href="' + + fullPath + '" id="themeRef">'); + } + else + linkTag.attr('href', fullPath); + + colorizeCoverageBars(); +} + +function initPoller() +{ + $(convey.poller).on('serverstarting', function(event) + { + log("Server is starting..."); + convey.status = "starting"; + showServerDown("Server starting"); + $('#run-tests').addClass('spin-slowly disabled'); + }); + + $(convey.poller).on('pollsuccess', function(event, data) + { + if (convey.status !== "starting") + hideServerDown(); + + // These two if statements determine if the server is now busy + // (and wasn't before) or is not busy (regardless of whether it was before) + if ((!convey.status || convey.status === "idle") + && data.status && data.status !== "idle") + $('#run-tests').addClass('spin-slowly disabled'); + else if (convey.status !== "idle" && data.status === "idle") + { + $('#run-tests').removeClass('spin-slowly disabled'); + } + + switch (data.status) + { + case "executing": + $(convey.poller).trigger('serverexec', data); + break; + case "idle": + $(convey.poller).trigger('serveridle', data); + break; + } + + convey.status = data.status; + }); + + $(convey.poller).on('pollfail', function(event, data) + { + log("Poll failed; server down"); + convey.status = "down"; + showServerDown("Server down"); + }); + + $(convey.poller).on('serverexec', function(event, data) + { + log("Server status: executing"); + $('.favicon').attr('href', '/favicon.ico'); // indicates running tests + }); + + $(convey.poller).on('serveridle', function(event, data) + { + log("Server status: idle"); + log("Tests have finished executing"); + latest(); + }); + + convey.poller.start(); +} + +function wireup() +{ + log("Wireup"); + + customMarkupPipes(); + + var themes = []; + for (var k in convey.config.themes) + themes.push({ id: k, name: convey.config.themes[k].name }); + $('#theme').html(render('tpl-theme-enum', themes)); + + enumSel("theme", convey.theme); + + loadSettingsFromStorage(); + + $('#stories').on('click', '.toggle-all-pkg', function(event) + { + if ($(this).closest('.story-pkg').data('pkg-state') === "expanded") + collapseAll(); + else + expandAll(); + return suppress(event); + }); + + // Wireup the settings switches + $('.enum#theme').on('click', 'li:not(.sel)', function() + { + loadTheme($(this).data('theme')); + }); + $('.enum#pkg-expand-collapse').on('click', 'li:not(.sel)', function() + { + var newSetting = $(this).data('pkg-expand-collapse'); + convey.packageStates = {}; + save('pkg-expand-collapse', newSetting); + if (newSetting === "expanded") + expandAll(); + else + collapseAll(); + }); + $('.enum#show-debug-output').on('click', 'li:not(.sel)', function() + { + var newSetting = $(this).data('show-debug-output'); + save('show-debug-output', newSetting); + if (newSetting === "show") + $('.story-line-desc .message').show(); + else + $('.story-line-desc .message').hide(); + }); + $('.enum#ui-effects').on('click', 'li:not(.sel)', function() + { + var newSetting = $(this).data('ui-effects'); + convey.uiEffects = newSetting; + save('ui-effects', newSetting); + }); + // End settings wireup + + convey.layout.header = $('header').first(); + convey.layout.frame = $('.frame').first(); + convey.layout.footer = $('footer').last(); + + updateWatchPath(); + + $('#path').change(function() + { + // Updates the watched directory with the server and makes sure it exists + var tb = $(this); + var newpath = encodeURIComponent($.trim(tb.val())); + $.post('/watch?root='+newpath) + .done(function() { tb.removeClass('error'); }) + .fail(function() { tb.addClass('error'); }); + convey.framesOnSamePath = 1; + }); + + $('#run-tests').click(function() + { + var self = $(this); + if (self.hasClass('spin-slowly') || self.hasClass('disabled')) + return; + log("Test run invoked from web UI"); + $.get("/execute"); + }); + + $('#play-pause').click(function() + { + $.get('/pause'); + + if ($(this).hasClass(convey.layout.selClass)) + { + // Un-pausing + if (!$('footer .replay').is(':visible')) + $('footer .recording').show(); + $('footer .paused').hide(); + log("Resuming auto-execution of tests"); + } + else + { + // Pausing + $('footer .recording').hide(); + $('footer .paused').show(); + log("Pausing auto-execution of tests"); + } + + $(this).toggleClass("throb " + convey.layout.selClass); + }); + + $('#toggle-notif').click(function() + { + log("Turning notifications " + (notif() ? "off" : "on")); + $(this).toggleClass("fa-bell-o fa-bell " + convey.layout.selClass); + save('notifications', !notif()); + + if (notif() && 'Notification' in window) + { + if (Notification.permission !== 'denied') + { + Notification.requestPermission(function(per) + { + if (!('permission' in Notification)) + Notification.permission = per; + }); + } + else + log("Permission denied to show desktop notification"); + } + }); + + $('#show-history').click(function() + { + toggle($('.history'), $(this)); + }); + + $('#show-settings').click(function() + { + toggle($('.settings'), $(this)); + }); + + $('#show-gen').click(function() { + var writer = window.open("/composer.html"); + if (window.focus) + writer.focus(); + }); + + // Wire-up the tipsy tooltips + $('.controls li, .pkg-cover-name').tipsy({ live: true }); + $('footer .replay').tipsy({ live: true, gravity: 'e' }); + $('#path').tipsy({ delayIn: 500 }); + $('.ignore').tipsy({ live: true, gravity: $.fn.tipsy.autoNS }); + $('.disabled').tipsy({ live: true, gravity: $.fn.tipsy.autoNS }); + $('#logo').tipsy({ gravity: 'w' }); + + + $('.toggler').not('.narrow').prepend('<i class="fa fa-angle-up fa-lg"></i>'); + $('.toggler.narrow').prepend('<i class="fa fa-angle-down fa-lg"></i>'); + + $('.toggler').not('.narrow').click(function() + { + var target = $('#' + $(this).data('toggle')); + $('.fa-angle-down, .fa-angle-up', this).toggleClass('fa-angle-down fa-angle-up'); + target.toggle(); + }); + + $('.toggler.narrow').click(function() + { + var target = $('#' + $(this).data('toggle')); + $('.fa-angle-down, .fa-angle-up', this).toggleClass('fa-angle-down fa-angle-up'); + target.toggleClass('hide-narrow show-narrow'); + }); + + // Enumerations are horizontal lists where one item can be selected at a time + $('.enum').on('click', 'li', enumSel); + + // Start ticking time + convey.intervals.time = setInterval(convey.intervalFuncs.time, 1000); + convey.intervals.momentjs = setInterval(convey.intervalFuncs.momentjs, 5000); + convey.intervalFuncs.time(); + + // Ignore/un-ignore package + $('#stories').on('click', '.fa.ignore', function(event) + { + var pkg = $(this).data('pkg'); + if ($(this).hasClass('disabled')) + return; + else if ($(this).hasClass('unwatch')) + $.get("/ignore", { paths: pkg }); + else + $.get("/reinstate", { paths: pkg }); + $(this).toggleClass('watch unwatch fa-eye fa-eye-slash clr-red'); + return suppress(event); + }); + + // Show "All" link when hovering the toggler on packages in the stories + $('#stories').on({ + mouseenter: function() { $('.toggle-all-pkg', this).stop().show('fast'); }, + mouseleave: function() { $('.toggle-all-pkg', this).stop().hide('fast'); } + }, '.pkg-toggle-container'); + + // Toggle a package in the stories when clicked + $('#stories').on('click', '.story-pkg', function(event) + { + togglePackage(this, true); + return suppress(event); + }); + + // Select a story line when it is clicked + $('#stories').on('click', '.story-line', function() + { + $('.story-line-sel').not(this).removeClass('story-line-sel'); + $(this).toggleClass('story-line-sel'); + }); + + // Render a frame from the history when clicked + $('.history .container').on('click', '.item', function(event) + { + var frame = getFrame($(this).data("frameid")); + changeStatus(frame.overall.status, true); + renderFrame(frame); + $(this).addClass('selected'); + + // Update current status down in the footer + if ($(this).is(':first-child')) + { + // Now on current frame + $('footer .replay').hide(); + + if ($('#play-pause').hasClass(convey.layout.selClass)) // Was/is paused + $('footer .paused').show(); + else + $('footer .recording').show(); // Was/is recording + } + else + { + $('footer .recording, footer .replay').hide(); + $('footer .replay').show(); + } + return suppress(event); + }); + + $('footer').on('click', '.replay', function() + { + // Clicking "REPLAY" in the corner should bring them back to the current frame + // and hide, if visible, the history panel for convenience + $('.history .item:first-child').click(); + if ($('#show-history').hasClass('sel')) + $('#show-history').click(); + }); + + // Keyboard shortcuts! + $(document).keydown(function(e) + { + if (e.ctrlKey || e.metaKey || e.shiftKey) + return; + + switch (e.keyCode) + { + case 67: // c + $('#show-gen').click(); + break; + case 82: // r + $('#run-tests').click(); + break; + case 78: // n + $('#toggle-notif').click(); + break; + case 87: // w + $('#path').focus(); + break; + case 80: // p + $('#play-pause').click(); + break; + } + + return suppress(e); + }); + $('body').on('keydown', 'input, textarea, select', function(e) + { + // If user is typing something, don't let this event bubble + // up to the document to annoyingly fire keyboard shortcuts + e.stopPropagation(); + }); + + // Keep everything positioned and sized properly on window resize + reframe(); + $(window).resize(reframe); +} + +function expandAll() +{ + $('.story-pkg').each(function() { expandPackage($(this).data('pkg')); }); +} + +function collapseAll() +{ + $('.story-pkg').each(function() { collapsePackage($(this).data('pkg')); }); +} + +function expandPackage(pkgId) +{ + var pkg = $('.story-pkg.pkg-'+pkgId); + var rows = $('.story-line.pkg-'+pkgId); + + pkg.data('pkg-state', "expanded").addClass('expanded').removeClass('collapsed'); + + $('.pkg-toggle', pkg) + .addClass('fa-minus-square-o') + .removeClass('fa-plus-square-o'); + + rows.show(); +} + +function collapsePackage(pkgId) +{ + var pkg = $('.story-pkg.pkg-'+pkgId); + var rows = $('.story-line.pkg-'+pkgId); + + pkg.data('pkg-state', "collapsed").addClass('collapsed').removeClass('expanded'); + + $('.pkg-toggle', pkg) + .addClass('fa-plus-square-o') + .removeClass('fa-minus-square-o'); + + rows.hide(); +} + +function togglePackage(storyPkgElem) +{ + var pkgId = $(storyPkgElem).data('pkg'); + if ($(storyPkgElem).data('pkg-state') === "expanded") + { + collapsePackage(pkgId); + convey.packageStates[$(storyPkgElem).data('pkg-name')] = "collapsed"; + } + else + { + expandPackage(pkgId); + convey.packageStates[$(storyPkgElem).data('pkg-name')] = "expanded"; + } +} + +function loadSettingsFromStorage() +{ + var pkgExpCollapse = get("pkg-expand-collapse"); + if (!pkgExpCollapse) + { + pkgExpCollapse = "expanded"; + save("pkg-expand-collapse", pkgExpCollapse); + } + enumSel("pkg-expand-collapse", pkgExpCollapse); + + var showDebugOutput = get("show-debug-output"); + if (!showDebugOutput) + { + showDebugOutput = "show"; + save("show-debug-output", showDebugOutput); + } + enumSel("show-debug-output", showDebugOutput); + + var uiEffects = get("ui-effects"); + if (uiEffects === null) + uiEffects = "true"; + convey.uiEffects = uiEffects === "true"; + enumSel("ui-effects", uiEffects); + + if (notif()) + $('#toggle-notif').toggleClass("fa-bell-o fa-bell " + convey.layout.selClass); +} + + + + + + + + + + + +function latest() +{ + log("Fetching latest test results"); + $.getJSON("/latest", process); +} + +function process(data, status, jqxhr) +{ + if (!data || !data.Revision) + { + log("No data received or revision timestamp was missing"); + return; + } + + if (data.Paused && !$('#play-pause').hasClass(convey.layout.selClass)) + { + $('footer .recording').hide(); + $('footer .paused').show(); + $('#play-pause').toggleClass("throb " + convey.layout.selClass); + } + + if (current() && data.Revision === current().results.Revision) + { + log("No changes"); + changeStatus(current().overall.status); // re-assures that status is unchanged + return; + } + + + // Put the new frame in the queue so we can use current() to get to it + convey.history.push(newFrame()); + convey.framesOnSamePath++; + + // Store the raw results in our frame + current().results = data; + + log("Updating watch path"); + updateWatchPath(); + + // Remove all templated items from the DOM as we'll + // replace them with new ones; also remove tipsy tooltips + // that may have lingered around + $('.templated, .tipsy').remove(); + + var uniqueID = 0; + var coverageAvgHelper = { countedPackages: 0, coverageSum: 0 }; + var packages = { + tested: [], + ignored: [], + coverage: {}, + nogofiles: [], + notestfiles: [], + notestfn: [] + }; + + log("Compiling package statistics"); + + // Look for failures and panics through the packages->tests->stories... + for (var i in data.Packages) + { + pkg = makeContext(data.Packages[i]); + current().overall.duration += pkg.Elapsed; + pkg._id = uniqueID++; + + if (pkg.Outcome === "build failure") + { + current().overall.failedBuilds++; + current().failedBuilds.push(pkg); + continue; + } + + + if (pkg.Outcome === "no go code") + packages.nogofiles.push(pkg); + else if (pkg.Outcome === "no test files") + packages.notestfiles.push(pkg); + else if (pkg.Outcome === "no test functions") + packages.notestfn.push(pkg); + else if (pkg.Outcome === "ignored" || pkg.Outcome === "disabled") + packages.ignored.push(pkg); + else + { + if (pkg.Coverage >= 0) + coverageAvgHelper.coverageSum += pkg.Coverage; + coverageAvgHelper.countedPackages++; + packages.coverage[pkg.PackageName] = pkg.Coverage; + packages.tested.push(pkg); + } + + + for (var j in pkg.TestResults) + { + test = makeContext(pkg.TestResults[j]); + test._id = uniqueID++; + test._pkgid = pkg._id; + test._pkg = pkg.PackageName; + + if (test.Stories.length === 0) + { + // Here we've got ourselves a classic Go test, + // not a GoConvey test that has stories and assertions + // so we'll treat this whole test as a single assertion + current().overall.assertions++; + + if (test.Error) + { + test._status = convey.statuses.panic; + pkg._panicked++; + test._panicked++; + current().assertions.panicked.push(test); + } + else if (test.Passed === false) + { + test._status = convey.statuses.fail; + pkg._failed++; + test._failed++; + current().assertions.failed.push(test); + } + else if (test.Skipped) + { + test._status = convey.statuses.skipped; + pkg._skipped++; + test._skipped++; + current().assertions.skipped.push(test); + } + else + { + test._status = convey.statuses.pass; + pkg._passed++; + test._passed++; + current().assertions.passed.push(test); + } + } + else + test._status = convey.statuses.pass; + + var storyPath = [{ Depth: -1, Title: test.TestName, _id: test._id }]; // Maintains the current assertion's story as we iterate + + for (var k in test.Stories) + { + var story = makeContext(test.Stories[k]); + + story._id = uniqueID; + story._pkgid = pkg._id; + current().overall.assertions += story.Assertions.length; + + // Establish the current story path so we can report the context + // of failures and panicks more conveniently at the top of the page + if (storyPath.length > 0) + for (var x = storyPath[storyPath.length - 1].Depth; x >= test.Stories[k].Depth; x--) + storyPath.pop(); + storyPath.push({ Depth: test.Stories[k].Depth, Title: test.Stories[k].Title, _id: test.Stories[k]._id }); + + + for (var l in story.Assertions) + { + var assertion = story.Assertions[l]; + assertion._id = uniqueID; + assertion._pkg = pkg.PackageName; + assertion._pkgId = pkg._id; + assertion._failed = !!assertion.Failure; + assertion._panicked = !!assertion.Error; + assertion._maxDepth = storyPath[storyPath.length - 1].Depth; + $.extend(assertion._path = [], storyPath); + + if (assertion.Failure) + { + current().assertions.failed.push(assertion); + pkg._failed++; + test._failed++; + story._failed++; + } + if (assertion.Error) + { + current().assertions.panicked.push(assertion); + pkg._panicked++; + test._panicked++; + story._panicked++; + } + if (assertion.Skipped) + { + current().assertions.skipped.push(assertion); + pkg._skipped++; + test._skipped++; + story._skipped++; + } + if (!assertion.Failure && !assertion.Error && !assertion.Skipped) + { + current().assertions.passed.push(assertion); + pkg._passed++; + test._passed++; + story._passed++; + } + } + + assignStatus(story); + uniqueID++; + } + + if (!test.Passed && !test._failed && !test._panicked) + { + // Edge case: Developer is using the GoConvey DSL, but maybe + // in some cases is using t.Error() instead of So() assertions. + // This can be detected, assuming all child stories with + // assertions (in this test) are passing. + test._status = convey.statuses.fail; + pkg._failed++; + test._failed++; + current().assertions.failed.push(test); + } + } + } + + current().overall.passed = current().assertions.passed.length; + current().overall.panics = current().assertions.panicked.length; + current().overall.failures = current().assertions.failed.length; + current().overall.skipped = current().assertions.skipped.length; + + current().overall.coverage = Math.round((coverageAvgHelper.coverageSum / (coverageAvgHelper.countedPackages || 1)) * 100) / 100; + current().overall.duration = Math.round(current().overall.duration * 1000) / 1000; + + // Compute the coverage delta (difference in overall coverage between now and last frame) + // Only compare coverage on the same watch path + var coverDelta = current().overall.coverage; + if (convey.framesOnSamePath > 2) + coverDelta = current().overall.coverage - convey.history[convey.history.length - 2].overall.coverage; + current().coverDelta = Math.round(coverDelta * 100) / 100; + + + // Build failures trump panics, + // Panics trump failures, + // Failures trump pass. + if (current().overall.failedBuilds) + changeStatus(convey.statuses.buildfail); + else if (current().overall.panics) + changeStatus(convey.statuses.panic); + else if (current().overall.failures) + changeStatus(convey.statuses.fail); + else + changeStatus(convey.statuses.pass); + + // Save our organized package lists + current().packages = packages; + + log(" Assertions: " + current().overall.assertions); + log(" Passed: " + current().overall.passed); + log(" Skipped: " + current().overall.skipped); + log(" Failures: " + current().overall.failures); + log(" Panics: " + current().overall.panics); + log("Build Failures: " + current().overall.failedBuilds); + log(" Coverage: " + current().overall.coverage + "% (" + showCoverDelta(current().coverDelta) + ")"); + + // Save timestamp when this test was executed + convey.moments['last-test'] = moment(); + + + + // Render... render ALL THE THINGS! (All model/state modifications are DONE!) + renderFrame(current()); + // Now, just finish up miscellaneous UI things + + + // Add this frame to the history pane + var framePiece = render('tpl-history', current()); + $('.history .container').prepend(framePiece); + $('.history .item:first-child').addClass('selected'); + convey.moments['frame-'+current().id] = moment(); + if (convey.history.length > convey.maxHistory) + { + // Delete the oldest frame out of the history pane if we have too many + convey.history.splice(0, 1); + $('.history .container .item').last().remove(); + } + + // Now add the momentjs time to the new frame in the history + convey.intervalFuncs.momentjs(); + + // Show notification, if enabled + if (notif()) + { + log("Showing notification"); + if (convey.notif) + { + clearTimeout(convey.notifTimer); + convey.notif.close(); + } + + var notifText = notifSummary(current()) + + convey.notif = new Notification(notifText.title, { + body: notifText.body, + icon: $('.favicon').attr('href') + }); + + convey.notifTimer = setTimeout(function() { convey.notif.close(); }, 5000); + } + + // Update title in title bar + if (current().overall.passed === current().overall.assertions && current().overall.status.class === "ok") + $('title').text("GoConvey (ALL PASS)"); + else + $('title').text("GoConvey [" + current().overall.status.text + "] " + current().overall.passed + "/" + current().overall.assertions); + + // All done! + log("Processing complete"); +} + + + + + + + + + + + + + + + + + +// Updates the entire UI given a frame from the history +function renderFrame(frame) +{ + log("Rendering frame (id: " + frame.id + ")"); + + $('#coverage').html(render('tpl-coverage', frame.packages.tested.sort(sortPackages))); + $('#ignored').html(render('tpl-ignored', frame.packages.ignored.sort(sortPackages))); + $('#nogofiles').html(render('tpl-nogofiles', frame.packages.nogofiles.sort(sortPackages))); + $('#notestfiles').html(render('tpl-notestfiles', frame.packages.notestfiles.sort(sortPackages))); + $('#notestfn').html(render('tpl-notestfn', frame.packages.notestfn.sort(sortPackages))); + + if (frame.overall.failedBuilds) + { + $('.buildfailures').show(); + $('#buildfailures').html(render('tpl-buildfailures', frame.failedBuilds)); + } + else + $('.buildfailures').hide(); + + if (frame.overall.panics) + { + $('.panics').show(); + $('#panics').html(render('tpl-panics', frame.assertions.panicked)); + } + else + $('.panics').hide(); + + + if (frame.overall.failures) + { + $('.failures').show(); + $('#failures').html(render('tpl-failures', frame.assertions.failed)); + $(".failure").each(function() { + $(this).prettyTextDiff(); + }); + } + else + $('.failures').hide(); + + $('#stories').html(render('tpl-stories', frame.packages.tested.sort(sortPackages))); + $('#stories').append(render('tpl-stories', frame.packages.ignored.sort(sortPackages))); + + var pkgDefaultView = get('pkg-expand-collapse'); + $('.story-pkg.expanded').each(function() + { + if (pkgDefaultView === "collapsed" && convey.packageStates[$(this).data('pkg-name')] !== "expanded") + collapsePackage($(this).data('pkg')); + }); + + redrawCoverageBars(); + + $('#assert-count').html("<b>"+frame.overall.assertions+"</b> assertion" + + (frame.overall.assertions !== 1 ? "s" : "")); + $('#skip-count').html("<b>"+frame.assertions.skipped.length + "</b> skipped"); + $('#fail-count').html("<b>"+frame.assertions.failed.length + "</b> failed"); + $('#panic-count').html("<b>"+frame.assertions.panicked.length + "</b> panicked"); + $('#duration').html("<b>"+frame.overall.duration + "</b>s"); + + $('#narrow-assert-count').html("<b>"+frame.overall.assertions+"</b>"); + $('#narrow-skip-count').html("<b>"+frame.assertions.skipped.length + "</b>"); + $('#narrow-fail-count').html("<b>"+frame.assertions.failed.length + "</b>"); + $('#narrow-panic-count').html("<b>"+frame.assertions.panicked.length + "</b>"); + + $('.history .item').removeClass('selected'); + + if (get('show-debug-output') === "hide") + $('.story-line-desc .message').hide(); + + log("Rendering finished"); +} + + + + + + + +function enumSel(id, val) +{ + if (typeof id === "string" && typeof val === "string") + { + $('.enum#'+id+' > li').each(function() + { + if ($(this).data(id).toString() === val) + { + $(this).addClass(convey.layout.selClass).siblings().removeClass(convey.layout.selClass); + return false; + } + }); + } + else + $(this).addClass(convey.layout.selClass).siblings().removeClass(convey.layout.selClass); +} + +function toggle(jqelem, switchelem) +{ + var speed = 250; + var transition = 'easeInOutQuart'; + var containerSel = '.container'; + + if (!jqelem.is(':visible')) + { + $(containerSel, jqelem).css('opacity', 0); + jqelem.stop().slideDown(speed, transition, function() + { + if (switchelem) + switchelem.toggleClass(convey.layout.selClass); + $(containerSel, jqelem).stop().animate({ + opacity: 1 + }, speed); + reframe(); + }); + } + else + { + $(containerSel, jqelem).stop().animate({ + opacity: 0 + }, speed, function() + { + if (switchelem) + switchelem.toggleClass(convey.layout.selClass); + jqelem.stop().slideUp(speed, transition, function() { reframe(); }); + }); + } +} + +function changeStatus(newStatus, isHistoricalFrame) +{ + if (!newStatus || !newStatus.class || !newStatus.text) + newStatus = convey.statuses.pass; + + var sameStatus = newStatus.class === convey.overallClass; + + // The CSS class .flash and the jQuery UI 'pulsate' effect don't play well together. + // This series of callbacks does the flickering/pulsating as well as + // enabling/disabling flashing in the proper order so that they don't overlap. + // TODO: I suppose the pulsating could also be done with just CSS, maybe...? + + if (convey.uiEffects) + { + var times = sameStatus ? 3 : 2; + var duration = sameStatus ? 500 : 300; + + $('.overall .status').removeClass('flash').effect("pulsate", {times: times}, duration, function() + { + $(this).text(newStatus.text); + + if (newStatus !== convey.statuses.pass) // only flicker extra when not currently passing + { + $(this).effect("pulsate", {times: 1}, 300, function() + { + $(this).effect("pulsate", {times: 1}, 500, function() + { + if (newStatus === convey.statuses.panic + || newStatus === convey.statuses.buildfail) + $(this).addClass('flash'); + else + $(this).removeClass('flash'); + }); + }); + } + }); + } + else + $('.overall .status').text(newStatus.text); + + if (!sameStatus) // change the color + $('.overall').switchClass(convey.overallClass, newStatus.class, 1000); + + if (!isHistoricalFrame) + current().overall.status = newStatus; + convey.overallClass = newStatus.class; + $('.favicon').attr('href', '/resources/ico/goconvey-'+newStatus.class+'.ico'); +} + +function updateWatchPath() +{ + $.get("/watch", function(data) + { + var newPath = $.trim(data); + if (newPath !== $('#path').val()) + convey.framesOnSamePath = 1; + $('#path').val(newPath); + }); +} + +function notifSummary(frame) +{ + var body = frame.overall.passed + " passed, "; + + if (frame.overall.failedBuilds) + body += frame.overall.failedBuilds + " build" + (frame.overall.failedBuilds !== 1 ? "s" : "") + " failed, "; + if (frame.overall.failures) + body += frame.overall.failures + " failed, "; + if (frame.overall.panics) + body += frame.overall.panics + " panicked, "; + body += frame.overall.skipped + " skipped"; + + body += "\r\n" + frame.overall.duration + "s"; + + if (frame.coverDelta > 0) + body += "\r\n↑ coverage (" + showCoverDelta(frame.coverDelta) + ")"; + else if (frame.coverDelta < 0) + body += "\r\n↓ coverage (" + showCoverDelta(frame.coverDelta) + ")"; + + return { + title: frame.overall.status.text.toUpperCase(), + body: body + }; +} + +function redrawCoverageBars() +{ + $('.pkg-cover-bar').each(function() + { + var pkgName = $(this).data("pkg"); + var hue = $(this).data("width"); + var hueDiff = hue; + + if (convey.history.length > 1) + { + var oldHue = convey.history[convey.history.length - 2].packages.coverage[pkgName] || 0; + $(this).width(oldHue + "%"); + hueDiff = hue - oldHue; + } + + $(this).animate({ + width: "+=" + hueDiff + "%" + }, 1250); + }); + + colorizeCoverageBars(); +} + +function colorizeCoverageBars() +{ + var colorTpl = convey.config.themes[convey.theme].coverage + || "hsla({{hue}}, 75%, 30%, .3)"; //default color template + + $('.pkg-cover-bar').each(function() + { + var hue = $(this).data("width"); + $(this).css({ + background: colorTpl.replace("{{hue}}", hue) + }); + }); +} + + +function getFrame(id) +{ + for (var i in convey.history) + if (convey.history[i].id === id) + return convey.history[i]; +} + +function render(templateID, context) +{ + var tpl = $('#' + templateID).text(); + return $($.trim(Mark.up(tpl, context))); +} + +function reframe() +{ + var heightBelowHeader = $(window).height() - convey.layout.header.outerHeight(); + var middleHeight = heightBelowHeader - convey.layout.footer.outerHeight(); + convey.layout.frame.height(middleHeight); + + var pathWidth = $(window).width() - $('#logo').outerWidth() - $('#control-buttons').outerWidth() - 10; + $('#path-container').width(pathWidth); +} + +function notif() +{ + return get('notifications') === "true"; // stored as strings +} + +function showServerDown(message) +{ + $('.server-down .notice-message').text(message); + $('.server-down').show(); + $('.server-not-down').hide(); + reframe(); +} + +function hideServerDown() +{ + $('.server-down').hide(); + $('.server-not-down').show(); + reframe(); +} + +function log(msg) +{ + var jqLog = $('#log'); + if (jqLog.length > 0) + { + var t = new Date(); + var h = zerofill(t.getHours(), 2); + var m = zerofill(t.getMinutes(), 2); + var s = zerofill(t.getSeconds(), 2); + var ms = zerofill(t.getMilliseconds(), 3); + date = h + ":" + m + ":" + s + "." + ms; + + $(jqLog).append(render('tpl-log-line', { time: date, msg: msg })); + $(jqLog).parent('.col').scrollTop(jqLog[0].scrollHeight); + } + else + console.log(msg); +} + +function zerofill(val, count) +{ + // Cheers to http://stackoverflow.com/a/9744576/1048862 + var pad = new Array(1 + count).join('0'); + return (pad + val).slice(-pad.length); +} + +// Sorts packages ascending by only the last part of their name +// Can be passed into Array.sort() +function sortPackages(a, b) +{ + var aPkg = splitPathName(a.PackageName); + var bPkg = splitPathName(b.PackageName); + + if (aPkg.length === 0 || bPkg.length === 0) + return 0; + + var aName = aPkg.parts[aPkg.parts.length - 1].toLowerCase(); + var bName = bPkg.parts[bPkg.parts.length - 1].toLowerCase(); + + if (aName < bName) + return -1; + else if (aName > bName) + return 1; + else + return 0; + + /* + MEMO: Use to sort by entire package name: + if (a.PackageName < b.PackageName) return -1; + else if (a.PackageName > b.PackageName) return 1; + else return 0; + */ +} + +function get(key) +{ + var val = localStorage.getItem(key); + if (val && (val[0] === '[' || val[0] === '{')) + return JSON.parse(val); + else + return val; +} + +function save(key, val) +{ + if (typeof val === 'object') + val = JSON.stringify(val); + else if (typeof val === 'number' || typeof val === 'boolean') + val = val.toString(); + localStorage.setItem(key, val); +} + +function splitPathName(str) +{ + var delim = str.indexOf('\\') > -1 ? '\\' : '/'; + return { delim: delim, parts: str.split(delim) }; +} + +function newFrame() +{ + return { + results: {}, // response from server (with some of our own context info) + packages: {}, // packages organized into statuses for convenience (like with coverage) + overall: emptyOverall(), // overall status info, compiled from server's response + assertions: emptyAssertions(), // lists of assertions, compiled from server's response + failedBuilds: [], // list of packages that failed to build + timestamp: moment(), // the timestamp of this "freeze-state" + id: convey.frameCounter++, // unique ID for this frame + coverDelta: 0 // difference in total coverage from the last frame to this one + }; +} + +function emptyOverall() +{ + return { + status: {}, + duration: 0, + assertions: 0, + passed: 0, + panics: 0, + failures: 0, + skipped: 0, + failedBuilds: 0, + coverage: 0 + }; +} + +function emptyAssertions() +{ + return { + passed: [], + failed: [], + panicked: [], + skipped: [] + }; +} + +function makeContext(obj) +{ + obj._passed = 0; + obj._failed = 0; + obj._panicked = 0; + obj._skipped = 0; + obj._status = ''; + return obj; +} + +function current() +{ + return convey.history[convey.history.length - 1]; +} + +function assignStatus(obj) +{ + if (obj._skipped) + obj._status = 'skip'; + else if (obj.Outcome === "ignored") + obj._status = convey.statuses.ignored; + else if (obj._panicked) + obj._status = convey.statuses.panic; + else if (obj._failed || obj.Outcome === "failed") + obj._status = convey.statuses.fail; + else + obj._status = convey.statuses.pass; +} + +function showCoverDelta(delta) +{ + if (delta > 0) + return "+" + delta + "%"; + else if (delta === 0) + return "±" + delta + "%"; + else + return delta + "%"; +} + +function customMarkupPipes() +{ + // MARKUP.JS custom pipes + Mark.pipes.relativePath = function(str) + { + basePath = new RegExp($('#path').val()+'[\\/]', 'gi'); + return str.replace(basePath, ''); + }; + Mark.pipes.htmlSafe = function(str) + { + return str.replace(/</g, "<").replace(/>/g, ">"); + }; + Mark.pipes.ansiColours = ansispan; + Mark.pipes.boldPkgName = function(str) + { + var pkg = splitPathName(str); + pkg.parts[0] = '<span class="not-pkg-name">' + pkg.parts[0]; + pkg.parts[pkg.parts.length - 1] = "</span><b>" + pkg.parts[pkg.parts.length - 1] + "</b>"; + return pkg.parts.join(pkg.delim); + }; + Mark.pipes.needsDiff = function(test) + { + return !!test.Failure && (test.Expected !== "" || test.Actual !== ""); + }; + Mark.pipes.coveragePct = function(str) + { + // Expected input: 75% to be represented as: "75.0" + var num = parseInt(str); // we only need int precision + if (num < 0) + return "0"; + else if (num <= 5) + return "5px"; // Still shows low coverage + else if (num > 100) + str = "100"; + return str; + }; + Mark.pipes.coverageDisplay = function(str) + { + var num = parseFloat(str); + return num < 0 ? "" : num + "% coverage"; + }; + Mark.pipes.coverageReportName = function(str) + { + return str.replace(/\//g, "-"); + }; +} + +function suppress(event) +{ + if (!event) + return false; + if (event.preventDefault) + event.preventDefault(); + if (event.stopPropagation) + event.stopPropagation(); + event.cancelBubble = true; + return false; +} |