Initial commit after upgrading to php 8.3 and splitting to seperated tool chain
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Enum;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Enum\BackedEnumTrait;
|
||||
|
||||
// String-backed enum
|
||||
enum Color: string
|
||||
{
|
||||
use BackedEnumTrait;
|
||||
|
||||
case Red = 'red';
|
||||
case Green = 'green';
|
||||
case Blue = 'blue';
|
||||
}
|
||||
|
||||
// Integer-backed enum
|
||||
enum Priority: int
|
||||
{
|
||||
use BackedEnumTrait;
|
||||
|
||||
case Low = 1;
|
||||
case Medium = 2;
|
||||
case High = 3;
|
||||
}
|
||||
|
||||
final class BackedEnumTraitTest extends TestCase
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// values()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testValuesReturnsAllStringValues(): void
|
||||
{
|
||||
self::assertSame(['red', 'green', 'blue'], Color::values());
|
||||
}
|
||||
|
||||
public function testValuesReturnsAllIntValues(): void
|
||||
{
|
||||
self::assertSame([1, 2, 3], Priority::values());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// hasValue()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testHasValueReturnsTrueForExistingValue(): void
|
||||
{
|
||||
self::assertTrue(Color::hasValue('green'));
|
||||
self::assertTrue(Priority::hasValue(2));
|
||||
}
|
||||
|
||||
public function testHasValueReturnsFalseForUnknownValue(): void
|
||||
{
|
||||
self::assertFalse(Color::hasValue('yellow'));
|
||||
self::assertFalse(Priority::hasValue(99));
|
||||
}
|
||||
|
||||
public function testHasValueIsStrictlyTyped(): void
|
||||
{
|
||||
// integer 1 must not match string '1'
|
||||
self::assertFalse(Color::hasValue(1));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// fromName()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testFromNameReturnsCorrectCase(): void
|
||||
{
|
||||
self::assertSame(Color::Blue, Color::fromName('Blue'));
|
||||
self::assertSame(Priority::High, Priority::fromName('High'));
|
||||
}
|
||||
|
||||
public function testFromNameThrowsValueErrorForUnknownName(): void
|
||||
{
|
||||
$this->expectException(\ValueError::class);
|
||||
Color::fromName('Purple');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// tryFromName()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testTryFromNameReturnsCorrectCase(): void
|
||||
{
|
||||
self::assertSame(Color::Red, Color::tryFromName('Red'));
|
||||
}
|
||||
|
||||
public function testTryFromNameReturnsNullForUnknownName(): void
|
||||
{
|
||||
self::assertNull(Color::tryFromName('Magenta'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// toArray() — inherited via EnumTrait, but backed path tested here
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testToArrayReturnsValueToNameMapping(): void
|
||||
{
|
||||
self::assertSame(
|
||||
['red' => 'Red', 'green' => 'Green', 'blue' => 'Blue'],
|
||||
Color::toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
public function testToArrayForIntBackedEnum(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[1 => 'Low', 2 => 'Medium', 3 => 'High'],
|
||||
Priority::toArray(),
|
||||
);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// is() with scalar (backed enum path)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testIsReturnsTrueForMatchingValue(): void
|
||||
{
|
||||
self::assertTrue(Color::Green->is('green'));
|
||||
}
|
||||
|
||||
public function testIsReturnsFalseForOtherValue(): void
|
||||
{
|
||||
self::assertFalse(Color::Green->is('red'));
|
||||
}
|
||||
|
||||
public function testIsReturnsTrueForIntValue(): void
|
||||
{
|
||||
self::assertTrue(Priority::Medium->is(2));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// find() — backed enum paths (value + case-insensitive)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testFindByValue(): void
|
||||
{
|
||||
self::assertSame(Color::Blue, Color::find('blue'));
|
||||
}
|
||||
|
||||
public function testFindByValueCaseInsensitive(): void
|
||||
{
|
||||
self::assertSame(Color::Red, Color::find('RED', ignoreCase: true));
|
||||
}
|
||||
|
||||
public function testFindByNameCaseInsensitive(): void
|
||||
{
|
||||
self::assertSame(Color::Green, Color::find('green', ignoreCase: true));
|
||||
}
|
||||
|
||||
public function testFindReturnsNullForUnknown(): void
|
||||
{
|
||||
self::assertNull(Color::find('violet'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// toValueArray()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testToValueArrayReturnsNameToValueMapping(): void
|
||||
{
|
||||
self::assertSame(
|
||||
['Red' => 'red', 'Green' => 'green', 'Blue' => 'blue'],
|
||||
Color::toValueArray(),
|
||||
);
|
||||
}
|
||||
|
||||
public function testToValueArrayForIntBackedEnum(): void
|
||||
{
|
||||
self::assertSame(
|
||||
['Low' => 1, 'Medium' => 2, 'High' => 3],
|
||||
Priority::toValueArray(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Enum;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Enum\BackedEnumTrait;
|
||||
use Tez\Utils\Enum\BitFlagTrait;
|
||||
|
||||
enum Permission: int
|
||||
{
|
||||
use BackedEnumTrait;
|
||||
use BitFlagTrait;
|
||||
|
||||
case Read = 1;
|
||||
case Write = 2;
|
||||
case Delete = 4;
|
||||
case Admin = 8;
|
||||
}
|
||||
|
||||
final class BitFlagTraitTest extends TestCase
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// combine()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testCombineWithNoArgumentsReturnsZero(): void
|
||||
{
|
||||
self::assertSame(0, Permission::combine());
|
||||
}
|
||||
|
||||
public function testCombineWithSingleCase(): void
|
||||
{
|
||||
self::assertSame(1, Permission::combine(Permission::Read));
|
||||
}
|
||||
|
||||
public function testCombineWithTwoCases(): void
|
||||
{
|
||||
self::assertSame(3, Permission::combine(Permission::Read, Permission::Write));
|
||||
}
|
||||
|
||||
public function testCombineWithAllCases(): void
|
||||
{
|
||||
self::assertSame(15, Permission::combine(
|
||||
Permission::Read,
|
||||
Permission::Write,
|
||||
Permission::Delete,
|
||||
Permission::Admin,
|
||||
));
|
||||
}
|
||||
|
||||
public function testCombineIsDeduplicated(): void
|
||||
{
|
||||
self::assertSame(1, Permission::combine(Permission::Read, Permission::Read));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// fromBitmask()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testFromBitmaskWithZeroReturnsEmptyArray(): void
|
||||
{
|
||||
self::assertSame([], Permission::fromBitmask(0));
|
||||
}
|
||||
|
||||
public function testFromBitmaskWithSingleBitReturnsSingleCase(): void
|
||||
{
|
||||
self::assertSame([Permission::Read], Permission::fromBitmask(1));
|
||||
}
|
||||
|
||||
public function testFromBitmaskWithCombinedMaskReturnsMatchingCases(): void
|
||||
{
|
||||
$mask = Permission::combine(Permission::Read, Permission::Write);
|
||||
|
||||
self::assertSame([Permission::Read, Permission::Write], Permission::fromBitmask($mask));
|
||||
}
|
||||
|
||||
public function testFromBitmaskWithAllBitsReturnsAllCases(): void
|
||||
{
|
||||
$mask = Permission::combine(
|
||||
Permission::Read,
|
||||
Permission::Write,
|
||||
Permission::Delete,
|
||||
Permission::Admin,
|
||||
);
|
||||
|
||||
self::assertSame(
|
||||
[Permission::Read, Permission::Write, Permission::Delete, Permission::Admin],
|
||||
Permission::fromBitmask($mask),
|
||||
);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isSetIn()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testIsSetInReturnsTrueWhenBitIsPresent(): void
|
||||
{
|
||||
$mask = Permission::combine(Permission::Read, Permission::Write);
|
||||
|
||||
self::assertTrue(Permission::Read->isSetIn($mask));
|
||||
self::assertTrue(Permission::Write->isSetIn($mask));
|
||||
}
|
||||
|
||||
public function testIsSetInReturnsFalseWhenBitIsAbsent(): void
|
||||
{
|
||||
$mask = Permission::combine(Permission::Read, Permission::Write);
|
||||
|
||||
self::assertFalse(Permission::Delete->isSetIn($mask));
|
||||
self::assertFalse(Permission::Admin->isSetIn($mask));
|
||||
}
|
||||
|
||||
public function testIsSetInReturnsFalseForZeroMask(): void
|
||||
{
|
||||
self::assertFalse(Permission::Read->isSetIn(0));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// addTo()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testAddToSetsBit(): void
|
||||
{
|
||||
$mask = Permission::combine(Permission::Read);
|
||||
$mask = Permission::Write->addTo($mask);
|
||||
|
||||
self::assertSame(3, $mask);
|
||||
self::assertTrue(Permission::Write->isSetIn($mask));
|
||||
}
|
||||
|
||||
public function testAddToIsIdempotent(): void
|
||||
{
|
||||
$mask = Permission::combine(Permission::Read);
|
||||
$mask = Permission::Read->addTo($mask);
|
||||
|
||||
self::assertSame(1, $mask);
|
||||
}
|
||||
|
||||
public function testAddToOnEmptyMask(): void
|
||||
{
|
||||
self::assertSame(4, Permission::Delete->addTo(0));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// removeFrom()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testRemoveFromClearsBit(): void
|
||||
{
|
||||
$mask = Permission::combine(Permission::Read, Permission::Write);
|
||||
$mask = Permission::Write->removeFrom($mask);
|
||||
|
||||
self::assertSame(1, $mask);
|
||||
self::assertFalse(Permission::Write->isSetIn($mask));
|
||||
}
|
||||
|
||||
public function testRemoveFromIsIdempotent(): void
|
||||
{
|
||||
$mask = Permission::combine(Permission::Read);
|
||||
$mask = Permission::Write->removeFrom($mask);
|
||||
|
||||
self::assertSame(1, $mask);
|
||||
}
|
||||
|
||||
public function testRemoveFromOnZeroMaskReturnsZero(): void
|
||||
{
|
||||
self::assertSame(0, Permission::Read->removeFrom(0));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Workflow: add then remove
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testAddThenRemoveRestoresOriginalMask(): void
|
||||
{
|
||||
$original = Permission::combine(Permission::Read, Permission::Delete);
|
||||
$modified = Permission::Write->addTo($original);
|
||||
$restored = Permission::Write->removeFrom($modified);
|
||||
|
||||
self::assertSame($original, $restored);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Enum;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Enum\BackedEnumTrait;
|
||||
use Tez\Utils\Enum\ComparableTrait;
|
||||
|
||||
enum Level: int
|
||||
{
|
||||
use BackedEnumTrait;
|
||||
use ComparableTrait;
|
||||
|
||||
case Low = 1;
|
||||
case Medium = 2;
|
||||
case High = 3;
|
||||
case Ultra = 4;
|
||||
}
|
||||
|
||||
final class ComparableTraitTest extends TestCase
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// isGreaterThan()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testIsGreaterThanReturnsTrueWhenHigher(): void
|
||||
{
|
||||
self::assertTrue(Level::High->isGreaterThan(Level::Low));
|
||||
}
|
||||
|
||||
public function testIsGreaterThanReturnsFalseWhenEqual(): void
|
||||
{
|
||||
self::assertFalse(Level::Medium->isGreaterThan(Level::Medium));
|
||||
}
|
||||
|
||||
public function testIsGreaterThanReturnsFalseWhenLower(): void
|
||||
{
|
||||
self::assertFalse(Level::Low->isGreaterThan(Level::High));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isGreaterThanOrEqual()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testIsGreaterThanOrEqualReturnsTrueWhenHigher(): void
|
||||
{
|
||||
self::assertTrue(Level::High->isGreaterThanOrEqual(Level::Low));
|
||||
}
|
||||
|
||||
public function testIsGreaterThanOrEqualReturnsTrueWhenEqual(): void
|
||||
{
|
||||
self::assertTrue(Level::Medium->isGreaterThanOrEqual(Level::Medium));
|
||||
}
|
||||
|
||||
public function testIsGreaterThanOrEqualReturnsFalseWhenLower(): void
|
||||
{
|
||||
self::assertFalse(Level::Low->isGreaterThanOrEqual(Level::High));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isLessThan()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testIsLessThanReturnsTrueWhenLower(): void
|
||||
{
|
||||
self::assertTrue(Level::Low->isLessThan(Level::High));
|
||||
}
|
||||
|
||||
public function testIsLessThanReturnsFalseWhenEqual(): void
|
||||
{
|
||||
self::assertFalse(Level::Medium->isLessThan(Level::Medium));
|
||||
}
|
||||
|
||||
public function testIsLessThanReturnsFalseWhenHigher(): void
|
||||
{
|
||||
self::assertFalse(Level::High->isLessThan(Level::Low));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isLessThanOrEqual()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testIsLessThanOrEqualReturnsTrueWhenLower(): void
|
||||
{
|
||||
self::assertTrue(Level::Low->isLessThanOrEqual(Level::High));
|
||||
}
|
||||
|
||||
public function testIsLessThanOrEqualReturnsTrueWhenEqual(): void
|
||||
{
|
||||
self::assertTrue(Level::Medium->isLessThanOrEqual(Level::Medium));
|
||||
}
|
||||
|
||||
public function testIsLessThanOrEqualReturnsFalseWhenHigher(): void
|
||||
{
|
||||
self::assertFalse(Level::High->isLessThanOrEqual(Level::Low));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// between()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testBetweenReturnsTrueWhenWithinRange(): void
|
||||
{
|
||||
self::assertTrue(Level::Medium->between(Level::Low, Level::High));
|
||||
}
|
||||
|
||||
public function testBetweenReturnsTrueAtLowerBound(): void
|
||||
{
|
||||
self::assertTrue(Level::Low->between(Level::Low, Level::High));
|
||||
}
|
||||
|
||||
public function testBetweenReturnsTrueAtUpperBound(): void
|
||||
{
|
||||
self::assertTrue(Level::High->between(Level::Low, Level::High));
|
||||
}
|
||||
|
||||
public function testBetweenReturnsFalseWhenBelowRange(): void
|
||||
{
|
||||
self::assertFalse(Level::Low->between(Level::Medium, Level::Ultra));
|
||||
}
|
||||
|
||||
public function testBetweenReturnsFalseWhenAboveRange(): void
|
||||
{
|
||||
self::assertFalse(Level::Ultra->between(Level::Low, Level::High));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// clamp()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testClampReturnsValueWhenWithinRange(): void
|
||||
{
|
||||
self::assertSame(Level::Medium, Level::Medium->clamp(Level::Low, Level::High));
|
||||
}
|
||||
|
||||
public function testClampReturnsMinWhenBelowRange(): void
|
||||
{
|
||||
self::assertSame(Level::Medium, Level::Low->clamp(Level::Medium, Level::Ultra));
|
||||
}
|
||||
|
||||
public function testClampReturnsMaxWhenAboveRange(): void
|
||||
{
|
||||
self::assertSame(Level::High, Level::Ultra->clamp(Level::Low, Level::High));
|
||||
}
|
||||
|
||||
public function testClampReturnsMinWhenEqualToMin(): void
|
||||
{
|
||||
self::assertSame(Level::Low, Level::Low->clamp(Level::Low, Level::High));
|
||||
}
|
||||
|
||||
public function testClampReturnsMaxWhenEqualToMax(): void
|
||||
{
|
||||
self::assertSame(Level::High, Level::High->clamp(Level::Low, Level::High));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Enum;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Enum\EnumCollectionTrait;
|
||||
|
||||
enum Suit: string
|
||||
{
|
||||
use EnumCollectionTrait;
|
||||
|
||||
case Hearts = 'H';
|
||||
case Diamonds = 'D';
|
||||
case Clubs = 'C';
|
||||
case Spades = 'S';
|
||||
}
|
||||
|
||||
enum Compass
|
||||
{
|
||||
use EnumCollectionTrait;
|
||||
|
||||
case North;
|
||||
case East;
|
||||
case South;
|
||||
case West;
|
||||
}
|
||||
|
||||
final class EnumCollectionTraitTest extends TestCase
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// random()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testRandomReturnsACaseInstance(): void
|
||||
{
|
||||
$case = Suit::random();
|
||||
|
||||
self::assertInstanceOf(Suit::class, $case);
|
||||
}
|
||||
|
||||
public function testRandomEventuallyReturnsAllCases(): void
|
||||
{
|
||||
$seen = [];
|
||||
|
||||
for ($i = 0; $i < 200; $i++) {
|
||||
$seen[Suit::random()->name] = true;
|
||||
}
|
||||
|
||||
self::assertCount(4, $seen, 'Expected all 4 suits to appear within 200 draws');
|
||||
}
|
||||
|
||||
public function testRandomWorksForPureEnum(): void
|
||||
{
|
||||
$case = Compass::random();
|
||||
|
||||
self::assertInstanceOf(Compass::class, $case);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// except()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testExceptRemovesGivenCases(): void
|
||||
{
|
||||
$result = Suit::except(Suit::Hearts, Suit::Spades);
|
||||
|
||||
self::assertSame([Suit::Diamonds, Suit::Clubs], $result);
|
||||
}
|
||||
|
||||
public function testExceptWithNoCasesReturnsAll(): void
|
||||
{
|
||||
self::assertSame(Suit::cases(), Suit::except());
|
||||
}
|
||||
|
||||
public function testExceptWithAllCasesReturnsEmpty(): void
|
||||
{
|
||||
$result = Suit::except(...Suit::cases());
|
||||
|
||||
self::assertSame([], $result);
|
||||
}
|
||||
|
||||
public function testExceptPreservesEnumOrder(): void
|
||||
{
|
||||
$result = Suit::except(Suit::Diamonds);
|
||||
|
||||
self::assertSame([Suit::Hearts, Suit::Clubs, Suit::Spades], $result);
|
||||
}
|
||||
|
||||
public function testExceptWorksForPureEnum(): void
|
||||
{
|
||||
$result = Compass::except(Compass::North, Compass::South);
|
||||
|
||||
self::assertSame([Compass::East, Compass::West], $result);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// only()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testOnlyReturnsRequestedCases(): void
|
||||
{
|
||||
$result = Suit::only(Suit::Hearts, Suit::Spades);
|
||||
|
||||
self::assertSame([Suit::Hearts, Suit::Spades], $result);
|
||||
}
|
||||
|
||||
public function testOnlyPreservesEnumDefinitionOrder(): void
|
||||
{
|
||||
// Pass in reverse order — result must follow enum definition order
|
||||
$result = Suit::only(Suit::Spades, Suit::Hearts);
|
||||
|
||||
self::assertSame([Suit::Hearts, Suit::Spades], $result);
|
||||
}
|
||||
|
||||
public function testOnlyWithNoCasesReturnsEmpty(): void
|
||||
{
|
||||
self::assertSame([], Suit::only());
|
||||
}
|
||||
|
||||
public function testOnlyWithAllCasesReturnsAll(): void
|
||||
{
|
||||
$result = Suit::only(...Suit::cases());
|
||||
|
||||
self::assertSame(Suit::cases(), $result);
|
||||
}
|
||||
|
||||
public function testOnlyWorksForPureEnum(): void
|
||||
{
|
||||
$result = Compass::only(Compass::East, Compass::West);
|
||||
|
||||
self::assertSame([Compass::East, Compass::West], $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Enum;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Enum\BackedEnumTrait;
|
||||
use Tez\Utils\Enum\EnumTrait;
|
||||
|
||||
// Pure (unit) enum used across all EnumTrait tests
|
||||
enum Season
|
||||
{
|
||||
use EnumTrait;
|
||||
|
||||
case Spring;
|
||||
case Summer;
|
||||
case Autumn;
|
||||
case Winter;
|
||||
}
|
||||
|
||||
// Backed enum to verify backed paths of EnumTrait (toArray, is, find)
|
||||
enum Direction: string
|
||||
{
|
||||
use BackedEnumTrait;
|
||||
|
||||
case North = 'north';
|
||||
case South = 'south';
|
||||
case East = 'east';
|
||||
case West = 'west';
|
||||
}
|
||||
|
||||
final class EnumTraitTest extends TestCase
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// is()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testIsReturnsTrueForSameInstance(): void
|
||||
{
|
||||
self::assertTrue(Season::Summer->is(Season::Summer));
|
||||
}
|
||||
|
||||
public function testIsReturnsFalseForNull(): void
|
||||
{
|
||||
self::assertFalse(Season::Summer->is(null));
|
||||
}
|
||||
|
||||
public function testIsReturnsFalseForDifferentCase(): void
|
||||
{
|
||||
self::assertFalse(Season::Summer->is(Season::Winter));
|
||||
}
|
||||
|
||||
public function testIsReturnsTrueForMatchingStringName(): void
|
||||
{
|
||||
self::assertTrue(Season::Autumn->is('Autumn'));
|
||||
}
|
||||
|
||||
public function testIsReturnsFalseForNonMatchingStringName(): void
|
||||
{
|
||||
self::assertFalse(Season::Autumn->is('Spring'));
|
||||
}
|
||||
|
||||
public function testIsReturnsTrueForBackedEnumByValue(): void
|
||||
{
|
||||
self::assertTrue(Direction::North->is('north'));
|
||||
}
|
||||
|
||||
public function testIsReturnsFalseForBackedEnumWithWrongValue(): void
|
||||
{
|
||||
self::assertFalse(Direction::North->is('south'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// isOneOf() / isNoneOf()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testIsOneOfReturnsTrueWhenMatchFound(): void
|
||||
{
|
||||
self::assertTrue(Season::Winter->isOneOf([Season::Autumn, Season::Winter]));
|
||||
}
|
||||
|
||||
public function testIsOneOfReturnsFalseWhenNoMatch(): void
|
||||
{
|
||||
self::assertFalse(Season::Spring->isOneOf([Season::Autumn, Season::Winter]));
|
||||
}
|
||||
|
||||
public function testIsOneOfReturnsFalseForNull(): void
|
||||
{
|
||||
self::assertFalse(Season::Spring->isOneOf(null));
|
||||
}
|
||||
|
||||
public function testIsNoneOfIsInverseOfIsOneOf(): void
|
||||
{
|
||||
self::assertTrue(Season::Spring->isNoneOf([Season::Autumn, Season::Winter]));
|
||||
self::assertFalse(Season::Winter->isNoneOf([Season::Autumn, Season::Winter]));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// toArray()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testToArrayForPureEnumReturnsNameToNameMapping(): void
|
||||
{
|
||||
$expected = [
|
||||
'Spring' => 'Spring',
|
||||
'Summer' => 'Summer',
|
||||
'Autumn' => 'Autumn',
|
||||
'Winter' => 'Winter',
|
||||
];
|
||||
|
||||
self::assertSame($expected, Season::toArray());
|
||||
}
|
||||
|
||||
public function testToArrayForBackedEnumReturnsValueToNameMapping(): void
|
||||
{
|
||||
$expected = [
|
||||
'north' => 'North',
|
||||
'south' => 'South',
|
||||
'east' => 'East',
|
||||
'west' => 'West',
|
||||
];
|
||||
|
||||
self::assertSame($expected, Direction::toArray());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// find()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testFindReturnsNullForNull(): void
|
||||
{
|
||||
self::assertNull(Season::find(null));
|
||||
}
|
||||
|
||||
public function testFindReturnsEnumInstanceWhenPassedItself(): void
|
||||
{
|
||||
self::assertSame(Season::Spring, Season::find(Season::Spring));
|
||||
}
|
||||
|
||||
public function testFindByNameForPureEnum(): void
|
||||
{
|
||||
self::assertSame(Season::Summer, Season::find('Summer'));
|
||||
}
|
||||
|
||||
public function testFindReturnsNullForUnknownName(): void
|
||||
{
|
||||
self::assertNull(Season::find('Unknown'));
|
||||
}
|
||||
|
||||
public function testFindIgnoreCasePureEnum(): void
|
||||
{
|
||||
self::assertSame(Season::Winter, Season::find('winter', ignoreCase: true));
|
||||
}
|
||||
|
||||
public function testFindByValueForBackedEnum(): void
|
||||
{
|
||||
self::assertSame(Direction::East, Direction::find('east'));
|
||||
}
|
||||
|
||||
public function testFindIgnoreCaseBackedEnum(): void
|
||||
{
|
||||
self::assertSame(Direction::West, Direction::find('WEST', ignoreCase: true));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// filter()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testFilterReturnsCasesMatchingCallback(): void
|
||||
{
|
||||
$result = Season::filter(fn(Season $c) => in_array($c, [Season::Spring, Season::Summer], true));
|
||||
|
||||
self::assertCount(2, $result);
|
||||
self::assertContains(Season::Spring, $result);
|
||||
self::assertContains(Season::Summer, $result);
|
||||
}
|
||||
|
||||
public function testFilterReturnsEmptyArrayWhenNothingMatches(): void
|
||||
{
|
||||
$result = Season::filter(fn() => false);
|
||||
|
||||
self::assertSame([], $result);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ordinal()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testOrdinalReturnsZeroBasedPosition(): void
|
||||
{
|
||||
self::assertSame(0, Season::Spring->ordinal());
|
||||
self::assertSame(1, Season::Summer->ordinal());
|
||||
self::assertSame(2, Season::Autumn->ordinal());
|
||||
self::assertSame(3, Season::Winter->ordinal());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// first() / last()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testFirstReturnsFirstCase(): void
|
||||
{
|
||||
self::assertSame(Season::Spring, Season::first());
|
||||
}
|
||||
|
||||
public function testLastReturnsLastCase(): void
|
||||
{
|
||||
self::assertSame(Season::Winter, Season::last());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Enum;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Enum\EnumTrait;
|
||||
use Tez\Utils\Enum\StepTrait;
|
||||
|
||||
enum OrderStatus
|
||||
{
|
||||
use EnumTrait;
|
||||
use StepTrait;
|
||||
|
||||
case Draft;
|
||||
case Pending;
|
||||
case Shipped;
|
||||
case Delivered;
|
||||
case Cancelled;
|
||||
}
|
||||
|
||||
final class StepTraitTest extends TestCase
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// next()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testNextReturnsFollowingCase(): void
|
||||
{
|
||||
self::assertSame(OrderStatus::Shipped, OrderStatus::Pending->next());
|
||||
}
|
||||
|
||||
public function testNextFromFirstReturnsSecond(): void
|
||||
{
|
||||
self::assertSame(OrderStatus::Pending, OrderStatus::Draft->next());
|
||||
}
|
||||
|
||||
public function testNextFromLastReturnsNull(): void
|
||||
{
|
||||
self::assertNull(OrderStatus::Cancelled->next());
|
||||
}
|
||||
|
||||
public function testNextFromSecondToLastReturnsLast(): void
|
||||
{
|
||||
self::assertSame(OrderStatus::Cancelled, OrderStatus::Delivered->next());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// prev()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testPrevReturnsPrecedingCase(): void
|
||||
{
|
||||
self::assertSame(OrderStatus::Draft, OrderStatus::Pending->prev());
|
||||
}
|
||||
|
||||
public function testPrevFromLastReturnsPenultimate(): void
|
||||
{
|
||||
self::assertSame(OrderStatus::Delivered, OrderStatus::Cancelled->prev());
|
||||
}
|
||||
|
||||
public function testPrevFromFirstReturnsNull(): void
|
||||
{
|
||||
self::assertNull(OrderStatus::Draft->prev());
|
||||
}
|
||||
|
||||
public function testPrevFromSecondReturnsFirst(): void
|
||||
{
|
||||
self::assertSame(OrderStatus::Draft, OrderStatus::Pending->prev());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Chaining
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testNextThenPrevReturnsSameCase(): void
|
||||
{
|
||||
$case = OrderStatus::Pending;
|
||||
|
||||
self::assertSame($case, $case->next()?->prev());
|
||||
}
|
||||
|
||||
public function testPrevThenNextReturnsSameCase(): void
|
||||
{
|
||||
$case = OrderStatus::Shipped;
|
||||
|
||||
self::assertSame($case, $case->prev()?->next());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Single-case enum edge case
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testNextAndPrevReturnNullForOnlyCase(): void
|
||||
{
|
||||
self::assertNull(Alone::Only->next());
|
||||
self::assertNull(Alone::Only->prev());
|
||||
}
|
||||
}
|
||||
|
||||
enum Alone
|
||||
{
|
||||
use StepTrait;
|
||||
|
||||
case Only;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tez\Utils\Tests\Enum;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tez\Utils\Enum\BackedEnumTrait;
|
||||
use Tez\Utils\Enum\TranslatableTrait;
|
||||
|
||||
enum StatusWithLabels: string
|
||||
{
|
||||
use BackedEnumTrait;
|
||||
use TranslatableTrait;
|
||||
|
||||
case Active = 'active';
|
||||
case Pending = 'pending';
|
||||
case Archived = 'archived';
|
||||
|
||||
/** @return array<string, string> */
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
self::Active->name => 'Aktiv',
|
||||
self::Pending->name => 'Ausstehend',
|
||||
// Archived intentionally has no label → fallback to name
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
enum PureWithLabels
|
||||
{
|
||||
use TranslatableTrait;
|
||||
|
||||
case Open;
|
||||
case Closed;
|
||||
|
||||
/** @return array<string, string> */
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
self::Open->name => 'Offen',
|
||||
self::Closed->name => 'Geschlossen',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
enum NoLabels: int
|
||||
{
|
||||
use TranslatableTrait;
|
||||
|
||||
case One = 1;
|
||||
case Two = 2;
|
||||
}
|
||||
|
||||
final class TranslatableTraitTest extends TestCase
|
||||
{
|
||||
// -------------------------------------------------------------------------
|
||||
// label()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testLabelReturnsConfiguredString(): void
|
||||
{
|
||||
self::assertSame('Aktiv', StatusWithLabels::Active->label());
|
||||
self::assertSame('Ausstehend', StatusWithLabels::Pending->label());
|
||||
}
|
||||
|
||||
public function testLabelFallsBackToCaseNameWhenNotConfigured(): void
|
||||
{
|
||||
self::assertSame('Archived', StatusWithLabels::Archived->label());
|
||||
}
|
||||
|
||||
public function testLabelWorksForPureEnum(): void
|
||||
{
|
||||
self::assertSame('Offen', PureWithLabels::Open->label());
|
||||
self::assertSame('Geschlossen', PureWithLabels::Closed->label());
|
||||
}
|
||||
|
||||
public function testLabelFallsBackWhenNoLabelsMethodOverridden(): void
|
||||
{
|
||||
self::assertSame('One', NoLabels::One->label());
|
||||
self::assertSame('Two', NoLabels::Two->label());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// labelsArray()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testLabelsArrayReturnsAllCasesMappedToLabels(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[
|
||||
'Active' => 'Aktiv',
|
||||
'Pending' => 'Ausstehend',
|
||||
'Archived' => 'Archived',
|
||||
],
|
||||
StatusWithLabels::labelsArray(),
|
||||
);
|
||||
}
|
||||
|
||||
public function testLabelsArrayWorksForPureEnum(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[
|
||||
'Open' => 'Offen',
|
||||
'Closed' => 'Geschlossen',
|
||||
],
|
||||
PureWithLabels::labelsArray(),
|
||||
);
|
||||
}
|
||||
|
||||
public function testLabelsArrayFallsBackToNamesWhenEmpty(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[
|
||||
'One' => 'One',
|
||||
'Two' => 'Two',
|
||||
],
|
||||
NoLabels::labelsArray(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user