howl.signal

.all contains all registered signals

signal.register 'foo', description: 'bar'
assert.same description: 'bar', signal.all.foo

.register(name, options)

raises an error if mandatory fields are missing

assert.raises 'description', -> signal.register 'foo'

.unregister(name)

unregisters the specified signal

signal.register 'frob', description: 'bar'
signal.unregister 'frob'
assert.is_nil signal.all.frob

(trying to use a non-registered signal)

emit raises an error

assert.raises 'none', -> signal.emit 'none'

connect raises an error

assert.raises 'none', -> signal.connect 'none', -> true

(with a registered signal)

before_each -> signal.register 'foo', description: 'bar'
after_each -> signal.unregister 'foo'

allows name based signals to be broadcasted to any number of handlers

handler1 = spy.new -> nil
handler2 = spy.new -> nil
signal.connect 'foo', handler1
signal.connect 'foo', handler2
signal.emit 'foo'
assert.spy(handler1).was_called!
assert.spy(handler2).was_called!

allows connecting handlers before existing handlers

value = nil
signal.connect 'foo', -> value = 'first'
signal.connect 'foo', (-> value = 'second'), 1
signal.emit 'foo'
assert.equal value, 'first'

allows disconnecting handlers

handler = spy.new -> true
signal.connect 'foo', handler
signal.disconnect 'foo', handler
signal.emit 'foo'
assert.spy(handler).was.not_called!

.emit

raises an error when called with more than two parameters

assert.raises 'parameter', -> signal.emit 'foo', {}, 2

raises an error when the second parameter is not a table

assert.raises 'table', -> signal.emit 'foo', 2

returns false if no handlers returned true

assert.is_false signal.emit 'foo'
signal.connect 'foo', -> 'this is fortunately not true'
assert.is_false signal.emit 'foo'

invokes all handlers in their own coroutines

coros = {}
coro_register = ->
  co, main = coroutine.running!
  coros[co] = true unless main

handler1 = spy.new coro_register
handler2 = spy.new coro_register
signal.connect 'foo', handler1
signal.connect 'foo', handler2
signal.emit 'foo'
assert.equal 2, #[v for _, v in pairs coros]

(when a handler returns .abort)

skips invoking subsequent handlers

handler2 = spy.new -> true
signal.connect 'foo', -> signal.abort
signal.connect 'foo', handler2
signal.emit 'foo'
assert.spy(handler2).was.not_called!

returns .abort

signal.connect 'foo', -> signal.abort
assert.equals signal.abort, signal.emit 'foo'

(when a handler raises an error)

logs an error message

signal.connect 'foo', -> error 'BOOM'
signal.emit 'foo'
assert.match log.last_error.message, 'BOOM'

continues processing subsequent handlers

handler2 = spy.new -> true
signal.connect 'foo', -> error 'BOOM'
signal.connect 'foo', handler2
signal.emit 'foo'
assert.spy(handler2).was_called!

(when a handler yields)

continues on invoking subsequent handlers

handler2 = spy.new -> true
signal.connect 'foo', -> coroutine.yield false
signal.connect 'foo', handler2
signal.emit 'foo'
assert.spy(handler2).was_called!

returns false

signal.connect 'foo', -> coroutine.yield true
assert.is_false signal.emit 'foo'