// ========================================================================== // Project: CoreTest Unit Testing Library // Copyright: ©2010 Apple Inc. All rights reserved. // License: Licened under MIT license (see license.js) // ========================================================================== /*globals Ct */ require('../core'); var utils = require('../utils'), $ = require('../private/jquery'); require('./default'); // add Ct.DefaultLogger var textDiv, textNode; // convert a string into HTML-escaped text. function _text(str) { if (!textNode) { textNode = document.createTextNode(''); } if (!textDiv) { textDiv = document.createElement('div'); textDiv.appendChild(textNode); } textNode.nodeValue = str; return textDiv.innerHTML.toString().replace(/\n/g, '
'); } var PlanEntry = utils.extend({ init: function(owner, planName) { this.owner = owner; this.name = planName; this.modules = {}; this.status = { passed: 0, failed: 0, errors: 0, warnings: 0 }; }, render: function(targetEl){ if (this.layer) { return false; } this.layer = $( '
  • '+ ''+_text(this.name)+''+ ''+ '
  • ' ); this.layer.appendTo(targetEl); for (var key in this.modules) { this.renderModule(this.modules[key]); } return true; }, module: function(moduleName){ if (this.modules[moduleName]) { return this.modules[moduleName]; } var module = new ModuleEntry(this, moduleName); this.modules[moduleName] = module; if (this.layer) { this.renderModule(module); } return module; }, incrementStatus: function(status) { if (this.status[status] !== undefined) { this.status[status]++; } this.owner.incrementStatus(status); }, renderModule: function(module) { return module.render(this.layer.children('ul')); } }); var ModuleEntry = utils.extend({ init: function(plan, moduleName) { this.plan = plan; this.name = moduleName; this.tests = {}; this.status = { passed: 0, failed: 0, errors: 0, warnings: 0 }; }, render: function(targetEl) { if (this.layer) { return false; } var didPass = ((this.status.errors + this.status.warnings + this.status.failed) === 0 && this.status.passed > 0) ? ' passed' : ''; this.layer = $( '
  • '+ ''+_text(this.name)+''+ ''+ '
  • ' ); this.layer.appendTo(targetEl); for (var key in this.tests) { this.renderTest(this.tests[key]); } return true; }, update: function(){ if (!this.layer) { return false; } var didPass = ((this.status.errors + this.status.warnings + this.status.failed) === 0 && this.status.passed > 0) ? ' passed' : ''; this.layer.attr('class', 'module'+didPass); return true; }, test: function(testName){ if (this.tests[testName]) { return this.tests[testName]; } var test = new TestEntry(this, testName); this.tests[testName] = test; this.plan.owner.status.tests++; if (this.layer) { this.renderTest(test); } return test; }, incrementStatus: function(status) { if (this.status[status] !== undefined) { this.status[status]++; } this.plan.incrementStatus(status); this.update(); }, renderTest: function(test) { return test.render(this.layer.children('ul')); } }); var TestEntry = utils.extend({ init: function(module, testName) { this.module = module; this.name = testName; this.assertions = []; this.status = { passed: 0, failed: 0, errors: 0, warnings: 0 }; }, render: function(targetEl) { if (this.layer) { return false; } var statsum = [], key; for(key in this.status) { if (this.status[key]>0) { statsum.push(_text(key)); } } this.layer = $( '
  • '+ ''+_text(this.name)+''+ ''+ ''+this.status.passed+''+ ''+this.status.warnings+''+ ''+this.status.failed+''+ ''+this.status.errors+''+ ''+ ''+ '
  • ' ); this.layer.appendTo(targetEl); for (var idx=0,len=this.assertions.length; idx < len; idx++) { this.renderAssertion(this.assertions[idx]); } return true; }, update: function(){ if (!this.layer) { return false; } var statsum = [], key; for(key in this.status) { if (this.status[key]>0) { statsum.push(_text(key)); } } this.layer.attr('class', 'test '+statsum.join(' ')); this.layer.find('.status .passed').html(this.status.passed); this.layer.find('.status .warnings').html(this.status.warnings); this.layer.find('.status .failed').html(this.status.failed); this.layer.find('.status .errors').html(this.status.errors); return true; }, add: function(status, message){ var assertion = new AssertionEntry(this, status, message); this.assertions.push(assertion); this.incrementStatus(status); if (this.layer) { this.renderAssertion(assertion); } return assertion; }, incrementStatus: function(status) { if (this.status[status] !== undefined) { this.status[status]++; } this.module.incrementStatus(status); this.update(); }, renderAssertion: function(assertion) { return assertion.render(this.layer.children('ul')); } }); var AssertionEntry = utils.extend({ init: function(test, status, message) { this.test = test; this.status = status; this.message = message; }, render: function(targetEl) { if (this.layer) { return false; } this.layer = $( '
  • '+ ''+_text(this.message)+''+ ''+_text(this.status)+''+ '
  • ' ); this.layer.appendTo(targetEl); return true; }, }); // default template used for display. Note we use classes here - not IDs. // This way multiple instances can be on the same page at once. var html = ['
    ', '
    UserAgent
    ', '
    ', '', 'Running...', '
    ', '', '
    '].join(''); /** BrowserLogger logs output to the HTML display in a browser. @extends Ct.DefaultLogger */ Ct.BrowserLogger = utils.extend(Ct.DefaultLogger, /** @scope Ct.DummyLogger.prototype */{ name: 'better-browser', init: function() { Ct.DefaultLogger.prototype.init.apply(this, arguments); this.plans = []; this.status = { passed: 0, failed: 0, errors: 0, warnings: 0, tests: 0, assertions: 0 }; }, setupDisplay: function() { if (this._displayWasSetup) { return; } this._displayWasSetup = true ; var layer, logger; layer = this.layer = $(html); $('body').append(layer); // write in the user agent layer.find('.useragent').text(navigator.userAgent); // listen to change event this.checkboxLayer = layer.find('.hide-passed input[type=checkbox]'); logger = this; this.checkboxLayer.change(function() { logger.hidePassedTestsDidChange(logger.checkboxLayer.attr('checked')); }); }, hidePassedTestsDidChange: function(aFlag) { this.setupDisplay(); if (aFlag) { this.layer.find('ul.detail').addClass('hide-passed'); } else { this.layer.find('ul.detail').removeClass('hide-passed'); } }, summarize: function(final){ var status = this.status, statusMsg = '', hasErrors, key; if (!final) { statusMsg += 'Running... '; } statusMsg += 'Completed '+status.assertions+' assertions in '+status.tests+' tests: '+ ''+status.passed+' passed' hasErrors = (status.failed + status.errors + status.warnings)>0; if (hasErrors) { for(key in status) { if (!status.hasOwnProperty(key) || (key==='passed')) continue; if ((key==='tests') || (key==='assertions')) continue; statusMsg += ''+status[key]+' '+key+''; } } this.layer.find('.final-status').html(statusMsg); if (final) { var checkbox = this.layer.find('.hide-passed input'); if (hasErrors) { checkbox.attr('disabled', false).attr('checked', true); this.hidePassedTestsDidChange(true); } else { checkbox.attr('disabled', true).attr('checked', false); this.hidePassedTestsDidChange(false); } } }, incrementStatus: function(status){ if (this.status[status] !== undefined) { this.status[status]++; } this.status.assertions++; this.summarize(); }, // .......................................................... // CORE API - Overide in your subclass // begin: function(planName) { this.setupDisplay(); var plan = new PlanEntry(this, planName); this.plans.push(plan); this.currentPlan = plan; plan.render(this.layer.find('ul.detail')); }, end: function(planName) { this.currentPlan = null; this.summarize(true); }, add: function(status, testInfo, message) { var plan = this.currentPlan; if (!plan) throw "add called outside of plan"; // NOTE: We only ever have one module right now. If we allow further nesting at some point we // need to change the way this behaves. var testName = testInfo.testName, moduleName = testInfo.moduleNames[0] || 'default'; plan.module(moduleName).test(testName).add(status, message); } }); // make available to those directly importing this module exports = __module.exports= Ct.BrowserLogger; exports.BrowserLogger = Ct.BrowserLogger;