232 lines
10 KiB
PHP
232 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
|
|
use App\Support\Ui\ActionSurface\ActionSurfaceDiscoveredComponent;
|
|
use App\Support\Ui\ActionSurface\ActionSurfaceExemptions;
|
|
use App\Support\Ui\ActionSurface\ActionSurfaceProfileDefinition;
|
|
use App\Support\Ui\ActionSurface\ActionSurfaceSlotRequirement;
|
|
use App\Support\Ui\ActionSurface\ActionSurfaceValidator;
|
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceComponentType;
|
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
|
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfacePanelScope;
|
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
|
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
|
|
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceType;
|
|
|
|
final class ActionSurfaceValidatorCompleteStub
|
|
{
|
|
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
|
{
|
|
return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView, ActionSurfaceType::CrudListFirstResource)
|
|
->satisfy(ActionSurfaceSlot::ListHeader)
|
|
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
|
|
->satisfy(ActionSurfaceSlot::ListRowMoreMenu)
|
|
->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
|
|
->satisfy(ActionSurfaceSlot::ListEmptyState)
|
|
->satisfy(ActionSurfaceSlot::DetailHeader);
|
|
}
|
|
}
|
|
|
|
final class ActionSurfaceValidatorMissingSlotStub
|
|
{
|
|
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
|
{
|
|
return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView, ActionSurfaceType::CrudListFirstResource)
|
|
->satisfy(ActionSurfaceSlot::ListHeader)
|
|
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value);
|
|
}
|
|
}
|
|
|
|
final class ActionSurfaceValidatorRunLogNoExportStub
|
|
{
|
|
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
|
{
|
|
return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::RunLog, ActionSurfaceType::ReadOnlyRegistryReport)
|
|
->withDefaults(new \App\Support\Ui\ActionSurface\ActionSurfaceDefaults(
|
|
moreGroupLabel: 'More',
|
|
exportIsDefaultBulkActionForReadOnly: false,
|
|
))
|
|
->satisfy(ActionSurfaceSlot::ListHeader)
|
|
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
|
|
->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
|
|
->satisfy(ActionSurfaceSlot::ListEmptyState)
|
|
->satisfy(ActionSurfaceSlot::DetailHeader);
|
|
}
|
|
}
|
|
|
|
final class ActionSurfaceValidatorExemptSlotWithoutReasonStub
|
|
{
|
|
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
|
{
|
|
return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView, ActionSurfaceType::CrudListFirstResource)
|
|
->setSlot(ActionSurfaceSlot::ListHeader, ActionSurfaceSlotRequirement::exempt())
|
|
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
|
|
->satisfy(ActionSurfaceSlot::ListRowMoreMenu)
|
|
->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
|
|
->satisfy(ActionSurfaceSlot::ListEmptyState)
|
|
->satisfy(ActionSurfaceSlot::DetailHeader);
|
|
}
|
|
}
|
|
|
|
final class ActionSurfaceValidatorMissingSurfaceTypeStub
|
|
{
|
|
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
|
{
|
|
return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView, version: 2)
|
|
->satisfy(ActionSurfaceSlot::ListHeader)
|
|
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
|
|
->satisfy(ActionSurfaceSlot::ListRowMoreMenu)
|
|
->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
|
|
->satisfy(ActionSurfaceSlot::ListEmptyState)
|
|
->satisfy(ActionSurfaceSlot::DetailHeader);
|
|
}
|
|
}
|
|
|
|
final class ActionSurfaceValidatorIncompatibleInspectAffordanceStub
|
|
{
|
|
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
|
{
|
|
return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView, ActionSurfaceType::CrudListFirstResource)
|
|
->satisfy(ActionSurfaceSlot::ListHeader)
|
|
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ViewAction->value)
|
|
->satisfy(ActionSurfaceSlot::ListRowMoreMenu)
|
|
->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
|
|
->satisfy(ActionSurfaceSlot::ListEmptyState)
|
|
->satisfy(ActionSurfaceSlot::DetailHeader);
|
|
}
|
|
}
|
|
|
|
final class ActionSurfaceValidatorPrimaryLinkColumnWithoutReasonStub
|
|
{
|
|
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
|
{
|
|
return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport)
|
|
->satisfy(ActionSurfaceSlot::ListHeader)
|
|
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::PrimaryLinkColumn->value)
|
|
->satisfy(ActionSurfaceSlot::ListRowMoreMenu)
|
|
->satisfy(ActionSurfaceSlot::ListBulkMoreGroup)
|
|
->satisfy(ActionSurfaceSlot::ListEmptyState);
|
|
}
|
|
}
|
|
|
|
final class ActionSurfaceValidatorNoDeclarationStub {}
|
|
|
|
function actionSurfaceComponent(string $className): ActionSurfaceDiscoveredComponent
|
|
{
|
|
return new ActionSurfaceDiscoveredComponent(
|
|
className: $className,
|
|
componentType: ActionSurfaceComponentType::Resource,
|
|
panelScopes: [ActionSurfacePanelScope::Tenant],
|
|
);
|
|
}
|
|
|
|
it('passes when all required slots are declared', function (): void {
|
|
$validator = new ActionSurfaceValidator(
|
|
profileDefinition: new ActionSurfaceProfileDefinition,
|
|
exemptions: new ActionSurfaceExemptions([]),
|
|
);
|
|
|
|
$result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorCompleteStub::class)]);
|
|
|
|
expect($result->hasIssues())->toBeFalse($result->formatForAssertion());
|
|
});
|
|
|
|
it('fails when a required slot is missing', function (): void {
|
|
$validator = new ActionSurfaceValidator(
|
|
profileDefinition: new ActionSurfaceProfileDefinition,
|
|
exemptions: new ActionSurfaceExemptions([]),
|
|
);
|
|
|
|
$result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorMissingSlotStub::class)]);
|
|
|
|
expect($result->hasIssues())->toBeTrue();
|
|
expect($result->formatForAssertion())->toContain('Required slot is not declared');
|
|
});
|
|
|
|
it('fails missing declarations when no baseline exemption exists', function (): void {
|
|
$validator = new ActionSurfaceValidator(
|
|
profileDefinition: new ActionSurfaceProfileDefinition,
|
|
exemptions: new ActionSurfaceExemptions([]),
|
|
);
|
|
|
|
$result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorNoDeclarationStub::class)]);
|
|
|
|
expect($result->hasIssues())->toBeTrue();
|
|
expect($result->formatForAssertion())->toContain('Missing action-surface declaration');
|
|
});
|
|
|
|
it('fails behavior-aware declarations when surface type is missing', function (): void {
|
|
$validator = new ActionSurfaceValidator(
|
|
profileDefinition: new ActionSurfaceProfileDefinition,
|
|
exemptions: new ActionSurfaceExemptions([]),
|
|
);
|
|
|
|
$result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorMissingSurfaceTypeStub::class)]);
|
|
|
|
expect($result->hasIssues())->toBeTrue();
|
|
expect($result->formatForAssertion())->toContain('Behavior-aware declarations must define a surface type');
|
|
});
|
|
|
|
it('fails behavior-aware declarations when surface type and inspect affordance are incompatible', function (): void {
|
|
$validator = new ActionSurfaceValidator(
|
|
profileDefinition: new ActionSurfaceProfileDefinition,
|
|
exemptions: new ActionSurfaceExemptions([]),
|
|
);
|
|
|
|
$result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorIncompatibleInspectAffordanceStub::class)]);
|
|
|
|
expect($result->hasIssues())->toBeTrue();
|
|
expect($result->formatForAssertion())->toContain('Inspect affordance "view_action" is incompatible with surface type "crud_list_first_resource"');
|
|
});
|
|
|
|
it('fails primary-link-column inspect affordances without an explicit reason', function (): void {
|
|
$validator = new ActionSurfaceValidator(
|
|
profileDefinition: new ActionSurfaceProfileDefinition,
|
|
exemptions: new ActionSurfaceExemptions([]),
|
|
);
|
|
|
|
$result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorPrimaryLinkColumnWithoutReasonStub::class)]);
|
|
|
|
expect($result->hasIssues())->toBeTrue();
|
|
expect($result->formatForAssertion())->toContain('Primary link column inspect affordance requires a non-empty reason');
|
|
});
|
|
|
|
it('accepts missing declarations when explicit baseline exemption exists', function (): void {
|
|
$validator = new ActionSurfaceValidator(
|
|
profileDefinition: new ActionSurfaceProfileDefinition,
|
|
exemptions: new ActionSurfaceExemptions([
|
|
ActionSurfaceValidatorNoDeclarationStub::class => 'Retrofit intentionally deferred in validator unit test.',
|
|
]),
|
|
);
|
|
|
|
$result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorNoDeclarationStub::class)]);
|
|
|
|
expect($result->hasIssues())->toBeFalse($result->formatForAssertion());
|
|
});
|
|
|
|
it('requires a bulk exemption reason when run-log export default is disabled', function (): void {
|
|
$validator = new ActionSurfaceValidator(
|
|
profileDefinition: new ActionSurfaceProfileDefinition,
|
|
exemptions: new ActionSurfaceExemptions([]),
|
|
);
|
|
|
|
$result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorRunLogNoExportStub::class)]);
|
|
|
|
expect($result->hasIssues())->toBeTrue();
|
|
expect($result->formatForAssertion())->toContain('ReadOnly/RunLog profile disables Export default');
|
|
});
|
|
|
|
it('fails when slot exemption reason is missing', function (): void {
|
|
$validator = new ActionSurfaceValidator(
|
|
profileDefinition: new ActionSurfaceProfileDefinition,
|
|
exemptions: new ActionSurfaceExemptions([]),
|
|
);
|
|
|
|
$result = $validator->validateComponents([actionSurfaceComponent(ActionSurfaceValidatorExemptSlotWithoutReasonStub::class)]);
|
|
|
|
expect($result->hasIssues())->toBeTrue();
|
|
expect($result->formatForAssertion())->toContain('Slot is marked exempt but exemption reason is missing or empty');
|
|
});
|