Skip to content

Commit ee54c30

Browse files
committed
Merge branch 'eventtrait' into codeception5
2 parents 6408d75 + 2dde51c commit ee54c30

File tree

5 files changed

+360
-15
lines changed

5 files changed

+360
-15
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.idea/
22
composer.lock
33
vendor/
4-
core/
4+
core/
5+
modules/

src/Codeception/Module/DrupalBootstrap.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use Codeception\TestDrupalKernel;
99
use Symfony\Component\HttpFoundation\Request;
1010
use DrupalFinder\DrupalFinder;
11-
11+
use Codeception\Module\DrupalBootstrap\EventsAssertionsTrait;
1212

1313
/**
1414
* Class DrupalBootstrap.
@@ -25,6 +25,8 @@
2525
*/
2626
class DrupalBootstrap extends Module {
2727

28+
use EventsAssertionsTrait;
29+
2830
/**
2931
* Default module configuration.
3032
*
@@ -34,6 +36,13 @@ class DrupalBootstrap extends Module {
3436
'site_path' => 'sites/default',
3537
];
3638

39+
/**
40+
* Track wether we enabled the webprofiler module or not.
41+
*
42+
* @var bool
43+
*/
44+
protected $enabledWebProfiler = FALSE;
45+
3746
/**
3847
* DrupalBootstrap constructor.
3948
*
@@ -72,4 +81,25 @@ public function __construct(ModuleContainer $container, $config = NULL) {
7281
$kernel->bootTestEnvironment($this->_getConfig('site_path'), $request);
7382
}
7483

84+
/**
85+
* Enabled dependent modules.
86+
*/
87+
public function _beforeSuite($settings = []) {
88+
$module_handler = \Drupal::service('module_handler');
89+
if (!$module_handler->moduleExists('webprofiler')) {
90+
$this->enabledWebProfiler = TRUE;
91+
\Drupal::service('module_installer')->install(['webprofiler']);
92+
}
93+
}
94+
95+
/**
96+
* Disable modules which were enabled.
97+
*/
98+
public function _afterSuite($settings = []) {
99+
if ($this->enabledWebProfiler) {
100+
$this->enabledWebProfiler = FALSE;
101+
\Drupal::service('module_installer')->uninstall(['webprofiler']);
102+
}
103+
}
104+
75105
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codeception\Module\DrupalBootstrap;
6+
7+
use Drupal\webprofiler\DataCollector\EventsDataCollector;
8+
use Drupal\webprofiler\EventDispatcher\EventDispatcherTraceableInterface;
9+
use function get_class;
10+
use function is_array;
11+
use function is_object;
12+
13+
/**
14+
*
15+
*/
16+
trait EventsAssertionsTrait {
17+
18+
/**
19+
* Verifies that there were no orphan events during the test.
20+
*
21+
* An orphan event is an event that was triggered by manually executing the
22+
* [`dispatch()`](https://symfony.com/doc/current/components/event_dispatcher.html#dispatch-the-event) method
23+
* of the EventDispatcher but was not handled by any listener after it was dispatched.
24+
*
25+
* ```php
26+
* <?php
27+
* $I->dontSeeOrphanEvent();
28+
* $I->dontSeeOrphanEvent('App\MyEvent');
29+
* $I->dontSeeOrphanEvent(new App\Events\MyEvent());
30+
* $I->dontSeeOrphanEvent(['App\MyEvent', 'App\MyOtherEvent']);
31+
* ```
32+
*
33+
* @param string|object|string[] $expected
34+
*/
35+
public function dontSeeOrphanEvent($expected = NULL): void {
36+
$eventCollector = $this->grabEventCollector();
37+
38+
$data = $eventCollector->getOrphanedEvents();
39+
$expected = is_array($expected) ? $expected : [$expected];
40+
41+
if ($expected === NULL) {
42+
$this->assertSame(0, count($data));
43+
}
44+
else {
45+
$this->assertEventNotTriggered($data, $expected);
46+
}
47+
}
48+
49+
/**
50+
* Verifies that one or more event listeners were not called during the test.
51+
*
52+
* ```php
53+
* <?php
54+
* $I->dontSeeEventTriggered('App\MyEvent');
55+
* $I->dontSeeEventTriggered(new App\Events\MyEvent());
56+
* $I->dontSeeEventTriggered(['App\MyEvent', 'App\MyOtherEvent']);
57+
* $I->dontSeeEventTriggered('my_event_string_name');
58+
* $I->dontSeeEventTriggered(['my_event_string', 'my_other_event_string]);
59+
* ```
60+
*
61+
* @param string|object|string[] $expected
62+
*/
63+
public function dontSeeEventTriggered($expected): void {
64+
$eventCollector = $this->grabEventCollector();
65+
66+
$data = $eventCollector->getCalledListeners();
67+
$expected = is_array($expected) ? $expected : [$expected];
68+
69+
$this->assertEventNotTriggered($data, $expected);
70+
}
71+
72+
/**
73+
* Verifies that one or more orphan events were dispatched during the test.
74+
*
75+
* An orphan event is an event that was triggered by manually executing the
76+
* [`dispatch()`](https://symfony.com/doc/current/components/event_dispatcher.html#dispatch-the-event) method
77+
* of the EventDispatcher but was not handled by any listener after it was dispatched.
78+
*
79+
* ```php
80+
* <?php
81+
* $I->seeOrphanEvent('App\MyEvent');
82+
* $I->seeOrphanEvent(new App\Events\MyEvent());
83+
* $I->seeOrphanEvent(['App\MyEvent', 'App\MyOtherEvent']);
84+
* $I->seeOrphanEvent('my_event_string_name');
85+
* $I->seeOrphanEvent(['my_event_string_name', 'my_other_event_string]);
86+
* ```
87+
*
88+
* @param string|object|string[] $expected
89+
*/
90+
public function seeOrphanEvent($expected): void {
91+
$eventCollector = $this->grabEventCollector();
92+
93+
$data = $eventCollector->getOrphanedEvents();
94+
$expected = is_array($expected) ? $expected : [$expected];
95+
96+
$this->assertEventTriggered($data, $expected);
97+
}
98+
99+
/**
100+
* Verifies that one or more event listeners were called during the test.
101+
*
102+
* ```php
103+
* <?php
104+
* $I->seeEventTriggered('App\MyEvent');
105+
* $I->seeEventTriggered(new App\Events\MyEvent());
106+
* $I->seeEventTriggered(['App\MyEvent', 'App\MyOtherEvent']);
107+
* $I->seeEventTriggered('my_event_string_name');
108+
* $I->seeEventTriggered(['my_event_string_name', 'my_other_event_string]);
109+
* ```
110+
*
111+
* @param string|object|string[] $expected
112+
*/
113+
public function seeEventTriggered($expected): void {
114+
$eventCollector = $this->grabEventCollector();
115+
116+
$data = $eventCollector->getCalledListeners();
117+
$expected = is_array($expected) ? $expected : [$expected];
118+
119+
$this->assertEventTriggered($data, $expected);
120+
}
121+
122+
/**
123+
*
124+
*/
125+
protected function assertEventNotTriggered(array $data, array $expected): void {
126+
foreach ($expected as $expectedEvent) {
127+
$expectedEvent = is_object($expectedEvent) ? get_class($expectedEvent) : $expectedEvent;
128+
$this->assertFalse(
129+
$this->eventWasTriggered($data, (string) $expectedEvent),
130+
"The '{$expectedEvent}' event triggered"
131+
);
132+
}
133+
}
134+
135+
/**
136+
*
137+
*/
138+
protected function assertEventTriggered(array $data, array $expected): void {
139+
if (count($data) === 0) {
140+
$this->fail('No event was triggered');
141+
}
142+
143+
foreach ($expected as $expectedEvent) {
144+
$expectedEvent = is_object($expectedEvent) ? get_class($expectedEvent) : $expectedEvent;
145+
$this->assertTrue(
146+
$this->eventWasTriggered($data, (string) $expectedEvent),
147+
"The '{$expectedEvent}' event did not trigger"
148+
);
149+
}
150+
}
151+
152+
/**
153+
*
154+
*/
155+
protected function eventWasTriggered(array $actual, string $expectedEvent): bool {
156+
$triggered = FALSE;
157+
158+
foreach ($actual as $name => $actualEvent) {
159+
// Called Listeners.
160+
if ($name === $expectedEvent && !empty($actualEvent)) {
161+
$triggered = TRUE;
162+
}
163+
}
164+
165+
return $triggered;
166+
}
167+
168+
/**
169+
* Get the event data collector service.
170+
*/
171+
protected function grabEventCollector(): EventsDataCollector {
172+
$event_dispatcher = \Drupal::service('event_dispatcher');
173+
if ($event_dispatcher instanceof EventDispatcherTraceableInterface) {
174+
$collector = new EventsDataCollector($event_dispatcher);
175+
$collector->lateCollect();
176+
return $collector;
177+
}
178+
else {
179+
throw new \Exception('Webprofiler module is required for testing events.');
180+
}
181+
}
182+
183+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
namespace Codeception\Module;
4+
5+
use Drupal\Core\Entity\FieldableEntityInterface;
6+
use Drupal\group\Entity\GroupInterface;
7+
use Drupal\user\Entity\User;
8+
use Drupal\user\UserInterface;
9+
10+
/**
11+
* Class DrupalGroup.
12+
*
13+
* ### Example
14+
* #### Example (DrupalGroup)
15+
* modules:
16+
* - DrupalGroup:
17+
* cleanup_test: true
18+
* cleanup_failed: false
19+
* cleanup_suite: true
20+
* route_entities:
21+
* - node
22+
* - taxonomy_term.
23+
*
24+
* @package Codeception\Module
25+
*/
26+
class DrupalGroup extends DrupalEntity {
27+
28+
/**
29+
* Wrapper method to create a group.
30+
*
31+
* Improves readbility of tests by having the method read "create group".
32+
*/
33+
public function createGroup(array $values = [], $validate = TRUE) {
34+
$type = 'group';
35+
36+
if (!array_key_exists('uid', $values)) {
37+
$values['uid'] = 1;
38+
}
39+
40+
try {
41+
$entity = \Drupal::entityTypeManager()
42+
->getStorage($type)
43+
->create($values);
44+
if ($validate && $entity instanceof FieldableEntityInterface) {
45+
$violations = $entity->validate();
46+
if ($violations->count() > 0) {
47+
$message = PHP_EOL;
48+
foreach ($violations as $violation) {
49+
$message .= $violation->getPropertyPath() . ': ' . $violation->getMessage() . PHP_EOL;
50+
}
51+
throw new \Exception($message);
52+
}
53+
}
54+
// Group specific entity save options.
55+
$entity->setOwner(User::load($values['uid'] ?? 1));
56+
$entity->save();
57+
}
58+
catch (\Exception $e) {
59+
$this->fail('Could not create group entity. Error message: ' . $e->getMessage());
60+
}
61+
if (!empty($entity)) {
62+
$this->registerTestEntity($entity->getEntityTypeId(), $entity->id());
63+
64+
return $entity;
65+
}
66+
67+
return FALSE;
68+
}
69+
70+
/**
71+
* Join the defined group.
72+
*
73+
* @param \Drupal\group\Entity\GroupInterface $group
74+
* Instance of a group.
75+
* @param \Drupal\user\UserInterface $user
76+
* A drupal user to add to the group.
77+
*
78+
* @return \Drupal\group\GroupMembership|false
79+
* Returns the group content entity, FALSE otherwise.
80+
*/
81+
public function joinGroup(GroupInterface $group, UserInterface $user) {
82+
$group->addMember($user);
83+
return $group->getMember($user);
84+
}
85+
86+
/**
87+
* Leave a group.
88+
*
89+
* @param \Drupal\group\Entity\GroupInterface $group
90+
* Instance of a group.
91+
* @param \Drupal\user\UserInterface $user
92+
* A drupal user to add to the group.
93+
*
94+
* @return bool
95+
* Returns the TRUE if the user is no longer a member of the group,
96+
* FALSE otherwise.
97+
*/
98+
public function leaveGroup(GroupInterface $group, UserInterface $user) {
99+
$group->removeMember($user);
100+
// Get member should return FALSE if the user isn't a member so we
101+
// reverse the logic. If they are still a member it'll cast to TRUE.
102+
$is_member = (bool) $group->getMember($user);
103+
return !$is_member;
104+
}
105+
106+
}

0 commit comments

Comments
 (0)