# Capture statements that were logged to the console # during spec execution. # # To do so it substitues it's own functions for the console. # extendObject = (a, b)-> for key,value of b a[key] = value if b.hasOwnProperty(key) return a isObject =(obj)-> type = typeof obj; return type == 'function' || type == 'object' && !!obj; isFunction = (obj)-> return typeof obj == 'function' || false; class ConsoleCapture # Instead of attempting to de-activate the console dot reporter in hacky ways, # just ignore it's output @DOT_REPORTER_MATCH = /\[\d+m[F.]..0m/ @levels: ['log','info','warn','error','debug' ] @original = console @original_levels = {} @original_levels[level] = console[level] for level in ConsoleCapture.levels constructor:-> @original = {} @captured = [] this._reassign_level( level ) for level in ConsoleCapture.levels revert: -> for level in ConsoleCapture.levels ConsoleCapture.original[level] = ConsoleCapture.original_levels[level] _reassign_level: ( level )-> my = this console[level] = -> args = Array.prototype.slice.call(arguments, 0) return if args[0] && args[0].toString && args[0].toString().match( ConsoleCapture.DOT_REPORTER_MATCH ) my.captured.push( [ level ].concat( args ) ) ConsoleCapture.original_levels[ level ].apply( ConsoleCapture.original, arguments ) # Implements a Jasmine reporter class GuardReporter @STACK_MATCHER=new RegExp("__spec__\/(.*):([0-9]+)","g") jasmineStarted: -> @console = new ConsoleCapture(); @startedAt = Date.now() @currentSuite = { suites: [] } @stack = [ @currentSuite ] suiteStarted: (suite)-> suite = extendObject({ specs: [], suites: [] }, suite ) @currentSuite.suites.push( suite ) @currentSuite = suite @stack.push(suite) suiteDone: (Suite)-> @stack.pop() @currentSuite = @stack[@stack.length-1] jasmineDone: -> @resultComplete = true specDone: (spec)-> @resultReceived = true spec = extendObject({ logs: @console.captured, errors: [] }, spec ) for failure in spec.failedExpectations||[] error = extendObject({trace:[]}, failure ) while match = GuardReporter.STACK_MATCHER.exec( failure.stack ) error.trace.push({ file: match[1], line: parseInt(match[2]) }) delete error.stack this.stringifyExpection(error) spec.errors.push( error ) delete spec.failedExpectations this.stringifyExpection(success) for success in spec.passedExpectations||[] @currentSuite.specs.push( spec ) this.resetConsoleLog() spec # if the expected object is very large, we don't want to # include it in the JSON reply. For instance a DOM Element # will actually end up including the entire page (including script source) stringifyExpection: (expected)-> for key in ['actual','expected'] if isFunction(expected[key]) expected[key] = expected[key].name || "function" else if isObject(expected[key]) expected[key] = expected[key].toString() resetConsoleLog: -> @console.revert() @console = new ConsoleCapture eachSuite: (suite)-> suites = [].concat( suite.suites ) for suite in suite.suites suites = suites.concat( this.eachSuite(suite) ) suites results: -> stats = { time : ( Date.now() - @startedAt ) / 1000 specs : 0 failed : 0 pending : 0 disabled : 0 } for suite in this.eachSuite(@stack[0]) stats.specs += suite.specs.length for spec in suite.specs stats[spec.status] += 1 unless undefined == stats[spec.status] { jasmine_version: jasmine?.version stats: stats suites: @stack[0].suites } if typeof module isnt 'undefined' and module.exports module.exports = GuardReporter else window.GuardReporter = GuardReporter