TenantAtlas/tests/Unit/Support/Diff/DiffPresenterTest.php
2026-03-14 13:31:24 +01:00

128 lines
4.2 KiB
PHP

<?php
declare(strict_types=1);
use App\Support\Diff\DiffPresenter;
use App\Support\Diff\DiffRowStatus;
it('classifies rows across all shared states and derives matching summary counts', function (): void {
$presentation = app(DiffPresenter::class)->present(
baseline: [
'description' => 'Before',
'display_name' => 'TenantPilot',
'retired_setting' => 'Legacy',
],
current: [
'description' => 'After',
'display_name' => 'TenantPilot',
'new_setting' => 'Enabled',
],
labels: [
'description' => 'Description',
'display_name' => 'Display name',
'new_setting' => 'New setting',
'retired_setting' => 'Retired setting',
],
);
$rows = collect($presentation->rows)->keyBy('key');
expect($presentation->summary->changedCount)->toBe(1)
->and($presentation->summary->addedCount)->toBe(1)
->and($presentation->summary->removedCount)->toBe(1)
->and($presentation->summary->unchangedCount)->toBe(1)
->and($presentation->summary->hasRows)->toBeTrue()
->and($rows->get('description')?->status)->toBe(DiffRowStatus::Changed)
->and($rows->get('display_name')?->status)->toBe(DiffRowStatus::Unchanged)
->and($rows->get('new_setting')?->status)->toBe(DiffRowStatus::Added)
->and($rows->get('retired_setting')?->status)->toBe(DiffRowStatus::Removed);
});
it('returns rows in deterministic label order', function (): void {
$presentation = app(DiffPresenter::class)->present(
baseline: [
'zeta_value' => 'same',
'alpha_value' => 'before',
],
current: [
'alpha_value' => 'after',
'middle_value' => 'new',
'zeta_value' => 'same',
],
labels: [
'alpha_value' => 'Alpha value',
'middle_value' => 'Middle value',
'zeta_value' => 'Zeta value',
],
);
expect(array_map(
static fn ($row): string => $row->label,
$presentation->rows,
))->toBe([
'Alpha value',
'Middle value',
'Zeta value',
]);
});
it('prepares inline list fragments for simple list comparisons', function (): void {
$presentation = app(DiffPresenter::class)->present(
baseline: [
'scope_tags' => ['Default', 'Legacy', 'Shared'],
],
current: [
'scope_tags' => ['Default', 'Shared', 'Workspace'],
],
labels: [
'scope_tags' => 'Scope tags',
],
);
$row = $presentation->rows[0] ?? null;
expect($row)->not->toBeNull()
->and($row?->isListLike)->toBeTrue()
->and($row?->addedItems)->toBe(['Workspace'])
->and($row?->removedItems)->toBe(['Legacy'])
->and($row?->unchangedItems)->toBe(['Default', 'Shared']);
});
it('falls back to generated labels and safe empty meta when optional metadata is missing or invalid', function (): void {
$presentation = app(DiffPresenter::class)->present(
baseline: [
'customSettingFoo' => 'Before',
],
current: [
'customSettingFoo' => 'After',
],
meta: [
'customSettingFoo' => 'invalid-meta-shape',
],
);
$row = $presentation->rows[0] ?? null;
expect($row)->not->toBeNull()
->and($row?->label)->toBe('Custom Setting Foo')
->and($row?->meta)->toBe([]);
});
it('returns a no-data presentation for empty or sparse compare payloads', function (): void {
$presentation = app(DiffPresenter::class)->present(
baseline: [],
current: [],
changedKeys: ['ghost_key'],
labels: ['ghost_key' => 'Ghost key'],
meta: ['ghost_key' => ['note' => 'unused']],
);
expect($presentation->rows)->toBe([])
->and($presentation->summary->hasRows)->toBeFalse()
->and($presentation->summary->changedCount)->toBe(0)
->and($presentation->summary->addedCount)->toBe(0)
->and($presentation->summary->removedCount)->toBe(0)
->and($presentation->summary->unchangedCount)->toBe(0)
->and($presentation->summary->message)->toBe('No diff data available.');
});