Files
XC_VM/tests/Unit/EventDispatcherTest.php
T
Divarion-D 432c8a010a chore(psr4): phase 3 (Core) — namespace Core/Events
Move Core/Events into XcVm\Core\Events and its sub-trees: \Contract
(StoppableEventInterface), \Auth, \Module, \Settings, \Stream (the event
classes), plus root EventDispatcher, ListenerProvider, ListensTo, AbstractEvent.

- Namespace 14 files across 6 namespaces; cross-namespace refs imported via use
  (AbstractEvent/EventDispatcher → Contract\StoppableEventInterface; Stream events
  → root AbstractEvent). Built-in \Attribute (ListensTo) and ioncube \XC_VM
  (PackageInstalledEvent) qualified.
- Rewrite 'use ListensTo;' → FQCN; add use to referrers across src/ and tests/;
  fix leading-backslash refs (\EventDispatcher, \ListensTo, \PackageInstalledEvent
  from the Module commit) to their FQCNs.
- Tests: add real top-level use imports to ModuleLoaderBootTest (EventDispatcher)
  and ListensToAttributeTest (ListensTo) — the use-inserter skipped/mis-placed them
  due to a namespace() method and a heredoc fixture already containing the FQCN.
- phpstan-baseline.neon regenerated (292→292).

Completes Core/Container + Core/Events. Verified: php -l clean; PHPStan no errors;
PHPUnit 295/295; EventDispatcher resolves and AbstractEvent implements
XcVm\Core\Events\Contract\StoppableEventInterface.
2026-06-24 22:18:52 +03:00

139 lines
4.9 KiB
PHP

<?php
use XcVm\Core\Events\Contract\StoppableEventInterface;
use XcVm\Core\Events\EventDispatcher;
use XcVm\Core\Events\AbstractEvent;
use PHPUnit\Framework\TestCase;
final class EventDispatcherTest extends TestCase {
protected function setUp(): void {
EventDispatcher::clear();
}
protected function tearDown(): void {
EventDispatcher::clear();
}
// ── dispatch / listen ──────────────────────────────────────
public function testDispatchCallsMatchingListener(): void {
$received = null;
EventDispatcher::listen(TestPlainEvent::class, function (TestPlainEvent $e) use (&$received) {
$received = $e->value;
});
EventDispatcher::dispatch(new TestPlainEvent('hello'));
$this->assertSame('hello', $received);
}
public function testDispatchReturnsEvent(): void {
$event = new TestPlainEvent('x');
$returned = EventDispatcher::dispatch($event);
$this->assertSame($event, $returned);
}
public function testDispatchDoesNotCallUnrelatedListeners(): void {
$called = false;
EventDispatcher::listen(TestPlainEvent::class, function () use (&$called) {
$called = true;
});
EventDispatcher::dispatch(new TestOtherEvent());
$this->assertFalse($called);
}
// ── Priority ordering ──────────────────────────────────────
public function testListenersAreCalledInPriorityDescendingOrder(): void {
$order = [];
EventDispatcher::listen(TestPlainEvent::class, function () use (&$order) { $order[] = 'low'; }, 10);
EventDispatcher::listen(TestPlainEvent::class, function () use (&$order) { $order[] = 'high'; }, 50);
EventDispatcher::listen(TestPlainEvent::class, function () use (&$order) { $order[] = 'mid'; }, 30);
EventDispatcher::dispatch(new TestPlainEvent('x'));
$this->assertSame(['high', 'mid', 'low'], $order);
}
// ── StoppableEventInterface ────────────────────────────────
public function testStoppableEventStopsPropagation(): void {
$calls = 0;
EventDispatcher::listen(TestStoppableEvent::class, function (TestStoppableEvent $e) use (&$calls) {
$calls++;
$e->stopPropagation();
}, 10);
EventDispatcher::listen(TestStoppableEvent::class, function () use (&$calls) {
$calls++;
}, 5);
EventDispatcher::dispatch(new TestStoppableEvent());
$this->assertSame(1, $calls);
}
// ── hasListeners ───────────────────────────────────────────
public function testHasListenersReturnsTrueWhenRegistered(): void {
EventDispatcher::listen(TestPlainEvent::class, fn() => null);
$this->assertTrue(EventDispatcher::hasListeners(TestPlainEvent::class));
}
public function testHasListenersReturnsFalseWhenNone(): void {
$this->assertFalse(EventDispatcher::hasListeners(TestPlainEvent::class));
}
// ── unlisten / clear ───────────────────────────────────────
public function testUnlistenRemovesAllListenersForEvent(): void {
$called = false;
EventDispatcher::listen(TestPlainEvent::class, fn() => $called = true);
EventDispatcher::unlisten(TestPlainEvent::class);
EventDispatcher::dispatch(new TestPlainEvent('x'));
$this->assertFalse($called);
}
public function testClearRemovesAllListeners(): void {
$called = false;
EventDispatcher::listen(TestPlainEvent::class, fn() => $called = true);
EventDispatcher::clear();
EventDispatcher::dispatch(new TestPlainEvent('x'));
$this->assertFalse($called);
}
// ── Multiple listeners on same event ───────────────────────
public function testMultipleListenersAllCalled(): void {
$results = [];
EventDispatcher::listen(TestPlainEvent::class, function () use (&$results) { $results[] = 'a'; });
EventDispatcher::listen(TestPlainEvent::class, function () use (&$results) { $results[] = 'b'; });
EventDispatcher::dispatch(new TestPlainEvent('x'));
$this->assertCount(2, $results);
}
}
// ── Test event fixtures ────────────────────────────────────────────
final class TestPlainEvent {
public function __construct(public readonly string $value = '') {}
}
final class TestOtherEvent {}
final class TestStoppableEvent extends AbstractEvent {}