311 lines
11 KiB
PHP
311 lines
11 KiB
PHP
<?php
|
|
|
|
use App\Enums\ChangelogType;
|
|
use App\Enums\RoleEnum;
|
|
use App\Filament\Pages\System\Changelog\Index as ChangelogPage;
|
|
use App\Models\Changelog;
|
|
use App\Models\User;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Support\Carbon;
|
|
use Livewire\Livewire;
|
|
use Spatie\Permission\Models\Permission;
|
|
use Spatie\Permission\Models\Role;
|
|
|
|
/**
|
|
* Setup necessary roles and permissions
|
|
*/
|
|
beforeEach(function () {
|
|
Role::findOrCreate(RoleEnum::Developer->value);
|
|
Role::findOrCreate(RoleEnum::Kosma->value);
|
|
Role::findOrCreate(RoleEnum::Student->value);
|
|
|
|
$permissions = [
|
|
'View:Changelog',
|
|
'Create:Changelog',
|
|
'Update:Changelog',
|
|
'Delete:Changelog',
|
|
];
|
|
|
|
foreach ($permissions as $permission) {
|
|
Permission::findOrCreate($permission);
|
|
}
|
|
});
|
|
|
|
describe('Changelog Authorization', function () {
|
|
it('allows Developer to access the changelog page', function () {
|
|
$developer = User::factory()->create();
|
|
$developer->assignRole(RoleEnum::Developer);
|
|
$developer->givePermissionTo('View:Changelog');
|
|
|
|
$this->actingAs($developer);
|
|
Livewire::test(ChangelogPage::class)
|
|
->assertSuccessful();
|
|
});
|
|
|
|
it('restricts Students from accessing the changelog page', function () {
|
|
$student = User::factory()->create();
|
|
$student->assignRole(RoleEnum::Student);
|
|
|
|
$this->actingAs($student);
|
|
Livewire::test(ChangelogPage::class)
|
|
->assertStatus(403);
|
|
});
|
|
|
|
it('shows createChangelog action to users with Create:Changelog permission', function () {
|
|
$developer = User::factory()->create();
|
|
$developer->assignRole(RoleEnum::Developer);
|
|
$developer->givePermissionTo(['View:Changelog', 'Create:Changelog']);
|
|
|
|
$this->actingAs($developer);
|
|
Livewire::test(ChangelogPage::class)
|
|
->assertActionVisible('createChangelog');
|
|
});
|
|
|
|
it('hides createChangelog action from users without Create:Changelog permission', function () {
|
|
$kosma = User::factory()->create();
|
|
$kosma->assignRole(RoleEnum::Kosma);
|
|
$kosma->givePermissionTo('View:Changelog');
|
|
|
|
$this->actingAs($kosma);
|
|
Livewire::test(ChangelogPage::class)
|
|
->assertActionHidden('createChangelog');
|
|
});
|
|
});
|
|
|
|
describe('Changelog Model', function () {
|
|
it('has the correct casts configured', function () {
|
|
$changelog = Changelog::factory()->create([
|
|
'type' => ChangelogType::Feature,
|
|
'changes' => ['Tambah fitur baru', 'Update UI'],
|
|
]);
|
|
|
|
expect($changelog->type)->toBeInstanceOf(ChangelogType::class);
|
|
expect($changelog->type)->toBe(ChangelogType::Feature);
|
|
expect($changelog->changes)->toBeArray();
|
|
expect($changelog->release_date)->toBeInstanceOf(Carbon::class);
|
|
});
|
|
|
|
it('supports soft deletes', function () {
|
|
$changelog = Changelog::factory()->create();
|
|
|
|
$changelog->delete();
|
|
|
|
expect(Changelog::find($changelog->id))->toBeNull();
|
|
expect(Changelog::withTrashed()->find($changelog->id))->not->toBeNull();
|
|
expect($changelog->refresh()->trashed())->toBeTrue();
|
|
});
|
|
|
|
it('can be force deleted', function () {
|
|
$changelog = Changelog::factory()->create();
|
|
$changelog->delete();
|
|
$changelog->forceDelete();
|
|
|
|
expect(Changelog::withTrashed()->find($changelog->id))->toBeNull();
|
|
$this->assertDatabaseMissing('changelogs', ['id' => $changelog->id]);
|
|
});
|
|
|
|
it('enforces unique version constraint', function () {
|
|
Changelog::factory()->create(['version' => 'v1.0.0']);
|
|
|
|
expect(fn () => Changelog::factory()->create(['version' => 'v1.0.0']))
|
|
->toThrow(QueryException::class);
|
|
});
|
|
});
|
|
|
|
describe('Changelog CRUD via Filament Page', function () {
|
|
beforeEach(function () {
|
|
$user = User::factory()->create();
|
|
$user->assignRole(RoleEnum::Developer);
|
|
$user->givePermissionTo(['View:Changelog', 'Create:Changelog', 'Update:Changelog', 'Delete:Changelog']);
|
|
$this->actingAs($user);
|
|
});
|
|
|
|
it('can create a changelog entry', function () {
|
|
Livewire::test(ChangelogPage::class)
|
|
->callAction('createChangelog', [
|
|
'version' => 'v2.0.0',
|
|
'release_date' => now()->toDateString(),
|
|
'type' => ChangelogType::Feature->value,
|
|
'title' => 'Fitur Keren Baru',
|
|
'changes' => ['Tambah fitur A', 'Tambah fitur B'],
|
|
'description' => 'Deskripsi singkat fitur baru.',
|
|
])
|
|
->assertHasNoActionErrors();
|
|
|
|
$this->assertDatabaseHas('changelogs', [
|
|
'version' => 'v2.0.0',
|
|
'title' => 'Fitur Keren Baru',
|
|
]);
|
|
});
|
|
|
|
it('validates required fields when creating a changelog', function () {
|
|
Livewire::test(ChangelogPage::class)
|
|
->callAction('createChangelog', [
|
|
'version' => '',
|
|
'title' => '',
|
|
])
|
|
->assertHasActionErrors(['version', 'title']);
|
|
});
|
|
|
|
it('validates unique version when creating a changelog', function () {
|
|
Changelog::factory()->create(['version' => 'v1.5.0']);
|
|
|
|
Livewire::test(ChangelogPage::class)
|
|
->callAction('createChangelog', [
|
|
'version' => 'v1.5.0',
|
|
'release_date' => now()->toDateString(),
|
|
'type' => ChangelogType::BugFix->value,
|
|
'title' => 'Duplikat Versi',
|
|
'changes' => ['Fix A'],
|
|
'description' => 'Deskripsi.',
|
|
])
|
|
->assertHasActionErrors(['version']);
|
|
});
|
|
|
|
it('can edit an existing changelog entry', function () {
|
|
$changelog = Changelog::factory()->create([
|
|
'title' => 'Judul Lama',
|
|
'type' => ChangelogType::BugFix,
|
|
'changes' => ['Fix satu bug'],
|
|
]);
|
|
|
|
Livewire::test(ChangelogPage::class)
|
|
->callAction('editChangelog', [
|
|
'version' => $changelog->version,
|
|
'release_date' => $changelog->release_date->toDateString(),
|
|
'type' => ChangelogType::Improvement->value,
|
|
'title' => 'Judul Baru',
|
|
'changes' => ['Perbaikan performa'],
|
|
'description' => $changelog->description,
|
|
], ['record' => $changelog->id])
|
|
->assertHasNoActionErrors();
|
|
|
|
expect($changelog->refresh()->title)->toBe('Judul Baru');
|
|
expect($changelog->refresh()->type)->toBe(ChangelogType::Improvement);
|
|
});
|
|
|
|
it('can delete a changelog entry (soft delete)', function () {
|
|
$changelog = Changelog::factory()->create();
|
|
|
|
Livewire::test(ChangelogPage::class)
|
|
->callAction('deleteChangelog', [], ['record' => $changelog->id])
|
|
->assertHasNoActionErrors();
|
|
|
|
$this->assertSoftDeleted('changelogs', ['id' => $changelog->id]);
|
|
});
|
|
});
|
|
|
|
describe('Changelog Computed Properties', function () {
|
|
beforeEach(function () {
|
|
$user = User::factory()->create();
|
|
$user->assignRole(RoleEnum::Developer);
|
|
$user->givePermissionTo('View:Changelog');
|
|
$this->actingAs($user);
|
|
});
|
|
|
|
it('returns changelogs sorted by latest release date', function () {
|
|
$oldest = Changelog::factory()->create(['release_date' => now()->subYear()]);
|
|
$newest = Changelog::factory()->create(['release_date' => now()]);
|
|
|
|
$component = Livewire::test(ChangelogPage::class);
|
|
|
|
// changelogs() computed prop returns latest first
|
|
$ids = Changelog::latest('release_date')->pluck('id')->toArray();
|
|
expect($ids[0])->toBe($newest->id);
|
|
expect($ids[1])->toBe($oldest->id);
|
|
});
|
|
|
|
it('reflects latest version in techStack', function () {
|
|
// Clear and create changelogs in order
|
|
Changelog::factory()->create(['version' => 'v1.0.0', 'release_date' => now()->subDays(5)]);
|
|
Changelog::factory()->create(['version' => 'v2.0.0', 'release_date' => now()]);
|
|
|
|
$latestVersion = Changelog::latest('release_date')->first()->version;
|
|
expect($latestVersion)->toBe('v2.0.0');
|
|
});
|
|
|
|
it('shows changelog entries on the page', function () {
|
|
$changelog = Changelog::factory()->create(['title' => 'Update Kece']);
|
|
|
|
Livewire::test(ChangelogPage::class)
|
|
->assertSee('Update Kece');
|
|
});
|
|
|
|
it('shows empty state message when no changelogs exist', function () {
|
|
Changelog::query()->forceDelete();
|
|
|
|
// The page should still render without errors when empty
|
|
Livewire::test(ChangelogPage::class)
|
|
->assertSuccessful();
|
|
});
|
|
});
|
|
|
|
describe('ChangelogType Enum', function () {
|
|
it('returns correct label for each type', function () {
|
|
expect(ChangelogType::Feature->getLabel())->toContain('Fitur Baru');
|
|
expect(ChangelogType::Improvement->getLabel())->toContain('Peningkatan');
|
|
expect(ChangelogType::BugFix->getLabel())->toContain('Perbaikan Bug');
|
|
expect(ChangelogType::Security->getLabel())->toContain('Keamanan');
|
|
});
|
|
|
|
it('returns correct color for each type', function () {
|
|
expect(ChangelogType::Feature->getColor())->toBe('success');
|
|
expect(ChangelogType::Improvement->getColor())->toBe('info');
|
|
expect(ChangelogType::BugFix->getColor())->toBe('danger');
|
|
expect(ChangelogType::Security->getColor())->toBe('warning');
|
|
});
|
|
|
|
it('returns correct icon for each type', function () {
|
|
expect(ChangelogType::Feature->getIcon())->toBe('heroicon-o-rocket-launch');
|
|
expect(ChangelogType::BugFix->getIcon())->toBe('heroicon-o-bug-ant');
|
|
expect(ChangelogType::Security->getIcon())->toBe('heroicon-o-shield-check');
|
|
});
|
|
});
|
|
|
|
describe('Changelog Read Status', function () {
|
|
beforeEach(function () {
|
|
$user = User::factory()->create();
|
|
$user->assignRole(RoleEnum::Developer);
|
|
$user->givePermissionTo('View:Changelog');
|
|
$this->actingAs($user);
|
|
});
|
|
|
|
it('can mark a changelog as read', function () {
|
|
$changelog = Changelog::factory()->create();
|
|
|
|
Livewire::test(ChangelogPage::class)
|
|
->callAction('markAsReadAction', [], ['record' => $changelog->id])
|
|
->assertDispatched('refresh-changelog')
|
|
->assertDispatched('refresh-expansion', id: $changelog->id)
|
|
->assertNotified();
|
|
|
|
$this->assertDatabaseHas('changelog_user', [
|
|
'changelog_id' => $changelog->id,
|
|
'user_id' => auth()->id(),
|
|
]);
|
|
|
|
expect($changelog->refresh()->is_read)->toBeTrue();
|
|
});
|
|
|
|
it('sorts unread changelogs to the top', function () {
|
|
$read = Changelog::factory()->create(['release_date' => now()->subDays(1)]);
|
|
$unread = Changelog::factory()->create(['release_date' => now()->subDays(2)]);
|
|
|
|
// Mark one as read
|
|
$read->users()->attach(auth()->id(), ['read_at' => now()]);
|
|
|
|
$component = Livewire::test(ChangelogPage::class);
|
|
$changelogs = $component->get('changelogs');
|
|
|
|
// Unread should be first even if release_date is older
|
|
expect($changelogs->first()->id)->toBe($unread->id);
|
|
expect($changelogs->last()->id)->toBe($read->id);
|
|
});
|
|
|
|
it('shows a navigation badge for unread changelogs', function () {
|
|
Changelog::factory()->count(3)->create();
|
|
|
|
expect(ChangelogPage::getNavigationBadge())->toBe('3');
|
|
});
|
|
});
|