UNPKG

spectacular

Version:

Advanced BDD framework for CoffeeScript and JavaScript

447 lines (349 loc) 12.2 kB
spectacular.helpers = new spectacular.GlobalizableObject spectacular.helper = (name, value) -> spectacular.helpers.set name, value class spectacular.Environment @include spectacular.Globalizable constructor: (@options) -> @globalizable = 'it xit describe xdescribe expect before after given subject its itsInstance itsReturn withParameters fail pending success skip should shouldnt dependsOn spyOn whenPass fixture except only sharedExample itBehavesLike fixturePath registerFixtureHandler'.split(/\s+/g) @rootExampleGroup = new spectacular.ExampleGroup null, '' @currentExampleGroup = @rootExampleGroup @currentExample = null @runner = new spectacular.Runner(@rootExampleGroup, @options, this) @sharedExamples = {} @createExampleAlias 'the' @createExampleAlias 'specify' @createExampleGroupAlias 'context' @createOuterExampleAlias 'xcontext', 'xdescribe' @createOuterExampleAlias 'withArguments', 'withParameters' @createOuterExampleAlias 'itShould', 'itBehavesLike' @registerFixtureHandler 'json', @handleJSONFixture @registerFixtureHandler 'html', @handleHTMLFixture @registerFixtureHandler 'dom', @handleDOMFixture run: => @runner.run() @_globalize = Environment::globalize @_unglobalize = Environment::unglobalize createGlobalizedMethod: (name, block) -> unless name in @globalizable @globalizable.push name @[name] = block @globalizeMember name, block if @globalized createExampleGroupAlias: (name) -> @createOuterExampleAlias name, 'describe' createExampleAlias: (name) -> @createOuterExampleAlias name, 'it' createInnerExampleAlias: (newName, oldName) -> @createGlobalizedMethod newName, -> @notOutsideIt newName @[oldName].apply this, arguments createOuterExampleAlias: (newName, oldName) -> @createGlobalizedMethod newName, -> @notInsideIt newName @[oldName].apply this, arguments globalize: -> Environment._globalize.call(this) spectacular.factories.globalize() spectacular.matchers.globalize() spectacular.helpers.globalize() @globalizeObjectExtensions() unglobalize: -> Environment._unglobalize.call(this) spectacular.factories.unglobalize() spectacular.matchers.unglobalize() spectacular.helpers.unglobalize() @unglobalizeObjectExtensions() unglobalizeObjectExtensions: -> delete Object::should delete Object::shouldnt delete spectacular.global.should delete spectacular.global.shouldnt globalizeObjectExtensions: -> env = this Object.defineProperty Object.prototype, 'should', { writable: true configurable: true enumerable: false value: (matcher, neg=false) -> env.notOutsideIt 'should' return unless matcher? env.currentExample.result.addExpectation( new spectacular.Expectation( env.currentExample, @valueOf(), matcher, neg, new Error ) ) } Object.defineProperty Object.prototype, 'shouldnt', { writable: true configurable: true enumerable: false value: (matcher) -> env.notOutsideIt 'should' @should matcher, true } clone: -> optionsCopy = {} optionsCopy[k] = v for k,v of @options new spectacular.Environment optionsCopy notInsideIt: (method) -> if @currentExample? throw new Error "#{method} called inside a it block" notOutsideIt: (method) -> unless @currentExample? throw new Error "#{method} called outside a it block" notWihoutMatcher: (method) -> throw new Error "#{method} called without a matcher" fail: -> error = new Error 'Failed' if error.stack? stack = error.stack.split('\n') specIndex = spectacular.env.runner.findSpecFileInStack stack error.stack = stack[specIndex..].join('\n') if specIndex isnt -1 @currentExample.reject error pending: -> @currentExample.pending() skip: -> @currentExample.skip() success: -> it: (msgOrBlock, block) -> @notInsideIt 'it' [msgOrBlock, block] = ['', msgOrBlock] if typeof msgOrBlock is 'function' example = new spectacular.Example block, msgOrBlock, @currentExampleGroup @currentExampleGroup.addChild example example xit: (msgOrBlock, block) -> @notInsideIt 'xit' if typeof msgOrBlock is 'string' @it msgOrBlock, -> pending() else @it -> pending() before: (block) -> @notInsideIt 'before' @currentExampleGroup.ownBeforeHooks.push block after: (block) -> @notInsideIt 'after' @currentExampleGroup.ownAfterHooks.push block its: (property, block) -> @notInsideIt 'its' parentSubjectBlock = @currentExampleGroup.subjectBlock @emptyContext => @subject property, -> parentSubjectBlock?.call(this)[property] @specify "the #{property} property", block itsInstance: (property, options, block) -> @notInsideIt 'itsInstance' if typeof property is 'function' [property, options, block] = [block, {}, property] else if typeof property is 'object' [property, options, block] = [null, property, options] else if typeof options is 'function' [options, block] = [{}, options] parentSubjectBlock = @currentExampleGroup.subjectBlock unless parentSubjectBlock? throw new Error 'itsInstance called in context without a previous subject' @emptyContext => @subject 'instance', -> params = if options.with? if typeof options.with is 'function' options.with.call(this) else options.with else @parameters or [] build parentSubjectBlock?.call(this), params if property? @its property, block else @specify 'the instance', block itsReturn: (options, block) -> @notInsideIt 'itsReturn' [block, options] = [options, {}] if typeof options is 'function' parentSubjectBlock = @currentExampleGroup.subjectBlock unless parentSubjectBlock? throw new Error 'itsReturn called in context without a previous subject' @emptyContext => @subject 'returnedValue', -> context = if options.inContext? or options.in_context? ctx = options.inContext or options.in_context if typeof ctx is 'function' ctx.call(this) else ctx else this params = if options.with? if typeof options.with is 'function' options.with.call(this) else options.with else @parameters or [] parentSubjectBlock?.call(this).apply(context, params) @specify 'the returned value', block subject: (name, block) -> @notInsideIt 'subject' [name, block] = [block, name] if typeof name is 'function' subjectBlock = -> @["__#{name or 'subject'}"] ||= block.call this @currentExampleGroup.ownSubjectBlock = subjectBlock @given name, subjectBlock if name? given: (name, block) -> @notInsideIt 'given' @before -> Object.defineProperty this, name, { configurable: true enumerable: true get: -> @["__#{name}"] ||= block.call(this) } describe: (subject, options, block) -> [options, block] = [block, options] if typeof options is 'function' @notInsideIt 'describe' oldGroup = @currentExampleGroup @currentExampleGroup = currentGroup = new spectacular.ExampleGroup( block, subject, oldGroup, options ) oldGroup.addChild @currentExampleGroup try @currentExampleGroup.executeBlock() catch error finally @currentExampleGroup = oldGroup throw error if error? currentGroup xdescribe: (subject, options, block) -> @notInsideIt 'xdescribe' [options, block] = [block, options] if typeof options is 'function' describe subject, -> it -> pending() emptyContext: (block) -> @context '', block withParameters: (args...) -> @notInsideIt 'withParameters' @given 'parameters', -> if typeof args[0] is 'function' args[0].call(this) else args dependsOn: (spec) -> @notInsideIt 'dependsOn' @currentExampleGroup.ownDependencies.push spec whenPass: (block) -> @notInsideIt 'whenPass' previousContext = @currentExampleGroup @emptyContext => @currentExampleGroup.ownCascading = previousContext block() spyOn: (obj, method) -> @notOutsideIt 'spyOn' oldMethod = obj[method] context = @currentExample.context spy = (args...) -> spy.argsForCall.push args if spy.mock? spy.mock.apply(obj, args) else oldMethod.apply(obj, args) spy.spied = oldMethod spy.argsForCall = [] spy.andCallFake = (@mock) -> this spy.andReturns = (value) -> spy.andCallFake -> value spy.andCallThrough = (block) -> @mock = -> block.call context, oldMethod.apply this, arguments this utils.snakify spy @currentExample.ownAfterHooks.push -> obj[method] = oldMethod obj[method] = spy spy should: (matcher, neg=false) -> @notOutsideIt 'should' @notWihoutMatcher 'should' unless matcher? @currentExample.result.addExpectation( new spectacular.Expectation( @currentExample, @currentExample.subject, matcher, neg, new Error ) ) shouldnt: (matcher) -> @should matcher, true expect: (desc, value) -> value = desc if not value? and arguments.length < 2 @notOutsideIt 'expect' self = this err = new Error to: (matcher) -> self.notWihoutMatcher 'expect(...).to' unless matcher? self.currentExample.result.addExpectation( new spectacular.Expectation( self.currentExample, value, matcher, false, err, desc ) ) not: to: (matcher) -> self.notWihoutMatcher 'expect(...).not.to' unless matcher? self.currentExample.result.addExpectation( new spectacular.Expectation( self.currentExample, value, matcher, true, err, desc ) ) except: (example) -> example.inclusive = true only: (example) -> example.exclusive = true sharedExample: (name, block) -> if name of @sharedExamples throw new Error "shared example '#{name}' already registered" @sharedExamples[name] = block itBehavesLike: (name, options={}) -> unless name of @sharedExamples throw new Error "shared example '#{name}' not found" @sharedExamples[name].call null, options fixturePath: (file) -> "#{@options.fixturesRoot}/#{file}" fixture: (file, options={}) -> @notInsideIt 'fixture' name = options.as or 'fixture' env = this envOptions = @options ext = file.split('.')[-1..][0] @before (async) -> p = env.fixturePath file envOptions.loadFile(p) .then (fileContent) => env.handleFixture(ext, fileContent).then (result) => @[name] = result async.resolve() .fail (reason) -> async.reject reason registerFixtureHandler: (ext, proc) -> @fixtureHandlers ||= {} @fixtureHandlers[ext] = proc handleFixture: (ext, content) -> if ext of @fixtureHandlers @fixtureHandlers[ext].call this, content else spectacular.Promise.unit(content) handleJSONFixture: (content) -> spectacular.Promise.unit JSON.parse content handleDOMFixture: (content) -> spectacular.Promise.unit new spectacular.dom.DOMExpression content handleHTMLFixture: (content) -> parent = document.querySelector '#fixtures' unless parent? body = document.querySelector('body') fixtures = document.createElement('div') fixtures.id = 'fixtures' body.appendChild fixtures parent = document.querySelector '#fixtures' parent.innerHTML = content @currentExample.ownAfterHooks.push -> parent.innerHTML = '' spectacular.Promise.unit parent.childNodes toString: -> '[spectacular Environment]'