Skip to content

Commit

Permalink
add more logic to PendingScopedFeatureInteraction
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmastech committed Aug 17, 2024
1 parent 97ee903 commit cd04ff9
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 13 deletions.
28 changes: 19 additions & 9 deletions src/PendingScopedFeatureInteraction.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Laravel\Pennant;

use Illuminate\Support\Collection;
use Laravel\Pennant\Events\FeatureUnavailableForScope;
use RuntimeException;

class PendingScopedFeatureInteraction
Expand Down Expand Up @@ -74,7 +73,6 @@ public function loadMissing($features)
/**
* Load all defined features into memory.
*
* @param string|array<int, string> $features
* @return array<string, array<int, mixed>>
*/
public function loadAll()
Expand Down Expand Up @@ -102,10 +100,7 @@ public function value($feature)
public function values($features)
{
return Collection::make($this->rawValues($features))
->mapWithKeys(function($value, $key) {
$value = $value instanceof FeatureDoesNotMatchScope ? false : $value;
return [$key => $value];
})
->mapWithKeys(fn($value, $key) => [$key => $this->fromRaw($value)])
->all();
}

Expand Down Expand Up @@ -180,7 +175,7 @@ public function someAreActive($features)

return Collection::make($this->scope())
->every(fn ($scope) => Collection::make($features)
->some(fn ($feature) => $this->driver->get($feature, $scope) !== false));
->some(fn ($feature) => $this->fromRaw($this->driver->get($feature, $scope)) !== false));
}

/**
Expand All @@ -206,7 +201,7 @@ public function allAreInactive($features)

return Collection::make($features)
->crossJoin($this->scope())
->every(fn ($bits) => $this->driver->get(...$bits) === false);
->every(fn ($bits) => $this->fromRaw($this->driver->get(...$bits)) === false);
}

/**
Expand All @@ -221,7 +216,7 @@ public function someAreInactive($features)

return Collection::make($this->scope())
->every(fn ($scope) => Collection::make($features)
->some(fn ($feature) => $this->driver->get($feature, $scope) === false));
->some(fn ($feature) => $this->fromRaw($this->driver->get($feature, $scope)) === false));
}

/**
Expand Down Expand Up @@ -305,4 +300,19 @@ protected function scope()
{
return $this->scope ?: [null];
}

/**
* Replace FeatureDoesNotMatchScope with false.
*
* @param mixed $value
* @return false|mixed
*/
protected function fromRaw($value)
{
if ($value instanceof FeatureDoesNotMatchScope) {
return false;
}

return $value;
}
}
79 changes: 75 additions & 4 deletions tests/Feature/DatabaseDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1573,7 +1573,7 @@ public function testItCanLoadAllFeaturesForScope()

public function testCanGetAllWhenFeaturesAreDefinedForDifferentScopes(): void
{
// Given features
// Given features of varying scopes
Feature::define('for-teams', fn(Team $team) => true);
Feature::define('for-users', fn(User $user) => true);
Feature::define('for-nullable-users', fn(?User $user) => false);
Expand All @@ -1598,6 +1598,7 @@ public function testCanGetAllWhenFeaturesAreDefinedForDifferentScopes(): void
$features
);

// And an event was dispatched indicating that we tried to retrieve a feature not matched to scope
Event::assertDispatchedTimes(FeatureUnavailableForScope::class, 1);
Event::assertDispatched(function (FeatureUnavailableForScope $event) use ($user) {
return $event->feature === 'for-teams'
Expand All @@ -1607,10 +1608,10 @@ public function testCanGetAllWhenFeaturesAreDefinedForDifferentScopes(): void

public function testInvalidScopedFeatureReturnsFalse(): void
{
// Given
// Given scope belonging to a Team scope
Feature::define('yooo', fn(Team $team) => true);

// When
// When attempting to fetch that feature for a User scope
$result = Feature::for(new User)->active('yooo');

// Then
Expand All @@ -1619,7 +1620,7 @@ public function testInvalidScopedFeatureReturnsFalse(): void

public function testValuesReturnsFalseForFeaturesWhichDoNotBelongToScope(): void
{
// Given
// Given features with varying scopes
Feature::define('foo', fn(User $user) => true);
Feature::define('bar', fn(Team $team) => true);
Feature::define('zed', fn(mixed $v) => true);
Expand All @@ -1640,6 +1641,76 @@ public function testValuesReturnsFalseForFeaturesWhichDoNotBelongToScope(): void
'woof' => false,
], $features);
}

public function testSomeAreActiveWithMismatchedScopeTreatsAsFalse(): void
{
// Given features with varying scopes
Feature::define('for-teams', fn(Team $team) => true);
Feature::define('for-nullable', fn() => false);

// When
$result = Feature::for(new User)->someAreActive(['for-teams', 'for-nullable']);

// Then
$this->assertFalse($result);
}

public function testAllAreActiveTreatsMismatchedScopeAsFalse(): void
{
// Given features with varying scopes
Feature::define('for-team', fn(Team $team) => true);
Feature::define('for-user', fn(User $user) => true);

// When
$result = Feature::for(new User)->allAreActive(['for-team', 'for-user']);

// Then
$this->assertFalse($result);
}

public function testSomeAreInactiveWithMismatchedScopeTreatsAsFalse(): void
{
// Given features with varying scopes
Feature::define('for-teams', fn(Team $team) => true);
Feature::define('for-user', fn(User $user) => true);
Feature::define('for-null-scope', fn() => true);

// When
$result = Feature::for(new User)->someAreInactive([
'for-teams', 'for-user', 'for-null-scope'
]);

// Then
$this->assertTrue($result);
}

public function testAllAreInactiveWithMismatchedScope(): void
{
// Given features with varying scopes
Feature::define('for-teams', fn(Team $team) => true);
Feature::define('for-user', fn(User $user) => false);
Feature::define('for-null-scope', fn() => false);

// When
$result = Feature::for(new User)->allAreInactive(['for-teams', 'for-user', 'for-null-scope']);

// Then
$this->assertTrue($result);
}

public function test_mismatchedScopes_load(): void
{
// Given
Feature::define('for-teams', fn(Team $team) => true);
Feature::define('for-user', fn(User $user) => false);
Feature::define('for-null-scope', fn() => false);

// When
$result = Feature::for(new User)->load(['for-teams']);

// Then
$this->assertFalse($result['for-teams'][0]);
}
}

class UnregisteredFeature
Expand Down

0 comments on commit cd04ff9

Please sign in to comment.