spectacular
Version:
Advanced BDD framework for CoffeeScript and JavaScript
369 lines (297 loc) • 10.7 kB
text/coffeescript
## Expectation
class spectacular.Expectation
constructor: (,
,
matcher,
=false,
,
ownDescription) ->
= Object.create matcher
= if ownDescription?
"#{ownDescription} "
else
''
match: =>
promise = new spectacular.Promise
timeout = null
spectacular.Promise.unit()
.then =>
timeout = setTimeout =>
= false
= new Error 'matcher timed out'
if
= .messageForShouldnt
else
= .messageForShould
= "should#{if @not then ' not' else ''} #{@matcher.description}"
promise.resolve
, .timeout or 5000
.match()
.then () =>
clearTimeout timeout
= not if
promise.resolve
.fail () =>
clearTimeout timeout
= false
.message = .message unless .message?
.description = '' unless .description?
promise.reject
promise
createMessage: =>
if
= .messageForShouldnt
else
= .messageForShould
if not and not ?
if .stack?
stack = .stack.split('\n')
specIndex = spectacular.env.runner.findSpecFileInStack stack
.stack = stack[specIndex..].join('\n') if specIndex isnt -1
=
= "#{@ownDescription}should#{if @not then ' not' else ''} #{@matcher.description}"
= "#{@example.description} #{@ownDescription}should#{if @not then ' not' else ''} #{@matcher.description}"
## ExampleResult
class spectacular.ExampleResult
spectacular.HasCollection('expectations', 'expectation')
constructor: (, ) ->
= []
= spectacular.Promise.unit()
hasFailures: -> .some (e) -> not e.success
_addExpectation = ExampleResult::addExpectation
addExpectation: (expectation) ->
successHandler = => expectation.match().fail (e) =>
.error e
= .then successHandler
_addExpectation.call this, expectation
## Example
class spectacular.Example
spectacular.HasAncestors
spectacular.Describable
spectacular.AncestorsProperties
'subjectBlock'
'cascading'
'inclusive'
'exclusive'
'beforeHooks'
'afterHooks'
'dependencies'
constructor: (, ='', ) ->
= true if is ''
= []
= []
= []
= null
= false
= false
'subject', -> ||= ?.call()
'failed', -> ?.state in ['skipped', 'errored', 'failure']
'succeed', -> ?.state is 'success'
'reason', -> or ?.reason
'duration', ->
if ? and ?
.getTime() - .getTime()
else
0
'fullDescription', ->
expectationsDescriptions = .expectations.map (e) -> e.description
expectationsDescriptions = utils.literalEnumeration expectationsDescriptions
"#{@description} #{expectationsDescriptions}"
'ownDescriptionWithExpectations', ->
return .state if .expectations.length is 0 and is ''
expectationsDescriptions = .expectations.map (e) -> e.description
expectationsDescriptions = utils.literalEnumeration expectationsDescriptions
"#{@ownDescription} #{expectationsDescriptions}"
'identifiedAncestors', (e) -> e.options.id?
pending: ->
if ?.pending
.state = 'pending'
.resolve()
skip: ->
if ?.pending
.state = 'skipped'
error = new Error 'Skipped'
if error.stack?
stack = error.stack.split('\n')
specIndex = spectacular.env.runner.findSpecFileInStack stack
error.stack = stack[specIndex..].join('\n') if specIndex isnt -1
.reject error
resolve: =>
if ?.pending
if .hasFailures()
.state = 'failure'
.reject()
else if .expectationsCount() is 0
.state = 'pending'
.resolve()
else
.state = 'success'
.resolve()
reject: (reason) =>
if ?.pending
.state = 'failure'
.reject reason
error: (reason) ->
if ?.pending
.state = 'errored'
.reject reason
createContext: ->
context = {}
Object.defineProperty context, 'subject', get: =>
context
hasDependencies: -> .length > 0
dependenciesMet: -> true
run: ->
= new Date()
=
= new spectacular.Promise
= new spectacular.ExampleResult this
unless
return
afterPromise = new spectacular.Promise
(err) =>
= new Date()
return err if err?
.then => (err) =>
= new Date()
return err, afterPromise if err?
afterPromise.resolve()
.fail (reason) => (err) =>
= new Date()
return err, afterPromise if err?
afterPromise.reject reason
afterPromise
handleAfterError: (error, promise) ->
.state = 'errored'
= error
promise.reject error
runBefore: (callback) ->
befores = .concat()
befores, callback
runAfter: (callback) ->
afters = .concat()
afters, callback
runHooks: (hooks, callback) ->
next = (err) =>
return callback err if err?
if hooks.length is 0
callback()
else
hooks.shift(), next
next()
executeHook: (hook, next) ->
try
if hook
async = new spectacular.AsyncPromise
async.then => next()
async.fail (reason) => next reason
async.run()
hook.call(, async)
else
hook.call()
next()
catch e
next(e)
executeBlock: ->
return unless ?
try
if
async = new spectacular.AsyncPromise
async.then ,
async.fail (reason) => reason
async.run()
.call(, async)
else
.call()
catch e
e
executeExpectations: =>
.promise.then ,
toString: -> "[Example(#{@description})]"
acceptAsync: (func) -> func.signature().length is 1
## ExampleGroup
class spectacular.ExampleGroup extends spectacular.Example
spectacular.HasCollection('children', 'child')
spectacular.HasNestedCollection('descendants', through: 'children')
: (e) ->
e.children? and not e.inclusive
: (e) ->
not e.children? and not e.inclusive
: (e) ->
ExampleGroup.filterExamples(e) and e.exclusive
'exampleGroups', ExampleGroup.filterGroups
'examples', ExampleGroup.filterExamples
'exclusiveExamples', ExampleGroup.filterExclusiveExamples
'allExamples', ExampleGroup.filterExamples
'allExclusiveExamples', ExampleGroup.filterExclusiveExamples
'allExamplesWithDependecies', (e) ->
ExampleGroup.filterExamples(e) and e.hasDependencies()
'allExclusiveExamplesWithDependecies', (e) ->
ExampleGroup.filterExclusiveExamples(e) and e.hasDependencies()
'identifiedExamples', (e) -> e.options?.id?
'identifiedExamplesMap', ->
res = {}
res[e.options.id] = e for e in
res
'failed', -> .some (e) -> e.failed
'succeed', -> .every (e) -> e.succeed
'examplesSuceed', -> .every (e) -> e.succeed
= {
'::': 'instanceMemberAsSubject'
'#' : 'instanceMemberAsSubject'
'.' : 'classMemberAsSubject'
}
constructor: (block, desc, , ={}) ->
subject = null
switch typeof desc
when 'string'
tokenNotFound = true
for token, method of ExampleGroup.SUBJECTS_MAP
if desc.indexOf(token) is 0
@[method].call this, desc, token
tokenNotFound = false
if tokenNotFound and not ? or .description is ''
= true
else
= true
subject = desc
= => subject
desc = subject?.name or subject?._name or subject?.toString() or ''
super block, desc,
= []
run: ->
classMemberAsSubject: (desc, token) ->
= true
owner =
subject = owner?[desc.replace '.', '']
if typeof subject is 'function'
original = subject
subject = -> original.apply owner, arguments
= -> subject
instanceMemberAsSubject: (desc, token) ->
= true
type =
= ->
subject = null
if type
owner = build type, or []
subject = owner[desc.replace token, '']
= owner
if typeof subject is 'function'
original = subject
subject = -> original.apply owner, arguments
subject
executeBlock: ->
return it(-> pending()) unless ?
.call(this)
it(-> pending()) if .length is 0
hasExclusiveExamples: -> .length > 0
hasExamplesWithDependencies: ->
.some (e) -> e.hasDependencies()
hasExclusiveExamplesWithDependencies: ->
.some (e) -> e.hasDependencies()
toString: -> "[ExampleGroup(#{@description})]"