From 7b1d547f1195e4ea793e5f5db5eb71469bf0ff60 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Tue, 31 Mar 2026 11:02:13 +0700 Subject: [PATCH] test: add feature tests for Material management and create factories for Material and ClassSession models --- database/factories/ClassSessionFactory.php | 28 +++ database/factories/MaterialFactory.php | 28 +++ tests/Feature/MaterialTest.php | 248 +++++++++++++++++++++ 3 files changed, 304 insertions(+) create mode 100644 database/factories/ClassSessionFactory.php create mode 100644 database/factories/MaterialFactory.php create mode 100644 tests/Feature/MaterialTest.php diff --git a/database/factories/ClassSessionFactory.php b/database/factories/ClassSessionFactory.php new file mode 100644 index 0000000..97fa5ee --- /dev/null +++ b/database/factories/ClassSessionFactory.php @@ -0,0 +1,28 @@ + + */ +class ClassSessionFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'course_id' => Course::factory(), + 'session_number' => fake()->unique(true)->numberBetween(1, 16), + 'date' => fake()->date(), + 'start_time' => '08:00', + 'end_time' => '10:00', + ]; + } +} diff --git a/database/factories/MaterialFactory.php b/database/factories/MaterialFactory.php new file mode 100644 index 0000000..90951ba --- /dev/null +++ b/database/factories/MaterialFactory.php @@ -0,0 +1,28 @@ + + */ +class MaterialFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'course_id' => Course::factory(), + 'class_session_id' => ClassSession::factory(), + 'title' => fake()->sentence(3), + 'description' => fake()->paragraph(), + ]; + } +} diff --git a/tests/Feature/MaterialTest.php b/tests/Feature/MaterialTest.php new file mode 100644 index 0000000..c3e945c --- /dev/null +++ b/tests/Feature/MaterialTest.php @@ -0,0 +1,248 @@ +value); + Role::findOrCreate(RoleEnum::Kosma->value); + Role::findOrCreate(RoleEnum::Student->value); + + $permissions = [ + 'ViewAny:Material', + 'Create:Material', + 'View:Material', + 'Update:Material', + 'Delete:Material', + 'Restore:Material', + 'ForceDelete:Material', + 'RestoreAny:Material', + 'ForceDeleteAny:Material', + ]; + + foreach ($permissions as $permission) { + Permission::findOrCreate($permission); + } + + Storage::fake('public'); +}); + +describe('Material Authorization', function () { + it('allows all roles to view the list but restricts actions to Developer and Kosma', function () { + $developer = User::factory()->create(); + $developer->assignRole(RoleEnum::Developer); + $developer->givePermissionTo(['ViewAny:Material', 'Create:Material']); + + $kosma = User::factory()->create(); + $kosma->assignRole(RoleEnum::Kosma); + $kosma->givePermissionTo(['ViewAny:Material', 'Create:Material']); + + $regularStudent = User::factory()->create(); + $regularStudent->assignRole(RoleEnum::Student); + $regularStudent->givePermissionTo('ViewAny:Material'); + + $this->actingAs($developer); + Livewire::test(ManageMaterials::class)->assertSuccessful() + ->assertActionVisible('createMaterial'); + + $this->actingAs($kosma); + Livewire::test(ManageMaterials::class)->assertSuccessful() + ->assertActionVisible('createMaterial'); + + $this->actingAs($regularStudent); + Livewire::test(ManageMaterials::class)->assertSuccessful() + ->assertActionHidden('createMaterial'); + }); +}); + +describe('Material Model', function () { + it('has relationships with Course and ClassSession', function () { + $material = Material::factory()->create(); + expect($material->course)->toBeInstanceOf(Course::class); + expect($material->classSession)->toBeInstanceOf(ClassSession::class); + }); + + it('supports Soft Deletes', function () { + $material = Material::factory()->create(); + $material->delete(); + + expect($material->trashed())->toBeTrue(); + expect(Material::find($material->id))->toBeNull(); + }); +}); + +describe('Material CRUD', function () { + beforeEach(function () { + $user = User::factory()->create(); + $user->assignRole(RoleEnum::Developer); + $user->givePermissionTo(['ViewAny:Material', 'Create:Material', 'Update:Material', 'Delete:Material']); + $this->actingAs($user); + $this->currentSemester = app(GeneralSettings::class)->current_semester; + }); + + it('can create a material', function () { + $course = Course::factory()->create(['semester' => $this->currentSemester]); + $file = UploadedFile::fake()->create('test.pdf', 100, 'application/pdf'); + + Livewire::test(ManageMaterials::class) + ->callAction('createMaterial', [ + 'course_id' => $course->id, + 'title' => 'Introduction to PHP', + 'description' => 'Basics of PHP programming.', + 'pdf' => [$file], + ]) + ->assertHasNoActionErrors(); + + $this->assertDatabaseHas('materials', [ + 'course_id' => $course->id, + 'title' => 'Introduction to PHP', + ]); + }); + + it('can update material data', function () { + $material = Material::factory()->create(['class_session_id' => null]); + $material->course->update(['semester' => $this->currentSemester]); + $file = UploadedFile::fake()->create('updated.pdf', 100, 'application/pdf'); + + Livewire::test(ManageMaterials::class) + ->callTableAction('editMaterial', $material, [ + 'title' => 'Updated Title', + 'course_id' => $material->course_id, + 'pdf' => [$file], + ]) + ->assertHasNoTableActionErrors(); + + expect($material->refresh()->title)->toBe('Updated Title'); + }); + + it('can delete a material', function () { + $material = Material::factory()->create(); + $material->course->update(['semester' => $this->currentSemester]); + + Livewire::test(ManageMaterials::class) + ->callTableAction('delete', $material); + + expect(Material::find($material->id))->toBeNull(); + }); +}); + +describe('Material Restoration and Bulk Actions', function () { + beforeEach(function () { + $user = User::factory()->create(); + $user->assignRole(RoleEnum::Developer); + $user->givePermissionTo(['ViewAny:Material', 'Restore:Material', 'RestoreAny:Material']); + $this->actingAs($user); + $this->currentSemester = app(GeneralSettings::class)->current_semester; + }); + + it('can restore a deleted material', function () { + $material = Material::factory()->create(); + $material->course->update(['semester' => $this->currentSemester]); + $material->delete(); + + Livewire::test(ManageMaterials::class) + ->filterTable('trashed', 'only') + ->callTableAction('restore', $material); + + expect($material->refresh()->trashed())->toBeFalse(); + }); + + it('can perform bulk actions (Delete & Restore)', function () { + $materials = Material::factory()->count(3)->create(); + foreach ($materials as $m) { + $m->course->update(['semester' => $this->currentSemester]); + } + + // Bulk Delete + Livewire::test(ManageMaterials::class) + ->callTableBulkAction('delete', $materials); + + foreach ($materials as $material) { + expect($material->refresh()->trashed())->toBeTrue(); + } + + // Bulk Restore + Livewire::test(ManageMaterials::class) + ->filterTable('trashed', 'only') + ->callTableBulkAction('restore', $materials); + + foreach ($materials as $material) { + expect($material->refresh()->trashed())->toBeFalse(); + } + }); +}); + +describe('Search, Sorting, and Filters', function () { + beforeEach(function () { + $user = User::factory()->create(); + $user->assignRole(RoleEnum::Developer); + $user->givePermissionTo(['ViewAny:Material']); + $this->actingAs($user); + $this->currentSemester = app(GeneralSettings::class)->current_semester; + }); + + it('can search materials by title', function () { + $target = Material::factory()->create(['title' => 'Specific Material Title']); + $target->course->update(['semester' => $this->currentSemester]); + + $other = Material::factory()->create(['title' => 'Generic Title']); + $other->course->update(['semester' => $this->currentSemester]); + + Livewire::test(ManageMaterials::class) + ->searchTable('Specific Material Title') + ->assertCanSeeTableRecords([$target]) + ->assertCanNotSeeTableRecords([$other]); + }); + + it('can filter materials by course', function () { + $course1 = Course::factory()->create(['semester' => $this->currentSemester]); + $course2 = Course::factory()->create(['semester' => $this->currentSemester]); + + $m1 = Material::factory()->create(['course_id' => $course1->id]); + $m1->course->update(['semester' => $this->currentSemester]); + + $m2 = Material::factory()->create(['course_id' => $course2->id]); + $m2->course->update(['semester' => $this->currentSemester]); + + Livewire::test(ManageMaterials::class) + ->filterTable('course_id', $course1->id) + ->assertCanSeeTableRecords([$m1]) + ->assertCanNotSeeTableRecords([$m2]); + }); +}); + +describe('Material View Actions', function () { + beforeEach(function () { + $user = User::factory()->create(); + $user->assignRole(RoleEnum::Developer); + $user->givePermissionTo(['ViewAny:Material', 'View:Material']); + $this->actingAs($user); + $this->currentSemester = app(GeneralSettings::class)->current_semester; + }); + + it('can see viewMaterial action when media exists', function () { + $material = Material::factory()->create(); + $material->course->update(['semester' => $this->currentSemester]); + + $material->addMedia(UploadedFile::fake()->create('lecture.pdf', 100)) + ->toMediaCollection('materials'); + + expect($material->hasMedia('materials'))->toBeTrue(); + + Livewire::test(ManageMaterials::class) + ->assertTableActionVisible('viewMaterial', $material); + }); +});