Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Adapted EventListenerIntrospectionTrait to v3 API #20

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ pages:
site_name: zend-eventmanager
site_description: 'zend-eventmanager: Powerful event bus system for Zend Framework'
repo_url: 'https://github.com/zendframework/zend-eventmanager'
copyright: 'Copyright (c) 2015 <a href="http://www.zend.com/">Zend Technologies USA Inc.</a>'
copyright: 'Copyright (c) 2016 <a href="http://www.zend.com/">Zend Technologies USA Inc.</a>'
146 changes: 146 additions & 0 deletions src/Test/EventListenerIntrospectionTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copyright date is wrong.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update on merge; thanks for the heads-up!

* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/

namespace Zend\EventManager\Test;

use PHPUnit_Framework_Assert as Assert;
use ReflectionProperty;
use Zend\EventManager\EventManager;

/**
* Trait providing utility methods and assertions for use in PHPUnit test cases.
*
* This trait may be composed into a test case, and provides:
*
* - methods for introspecting events and listeners
* - methods for asserting listeners are attached at a specific priority
*
* Some functionality in this trait duplicates functionality present in the
* version 2 EventManagerInterface and/or EventManager implementation, but
* abstracts that functionality for use in v3.
*/
trait EventListenerIntrospectionTrait
{
/**
* Retrieve a list of event names from an event manager.
*
* @param EventManager $events
* @return string[]
*/
private function getEventsFromEventManager(EventManager $events)
{
$r = new ReflectionProperty($events, 'events');
$r->setAccessible(true);
$listeners = $r->getValue($events);
return array_keys($listeners);
}

/**
* Retrieve an interable list of listeners for an event.
*
* Given an event and an event manager, returns an iterator with the
* listeners for that event, in priority order.
*
* If $withPriority is true, the key values will be the priority at which
* the given listener is attached.
*
* Do not pass $withPriority if you want to cast the iterator to an array,
* as many listeners will likely have the same priority, and thus casting
* will collapse to the last added.
*
* @param string $event
* @param EventManager $events
* @param bool $withPriority
* @return \Traversable
*/
private function getListenersForEvent($event, EventManager $events, $withPriority = false)
{
$r = new ReflectionProperty($events, 'events');
$r->setAccessible(true);
$listeners = $r->getValue($events);

if (! isset($listeners[$event])) {
return $this->traverseListeners([]);
}

return $this->traverseListeners($listeners[$event], $withPriority);
}

/**
* Assert that a given listener exists at the specified priority.
*
* @param callable $expectedListener
* @param int $expectedPriority
* @param string $event
* @param EventManager $events
* @param string $message Failure message to use, if any.
*/
private function assertListenerAtPriority(
callable $expectedListener,
$expectedPriority,
$event,
EventManager $events,
$message = ''
) {
$message = $message ?: sprintf(
'Listener not found for event "%s" and priority %d',
$event,
$expectedPriority
);
$listeners = $this->getListenersForEvent($event, $events, true);
$found = false;
foreach ($listeners as $priority => $listener) {
if ($listener === $expectedListener
&& $priority === $expectedPriority
) {
$found = true;
break;
}
}
Assert::assertTrue($found, $message);
}

/**
* Returns an indexed array of listeners for an event.
*
* Returns an indexed array of listeners for an event, in priority order.
* Priority values will not be included; use this only for testing if
* specific listeners are present, or for a count of listeners.
*
* @param string $event
* @param EventManager $events
* @return callable[]
*/
private function getArrayOfListenersForEvent($event, EventManager $events)
{
return iterator_to_array($this->getListenersForEvent($event, $events));
}

/**
* Generator for traversing listeners in priority order.
*
* @param array $listeners
* @param bool $withPriority When true, yields priority as key.
*/
public function traverseListeners(array $queue, $withPriority = false)
{
krsort($queue, SORT_NUMERIC);

foreach ($queue as $priority => $listeners) {
$priority = (int) $priority;
foreach ($listeners as $listener) {
if ($withPriority) {
yield $priority => $listener;
} else {
yield $listener;
}
}
}
}
}
199 changes: 199 additions & 0 deletions test/Test/EventListenerIntrospectionTraitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zend-eventmanager for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-eventmanager/blob/master/LICENSE.md
*/

namespace ZendTest\EventManager\Test;

use PHPUnit_Framework_ExpectationFailedException as ExpectationFailedException;
use PHPUnit_Framework_TestCase as TestCase;
use Traversable;
use Zend\EventManager\EventManager;
use Zend\EventManager\Test\EventListenerIntrospectionTrait;

class EventListenerIntrospectionTraitTest extends TestCase
{
use EventListenerIntrospectionTrait;

public function setUp()
{
$this->events = new EventManager();
}

public function testGetEventsFromEventManagerReturnsEventList()
{
// @codingStandardsIgnoreStart
$this->events->attach('foo', function ($e) {});
$this->events->attach('bar', function ($e) {});
$this->events->attach('baz', function ($e) {});
// @codingStandardsIgnoreEnd

$this->assertEquals(['foo', 'bar', 'baz'], $this->getEventsFromEventManager($this->events));
}

public function testGetListenersForEventReturnsIteratorOfListenersForEventInPriorityOrder()
{
// @codingStandardsIgnoreStart
$callback1 = function ($e) {};
$callback2 = function ($e) {};
$callback3 = function ($e) {};
$callback4 = function ($e) {};
$callback5 = function ($e) {};
// @codingStandardsIgnoreEnd

$this->events->attach('foo', $callback5, 1);
$this->events->attach('foo', $callback1, 2);
$this->events->attach('foo', $callback4, 3);
$this->events->attach('foo', $callback3, 4);
$this->events->attach('foo', $callback2, 5);

$listeners = $this->getListenersForEvent('foo', $this->events);
$this->assertInstanceOf(Traversable::class, $listeners);
$listeners = iterator_to_array($listeners);

$this->assertEquals([
$callback5,
$callback1,
$callback4,
$callback3,
$callback2,
], $listeners);
}

public function testGetListenersForEventReturnsIteratorOfListenersInAttachmentOrderWhenSamePriority()
{
// @codingStandardsIgnoreStart
$callback1 = function ($e) {};
$callback2 = function ($e) {};
$callback3 = function ($e) {};
$callback4 = function ($e) {};
$callback5 = function ($e) {};
// @codingStandardsIgnoreEnd

$this->events->attach('foo', $callback5);
$this->events->attach('foo', $callback1);
$this->events->attach('foo', $callback4);
$this->events->attach('foo', $callback3);
$this->events->attach('foo', $callback2);

$listeners = $this->getListenersForEvent('foo', $this->events);
$this->assertInstanceOf(Traversable::class, $listeners);
$listeners = iterator_to_array($listeners);

$this->assertEquals([
$callback5,
$callback1,
$callback4,
$callback3,
$callback2,
], $listeners);
}

public function testGetListenersForEventCanReturnPriorityKeysWhenRequested()
{
// @codingStandardsIgnoreStart
$callback1 = function ($e) {};
$callback2 = function ($e) {};
$callback3 = function ($e) {};
$callback4 = function ($e) {};
$callback5 = function ($e) {};
// @codingStandardsIgnoreEnd

$this->events->attach('foo', $callback5, 1);
$this->events->attach('foo', $callback1, 2);
$this->events->attach('foo', $callback4, 3);
$this->events->attach('foo', $callback3, 4);
$this->events->attach('foo', $callback2, 5);

$listeners = $this->getListenersForEvent('foo', $this->events, true);
$this->assertInstanceOf(Traversable::class, $listeners);
$listeners = iterator_to_array($listeners);

$this->assertEquals([
1 => $callback5,
2 => $callback1,
3 => $callback4,
4 => $callback3,
5 => $callback2,
], $listeners);
}

public function testGetArrayOfListenersForEventReturnsArrayOfListenersInPriorityOrder()
{
// @codingStandardsIgnoreStart
$callback1 = function ($e) {};
$callback2 = function ($e) {};
$callback3 = function ($e) {};
$callback4 = function ($e) {};
$callback5 = function ($e) {};
// @codingStandardsIgnoreEnd

$this->events->attach('foo', $callback5, 1);
$this->events->attach('foo', $callback1, 1);
$this->events->attach('foo', $callback4, 3);
$this->events->attach('foo', $callback3, 2);
$this->events->attach('foo', $callback2, 2);

$listeners = $this->getArrayOfListenersForEvent('foo', $this->events);
$this->assertInternalType('array', $listeners);

$this->assertEquals([
$callback5,
$callback1,
$callback3,
$callback2,
$callback4,
], $listeners);
}

public function testAssertListenerAtPriorityPassesWhenListenerIsFound()
{
// @codingStandardsIgnoreStart
$callback = function ($e) {};
// @codingStandardsIgnoreEnd

$this->events->attach('foo', $callback, 7);

$this->assertListenerAtPriority($callback, 7, 'foo', $this->events);
}

public function testAssertListenerAtPriorityFailsWhenListenerIsNotFound()
{
// @codingStandardsIgnoreStart
$event = 'foo';
$listener = function ($e) {};
$priority = 7;
$this->events->attach($event, $listener, $priority);

$alternate = function ($e) {};

$permutations = [
'different-listener' => ['listener' => $alternate, 'priority' => $priority, 'event' => $event],
'different-priority' => ['listener' => $listener, 'priority' => $priority + 1, 'event' => $event],
'different-event' => ['listener' => $listener, 'priority' => $priority, 'event' => $event . '-FOO'],
];
// @codingStandardsIgnoreEnd

foreach ($permutations as $case => $arguments) {
try {
$this->assertListenerAtPriority(
$arguments['listener'],
$arguments['priority'],
$arguments['event'],
$this->events
);
$this->fail('assertListenerAtPriority assertion had a false positive for case ' . $case);
} catch (ExpectationFailedException $e) {
$this->assertContains(sprintf(
'Listener not found for event "%s" and priority %d',
$arguments['event'],
$arguments['priority']
), $e->getMessage(), sprintf('Assertion failure message was unexpected: %s', $e->getMessage()));
}
}
}
}