diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index 27e13df..b1c0e4e 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -89,7 +89,7 @@ jobs: - name: CS run: | cd galette-core/galette/plugins/plugin-activities - ../../vendor/bin/phpcs lib/ ./*.php + ../../vendor/bin/phpcs lib/ tests/ ./*.php - name: CS Fixer if: matrix.php-is-min diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 9a45b35..8c5bf52 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -18,6 +18,8 @@ 'cast_spaces' => ['space' => 'none'], 'single_line_empty_body' => false, 'no_unused_imports' => true, + // spaces around multiple catch types only, as phpcs expects - https://mlocati.github.io/php-cs-fixer-configurator/#version:3.90|fixer:types_spaces + 'types_spaces' => ['space' => 'none', 'space_multiple_catch' => 'single'], // rules for phpdoc // Removes @param, @return and @var tags that don't provide any useful information - https://mlocati.github.io/php-cs-fixer-configurator/#version:3.90|fixer:no_superfluous_phpdoc_tags 'no_superfluous_phpdoc_tags' => [ diff --git a/lib/GaletteActivities/Entity/Activity.php b/lib/GaletteActivities/Entity/Activity.php index 5d472e5..05fa55f 100644 --- a/lib/GaletteActivities/Entity/Activity.php +++ b/lib/GaletteActivities/Entity/Activity.php @@ -45,10 +45,10 @@ class Activity /** * Default constructor * - * @param Db $zdb Database instance - * @param null|int|ArrayObject $args Either a ResultSet row or its id for to load - * a specific activity, or null to just - * instanciate object + * @param Db $zdb Database instance + * @param null|int|ArrayObject $args Either a ResultSet row or its id for to load + * a specific activity, or null to just + * instanciate object */ public function __construct(Db $zdb, int|ArrayObject|null $args = null) { @@ -92,22 +92,22 @@ public function load(int $id): bool /** * Populate object from a resultset row * - * @param ArrayObject $r the resultset row + * @param ArrayObject $r the resultset row */ private function loadFromRS(ArrayObject $r): void { - $this->id = (int)$r->id_activity; - $this->name = $r->name; - $this->type = $r->type ?? ''; - if ($r->price !== null) { - $this->price = (float)$r->price; + $this->id = (int)$r['id_activity']; + $this->name = $r['name']; + $this->type = $r['type'] ?? ''; + if ($r['price'] !== null) { + $this->price = (float)$r['price']; } - if ($r->id_group !== null) { - $this->id_group = (int)$r->id_group; + if ($r['id_group'] !== null) { + $this->id_group = (int)$r['id_group']; $this->group = new Group($this->id_group); } - $this->creation_date = $r->creation_date; - $this->comment = $r->comment; + $this->creation_date = $r['creation_date']; + $this->comment = $r['comment']; } /** diff --git a/lib/GaletteActivities/Entity/Subscription.php b/lib/GaletteActivities/Entity/Subscription.php index 6a91139..b5dae60 100644 --- a/lib/GaletteActivities/Entity/Subscription.php +++ b/lib/GaletteActivities/Entity/Subscription.php @@ -53,10 +53,10 @@ class Subscription /** * Default constructor * - * @param Db $zdb Database instance - * @param null|int|ArrayObject $args Either a ResultSet row or its id for to load - * a specific subscription, or null to just - * instanciate object + * @param Db $zdb Database instance + * @param null|int|ArrayObject $args Either a ResultSet row or its id for to load + * a specific subscription, or null to just + * instanciate object */ public function __construct(Db $zdb, int|ArrayObject|null $args = null) { @@ -102,24 +102,24 @@ public function load(int $id): bool /** * Populate object from a resultset row * - * @param ArrayObject $r the resultset row + * @param ArrayObject $r the resultset row */ private function loadFromRS(ArrayObject $r): void { - $this->id = (int)$r->id_subscription; - $this->setActivity((int)$r->{Activity::PK}); - $this->setMember((int)$r->{Adherent::PK}); + $this->id = (int)$r['id_subscription']; + $this->setActivity((int)$r[Activity::PK]); + $this->setMember((int)$r[Adherent::PK]); $this->stored_activity = $this->id_activity; $this->stored_member = $this->id_member; - $this->paid = (bool)$r->is_paid; - if ($r->payment_amount !== null) { - $this->payment_amount = (float)$r->payment_amount; + $this->paid = (bool)$r['is_paid']; + if ($r['payment_amount'] !== null) { + $this->payment_amount = (float)$r['payment_amount']; } - $this->payment_method = (int)$r->payment_method; - $this->creation_date = $r->creation_date; - $this->subscription_date = $r->subscription_date; - $this->end_date = $r->end_date; - $this->comment = $r->comment ?? ''; + $this->payment_method = (int)$r['payment_method']; + $this->creation_date = $r['creation_date']; + $this->subscription_date = $r['subscription_date']; + $this->end_date = $r['end_date']; + $this->comment = $r['comment'] ?? ''; } /** diff --git a/phpstan.neon b/phpstan.neon index 299cd45..d2d1bae 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,6 +4,7 @@ parameters: level: 6 paths: - lib/ + - tests/ scanFiles: - _config.inc.php - ../../includes/sys_config/paths.inc.php @@ -14,5 +15,3 @@ parameters: - ../../lib/ universalObjectCratesClasses: - RKA\Session - ignoreErrors: - - '/Access to an undefined property ArrayObject.*/' diff --git a/tests/GaletteActivities/Controllers/Crud/tests/units/ActivitiesController.php b/tests/GaletteActivities/Controllers/Crud/tests/units/ActivitiesController.php index d039b62..2bcf25e 100644 --- a/tests/GaletteActivities/Controllers/Crud/tests/units/ActivitiesController.php +++ b/tests/GaletteActivities/Controllers/Crud/tests/units/ActivitiesController.php @@ -160,4 +160,123 @@ public function testConfirmRemovalCountsSubscriptions(): void $this->assertStringContainsString('2 subscriptions to this activity will be removed as well.', $body); $this->expectNoLogEntry(); } + + /** + * Activities list + */ + public function testList(): void + { + $this->logSuperAdmin(); + $group = $this->createGroup('Climbers'); + $this->insertActivity('Climbing', $group->getId(), ['price' => 12.5]); + $this->insertActivity('Hiking', null, ['price' => null]); + + $test_response = $this->app->handle($this->createRequest('activities_activities')); + $this->assertSame(200, $test_response->getStatusCode()); + $body = (string)$test_response->getBody(); + $this->assertStringContainsString('2 activities', $body); + $this->assertStringContainsString('Climbing', $body); + $this->assertStringContainsString('12.50', $body); + $this->assertStringContainsString('Climbers', $body); + $this->assertStringContainsString('Hiking', $body); + $this->expectNoLogEntry(); + } + + /** + * Creation and edition forms + */ + public function testForms(): void + { + $this->logSuperAdmin(); + $group = $this->createGroup('Climbers'); + $id = $this->insertActivity('Climbing', $group->getId(), ['type' => 'ESC', 'comment' => 'Indoor']); + + $test_response = $this->app->handle($this->createRequest('activities_activity_add')); + $this->assertSame(200, $test_response->getStatusCode()); + $body = (string)$test_response->getBody(); + $this->assertStringContainsString('action="' . $this->routeparser->urlFor('activities_storeactivity_add') . '"', $body); + $this->assertStringContainsString('Climbers', $body); + $this->expectNoLogEntry(); + + $test_response = $this->app->handle($this->createRequest('activities_activity_edit', ['id' => (string)$id])); + $this->assertSame(200, $test_response->getStatusCode()); + $body = (string)$test_response->getBody(); + $this->assertStringContainsString( + 'action="' . $this->routeparser->urlFor('activities_storeactivity_edit', ['id' => (string)$id]) . '"', + $body + ); + $this->assertStringContainsString('value="Climbing"', $body); + $this->assertStringContainsString('value="ESC"', $body); + $this->assertStringContainsString('Indoor', $body); + $this->assertMatchesRegularExpression('/getId() . '"\s+selected="selected"/', $body); + $this->expectNoLogEntry(); + } + + /** + * Activities are changed, invalid values are displayed again + */ + public function testEdit(): void + { + $this->logSuperAdmin(); + $id = $this->insertActivity('Climbing'); + $post = function (array $data) use ($id): \Psr\Http\Message\ResponseInterface { + $request = $this->createRequest('activities_storeactivity_edit', ['id' => (string)$id], 'POST') + ->withParsedBody($data + ['id' => (string)$id, 'comment' => '']); + return $this->app->handle($request); + }; + + $test_response = $post(['name' => 'Bouldering', 'price' => '8']); + $this->assertSame( + ['Location' => [$this->routeparser->urlFor('activities_activities')]], + $test_response->getHeaders() + ); + $this->expectNoLogEntry(); + $this->expectFlashData(['success_detected' => ['Activity has been modified.']]); + $activity = new \GaletteActivities\Entity\Activity($this->zdb, $id); + $this->assertSame('Bouldering', $activity->getName()); + $this->assertSame(8.0, $activity->getPrice()); + + $test_response = $post(['name' => 'Bouldering', 'type' => 'TOOLONG']); + $edit_url = $this->routeparser->urlFor('activities_activity_edit', ['id' => (string)$id]); + $this->assertSame(['Location' => [$edit_url]], $test_response->getHeaders()); + $this->assertSame(303, $test_response->getStatusCode()); + $this->expectLogEntry(\Analog\Analog::ERROR, 'Type is too long'); + $this->expectNoLogEntry(); + $this->expectFlashData(['error_detected' => ['Type is too long']]); + $this->assertSame('', (new \GaletteActivities\Entity\Activity($this->zdb, $id))->getType()); + + //form is displayed again from session + $this->assertNotNull($this->session->plugin_activities_activity); + $test_response = $this->app->handle($this->createRequest('activities_activity_edit', ['id' => (string)$id])); + $this->assertSame(200, $test_response->getStatusCode()); + $this->assertStringContainsString('value="Bouldering"', (string)$test_response->getBody()); + $this->assertFalse(isset($this->session->plugin_activities_activity)); + $this->expectNoLogEntry(); + } + + /** + * Activities are removed with their subscriptions + */ + public function testRemove(): void + { + $this->logSuperAdmin(); + $id = $this->insertActivity('Climbing'); + $this->insertSubscription($id, $this->getMemberOne()->id); + + //not confirmed + $request = $this->createRequest('activities_do_remove_activity', ['id' => (string)$id], 'POST'); + $this->app->handle($request->withParsedBody([])); + $this->expectFlashData(['error_detected' => ['Removal has not been confirmed!']]); + $this->assertSame(1, $this->countSubscriptions($id)); + + $test_response = $this->app->handle($request->withParsedBody(['confirm' => '1'])); + $this->assertSame( + ['Location' => [$this->routeparser->urlFor('activities_activities')]], + $test_response->getHeaders() + ); + $this->expectNoLogEntry(); + $this->expectFlashData(['success_detected' => ['Successfully deleted!']]); + $this->assertFalse((new \GaletteActivities\Entity\Activity($this->zdb))->load($id)); + $this->assertSame(0, $this->countSubscriptions($id)); + } } diff --git a/tests/GaletteActivities/Controllers/Crud/tests/units/SubscriptionsController.php b/tests/GaletteActivities/Controllers/Crud/tests/units/SubscriptionsController.php index 2a38390..7b215b9 100644 --- a/tests/GaletteActivities/Controllers/Crud/tests/units/SubscriptionsController.php +++ b/tests/GaletteActivities/Controllers/Crud/tests/units/SubscriptionsController.php @@ -210,4 +210,146 @@ public function testMemberFilter(): void $this->getSubscriptionsFilters()->reinit(); } + + /** + * Subscriptions list + */ + public function testList(): void + { + $this->logSuperAdmin(); + $member_one = $this->getMemberOne(); + $climbing = $this->insertActivity('Climbing'); + $this->insertSubscription($climbing, $member_one->id, ['payment_amount' => 12.5, 'is_paid' => true]); + $this->insertSubscription($this->insertActivity('Hiking'), $member_one->id, ['payment_amount' => 5]); + + $test_response = $this->app->handle($this->createRequest('activities_subscriptions')); + $this->assertSame(200, $test_response->getStatusCode()); + $body = (string)$test_response->getBody(); + $this->assertStringContainsString('2 subscriptions', $body); + $this->assertStringContainsString('Climbing', $body); + $this->assertStringContainsString('Hiking', $body); + $this->assertStringContainsString($member_one->sfullname, $body); + $this->assertStringContainsString('Found subscriptions total 17.5', $body); + $this->expectNoLogEntry(); + } + + /** + * Every filter is stored, and can be reset + */ + public function testFilters(): void + { + $this->logSuperAdmin(); + $member_one = $this->getMemberOne(); + $activity = $this->insertActivity('Climbing'); + + $request = $this->createRequest('activities_filter-subscriptionslist', [], 'POST'); + $test_response = $this->app->handle($request->withParsedBody([ + 'nbshow' => '20', + 'paid_filter' => (string)\GaletteActivities\Repository\Subscriptions::FILTER_NOT_PAID, + 'payment_type_filter' => (string)\Galette\Entity\PaymentType::CASH, + 'activity_filter' => (string)$activity, + 'member_filter' => (string)$member_one->id, + 'date_field' => (string)\GaletteActivities\Filters\SubscriptionsList::DATE_SUBSCRIPTION, + 'start_date_filter' => '2026-01-01', + 'end_date_filter' => '2026-12-31', + ])); + $this->assertSame( + ['Location' => [$this->routeparser->urlFor('activities_subscriptions')]], + $test_response->getHeaders() + ); + $this->assertSame(303, $test_response->getStatusCode()); + $this->expectNoLogEntry(); + + $filters = $this->getSubscriptionsFilters(); + $this->assertSame(20, $filters->show); + $this->assertSame(\GaletteActivities\Repository\Subscriptions::FILTER_NOT_PAID, (int)$filters->paid_filter); + $this->assertSame(\Galette\Entity\PaymentType::CASH, $filters->payment_type_filter); + $this->assertSame($activity, $filters->activity_filter); + $this->assertSame($member_one->id, $filters->member_filter); + $this->assertSame(\GaletteActivities\Filters\SubscriptionsList::DATE_SUBSCRIPTION, $filters->date_field); + $this->assertSame('2026-01-01', $filters->start_date_filter); + $this->assertSame('2026-12-31', $filters->end_date_filter); + + $this->app->handle($request->withParsedBody(['clear_filter' => '1'])); + $filters = $this->getSubscriptionsFilters(); + $this->assertNull($filters->activity_filter); + $this->assertNull($filters->member_filter); + $this->assertSame(-1, $filters->payment_type_filter); + $this->assertNull($filters->start_date_filter); + $this->expectNoLogEntry(); + } + + /** + * Creation form is reloaded with selected activity values, without storing + */ + public function testReloadForm(): void + { + $this->logSuperAdmin(); + $member_one = $this->getMemberOne(); + $activity = $this->insertActivity('Climbing', null, ['price' => 12.5]); + + $test_response = $this->app->handle($this->createRequest('activities_subscription_add', ['id_adh' => (string)$member_one->id])); + $this->assertSame(200, $test_response->getStatusCode()); + $this->expectNoLogEntry(); + + //activity change posts the form without save + $request = $this->createRequest('activities_storesubscription_add', [], 'POST') + ->withParsedBody(['activity' => (string)$activity, 'member' => (string)$member_one->id, 'id' => '']); + $test_response = $this->app->handle($request); + $this->assertSame( + ['Location' => [$this->routeparser->urlFor('activities_subscription_add')]], + $test_response->getHeaders() + ); + $this->expectLogEntry(\Analog\Analog::ERROR, 'Subscription date is mandatory'); + $this->expectNoLogEntry(); + $this->expectFlashData(['warning_detected' => ['Do not forget to store the subscription']]); + $this->assertSame(0, $this->countSubscriptions($activity)); + + $test_response = $this->app->handle($this->createRequest('activities_subscription_add')); + $this->assertSame(200, $test_response->getStatusCode()); + $body = (string)$test_response->getBody(); + $this->assertMatchesRegularExpression('/assertStringContainsString('placeholder="12.5"', $body); + $this->assertFalse(isset($this->session->plugin_activities_subscription)); + $this->expectNoLogEntry(); + } + + /** + * Subscriptions are edited + */ + public function testEdit(): void + { + $this->logSuperAdmin(); + $member_one = $this->getMemberOne(); + $activity = $this->insertActivity('Climbing'); + $id = $this->insertSubscription($activity, $member_one->id, ['comment' => 'First comment']); + + $test_response = $this->app->handle($this->createRequest('activities_subscription_edit', ['id' => (string)$id])); + $this->assertSame(200, $test_response->getStatusCode()); + $this->assertStringContainsString('First comment', (string)$test_response->getBody()); + $this->expectNoLogEntry(); + + $request = $this->createRequest('activities_storesubscription_edit', [], 'POST') + ->withParsedBody([ + 'id' => (string)$id, + 'activity' => (string)$activity, + 'member' => (string)$member_one->id, + 'subscription_date' => date('Y-m-d'), + 'end_date' => date('Y-m-d', strtotime('+1 year')), + 'payment_amount' => '7', + 'paid' => '1', + 'comment' => 'Changed comment', + 'save' => '1', + ]); + $test_response = $this->app->handle($request); + $this->assertSame(303, $test_response->getStatusCode()); + $this->expectNoLogEntry(); + $this->expectFlashData(['success_detected' => ['Subscription has been modified.']]); + + $subscription = new \GaletteActivities\Entity\Subscription($this->zdb, $id); + $this->assertSame('Changed comment', $subscription->getComment()); + $this->assertSame(7.0, $subscription->getAmount()); + $this->assertTrue($subscription->isPaid()); + $this->assertSame(1, $this->countSubscriptions($activity)); + } } diff --git a/tests/GaletteActivities/Entity/tests/units/Activity.php b/tests/GaletteActivities/Entity/tests/units/Activity.php index 2b54ec8..ce69008 100644 --- a/tests/GaletteActivities/Entity/tests/units/Activity.php +++ b/tests/GaletteActivities/Entity/tests/units/Activity.php @@ -11,6 +11,7 @@ namespace GaletteActivities\tests\units; use Galette\Tests\GaletteTestCase; +use GaletteActivities\tests\ActivitiesFixtures; /** * Activity tests @@ -19,6 +20,8 @@ */ class Activity extends GaletteTestCase { + use ActivitiesFixtures; + protected int $seed = 20240817102541; /** @@ -26,7 +29,9 @@ class Activity extends GaletteTestCase */ public function tearDown(): void { - $delete = $this->zdb->delete(ACTIVITIES_PREFIX . \GaletteActivities\Entity\Activity::TABLE); + $this->cleanActivities(); + + $delete = $this->zdb->delete(\Galette\Entity\Group::GROUPSUSERS_TABLE); $this->zdb->execute($delete); $delete = $this->zdb->delete(\Galette\Entity\Group::TABLE); @@ -194,4 +199,25 @@ public function testCheckPrice(): void $this->expectInvalid(['name' => 'Climbing', 'price' => 'twelve'], ['Price must be a number.']); } + + /** + * Subscriptions are removed with their activity, members stay in the group + */ + public function testRemoveCascades(): void + { + $member_one = $this->getMemberOne(); + $group = $this->createGroup('Activity group', [], [$member_one]); + $climbing = $this->insertActivity('Climbing', $group->getId()); + $hiking = $this->insertActivity('Hiking'); + $this->insertSubscription($climbing, $member_one->id); + $this->insertSubscription($hiking, $member_one->id); + + $activity = new \GaletteActivities\Entity\Activity($this->zdb, $climbing); + $this->assertSame($group->getId(), $activity->getGroup()?->getId()); + $this->assertTrue($activity->remove()); + + $this->assertSame(0, $this->countSubscriptions($climbing)); + $this->assertSame(1, $this->countSubscriptions($hiking)); + $this->assertTrue($this->isInGroup($group->getId(), $member_one->id)); + } } diff --git a/tests/GaletteActivities/Entity/tests/units/Subscription.php b/tests/GaletteActivities/Entity/tests/units/Subscription.php index 5f45f60..414a909 100644 --- a/tests/GaletteActivities/Entity/tests/units/Subscription.php +++ b/tests/GaletteActivities/Entity/tests/units/Subscription.php @@ -229,7 +229,7 @@ public function testCrud(): void //remove subscription $this->assertTrue($subscription->remove()); - $this->assertFalse($activity->load($subscription_id)); + $this->assertFalse((new \GaletteActivities\Entity\Subscription($this->zdb))->load($subscription_id)); //create a subscription with a group $subscription = new \GaletteActivities\Entity\Subscription($this->zdb); diff --git a/tests/GaletteActivities/Repository/tests/units/Activities.php b/tests/GaletteActivities/Repository/tests/units/Activities.php new file mode 100644 index 0000000..1b98fa9 --- /dev/null +++ b/tests/GaletteActivities/Repository/tests/units/Activities.php @@ -0,0 +1,79 @@ + + */ +class Activities extends GaletteTestCase +{ + use ActivitiesFixtures; + + protected int $seed = 20260927093012; + + /** + * Cleanup after each test method + */ + public function tearDown(): void + { + $this->cleanActivities(); + parent::tearDown(); + } + + /** + * Get activities names, in list order + * + * @param ActivitiesList $filters Filters + * + * @return array + */ + private function getListNames(ActivitiesList $filters): array + { + $activities = new \GaletteActivities\Repository\Activities($this->zdb, $this->login, $this->preferences, $filters); + $names = []; + foreach ($activities->getList() as $activity) { + $names[] = $activity->getName(); + } + $this->assertSame(3, $activities->getCount()); + return $names; + } + + /** + * Activities are ordered and paginated + */ + public function testList(): void + { + $this->insertActivity('Climbing', null, ['creation_date' => '2026-01-01']); + $this->insertActivity('Diving', null, ['creation_date' => '2026-03-01']); + $this->insertActivity('Hiking', null, ['creation_date' => '2026-02-01']); + + //newest first + $filters = new ActivitiesList(); + $this->assertSame(['Diving', 'Hiking', 'Climbing'], $this->getListNames($filters)); + + $filters->orderby = \GaletteActivities\Repository\Activities::ORDERBY_NAME; + $this->assertSame(['Hiking', 'Diving', 'Climbing'], $this->getListNames($filters)); + //same order again inverts direction + $filters->orderby = \GaletteActivities\Repository\Activities::ORDERBY_NAME; + $this->assertSame(['Climbing', 'Diving', 'Hiking'], $this->getListNames($filters)); + + $filters->show = 2; + $this->assertSame(['Climbing', 'Diving'], $this->getListNames($filters)); + $filters->current_page = 2; + $this->assertSame(['Hiking'], $this->getListNames($filters)); + } +} diff --git a/tests/GaletteActivities/Repository/tests/units/Subscriptions.php b/tests/GaletteActivities/Repository/tests/units/Subscriptions.php index eece2b6..75c3a6c 100644 --- a/tests/GaletteActivities/Repository/tests/units/Subscriptions.php +++ b/tests/GaletteActivities/Repository/tests/units/Subscriptions.php @@ -114,4 +114,116 @@ public function testPaid(): void $filters->paid_filter = \GaletteActivities\Repository\Subscriptions::FILTER_NOT_PAID; $this->assertSame([$not_paid], $this->getListIds($filters)); } + + /** + * Subscriptions are filtered by activity, member, payment type, dates and selection + */ + public function testFilters(): void + { + $member_one = $this->getMemberOne()->id; + $member_two = $this->getMemberTwo()->id; + $climbing = $this->insertActivity('Climbing'); + $hiking = $this->insertActivity('Hiking'); + $first = $this->insertSubscription($climbing, $member_one, [ + 'payment_method' => \Galette\Entity\PaymentType::CASH, + 'creation_date' => '2026-01-01', + 'subscription_date' => '2026-01-10', + 'end_date' => '2026-06-30', + ]); + $second = $this->insertSubscription($climbing, $member_two, [ + 'payment_method' => \Galette\Entity\PaymentType::OTHER, + 'creation_date' => '2026-02-01', + 'subscription_date' => '2026-03-01', + 'end_date' => '2026-12-31', + ]); + $third = $this->insertSubscription($hiking, $member_one, [ + 'payment_method' => \Galette\Entity\PaymentType::OTHER, + 'creation_date' => '2026-04-15', + 'subscription_date' => '2026-05-01', + 'end_date' => '2027-04-30', + ]); + + //no filter, latest end date first + $filters = new SubscriptionsList(); + $this->assertSame([$third, $second, $first], $this->getListIds($filters)); + + $filters->activity_filter = $climbing; + $this->assertSame([$second, $first], $this->getListIds($filters)); + $subscriptions = new \GaletteActivities\Repository\Subscriptions($this->zdb, $filters); + $subscriptions->getList(); + $this->assertSame(2, $subscriptions->getCount()); + + $filters = new SubscriptionsList(); + $filters->member_filter = $member_one; + $this->assertSame([$third, $first], $this->getListIds($filters)); + + $filters = new SubscriptionsList(); + $filters->payment_type_filter = \Galette\Entity\PaymentType::CASH; + $this->assertSame([$first], $this->getListIds($filters)); + + $filters = new SubscriptionsList(); + $filters->date_field = SubscriptionsList::DATE_SUBSCRIPTION; + $filters->start_date_filter = '2026-02-01'; + $this->assertSame([$third, $second], $this->getListIds($filters)); + + $filters = new SubscriptionsList(); + $filters->date_field = SubscriptionsList::DATE_END; + $filters->end_date_filter = '2026-12-31'; + $this->assertSame([$second, $first], $this->getListIds($filters)); + + $filters = new SubscriptionsList(); + $filters->date_field = SubscriptionsList::DATE_CREATION; + $filters->start_date_filter = '2026-02-01'; + $filters->end_date_filter = '2026-03-01'; + $this->assertSame([$second], $this->getListIds($filters)); + + $filters = new SubscriptionsList(); + $filters->selected = [$first, $third]; + $this->assertSame([$third, $first], $this->getListIds($filters)); + + //filters are reset + $filters->reinit(); + $this->assertSame([$third, $second, $first], $this->getListIds($filters)); + } + + /** + * Subscriptions are ordered on every column + */ + public function testOrders(): void + { + $member_one = $this->getMemberOne(); + $member_two = $this->getMemberTwo(); + $climbing = $this->insertActivity('Climbing'); + $hiking = $this->insertActivity('Hiking'); + $first = $this->insertSubscription($climbing, $member_one->id, [ + 'payment_amount' => 30, + 'subscription_date' => '2026-03-01', + 'end_date' => '2026-06-30', + ]); + $second = $this->insertSubscription($hiking, $member_two->id, [ + 'payment_amount' => 10, + 'subscription_date' => '2026-01-01', + 'end_date' => '2026-12-31', + ]); + + $by_name = strcmp($member_one->name . ' ' . $member_one->surname, $member_two->name . ' ' . $member_two->surname) > 0 + ? [$first, $second] + : [$second, $first]; + $expected = [ + \GaletteActivities\Repository\Subscriptions::ORDERBY_ACTIVITY => [$second, $first], + \GaletteActivities\Repository\Subscriptions::ORDERBY_MEMBER => $by_name, + \GaletteActivities\Repository\Subscriptions::ORDERBY_SUBSCRIPTIONDATE => [$first, $second], + \GaletteActivities\Repository\Subscriptions::ORDERBY_ENDDATE => [$second, $first], + \GaletteActivities\Repository\Subscriptions::ORDERBY_AMOUNT => [$first, $second], + ]; + foreach ($expected as $orderby => $ids) { + //default direction is descending + $filters = new SubscriptionsList(); + $filters->orderby = $orderby; + $filters->setDirection(\Galette\Enums\SQLOrder::DESC); + $this->assertSame($ids, $this->getListIds($filters), 'order ' . $orderby); + $filters->invertorder(); + $this->assertSame(array_reverse($ids), $this->getListIds($filters), 'order ' . $orderby . ' ascending'); + } + } } diff --git a/tests/GaletteActivities/tests/units/PluginGaletteActivities.php b/tests/GaletteActivities/tests/units/PluginGaletteActivities.php new file mode 100644 index 0000000..7ae6f67 --- /dev/null +++ b/tests/GaletteActivities/tests/units/PluginGaletteActivities.php @@ -0,0 +1,128 @@ + + */ +class PluginGaletteActivities extends GaletteTestCase +{ + use ActivitiesFixtures; + + protected int $seed = 20260927091512; + + /** + * Cleanup after each test method + */ + public function tearDown(): void + { + $this->login->logout(); + parent::tearDown(); + } + + /** + * Get plugin instance + */ + private function getPlugin(): \GaletteActivities\PluginGaletteActivities + { + return $this->container->get(\GaletteActivities\PluginGaletteActivities::class); + } + + /** + * Get routes names of menus entries + * + * @param array $menus Menus + * + * @return array> + */ + private function getMenusRoutes(array $menus): array + { + $routes = []; + foreach ($menus as $section => $menu) { + $routes[$section] = array_map( + fn(array $item): string => $item['route']['name'], + $menu['items'] + ); + } + return $routes; + } + + /** + * Menus and member actions are for staff only + */ + public function testMenusAndActions(): void + { + $plugin = $this->getPlugin(); + $member_one = $this->getMemberOne(); + $member_two = $this->getMemberTwo(); + $expected_menus = ['plugin_activities' => ['activities_activities', 'activities_subscriptions']]; + + //visitor + $this->assertSame([], $plugin->getMenus()); + $this->assertSame([], $plugin->getListActions($member_two)); + + //member, and group manager + $this->createGroup('Managed group', [$member_one], [$member_two]); + $this->logMember($this->dataAdherentOne()); + $this->assertTrue($this->login->isGroupManager()); + $this->assertSame([], $plugin->getMenus()); + $this->assertSame([], $plugin->getListActions($member_two)); + $this->assertSame([], $plugin->getDetailedActions($member_two)); + $this->login->logout(); + + //staff + $staff = $this->getStaffMember($member_one); + $this->logMember($this->dataAdherentOne()); + $this->assertTrue($this->login->isStaff()); + //results depend on logged in user + $plugin = $this->getPlugin(); + $this->assertSame($expected_menus, $this->getMenusRoutes($plugin->getMenus())); + $actions = $plugin->getListActions($member_two); + $this->assertCount(1, $actions); + $this->assertSame( + ['name' => 'activities_subscription_add', 'args' => ['id_adh' => $member_two->id]], + $actions[0]['route'] + ); + $this->assertStringContainsString($member_two->sname, $actions[0]['label']); + $this->assertSame($actions, $plugin->getDetailedActions($member_two)); + $this->login->logout(); + $this->resetStaffStatus($staff, $member_two); + + //administrator + $this->logSuperAdmin(); + $this->assertSame($expected_menus, $this->getMenusRoutes($plugin->getMenus())); + $this->assertCount(1, $plugin->getListActions($member_two)); + } + + /** + * Nothing public, no batch actions + */ + public function testPublicMenusAndBatchActions(): void + { + $plugin = $this->getPlugin(); + $this->logSuperAdmin(); + $this->assertSame([], $plugin->getPublicMenus()); + $this->assertSame([], $plugin->getBatchActions()); + } + + /** + * Plugin is installed once its tables exist + */ + public function testIsInstalled(): void + { + $this->assertTrue($this->getPlugin()->isInstalled()); + } +} diff --git a/tests/TestsBootstrap.php b/tests/TestsBootstrap.php index d444501..46dca5a 100644 --- a/tests/TestsBootstrap.php +++ b/tests/TestsBootstrap.php @@ -34,8 +34,8 @@ */ define('GALETTE_PLUGINS_PATH', __DIR__ . '/../../'); -$basepath = '../../../galette/'; +$basepath = __DIR__ . '/../../../'; // phpcs:ignore SlevomatCodingStandard.Variables.UnusedVariable.UnusedVariable -- used from Core testBootstrap -include_once '../../../tests/TestsBootstrap.php'; +include_once __DIR__ . '/../../../../tests/TestsBootstrap.php'; require_once __DIR__ . '/../_config.inc.php'; require_once __DIR__ . '/ActivitiesFixtures.php';