toBe('System'); }); test('getFriendlyModelName maps known classes', function () { expect(ActivityFormatter::getFriendlyModelName('App\\Models\\SystemSetting'))->toBe('System Config'); expect(ActivityFormatter::getFriendlyModelName('App\\Models\\User'))->toBe('User Profile'); expect(ActivityFormatter::getFriendlyModelName('App\\Models\\Role'))->toBe('Access Role'); expect(ActivityFormatter::getFriendlyModelName('App\\Models\\Permission'))->toBe('Access Permission'); }); test('getFriendlyModelName headlines unknown class basename', function () { expect(ActivityFormatter::getFriendlyModelName('App\\Models\\OtherSetting'))->toBe('Other Setting'); }); test('getEventBadgeClass returns correct class per event', function (string $event, string $expected) { expect(ActivityFormatter::getEventBadgeClass($event))->toBe($expected); })->with([ ['created', 'text-bg-success'], ['updated', 'text-bg-warning'], ['deleted', 'text-bg-danger'], ['restored', 'text-bg-info'], ['login', 'text-bg-info'], ['logout', 'text-bg-secondary'], ['password_changed', 'text-bg-primary'], ['unknown_event', 'text-bg-theme-1'], ]); test('getEventBadgeClass is case insensitive', function () { expect(ActivityFormatter::getEventBadgeClass('CREATED'))->toBe('text-bg-success'); expect(ActivityFormatter::getEventBadgeClass('Login'))->toBe('text-bg-info'); }); test('getEventIcon returns correct icon per event', function (string $event, string $expected) { expect(ActivityFormatter::getEventIcon($event))->toBe($expected); })->with([ ['created', 'bi-plus-circle'], ['updated', 'bi-pencil-square'], ['deleted', 'bi-trash'], ['logout', 'bi-box-arrow-right'], ['unknown', 'bi-info-circle'], ]); test('formatChanges on creation shows all non-sensitive fields', function () { $result = ActivityFormatter::formatChanges([ 'old' => [], 'attributes' => ['name' => 'Jane', 'email' => 'j@x.com', 'password' => 'secret'], ]); expect($result)->toHaveCount(2); $fields = array_column($result, 'field'); expect($fields)->toContain('Name'); expect($fields)->toContain('Email'); expect($fields)->not->toContain('Password'); }); test('formatChanges on update returns only changed fields', function () { $result = ActivityFormatter::formatChanges([ 'old' => ['name' => 'Old', 'email' => 'same@x.com'], 'attributes' => ['name' => 'New', 'email' => 'same@x.com'], ]); expect($result)->toHaveCount(1); expect($result[0]['field'])->toBe('Name'); expect($result[0]['old'])->toBe('Old'); expect($result[0]['new'])->toBe('New'); }); test('formatChanges filters out sensitive fields like password and remember_token', function () { $result = ActivityFormatter::formatChanges([ 'old' => ['password' => 'a', 'remember_token' => 'b', '2fa_secret' => 'c', 'api_token' => 'd'], 'attributes' => ['password' => 'a2', 'remember_token' => 'b2', '2fa_secret' => 'c2', 'api_token' => 'd2'], ]); expect($result)->toBeEmpty(); }); test('formatChanges formats null, bool, array, empty string', function () { $result = ActivityFormatter::formatChanges([ 'old' => [], 'attributes' => [ 'a_null' => null, 'a_bool_true' => true, 'a_bool_false' => false, 'a_array' => ['x' => 1], 'a_empty' => '', ], ]); $byField = collect($result)->keyBy('field'); expect($byField['A Null']['new'])->toBe('NULL'); expect($byField['A Bool True']['new'])->toBe('TRUE'); expect($byField['A Bool False']['new'])->toBe('FALSE'); expect($byField['A Array']['new'])->toBe(json_encode(['x' => 1], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); expect($byField['A Empty']['new'])->toBe('[empty]'); }); test('formatChanges truncates strings longer than 200 chars', function () { $long = str_repeat('x', 250); $result = ActivityFormatter::formatChanges([ 'old' => [], 'attributes' => ['big' => $long], ]); expect($result[0]['new'])->toEndWith('...'); expect(strlen($result[0]['new']))->toBe(200); });