Skip to content

Instantly share code, notes, and snippets.

@dellermann
Last active August 29, 2015 14:06
Show Gist options
  • Save dellermann/df2710ab03c9698c561c to your computer and use it in GitHub Desktop.
Save dellermann/df2710ab03c9698c561c to your computer and use it in GitHub Desktop.
Deferreds in test cases. This gist shows how to use `Deferred` or `Promise` in QUnit test cases where a chain of actions and assertions are needed.
# This function returns a `Promise` that is resolved when a particular
# event on the given element occurs.
newEventPromise = (elem, event, action) ->
$ = jQuery
that = this
# First, we create a new `Deferred` object.
deferred = $.Deferred()
# Then, we register a handle for the given event type (e. g. "click").
# If the event is triggered the `Deferred` object is resolved.
$(elem).one event, (ev) -> deferred.resolve [ev]
# If there is an action it is called here. Note the order of registering the
# event listener and calling the action. If it was swapped the action would
# be called first, and the event would be triggered before registering the
# `Deferred` with the `resolve` method, effectively causing the event comes
# to nothing.
action.call that if action?
# Now we obtain and return a `Promise` object from the `Deferred` object to
# prevent resolving or rejecting in the caller code.
deferred.promise()
# A sample use case may be defined as follows. It creates a chain of functions
# that are called in a particular order and reflect the actions and asserts.
$btn = $('#btn1')
# First, we obtain a `Promise` that is resolved if the button is clicked. After
# that, a click on the button is simulated.
newEventPromise($btn, 'click', -> $btn.click())
.then( ->
# This function is called after the button has been clicked for the
# first time. Here we can assert something, and, finally create a
# new `Promise` and click the button again.
console.log 'Clicked 1st time'
newEventPromise $btn, 'click', -> $btn.click()
)
.then( ->
# As above, this function is called when the button has been clicked
# for the second time. Also, we click the button again.
console.log 'Clicked 2nd time'
newEventPromise $btn, 'click', -> $btn.click()
)
.then( ->
# Same as above...
console.log 'Clicked 3rd time'
newEventPromise $btn, 'click', -> $btn.click()
)
# Finally, the button will be click no more and there is no event
# listener any longer.
.done -> console.log 'Clicked last time'
# First, I define a class that allows to use a Promise that is resolved when a
# particular trigger is called.
class TriggerChain
constructor: ->
# The triggerFunc variable contains a function that is called when
# triggered. Initially, we set it to `null` because it isn't initialized
# yet.
@triggerFunc = null
newPromise: (action) ->
$ = jQuery
that = this
# Here, we create a new `Deferred` object and register the trigger function.
# When it is called it resolves the `Deferred` object causing the chain to
# be executed.
deferred = $.Deferred()
@triggerFunc = -> deferred.resolveWith that, $.makeArray arguments
# Here we call an action function, if any. Note the order of defining the
# trigger function and calling the action. If it was swapped the action
# would be called first, and the trigger would be called before registering
# the `Deferred` with the `resolveWith` method, effectively causing the
# trigger comes to nothing.
action.call that if action?
# Now we obtain and return a `Promise` object from the `Deferred` object to
# prevent resolving or rejecting in the caller code.
deferred.promise()
trigger: ->
# Here, we call the trigger function if it is defined.
f = @triggerFunc
f.apply this, arguments if f
# A sample use case may be defined as follows. It creates a document list
# widget on element `#dl1` within a QUnit test case. The aspiration is to
# have a chain of functions that reflect the actions and asserts.
chain = new TriggerChain()
$('#dl1').documentlist
init: ->
# Initially, the document list loaded and the content of path `/` is
# displayed.
console.log 'should be in /'
$this = $(this)
# Now I want to define a chain of actions and asserts using `Promise`
# objects. First, we create a `Promise` and click on second folder
# in the list.
chain.newPromise( -> $this.find('> ul > li:nth-child(2)').click())
.then( (path) ->
# Here, the path has changed, we are in `/foo`, and the content
# of this folder has been loaded successfully.
console.log "should be in /foo :: /#{path}"
# TODO asserts here...
# We create a new promise and, as an action, we click the second link
# (the first folder, because the first link is a back link to `/`).
@newPromise -> $this.find('> ul > li:nth-child(2)').click()
)
.then( (path) ->
# The same case as in the `then` step above, except now we are in path
# `/foo/wheezy` and click the first link (the back button).
console.log "should be in /foo/wheezy :: /#{path}"
# TODO asserts here...
@newPromise -> $this.find('> ul > li:first-child').click()
)
.then( (path) ->
# Now, we are in path `/foo` again and click the back button.
console.log "should be in /foo :: /#{path}"
# TODO asserts here...
@newPromise -> $this.find('> ul > li:first-child').click()
)
.done( (path) ->
# Finally, we are in path `/` again where our journey ends.
console.log "should be in / :: /#{path}"
# TODO asserts here...
)
.fail( ->
# This function is called if anything in the previous steps went
# wrong.
console.log 'Anything went wrong...'
)
# This triggers if the path has changed and the content of the current
# folder has been loaded successfully and displayed. Each time the
# trigger is called the next step in the chain above is executed.
pathChanged: (path) -> chain.trigger path
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment