diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
new file mode 100644
index 0000000..855d8cb
--- /dev/null
+++ b/.github/workflows/run-tests.yml
@@ -0,0 +1,57 @@
+name: Tests
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: true
+ matrix:
+ os: [ubuntu-latest, windows-latest]
+ php: [8.0, 8.1, 8.2]
+ laravel: [^8.83.27, ^9.51.0, ^10.0.0]
+ stability: [prefer-lowest, prefer-stable]
+ include:
+ - laravel: ^10.0.0
+ testbench: ^8.0.0
+ - laravel: ^9.51.0
+ testbench: ^7.22.0
+ - laravel: ^8.83.27
+ testbench: ^6.25.1
+ exclude:
+ - php: 8.0
+ laravel: ^10.0.0
+ - php: 8.1
+ laravel: ^8.83.27
+
+
+ name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
+ coverage: xdebug
+
+ - name: Setup problem matchers
+ run: |
+ echo "::add-matcher::${{ runner.tool_cache }}/php.json"
+ echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
+
+ - name: Install dependencies
+ run: |
+ composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
+ composer update --${{ matrix.stability }} --prefer-dist --no-interaction
+ composer require nesbot/carbon:^2.68.1
+
+ - name: List Installed Dependencies
+ run: composer show -D
+
+ - name: Execute test
+ run: composer test -- --ci
diff --git a/composer.json b/composer.json
index 0743c66..0c8eb1d 100644
--- a/composer.json
+++ b/composer.json
@@ -22,13 +22,16 @@
}
],
"require": {
- "php": "^7.3|^8.0",
- "laravel/framework": "~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0"
+ "php": "^8.0",
+ "illuminate/contracts": "^8.83.27|^9.51.0|^10.0.0",
+ "spatie/laravel-package-tools": "^1.12"
},
"require-dev": {
- "orchestra/testbench": "~3.8|^4.0|^5.0|^7.0|^8.0",
+ "orchestra/testbench": "^6.25.1|^7.22.0|^8.0.0",
"mockery/mockery": "^0.9.4 || ~1.0",
- "phpunit/phpunit": "~8.5|^9.0"
+ "pestphp/pest": "^1.23.1|^2.11",
+ "pestphp/pest-plugin-laravel": "^1.4|^2.1",
+ "laravel/pint": "^1.5"
},
"autoload": {
"psr-4": {
@@ -51,6 +54,13 @@
}
},
"scripts": {
- "test": "vendor/bin/phpunit"
+ "test": "vendor/bin/pest",
+ "format": "vendor/bin/pint",
+ "format-dryrun": "vendor/bin/pint --test"
+ },
+ "config": {
+ "allow-plugins": {
+ "pestphp/pest-plugin": true
+ }
}
}
diff --git a/src/config/gamify.php b/config/gamify.php
similarity index 93%
rename from src/config/gamify.php
rename to config/gamify.php
index b40050a..0fb8f48 100644
--- a/src/config/gamify.php
+++ b/config/gamify.php
@@ -1,5 +1,7 @@
'\App\User',
@@ -33,5 +35,5 @@
],
// Default level
- 'badge_default_level' => 1
+ 'badge_default_level' => 1,
];
diff --git a/src/migrations/add_reputation_on_user_table.php.stub b/database/migrations/add_reputation_on_user_table.php.stub
similarity index 54%
rename from src/migrations/add_reputation_on_user_table.php.stub
rename to database/migrations/add_reputation_on_user_table.php.stub
index 25213db..39cd41c 100644
--- a/src/migrations/add_reputation_on_user_table.php.stub
+++ b/database/migrations/add_reputation_on_user_table.php.stub
@@ -1,31 +1,23 @@
getTable(), function (Blueprint $table) {
$table->unsignedInteger('reputation')->default(0)->after('remember_token');
});
}
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
+ public function down(): void
{
- Schema::table('users', function (Blueprint $table) {
+ Schema::table(app(config('gamify.payee_model'))->getTable(), function (Blueprint $table) {
$table->dropColumn('reputation');
});
}
diff --git a/src/migrations/create_gamify_tables.php.stub b/database/migrations/create_gamify_tables.php.stub
similarity index 83%
rename from src/migrations/create_gamify_tables.php.stub
rename to database/migrations/create_gamify_tables.php.stub
index d1f05af..2b8da5f 100644
--- a/src/migrations/create_gamify_tables.php.stub
+++ b/database/migrations/create_gamify_tables.php.stub
@@ -1,19 +1,15 @@
increments('id');
$table->string('name');
@@ -25,7 +21,6 @@ class CreateGamifyTables extends Migration
$table->timestamps();
});
- // badges table
Schema::create('badges', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
@@ -35,7 +30,6 @@ class CreateGamifyTables extends Migration
$table->timestamps();
});
- // user_badges pivot
Schema::create('user_badges', function (Blueprint $table) {
$table->primary(['user_id', 'badge_id']);
$table->unsignedInteger('user_id');
@@ -44,12 +38,7 @@ class CreateGamifyTables extends Migration
});
}
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
+ public function down(): void
{
Schema::dropIfExists('user_badges');
Schema::dropIfExists('badges');
diff --git a/phpunit.xml b/phpunit.xml
index 9024953..48dda83 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,21 +1,20 @@
-
-
-
- ./tests/
-
-
-
-
-
-
+
+
+ ./tests/Feature
+
+
+
+
+
diff --git a/pint.json b/pint.json
new file mode 100644
index 0000000..ed80727
--- /dev/null
+++ b/pint.json
@@ -0,0 +1,92 @@
+{
+ "preset": "psr12",
+ "exclude": [
+ "src"
+ ],
+ "notPath": [
+ ".phpunit.result.cache"
+ ],
+ "rules": {
+ "align_multiline_comment": true,
+ "array_indentation": true,
+ "array_syntax": true,
+ "assign_null_coalescing_to_coalesce_equal": true,
+ "binary_operator_spaces": true,
+ "blank_line_before_statement": {
+ "statements": [
+ "break",
+ "continue",
+ "declare",
+ "return",
+ "throw",
+ "try"
+ ]
+ },
+ "cast_spaces": true,
+ "class_attributes_separation": {
+ "elements": {
+ "method": "one"
+ }
+ },
+ "clean_namespace": true,
+ "combine_consecutive_issets": true,
+ "combine_consecutive_unsets": true,
+ "declare_strict_types": true,
+ "doctrine_annotation_indentation": true,
+ "doctrine_annotation_spaces": true,
+ "fully_qualified_strict_types": true,
+ "function_typehint_space": true,
+ "global_namespace_import": true,
+ "heredoc_indentation": true,
+ "include": true,
+ "lambda_not_used_import": true,
+ "linebreak_after_opening_tag": true,
+ "list_syntax": true,
+ "magic_constant_casing": true,
+ "magic_method_casing": true,
+ "method_argument_space": {
+ "on_multiline": "ensure_fully_multiline",
+ "keep_multiple_spaces_after_comma": true
+ },
+ "method_chaining_indentation": true,
+ "multiline_comment_opening_closing": true,
+ "multiline_whitespace_before_semicolons": true,
+ "native_function_casing": true,
+ "native_function_type_declaration_casing": true,
+ "no_alias_language_construct_call": true,
+ "no_alternative_syntax": true,
+ "no_empty_comment": true,
+ "no_empty_statement": true,
+ "no_extra_blank_lines": true,
+ "no_leading_namespace_whitespace": true,
+ "no_mixed_echo_print": true,
+ "no_multiline_whitespace_around_double_arrow": true,
+ "no_multiple_statements_per_line": true,
+ "no_singleline_whitespace_before_semicolons": true,
+ "no_spaces_around_offset": true,
+ "no_unneeded_import_alias": true,
+ "no_unused_imports": true,
+ "no_whitespace_before_comma_in_array": true,
+ "no_whitespace_in_blank_line": true,
+ "not_operator_with_space": true,
+ "not_operator_with_successor_space": true,
+ "php_unit_fqcn_annotation": true,
+ "phpdoc_line_span": {
+ "const": "single",
+ "method": "single",
+ "property": "single"
+ },
+ "phpdoc_scalar": true,
+ "phpdoc_single_line_var_spacing": true,
+ "phpdoc_var_without_name": true,
+ "simple_to_complex_string_variable": true,
+ "simplified_if_return": true,
+ "single_quote": true,
+ "standardize_not_equals": true,
+ "trailing_comma_in_multiline": true,
+ "trim_array_spaces": true,
+ "types_spaces": true,
+ "unary_operator_spaces": true,
+ "whitespace_after_comma_in_array": true
+ }
+}
diff --git a/src/GamifyServiceProvider.php b/src/GamifyServiceProvider.php
index af2335a..7e4e177 100644
--- a/src/GamifyServiceProvider.php
+++ b/src/GamifyServiceProvider.php
@@ -9,56 +9,36 @@
use QCod\Gamify\Console\MakeBadgeCommand;
use QCod\Gamify\Console\MakePointCommand;
use QCod\Gamify\Events\ReputationChanged;
+use Spatie\LaravelPackageTools\Package;
+use Spatie\LaravelPackageTools\PackageServiceProvider;
-class GamifyServiceProvider extends ServiceProvider
+class GamifyServiceProvider extends PackageServiceProvider
{
- /**
- * Perform post-registration booting of services.
- *
- * @return void
- */
- public function boot()
- {
- // publish config
- $this->publishes([
- __DIR__ . '/config/gamify.php' => config_path('gamify.php'),
- ], 'config');
-
- $this->mergeConfigFrom(__DIR__ . '/config/gamify.php', 'gamify');
-
- // publish migration
- if (!class_exists('CreateGamifyTables')) {
- $timestamp = date('Y_m_d_His', time());
- $this->publishes([
- __DIR__ . '/migrations/create_gamify_tables.php.stub' => database_path("/migrations/{$timestamp}_create_gamify_tables.php"),
- __DIR__ . '/migrations/add_reputation_on_user_table.php.stub' => database_path("/migrations/{$timestamp}_add_reputation_field_on_user_table.php"),
- ], 'migrations');
- }
- // register commands
- if ($this->app->runningInConsole()) {
- $this->commands([
+ public function configurePackage(Package $package): void
+ {
+ $package->name('gamify')
+ ->hasConfigFile()
+ ->hasMigrations([
+ 'add_reputation_on_user_table',
+ 'create_gamify_tables',
+ ])
+ ->hasCommands([
MakePointCommand::class,
MakeBadgeCommand::class,
]);
- }
+ }
- // register event listener
+ public function packageBooted(): void
+ {
Event::listen(ReputationChanged::class, SyncBadges::class);
}
- /**
- * Register bindings in the container.
- *
- * @return void
- */
- public function register()
+ public function packageRegistered(): void
{
$this->app->singleton('badges', function () {
return cache()->rememberForever('gamify.badges.all', function () {
- return $this->getBadges()->map(function ($badge) {
- return new $badge;
- });
+ return $this->getBadges()->map(fn($badge) => new $badge);
});
});
}
@@ -66,9 +46,9 @@ public function register()
/**
* Get all the badge inside app/Gamify/Badges folder
*
- * @return Collection
+ * @return Collection
*/
- protected function getBadges()
+ protected function getBadges(): Collection
{
$badgeRootNamespace = config(
'gamify.badge_namespace',
diff --git a/src/HasBadges.php b/src/HasBadges.php
index 6cdd50f..0f9e7f6 100644
--- a/src/HasBadges.php
+++ b/src/HasBadges.php
@@ -2,6 +2,10 @@
namespace QCod\Gamify;
+/**
+ * @property-read \Illuminate\Database\Eloquent\Collection $badges
+ * @property-read int|null $badges_count
+ */
trait HasBadges
{
/**
diff --git a/src/HasReputations.php b/src/HasReputations.php
index 8b46717..b0410cf 100644
--- a/src/HasReputations.php
+++ b/src/HasReputations.php
@@ -4,6 +4,10 @@
use QCod\Gamify\Events\ReputationChanged;
+/**
+ * @property-read \Illuminate\Database\Eloquent\Collection $reputations
+ * @property-read int|null $reputations_count
+ */
trait HasReputations
{
/**
diff --git a/tests/BadgeTest.php b/tests/BadgeTest.php
deleted file mode 100644
index 5df4071..0000000
--- a/tests/BadgeTest.php
+++ /dev/null
@@ -1,77 +0,0 @@
-createUser();
- $badge = $this->createBadge();
-
- $badge->awardTo($user);
-
- $this->assertCount(1, $user->badges);
- $this->assertTrue($user->badges->first()->is($badge));
- }
-
- /**
- * a badge can be remove from a user
- *
- * @test
- */
- public function a_badge_can_be_remove_from_a_user()
- {
- $user = $this->createUser();
- $badge = $this->createBadge();
- $badge->awardTo($user);
- $this->assertCount(1, $user->badges);
-
- $badge->removeFrom($user);
-
- $this->assertCount(0, $user->fresh()->badges);
- }
-
- /**
- * a badge is awarded if user point reached 1000
- *
- * @test
- */
- public function a_badge_is_awarded_if_user_point_reached_1000()
- {
- $user = $this->createUser();
- $this->assertCount(0, $user->badges);
-
- $user->addPoint(1001);
-
- $this->assertCount(1, $user->fresh()->badges);
-
- $user->reducePoint(10);
-
- $this->assertCount(0, $user->fresh()->badges);
- }
-
- /**
- * a badge is given when user first creats a post
- *
- * @test
- */
- public function a_badge_is_given_when_user_first_creats_a_post()
- {
- $user = $this->createUser();
- $this->assertCount(0, $user->badges);
-
- $this->createPost(['user_id' => $user->id]);
- $this->assertCount(0, $user->fresh()->badges);
-
- $user->addPoint(20);
- $this->assertCount(1, $user->fresh()->badges);
-
- $this->assertEquals('First Contribution', $user->fresh()->badges->first()->name);
- }
-}
diff --git a/tests/Feature/BadgeTest.php b/tests/Feature/BadgeTest.php
new file mode 100644
index 0000000..626e586
--- /dev/null
+++ b/tests/Feature/BadgeTest.php
@@ -0,0 +1,57 @@
+awardTo($user);
+
+ assertCount(1, $user->badges);
+ assertTrue($user->badges->first()->is($badge));
+});
+
+test('a badge can be remove from a user', function () {
+ $user = createUser();
+ $badge = createBadge();
+ $badge->awardTo($user);
+ assertCount(1, $user->badges);
+
+ $badge->removeFrom($user);
+
+ assertCount(0, $user->fresh()->badges);
+});
+
+test(
+ 'a badge is awarded if user point reached 1000',
+ function () {
+ $user = createUser();
+ assertCount(0, $user->badges);
+
+ $user->addPoint(1001);
+
+ assertCount(1, $user->fresh()->badges);
+
+ $user->reducePoint(10);
+
+ assertCount(0, $user->fresh()->badges);
+ }
+);
+
+test('a badge is given when user first creat a post', function () {
+ $user = createUser();
+ assertCount(0, $user->badges);
+
+ createPost(['user_id' => $user->id]);
+ assertCount(0, $user->fresh()->badges);
+
+ $user->addPoint(20);
+ assertCount(1, $user->fresh()->badges);
+
+ assertEquals('First Contribution', $user->fresh()->badges->first()->name);
+});
diff --git a/tests/Feature/PointTest.php b/tests/Feature/PointTest.php
new file mode 100644
index 0000000..4bb2aec
--- /dev/null
+++ b/tests/Feature/PointTest.php
@@ -0,0 +1,174 @@
+getName());
+});
+
+it('uses name property for point name if provided', function () {
+ $point = new FakeWelcomeUserWithNamePoint(1);
+
+ assertEquals('FakeName', $point->getName());
+});
+
+it('can get points for a point type', function () {
+ $point = new FakeCreatePostPoint(1);
+
+ assertEquals(10, $point->getPoints());
+});
+
+it('gives point to a user', function () {
+ $user = createUser();
+ $post = createPost(['user_id' => $user->id]);
+
+ $user->givePoint(new FakeCreatePostPoint($post));
+
+ assertEquals(10, $user->fresh()->getPoints());
+ assertCount(1, $user->reputations);
+ assertDatabaseHas('reputations', [
+ 'payee_id' => $user->id,
+ 'subject_type' => $post->getMorphClass(),
+ 'subject_id' => $post->id,
+ 'point' => 10,
+ 'name' => 'FakeCreatePostPoint',
+ ]);
+});
+
+it('can access a reputation payee and subject', function () {
+ $user = createUser();
+ $post = createPost(['user_id' => $user->id]);
+
+ $user->givePoint(new FakeCreatePostPoint($post));
+
+ $point = $user->reputations()->first();
+
+ assertEquals($user->id, $point->payee->id);
+ assertEquals($post->id, $point->subject->id);
+
+ assertEquals('FakeCreatePostPoint', $post->reputations->first()->name);
+});
+
+it('only adds unique point reward if property is set on point type', function () {
+ $user = createUser();
+ $post = createPost(['user_id' => $user->id]);
+
+ $user->givePoint(new FakeCreatePostPoint($post));
+ $user->givePoint(new FakeCreatePostPoint($post));
+
+ assertEquals(10, $user->fresh()->getPoints());
+ assertCount(1, $user->reputations);
+});
+
+it('can store duplicate reputations if no property set', function () {
+ $user = createUser();
+ $post = createPost(['user_id' => $user->id]);
+
+ $user->givePoint(new FakeWelcomeUserWithNamePoint($post));
+ $user->givePoint(new FakeWelcomeUserWithNamePoint($post));
+
+ assertEquals(60, $user->fresh()->getPoints());
+ assertCount(2, $user->reputations);
+});
+
+it('do not give point if qualifier returns false', function () {
+ $user = createUser();
+ $post = createPost(['user_id' => $user->id]);
+
+ $user->givePoint(new FakeWelcomeUserWithFalseQualifier($post));
+
+ assertEquals(0, $user->fresh()->getPoints());
+ assertCount(0, $user->reputations);
+});
+
+it('uses payee field on point as relation if no payee method override', function () {
+ $user = createUser();
+ $post = createPost(['user_id' => $user->id]);
+
+ $user->givePoint(new FakePayeeFieldPoint($post));
+
+ assertEquals(10, $user->fresh()->getPoints());
+ assertCount(1, $user->reputations);
+});
+
+it('can undo a reward by given model', function () {
+ $user = createUser();
+ $post = createPost(['user_id' => $user->id]);
+ $user->givePoint(new FakeWelcomeUserWithNamePoint($post));
+ $user->givePoint(new FakeWelcomeUserWithNamePoint($post));
+ assertEquals(60, $user->fresh()->getPoints());
+ assertCount(2, $user->reputations);
+
+ $user->undoPoint(new FakeWelcomeUserWithNamePoint($post));
+
+ assertEquals(30, $user->fresh()->getPoints());
+ assertCount(1, $user->fresh()->reputations);
+
+ $user->undoPoint(new FakeWelcomeUserWithNamePoint($post));
+
+ assertEquals(0, $user->fresh()->getPoints());
+ assertCount(0, $user->fresh()->reputations);
+});
+
+it('throws exception if no payee is returned', function () {
+ $user = createUser();
+ $user->givePoint(new FakePointTypeWithoutPayee());
+
+ assertEquals(0, $user->fresh()->getPoints());
+ assertCount(0, $user->reputations);
+})
+ ->throws(InvalidPayeeModel::class);
+
+it('throws exception if no subject is set', function () {
+ $user = createUser();
+
+ $user->givePoint(new FakePointTypeWithoutSubject());
+
+ assertEquals(0, $user->fresh()->getPoints());
+ assertCount(0, $user->reputations);
+})
+ ->throws(PointSubjectNotSet::class);
+
+it('throws exception if no points field or method is defined', function () {
+ $user = createUser();
+ $post = createPost(['user_id' => $user->id]);
+
+ $user->givePoint(new FakePointWithoutPoint($post));
+
+ assertEquals(0, $user->fresh()->getPoints());
+ assertCount(0, $user->reputations);
+})
+ ->throws(PointsNotDefined::class);
+
+it('gives and undo point via helper functions', function () {
+ $user = createUser();
+ $post = createPost(['user_id' => $user->id]);
+
+ givePoint(new FakePayeeFieldPoint($post), $user);
+
+ assertEquals(10, $user->fresh()->getPoints());
+ assertCount(1, $user->reputations);
+
+ undoPoint(new FakePayeeFieldPoint($post), $user);
+
+ $user = $user->fresh();
+ assertEquals(0, $user->getPoints());
+ assertCount(0, $user->reputations);
+});
diff --git a/tests/Feature/ReputationTest.php b/tests/Feature/ReputationTest.php
new file mode 100644
index 0000000..4a9109d
--- /dev/null
+++ b/tests/Feature/ReputationTest.php
@@ -0,0 +1,73 @@
+ 10]);
+
+ assertEquals(10, $user->getPoints());
+});
+
+it('gives reputation point to a user', function () {
+ $user = createUser();
+ assertEquals(0, $user->getPoints());
+
+ $user->addPoint(10);
+
+ assertEquals(10, $user->fresh()->getPoints());
+});
+
+it('reduces reputation point for a user', function () {
+ $user = createUser(['reputation' => 20]);
+ assertEquals(20, $user->reputation);
+
+ $user->reducePoint(5);
+
+ assertEquals(15, $user->fresh()->getPoints());
+});
+
+it('zeros reputation point of a user', function () {
+ $user = createUser(['reputation' => 50]);
+ assertEquals(50, $user->getPoints());
+
+ $user->resetPoint();
+
+ assertEquals(0, $user->fresh()->getPoints());
+});
+
+it('fires event on reputation change', function () {
+ Event::fake();
+
+ $user = createUser();
+ assertEquals(0, $user->getPoints());
+
+ $user->addPoint(10);
+
+ Event::assertDispatched(ReputationChanged::class, function ($event) use ($user) {
+ return ($event->point === 10 && $user->id == $event->user->id && $event->increment);
+ });
+
+ assertEquals(10, $user->fresh()->getPoints());
+});
+
+it('fires event on reputation reduced', function () {
+ Event::fake();
+
+ $user = createUser(['reputation' => 10]);
+
+ $user->reducePoint(3);
+
+ Event::assertDispatched(ReputationChanged::class, function ($event) use ($user) {
+ return ($event->point === 3 && $user->id == $event->user->id && ! $event->increment);
+ });
+
+ assertEquals(7, $user->fresh()->getPoints());
+});
diff --git a/tests/Badges/FirstContribution.php b/tests/Fixtures/Badges/FirstContribution.php
similarity index 79%
rename from tests/Badges/FirstContribution.php
rename to tests/Fixtures/Badges/FirstContribution.php
index bbbba16..367324e 100644
--- a/tests/Badges/FirstContribution.php
+++ b/tests/Fixtures/Badges/FirstContribution.php
@@ -1,9 +1,11 @@
subject = $subject;
+ }
+
+ public function payee()
+ {
+ return $this->getSubject()->user;
+ }
+}
diff --git a/tests/Fixtures/Fake/PointTypes/FakePayeeFieldPoint.php b/tests/Fixtures/Fake/PointTypes/FakePayeeFieldPoint.php
new file mode 100644
index 0000000..a12e1a9
--- /dev/null
+++ b/tests/Fixtures/Fake/PointTypes/FakePayeeFieldPoint.php
@@ -0,0 +1,20 @@
+subject = $subject;
+ }
+
+ /** @var string payee model relation on subject */
+ protected $payee = 'user';
+}
diff --git a/tests/Fixtures/Fake/PointTypes/FakePointTypeWithoutPayee.php b/tests/Fixtures/Fake/PointTypes/FakePointTypeWithoutPayee.php
new file mode 100644
index 0000000..7b9c5b2
--- /dev/null
+++ b/tests/Fixtures/Fake/PointTypes/FakePointTypeWithoutPayee.php
@@ -0,0 +1,16 @@
+subject = $subject;
+ }
+}
diff --git a/tests/Fixtures/Fake/PointTypes/FakeWelcomeUserWithFalseQualifier.php b/tests/Fixtures/Fake/PointTypes/FakeWelcomeUserWithFalseQualifier.php
new file mode 100644
index 0000000..3002bd6
--- /dev/null
+++ b/tests/Fixtures/Fake/PointTypes/FakeWelcomeUserWithFalseQualifier.php
@@ -0,0 +1,27 @@
+subject = $subject;
+ }
+
+ public function qualifier()
+ {
+ return false;
+ }
+
+ public function payee()
+ {
+ return $this->getSubject()->user;
+ }
+}
diff --git a/tests/Fixtures/Fake/PointTypes/FakeWelcomeUserWithNamePoint.php b/tests/Fixtures/Fake/PointTypes/FakeWelcomeUserWithNamePoint.php
new file mode 100644
index 0000000..a0295fe
--- /dev/null
+++ b/tests/Fixtures/Fake/PointTypes/FakeWelcomeUserWithNamePoint.php
@@ -0,0 +1,24 @@
+subject = $subject;
+ }
+
+ public function payee()
+ {
+ return $this->getSubject()->user;
+ }
+}
diff --git a/tests/Fixtures/Models/Post.php b/tests/Fixtures/Models/Post.php
new file mode 100644
index 0000000..95ed0d8
--- /dev/null
+++ b/tests/Fixtures/Models/Post.php
@@ -0,0 +1,46 @@
+ $reputations
+ */
+class Post extends Model
+{
+ public $table = 'test_posts';
+
+ protected $guarded = [];
+
+ public function replies(): HasMany
+ {
+ return $this->hasMany(Reply::class)->latest();
+ }
+
+ public function user(): BelongsTo
+ {
+ return $this->belongsTo(User::class);
+ }
+
+ public function bestReply(): HasOne
+ {
+ return $this->hasOne(Reply::class, 'id', 'best_reply_id');
+ }
+
+ public function reputations(): MorphMany
+ {
+ return $this->morphMany(Reputation::class, 'subject');
+ }
+}
diff --git a/tests/Fixtures/Models/Reply.php b/tests/Fixtures/Models/Reply.php
new file mode 100644
index 0000000..0a824af
--- /dev/null
+++ b/tests/Fixtures/Models/Reply.php
@@ -0,0 +1,30 @@
+belongsTo(User::class);
+ }
+
+ public function post(): BelongsTo
+ {
+ return $this->belongsTo(Post::class);
+ }
+}
diff --git a/tests/Fixtures/Models/User.php b/tests/Fixtures/Models/User.php
new file mode 100644
index 0000000..1b03076
--- /dev/null
+++ b/tests/Fixtures/Models/User.php
@@ -0,0 +1,27 @@
+ $posts
+ */
+class User extends Model
+{
+ use Gamify;
+
+ public $table = 'test_users';
+
+ protected $guarded = [];
+
+ public function posts(): HasMany
+ {
+ return $this->hasMany(Post::class);
+ }
+}
diff --git a/tests/Helpers.php b/tests/Helpers.php
new file mode 100644
index 0000000..3363032
--- /dev/null
+++ b/tests/Helpers.php
@@ -0,0 +1,46 @@
+forceFill(array_merge($attributes, [
+ 'name' => 'Saqueib',
+ 'email' => 'me@example.com',
+ 'password' => 'secret',
+ ]))->save();
+
+ return $user->fresh();
+}
+
+function createPost(array $attributes = []): Post
+{
+ $post = new Post();
+
+ $post->forceFill(array_merge($attributes, [
+ 'title' => 'Dummy post title',
+ 'body' => 'I am the content on dummy post',
+ 'user_id' => 1,
+ ]))->save();
+
+ return $post->fresh();
+}
+
+function createBadge(array $attributes = []): Badge
+{
+ $badge = new Badge();
+
+ $badge->forceFill(array_merge($attributes, [
+ 'name' => 'New Member',
+ 'description' => 'Welcome new user',
+ 'icon' => 'images/new-member-icon.svg',
+ ]))->save();
+
+ return $badge->fresh();
+}
diff --git a/tests/Models/Post.php b/tests/Models/Post.php
deleted file mode 100644
index ffbf963..0000000
--- a/tests/Models/Post.php
+++ /dev/null
@@ -1,33 +0,0 @@
-hasMany(Reply::class)->latest();
- }
-
- public function user()
- {
- return $this->belongsTo(User::class);
- }
-
- public function bestReply()
- {
- return $this->hasOne(Reply::class, 'id', 'best_reply_id');
- }
-
- public function reputations()
- {
- return $this->morphMany('QCod\Gamify\Reputation', 'subject');
- }
-}
diff --git a/tests/Models/Reply.php b/tests/Models/Reply.php
deleted file mode 100644
index ae0d711..0000000
--- a/tests/Models/Reply.php
+++ /dev/null
@@ -1,21 +0,0 @@
-belongsTo(User::class);
- }
-
- public function post()
- {
- return $this->belongsTo(Post::class);
- }
-}
diff --git a/tests/Models/User.php b/tests/Models/User.php
deleted file mode 100644
index b10bce5..0000000
--- a/tests/Models/User.php
+++ /dev/null
@@ -1,20 +0,0 @@
-hasMany(Post::class);
- }
-}
diff --git a/tests/Pest.php b/tests/Pest.php
new file mode 100644
index 0000000..c582fef
--- /dev/null
+++ b/tests/Pest.php
@@ -0,0 +1,10 @@
+in('Feature');
diff --git a/tests/PointTest.php b/tests/PointTest.php
deleted file mode 100644
index 13443e3..0000000
--- a/tests/PointTest.php
+++ /dev/null
@@ -1,351 +0,0 @@
-assertEquals('FakeCreatePostPoint', $point->getName());
- }
-
- /**
- * it uses name property for point name if provided
- *
- * @test
- */
- public function it_uses_name_property_for_point_name_if_provided()
- {
- $point = new FakeWelcomeUserWithNamePoint(1);
-
- $this->assertEquals('FakeName', $point->getName());
- }
-
- /**
- * it can get points for a point type
- *
- * @test
- */
- public function it_can_get_points_for_a_point_type()
- {
- $point = new FakeCreatePostPoint(1);
-
- $this->assertEquals(10, $point->getPoints());
- }
-
- /**
- * it gives point to a user
- *
- * @test
- */
- public function it_gives_point_to_a_user()
- {
- $user = $this->createUser();
- $post = $this->createPost(['user_id' => $user->id]);
-
- $user->givePoint(new FakeCreatePostPoint($post));
-
- $this->assertEquals(10, $user->fresh()->getPoints());
- $this->assertCount(1, $user->reputations);
- $this->assertDatabaseHas('reputations', [
- 'payee_id' => $user->id,
- 'subject_type' => $post->getMorphClass(),
- 'subject_id' => $post->id,
- 'point' => 10,
- 'name' => 'FakeCreatePostPoint'
- ]);
- }
-
- /**
- * it can access a reputation payee and subject
- *
- * @test
- */
- public function it_can_access_a_reputation_payee_and_subject()
- {
- $user = $this->createUser();
- $post = $this->createPost(['user_id' => $user->id]);
-
- $user->givePoint(new FakeCreatePostPoint($post));
-
- $point = $user->reputations()->first();
-
- $this->assertEquals($user->id, $point->payee->id);
- $this->assertEquals($post->id, $point->subject->id);
-
- $this->assertEquals('FakeCreatePostPoint', $post->reputations->first()->name);
- }
-
- /**
- * it only adds unique point reward if property is set on point type
- *
- * @test
- */
- public function it_only_adds_unique_point_reward_if_property_is_set_on_point_type()
- {
- $user = $this->createUser();
- $post = $this->createPost(['user_id' => $user->id]);
-
- $user->givePoint(new FakeCreatePostPoint($post));
- $user->givePoint(new FakeCreatePostPoint($post));
-
- $this->assertEquals(10, $user->fresh()->getPoints());
- $this->assertCount(1, $user->reputations);
- }
-
- /**
- * it can store duplicate reputations if no property set
- *
- * @test
- */
- public function it_can_store_duplicate_reputations_if_no_property_set()
- {
- $user = $this->createUser();
- $post = $this->createPost(['user_id' => $user->id]);
-
- $user->givePoint(new FakeWelcomeUserWithNamePoint($post));
- $user->givePoint(new FakeWelcomeUserWithNamePoint($post));
-
- $this->assertEquals(60, $user->fresh()->getPoints());
- $this->assertCount(2, $user->reputations);
- }
-
- /**
- * it do not give point if qualifier returns false
- *
- * @test
- */
- public function it_do_not_give_point_if_qualifier_returns_false()
- {
- $user = $this->createUser();
- $post = $this->createPost(['user_id' => $user->id]);
-
- $user->givePoint(new FakeWelcomeUserWithFalseQualifier($post));
-
- $this->assertEquals(0, $user->fresh()->getPoints());
- $this->assertCount(0, $user->reputations);
- }
-
- /**
- * it uses payee field on point as relation if no payee method override
- *
- * @test
- */
- public function it_uses_payee_field_on_point_as_relation_if_no_payee_method_override()
- {
- $user = $this->createUser();
- $post = $this->createPost(['user_id' => $user->id]);
-
- $user->givePoint(new FakePayeeFieldPoint($post));
-
- $this->assertEquals(10, $user->fresh()->getPoints());
- $this->assertCount(1, $user->reputations);
- }
-
- /**
- * it can undo a reward by given model
- *
- * @test
- */
- public function it_can_undo_a_reward_by_given_model()
- {
- $user = $this->createUser();
- $post = $this->createPost(['user_id' => $user->id]);
- $user->givePoint(new FakeWelcomeUserWithNamePoint($post));
- $user->givePoint(new FakeWelcomeUserWithNamePoint($post));
- $this->assertEquals(60, $user->fresh()->getPoints());
- $this->assertCount(2, $user->reputations);
-
- $user->undoPoint(new FakeWelcomeUserWithNamePoint($post));
-
- $this->assertEquals(30, $user->fresh()->getPoints());
- $this->assertCount(1, $user->fresh()->reputations);
-
- $user->undoPoint(new FakeWelcomeUserWithNamePoint($post));
-
- $this->assertEquals(0, $user->fresh()->getPoints());
- $this->assertCount(0, $user->fresh()->reputations);
- }
-
- /**
- * it throws exception if no payee is returned
- *
- * @test
- */
- public function it_throws_exception_if_no_payee_is_returned()
- {
- $user = $this->createUser();
- $this->expectException(InvalidPayeeModel::class);
-
- $user->givePoint(new FakePointTypeWithoutPayee());
-
- $this->assertEquals(0, $user->fresh()->getPoints());
- $this->assertCount(0, $user->reputations);
- }
-
- /**
- * it throws exception if no subject is set
- *
- * @test
- */
- public function it_throws_exception_if_no_subject_is_set()
- {
- $user = $this->createUser();
- $this->expectException(PointSubjectNotSet::class);
-
- $user->givePoint(new FakePointTypeWithoutSubject());
-
- $this->assertEquals(0, $user->fresh()->getPoints());
- $this->assertCount(0, $user->reputations);
- }
-
- /**
- * it throws exception if no points field or method is defined
- *
- * @test
- */
- public function it_throws_exception_if_no_points_field_or_method_is_defined()
- {
- $user = $this->createUser();
- $post = $this->createPost(['user_id' => $user->id]);
- $this->expectException(PointsNotDefined::class);
-
- $user->givePoint(new FakePointWithoutPoint($post));
-
- $this->assertEquals(0, $user->fresh()->getPoints());
- $this->assertCount(0, $user->reputations);
- }
-
- /**
- * it gives and undo point via helper functions
- *
- * @test
- */
- public function it_gives_and_undo_point_via_helper_functions()
- {
- $user = $this->createUser();
- $post = $this->createPost(['user_id' => $user->id]);
-
- givePoint(new FakePayeeFieldPoint($post), $user);
-
- $this->assertEquals(10, $user->fresh()->getPoints());
- $this->assertCount(1, $user->reputations);
-
- undoPoint(new FakePayeeFieldPoint($post), $user);
-
- $user = $user->fresh();
- $this->assertEquals(0, $user->getPoints());
- $this->assertCount(0, $user->reputations);
- }
-}
-
-class FakeCreatePostPoint extends PointType
-{
- protected $points = 10;
-
- public $allowDuplicates = false;
-
- public function __construct($subject)
- {
- $this->subject = $subject;
- }
-
- public function payee()
- {
- return $this->getSubject()->user;
- }
-}
-
-class FakeWelcomeUserWithNamePoint extends PointType
-{
- protected $name = 'FakeName';
-
- protected $points = 30;
-
- public function __construct($subject)
- {
- $this->subject = $subject;
- }
-
- public function payee()
- {
- return $this->getSubject()->user;
- }
-}
-
-class FakeWelcomeUserWithFalseQualifier extends PointType
-{
- protected $points = 10;
-
- public function __construct($subject)
- {
- $this->subject = $subject;
- }
-
- public function qualifier()
- {
- return false;
- }
-
- public function payee()
- {
- return $this->getSubject()->user;
- }
-}
-
-class FakePointTypeWithoutPayee extends PointType
-{
- protected $point = 24;
-
- public function payee()
- {
- }
-}
-
-class FakePointTypeWithoutSubject extends PointType
-{
- protected $point = 12;
-
- public function payee()
- {
- return new User();
- }
-}
-
-class FakePointWithoutPoint extends PointType
-{
- protected $payee = 'user';
-
- public function __construct($subject)
- {
- $this->subject = $subject;
- }
-}
-
-class FakePayeeFieldPoint extends PointType
-{
- protected $points = 10;
-
- public function __construct($subject)
- {
- $this->subject = $subject;
- }
-
- /**
- * @var string payee model relation on subject
- */
- protected $payee = 'user';
-}
diff --git a/tests/ReputationTest.php b/tests/ReputationTest.php
deleted file mode 100644
index f51a83b..0000000
--- a/tests/ReputationTest.php
+++ /dev/null
@@ -1,172 +0,0 @@
-createUser(['reputation' => 10]);
-
- $this->assertEquals(10, $user->getPoints());
- }
-
- /**
- * it gives reputation point to a user
- *
- * @test
- */
- public function it_gives_reputation_point_to_a_user()
- {
- $user = $this->createUser();
- $this->assertEquals(0, $user->getPoints());
-
- $user->addPoint(10);
-
- $this->assertEquals(10, $user->fresh()->getPoints());
- }
-
- /**
- * it reduces reputation point for a user
- *
- * @test
- */
- public function it_reduces_reputation_point_for_a_user()
- {
- $user = $this->createUser(['reputation' => 20]);
- $this->assertEquals(20, $user->reputation);
-
- $user->reducePoint(5);
-
- $this->assertEquals(15, $user->fresh()->getPoints());
- }
-
- /**
- * it zeros reputation point of a user
- *
- * @test
- */
- public function it_zeros_reputation_point_of_a_user()
- {
- $user = $this->createUser(['reputation' => 50]);
- $this->assertEquals(50, $user->getPoints());
-
- $user->resetPoint();
-
- $this->assertEquals(0, $user->fresh()->getPoints());
- }
-
- /**
- * it fires event on reputation change
- *
- * @test
- */
- public function it_fires_event_on_reputation_change()
- {
- Event::fake();
-
- $user = $this->createUser();
- $this->assertEquals(0, $user->getPoints());
-
- $user->addPoint(10);
-
- Event::assertDispatched(ReputationChanged::class, function ($event) use ($user) {
- return ($event->point === 10 && $user->id == $event->user->id && $event->increment);
- });
-
- $this->assertEquals(10, $user->fresh()->getPoints());
- }
-
- /**
- * it fires event on reputation reduced
- *
- * @test
- */
- public function it_fires_event_on_reputation_reduced()
- {
- Event::fake();
-
- $user = $this->createUser(['reputation' => 10]);
-
- $user->reducePoint(3);
-
- Event::assertDispatched(ReputationChanged::class, function ($event) use ($user) {
- return ($event->point === 3 && $user->id == $event->user->id && !$event->increment);
- });
-
- $this->assertEquals(7, $user->fresh()->getPoints());
- }
-}
-
-class FakePostCreated extends PointType
-{
- protected $points = 10;
-
- public function __construct($model)
- {
- $this->setSubject($model);
- }
-
- /**
- * Check qualification for this point
- *
- * @return bool
- */
- public function qualifier()
- {
- return true;
- }
-
- /**
- * User who will be recieving point
- *
- * @return Model
- */
- public function payee()
- {
- return $this->getSubject()->user;
- }
-}
-
-class FakeBestReply extends PointType
-{
- protected $points = 50;
-
- public function __construct($model)
- {
- $this->setSubject($model);
- }
-
- /**
- * Check qualification for this point
- *
- * @return bool
- */
- public function qualifier()
- {
- return !is_null($this->getSubject()->best_reply_id);
- }
-
- /**
- * User who will be recieving point
- *
- * @return Model
- */
- public function payee()
- {
- return $this->getSubject()->bestReply->user;
- }
-}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 05db068..ea41459 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -1,28 +1,66 @@
setUpDatabase($this->app);
+ }
+
+ protected function setUpDatabase($app): void
+ {
+ $schema = $app['db']->connection()->getSchemaBuilder();
+
+ $schema->create((new Post())->getTable(), function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('title');
+ $table->text('body');
+ $table->unsignedInteger('best_reply_id')->nullable();
+ $table->unsignedInteger('user_id');
+ $table->timestamps();
+ });
+ $schema->create((new Reply())->getTable(), function (Blueprint $table) {
+ $table->increments('id');
+ $table->text('body');
+ $table->unsignedInteger('user_id');
+ $table->unsignedInteger('post_id');
+ $table->timestamps();
+ });
+ $schema->create((new User())->getTable(), function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('name');
+ $table->string('email')->unique();
+ $table->timestamp('email_verified_at')->nullable();
+ $table->string('password');
+ $table->rememberToken();
+ $table->timestamps();
+ });
+
+ include_once __DIR__.'/../database/migrations/add_reputation_on_user_table.php.stub';
+ (new AddReputationFieldOnUserTable())->up();
- $this->loadMigrationsFrom(__DIR__ . '/database/migrations');
+ include_once __DIR__.'/../database/migrations/create_gamify_tables.php.stub';
+ (new CreateGamifyTables())->up();
}
- /**
- * @param \Illuminate\Foundation\Application $app
- */
- protected function getEnvironmentSetUp($app)
+ /** @param \Illuminate\Foundation\Application $app */
+ protected function getEnvironmentSetUp($app): void
{
$app['config']->set('database.default', 'testbench');
$app['config']->set('database.connections.testbench', [
@@ -31,80 +69,16 @@ protected function getEnvironmentSetUp($app)
'prefix' => '',
]);
- $app['config']->set('gamify.payee_model', '\QCod\Gamify\Tests\Models\User');
+ $app['config']->set('gamify.payee_model', User::class);
- // test badges
$app->singleton('badges', function () {
- return collect(['FirstContribution', 'FirstThousandPoints'])
- ->map(function ($badge) {
- return app("QCod\\Gamify\\Tests\Badges\\".$badge);
- });
+ return collect([FirstContribution::class, FirstThousandPoints::class])
+ ->map(fn (string $badge) => app($badge));
});
}
- /**
- * @param \Illuminate\Foundation\Application $app
- * @return array
- */
- protected function getPackageProviders($app)
- {
- return ['QCod\Gamify\GamifyServiceProvider'];
- }
-
- /**
- * Create a user
- *
- * @param array $attrs
- * @return User
- */
- public function createUser($attrs = [])
+ protected function getPackageProviders($app): array
{
- $user = new User();
-
- $user->forceFill(array_merge($attrs, [
- 'name' => 'Saqueib',
- 'email' => 'me@example.com',
- 'password' => 'secret'
- ]))->save();
-
- return $user->fresh();
- }
-
- /**
- * Create a post
- *
- * @param array $attrs
- * @return Post
- */
- public function createPost($attrs = [])
- {
- $post = new Post();
-
- $post->forceFill(array_merge($attrs, [
- 'title' => 'Dummy post title',
- 'body' => 'I am the content on dummy post',
- 'user_id' => 1
- ]))->save();
-
- return $post->fresh();
- }
-
- /**
- * Create a badge
- *
- * @param array $attrs
- * @return Badge
- */
- public function createBadge($attrs = [])
- {
- $badge = new Badge();
-
- $badge->forceFill(array_merge($attrs, [
- 'name' => 'New Member',
- 'description' => 'Welcome new user',
- 'icon' => 'images/new-member-icon.svg',
- ]))->save();
-
- return $badge->fresh();
+ return [GamifyServiceProvider::class];
}
}
diff --git a/tests/database/migrations/2018_09_10_104820_create_test_posts_table.php b/tests/database/migrations/2018_09_10_104820_create_test_posts_table.php
deleted file mode 100644
index 66e3c9f..0000000
--- a/tests/database/migrations/2018_09_10_104820_create_test_posts_table.php
+++ /dev/null
@@ -1,44 +0,0 @@
-increments('id');
- $table->string('title');
- $table->text('body');
- $table->unsignedInteger('best_reply_id')->nullable();
- $table->unsignedInteger('user_id');
- $table->timestamps();
- });
-
- Schema::create('replies', function (Blueprint $table) {
- $table->increments('id');
- $table->text('body');
- $table->unsignedInteger('user_id');
- $table->unsignedInteger('post_id');
- $table->timestamps();
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- Schema::dropIfExists('posts');
- Schema::dropIfExists('replies');
- }
-}
diff --git a/tests/database/migrations/2018_09_17_104820_create_test_users_table.php b/tests/database/migrations/2018_09_17_104820_create_test_users_table.php
deleted file mode 100644
index 0640c7b..0000000
--- a/tests/database/migrations/2018_09_17_104820_create_test_users_table.php
+++ /dev/null
@@ -1,37 +0,0 @@
-increments('id');
- $table->string('name');
- $table->string('email')->unique();
- $table->timestamp('email_verified_at')->nullable();
- $table->string('password');
- $table->rememberToken();
- $table->unsignedInteger('reputation')->default(0);
- $table->timestamps();
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- Schema::dropIfExists('users');
- }
-}
diff --git a/tests/database/migrations/2018_11_06_045032_create_test_reputations_table.php b/tests/database/migrations/2018_11_06_045032_create_test_reputations_table.php
deleted file mode 100644
index bce464b..0000000
--- a/tests/database/migrations/2018_11_06_045032_create_test_reputations_table.php
+++ /dev/null
@@ -1,37 +0,0 @@
-increments('id');
- $table->string('name');
- $table->mediumInteger('point', false)->default(0);
- $table->integer('subject_id')->nullable();
- $table->string('subject_type')->nullable();
- $table->unsignedInteger('payee_id')->nullable();
- $table->text('meta')->nullable();
- $table->timestamps();
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- Schema::dropIfExists('reputations');
- }
-}
diff --git a/tests/database/migrations/2018_11_06_205032_create_test_badges_table.php b/tests/database/migrations/2018_11_06_205032_create_test_badges_table.php
deleted file mode 100644
index 48b47c5..0000000
--- a/tests/database/migrations/2018_11_06_205032_create_test_badges_table.php
+++ /dev/null
@@ -1,43 +0,0 @@
-increments('id');
- $table->string('name');
- $table->string('description')->nullable();
- $table->string('icon')->nullable();
- $table->tinyInteger('level')->default(config('gamify.badge_default_level', 1));
- $table->timestamps();
- });
-
- Schema::create('user_badges', function (Blueprint $table) {
- $table->primary(['user_id', 'badge_id']);
- $table->unsignedInteger('user_id');
- $table->unsignedInteger('badge_id');
- $table->timestamps();
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- Schema::dropIfExists('user_badges');
- Schema::dropIfExists('badges');
- }
-}