From c96a7c5121ba830c4d24b4f77f031dc5461f6223 Mon Sep 17 00:00:00 2001
From: Eser DENIZ <srwiez@gmail.com>
Date: Wed, 18 Dec 2024 19:25:08 +0100
Subject: [PATCH] Fixes and improvements to powerMonitor (#445)

* fix: powerMonitor couldn't pass arguments with get methods

* feat: additional events

* fix: PowerMonitor Facade docblock

* feat: added fake class for tests
---
 src/Client/Client.php                         |   4 +-
 src/Contracts/PowerMonitor.php                |  17 +++
 src/Events/PowerMonitor/ScreenLocked.php      |  23 ++++
 src/Events/PowerMonitor/ScreenUnlocked.php    |  23 ++++
 src/Events/PowerMonitor/Shutdown.php          |  23 ++++
 .../PowerMonitor/UserDidBecomeActive.php      |  23 ++++
 .../PowerMonitor/UserDidResignActive.php      |  23 ++++
 src/Facades/PowerMonitor.php                  |  15 ++-
 src/Fakes/PowerMonitorFake.php                |  93 +++++++++++++
 src/NativeServiceProvider.php                 |   6 +
 src/PowerMonitor.php                          |   3 +-
 tests/Fakes/FakePowerMonitorTest.php          | 123 ++++++++++++++++++
 12 files changed, 370 insertions(+), 6 deletions(-)
 create mode 100644 src/Contracts/PowerMonitor.php
 create mode 100644 src/Events/PowerMonitor/ScreenLocked.php
 create mode 100644 src/Events/PowerMonitor/ScreenUnlocked.php
 create mode 100644 src/Events/PowerMonitor/Shutdown.php
 create mode 100644 src/Events/PowerMonitor/UserDidBecomeActive.php
 create mode 100644 src/Events/PowerMonitor/UserDidResignActive.php
 create mode 100644 src/Fakes/PowerMonitorFake.php
 create mode 100644 tests/Fakes/FakePowerMonitorTest.php

diff --git a/src/Client/Client.php b/src/Client/Client.php
index e444ea4..9a8ec81 100644
--- a/src/Client/Client.php
+++ b/src/Client/Client.php
@@ -21,9 +21,9 @@ public function __construct()
             ->asJson();
     }
 
-    public function get(string $endpoint): Response
+    public function get(string $endpoint, array|string|null $query = null): Response
     {
-        return $this->client->get($endpoint);
+        return $this->client->get($endpoint, $query);
     }
 
     public function post(string $endpoint, array $data = []): Response
diff --git a/src/Contracts/PowerMonitor.php b/src/Contracts/PowerMonitor.php
new file mode 100644
index 0000000..e8ec3d5
--- /dev/null
+++ b/src/Contracts/PowerMonitor.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Native\Laravel\Contracts;
+
+use Native\Laravel\Enums\SystemIdleStatesEnum;
+use Native\Laravel\Enums\ThermalStatesEnum;
+
+interface PowerMonitor
+{
+    public function getSystemIdleState(int $threshold): SystemIdleStatesEnum;
+
+    public function getSystemIdleTime(): int;
+
+    public function getCurrentThermalState(): ThermalStatesEnum;
+
+    public function isOnBatteryPower(): bool;
+}
diff --git a/src/Events/PowerMonitor/ScreenLocked.php b/src/Events/PowerMonitor/ScreenLocked.php
new file mode 100644
index 0000000..dbaca58
--- /dev/null
+++ b/src/Events/PowerMonitor/ScreenLocked.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Native\Laravel\Events\PowerMonitor;
+
+use Illuminate\Broadcasting\Channel;
+use Illuminate\Broadcasting\InteractsWithSockets;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Queue\SerializesModels;
+
+class ScreenLocked implements ShouldBroadcastNow
+{
+    use Dispatchable, InteractsWithSockets, SerializesModels;
+
+    public function __construct() {}
+
+    public function broadcastOn()
+    {
+        return [
+            new Channel('nativephp'),
+        ];
+    }
+}
diff --git a/src/Events/PowerMonitor/ScreenUnlocked.php b/src/Events/PowerMonitor/ScreenUnlocked.php
new file mode 100644
index 0000000..b4d1524
--- /dev/null
+++ b/src/Events/PowerMonitor/ScreenUnlocked.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Native\Laravel\Events\PowerMonitor;
+
+use Illuminate\Broadcasting\Channel;
+use Illuminate\Broadcasting\InteractsWithSockets;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Queue\SerializesModels;
+
+class ScreenUnlocked implements ShouldBroadcastNow
+{
+    use Dispatchable, InteractsWithSockets, SerializesModels;
+
+    public function __construct() {}
+
+    public function broadcastOn()
+    {
+        return [
+            new Channel('nativephp'),
+        ];
+    }
+}
diff --git a/src/Events/PowerMonitor/Shutdown.php b/src/Events/PowerMonitor/Shutdown.php
new file mode 100644
index 0000000..19e563c
--- /dev/null
+++ b/src/Events/PowerMonitor/Shutdown.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Native\Laravel\Events\PowerMonitor;
+
+use Illuminate\Broadcasting\Channel;
+use Illuminate\Broadcasting\InteractsWithSockets;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Queue\SerializesModels;
+
+class Shutdown implements ShouldBroadcastNow
+{
+    use Dispatchable, InteractsWithSockets, SerializesModels;
+
+    public function __construct() {}
+
+    public function broadcastOn()
+    {
+        return [
+            new Channel('nativephp'),
+        ];
+    }
+}
diff --git a/src/Events/PowerMonitor/UserDidBecomeActive.php b/src/Events/PowerMonitor/UserDidBecomeActive.php
new file mode 100644
index 0000000..e077a52
--- /dev/null
+++ b/src/Events/PowerMonitor/UserDidBecomeActive.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Native\Laravel\Events\PowerMonitor;
+
+use Illuminate\Broadcasting\Channel;
+use Illuminate\Broadcasting\InteractsWithSockets;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Queue\SerializesModels;
+
+class UserDidBecomeActive implements ShouldBroadcastNow
+{
+    use Dispatchable, InteractsWithSockets, SerializesModels;
+
+    public function __construct() {}
+
+    public function broadcastOn()
+    {
+        return [
+            new Channel('nativephp'),
+        ];
+    }
+}
diff --git a/src/Events/PowerMonitor/UserDidResignActive.php b/src/Events/PowerMonitor/UserDidResignActive.php
new file mode 100644
index 0000000..5608102
--- /dev/null
+++ b/src/Events/PowerMonitor/UserDidResignActive.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Native\Laravel\Events\PowerMonitor;
+
+use Illuminate\Broadcasting\Channel;
+use Illuminate\Broadcasting\InteractsWithSockets;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Queue\SerializesModels;
+
+class UserDidResignActive implements ShouldBroadcastNow
+{
+    use Dispatchable, InteractsWithSockets, SerializesModels;
+
+    public function __construct() {}
+
+    public function broadcastOn()
+    {
+        return [
+            new Channel('nativephp'),
+        ];
+    }
+}
diff --git a/src/Facades/PowerMonitor.php b/src/Facades/PowerMonitor.php
index 5233856..d72d9d7 100644
--- a/src/Facades/PowerMonitor.php
+++ b/src/Facades/PowerMonitor.php
@@ -3,17 +3,26 @@
 namespace Native\Laravel\Facades;
 
 use Illuminate\Support\Facades\Facade;
+use Native\Laravel\Contracts\PowerMonitor as PowerMonitorContract;
+use Native\Laravel\Fakes\PowerMonitorFake;
 
 /**
- * @method static \Native\Laravel\Enums\SystemIdelStatesEnum getSystemIdleState(int $threshold)
+ * @method static \Native\Laravel\Enums\SystemIdleStatesEnum getSystemIdleState(int $threshold)
  * @method static int getSystemIdleTime()
  * @method static \Native\Laravel\Enums\ThermalStatesEnum getCurrentThermalState()
  * @method static bool isOnBatteryPower()
  */
 class PowerMonitor extends Facade
 {
-    protected static function getFacadeAccessor()
+    public static function fake()
     {
-        return \Native\Laravel\PowerMonitor::class;
+        return tap(static::getFacadeApplication()->make(PowerMonitorFake::class), function ($fake) {
+            static::swap($fake);
+        });
+    }
+
+    protected static function getFacadeAccessor(): string
+    {
+        return PowerMonitorContract::class;
     }
 }
diff --git a/src/Fakes/PowerMonitorFake.php b/src/Fakes/PowerMonitorFake.php
new file mode 100644
index 0000000..2af9916
--- /dev/null
+++ b/src/Fakes/PowerMonitorFake.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Native\Laravel\Fakes;
+
+use Closure;
+use Native\Laravel\Contracts\PowerMonitor as PowerMonitorContract;
+use Native\Laravel\Enums\SystemIdleStatesEnum;
+use Native\Laravel\Enums\ThermalStatesEnum;
+use PHPUnit\Framework\Assert as PHPUnit;
+
+class PowerMonitorFake implements PowerMonitorContract
+{
+    public array $getSystemIdleStateCalls = [];
+
+    public int $getSystemIdleStateCount = 0;
+
+    public int $getSystemIdleTimeCount = 0;
+
+    public int $getCurrentThermalStateCount = 0;
+
+    public int $isOnBatteryPowerCount = 0;
+
+    public function getSystemIdleState(int $threshold): SystemIdleStatesEnum
+    {
+        $this->getSystemIdleStateCount++;
+
+        $this->getSystemIdleStateCalls[] = $threshold;
+
+        return SystemIdleStatesEnum::UNKNOWN;
+    }
+
+    public function getSystemIdleTime(): int
+    {
+        $this->getSystemIdleTimeCount++;
+
+        return 0;
+    }
+
+    public function getCurrentThermalState(): ThermalStatesEnum
+    {
+        $this->getCurrentThermalStateCount++;
+
+        return ThermalStatesEnum::UNKNOWN;
+    }
+
+    public function isOnBatteryPower(): bool
+    {
+        $this->isOnBatteryPowerCount++;
+
+        return false;
+    }
+
+    /**
+     * @param  int|Closure(int): bool  $key
+     */
+    public function assertGetSystemIdleState(int|Closure $key): void
+    {
+        if (is_callable($key) === false) {
+            PHPUnit::assertContains($key, $this->getSystemIdleStateCalls);
+
+            return;
+        }
+
+        $hit = empty(
+            array_filter(
+                $this->getSystemIdleStateCalls,
+                fn (string $keyIteration) => $key($keyIteration) === true
+            )
+        ) === false;
+
+        PHPUnit::assertTrue($hit);
+    }
+
+    public function assertGetSystemIdleStateCount(int $count): void
+    {
+        PHPUnit::assertSame($count, $this->getSystemIdleStateCount);
+    }
+
+    public function assertGetSystemIdleTimeCount(int $count): void
+    {
+        PHPUnit::assertSame($count, $this->getSystemIdleTimeCount);
+    }
+
+    public function assertGetCurrentThermalStateCount(int $count): void
+    {
+        PHPUnit::assertSame($count, $this->getCurrentThermalStateCount);
+    }
+
+    public function assertIsOnBatteryPowerCount(int $count): void
+    {
+        PHPUnit::assertSame($count, $this->isOnBatteryPowerCount);
+    }
+}
diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php
index 89c0740..079bb0a 100644
--- a/src/NativeServiceProvider.php
+++ b/src/NativeServiceProvider.php
@@ -16,11 +16,13 @@
 use Native\Laravel\Commands\SeedDatabaseCommand;
 use Native\Laravel\Contracts\ChildProcess as ChildProcessContract;
 use Native\Laravel\Contracts\GlobalShortcut as GlobalShortcutContract;
+use Native\Laravel\Contracts\PowerMonitor as PowerMonitorContract;
 use Native\Laravel\Contracts\WindowManager as WindowManagerContract;
 use Native\Laravel\Events\EventWatcher;
 use Native\Laravel\Exceptions\Handler;
 use Native\Laravel\GlobalShortcut as GlobalShortcutImplementation;
 use Native\Laravel\Logging\LogWatcher;
+use Native\Laravel\PowerMonitor as PowerMonitorImplementation;
 use Native\Laravel\Windows\WindowManager as WindowManagerImplementation;
 use Spatie\LaravelPackageTools\Package;
 use Spatie\LaravelPackageTools\PackageServiceProvider;
@@ -66,6 +68,10 @@ public function packageRegistered()
             return $app->make(GlobalShortcutImplementation::class);
         });
 
+        $this->app->bind(PowerMonitorContract::class, function (Foundation $app) {
+            return $app->make(PowerMonitorImplementation::class);
+        });
+
         if (config('nativephp-internal.running')) {
             $this->app->singleton(
                 \Illuminate\Contracts\Debug\ExceptionHandler::class,
diff --git a/src/PowerMonitor.php b/src/PowerMonitor.php
index ea0d587..c0307b1 100644
--- a/src/PowerMonitor.php
+++ b/src/PowerMonitor.php
@@ -3,10 +3,11 @@
 namespace Native\Laravel;
 
 use Native\Laravel\Client\Client;
+use Native\Laravel\Contracts\PowerMonitor as PowerMonitorContract;
 use Native\Laravel\Enums\SystemIdleStatesEnum;
 use Native\Laravel\Enums\ThermalStatesEnum;
 
-class PowerMonitor
+class PowerMonitor implements PowerMonitorContract
 {
     public function __construct(protected Client $client) {}
 
diff --git a/tests/Fakes/FakePowerMonitorTest.php b/tests/Fakes/FakePowerMonitorTest.php
new file mode 100644
index 0000000..4761877
--- /dev/null
+++ b/tests/Fakes/FakePowerMonitorTest.php
@@ -0,0 +1,123 @@
+<?php
+
+use Native\Laravel\Contracts\PowerMonitor as PowerMonitorContract;
+use Native\Laravel\Facades\PowerMonitor;
+use Native\Laravel\Fakes\PowerMonitorFake;
+use PHPUnit\Framework\AssertionFailedError;
+
+use function Pest\Laravel\swap;
+
+it('swaps implementations using facade', function () {
+    PowerMonitor::fake();
+
+    expect(app(PowerMonitorContract::class))
+        ->toBeInstanceOf(PowerMonitorFake::class);
+});
+
+it('asserts getSystemIdleState using int', function () {
+    swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class));
+
+    $fake->getSystemIdleState(10);
+    $fake->getSystemIdleState(60);
+
+    $fake->assertGetSystemIdleState(10);
+    $fake->assertGetSystemIdleState(60);
+
+    try {
+        $fake->assertGetSystemIdleState(20);
+    } catch (AssertionFailedError) {
+        return;
+    }
+
+    $this->fail('Expected assertion to fail');
+});
+
+it('asserts getSystemIdleState using callable', function () {
+    swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class));
+
+    $fake->getSystemIdleState(10);
+    $fake->getSystemIdleState(60);
+
+    $fake->assertGetSystemIdleState(fn (int $key) => $key === 10);
+    $fake->assertGetSystemIdleState(fn (int $key) => $key === 60);
+
+    try {
+        $fake->assertGetSystemIdleState(fn (int $key) => $key === 20);
+    } catch (AssertionFailedError) {
+        return;
+    }
+
+    $this->fail('Expected assertion to fail');
+});
+
+it('asserts getSystemIdleState count', function () {
+    swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class));
+
+    $fake->getSystemIdleState(10);
+    $fake->getSystemIdleState(20);
+    $fake->getSystemIdleState(60);
+
+    $fake->assertGetSystemIdleStateCount(3);
+
+    try {
+        $fake->assertGetSystemIdleStateCount(2);
+    } catch (AssertionFailedError) {
+        return;
+    }
+
+    $this->fail('Expected assertion to fail');
+});
+
+it('asserts getSystemIdleTime count', function () {
+    swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class));
+
+    $fake->getSystemIdleTime();
+    $fake->getSystemIdleTime();
+    $fake->getSystemIdleTime();
+
+    $fake->assertGetSystemIdleTimeCount(3);
+
+    try {
+        $fake->assertGetSystemIdleTimeCount(2);
+    } catch (AssertionFailedError) {
+        return;
+    }
+
+    $this->fail('Expected assertion to fail');
+});
+
+it('asserts getCurrentThermalState count', function () {
+    swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class));
+
+    $fake->getCurrentThermalState();
+    $fake->getCurrentThermalState();
+    $fake->getCurrentThermalState();
+
+    $fake->assertGetCurrentThermalStateCount(3);
+
+    try {
+        $fake->assertGetCurrentThermalStateCount(2);
+    } catch (AssertionFailedError) {
+        return;
+    }
+
+    $this->fail('Expected assertion to fail');
+});
+
+it('asserts isOnBatteryPower count', function () {
+    swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class));
+
+    $fake->isOnBatteryPower();
+    $fake->isOnBatteryPower();
+    $fake->isOnBatteryPower();
+
+    $fake->assertIsOnBatteryPowerCount(3);
+
+    try {
+        $fake->assertIsOnBatteryPowerCount(2);
+    } catch (AssertionFailedError) {
+        return;
+    }
+
+    $this->fail('Expected assertion to fail');
+});