Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ jobs:
- uses: ramsey/composer-install@v3
- run: composer phpunit

code-coverage:
name: Code Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
coverage: pcov
- uses: ramsey/composer-install@v3
- name: Generate coverage report
run: vendor/bin/phpunit --coverage-clover=coverage.xml
- name: Upload to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}

static-analysis:
name: Static Analysis
runs-on: ubuntu-latest
Expand All @@ -26,4 +43,5 @@ jobs:
with:
php-version: '8.5'
- uses: ramsey/composer-install@v3
- run: composer phpcs
- run: composer phpstan
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
}
},
"scripts": {
"coverage": "vendor/bin/phpunit --coverage-text",
"phpcs": "vendor/bin/phpcs",
"phpstan": "vendor/bin/phpstan analyze",
"phpunit": "vendor/bin/phpunit",
Expand Down
130 changes: 130 additions & 0 deletions tests/AbstractMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
use PHPUnit\Framework\TestCase;
use ReflectionObject;
use Respect\Data\Collections\Collection;
use Respect\Data\Styles\CakePHP;
use Respect\Data\Styles\Standard;
use SplObjectStorage;
use stdClass;

#[CoversClass(AbstractMapper::class)]
class AbstractMapperTest extends TestCase
Expand Down Expand Up @@ -82,4 +86,130 @@ public function magicGetterShouldBypassToCollection(): void
$expected->setMapper($this->mapper);
$this->assertEquals($expected->bar->baz, $collection);
}

#[Test]
public function getStyleShouldReturnDefaultStandard(): void
{
$style = $this->mapper->getStyle();
$this->assertInstanceOf(Standard::class, $style);
}

#[Test]
public function getStyleShouldReturnSameInstanceOnSubsequentCalls(): void
{
$style1 = $this->mapper->getStyle();
$style2 = $this->mapper->getStyle();
$this->assertSame($style1, $style2);
}

#[Test]
public function setStyleShouldChangeStyle(): void
{
$style = new CakePHP();
$result = $this->mapper->setStyle($style);
$this->assertSame($style, $this->mapper->getStyle());
$this->assertSame($this->mapper, $result);
}

#[Test]
public function persistShouldMarkObjectAsTracked(): void
{
$entity = new stdClass();
$collection = Collection::foo();
$this->mapper->persist($entity, $collection);
$this->assertTrue($this->mapper->isTracked($entity));
}

#[Test]
public function persistAlreadyTrackedShouldReturnTrue(): void
{
$entity = new stdClass();
$collection = Collection::foo();
$this->mapper->markTracked($entity, $collection);
$result = $this->mapper->persist($entity, $collection);
$this->assertTrue($result);
}

#[Test]
public function removeShouldMarkObjectAsTracked(): void
{
$entity = new stdClass();
$collection = Collection::foo();
$result = $this->mapper->remove($entity, $collection);
$this->assertTrue($result);
$this->assertTrue($this->mapper->isTracked($entity));
}

#[Test]
public function removeAlreadyTrackedShouldReturnTrue(): void
{
$entity = new stdClass();
$collection = Collection::foo();
$this->mapper->markTracked($entity, $collection);
$result = $this->mapper->remove($entity, $collection);
$this->assertTrue($result);
}

#[Test]
public function isTrackedShouldReturnFalseForUntrackedEntity(): void
{
$this->assertFalse($this->mapper->isTracked(new stdClass()));
}

#[Test]
public function markTrackedShouldReturnTrue(): void
{
$entity = new stdClass();
$collection = Collection::foo();
$this->assertTrue($this->mapper->markTracked($entity, $collection));
}

#[Test]
public function resetShouldClearChangedRemovedAndNew(): void
{
$entity = new stdClass();
$collection = Collection::foo();
$this->mapper->persist($entity, $collection);
$this->mapper->remove($entity, $collection);
$this->mapper->reset();

$ref = new ReflectionObject($this->mapper);

$newProp = $ref->getProperty('new');
/** @var SplObjectStorage<object, mixed> $newStorage */
$newStorage = $newProp->getValue($this->mapper);
$this->assertCount(0, $newStorage);

$changedProp = $ref->getProperty('changed');
/** @var SplObjectStorage<object, mixed> $changedStorage */
$changedStorage = $changedProp->getValue($this->mapper);
$this->assertCount(0, $changedStorage);

$removedProp = $ref->getProperty('removed');
/** @var SplObjectStorage<object, mixed> $removedStorage */
$removedStorage = $removedProp->getValue($this->mapper);
$this->assertCount(0, $removedStorage);
}

#[Test]
public function issetShouldReturnTrueForRegisteredCollection(): void
{
$coll = Collection::foo();
$this->mapper->registerCollection('my_alias', $coll);
$this->assertTrue(isset($this->mapper->my_alias));
}

#[Test]
public function issetShouldReturnFalseForUnregisteredCollection(): void
{
$this->assertFalse(isset($this->mapper->nonexistent));
}

#[Test]
public function magicGetShouldReturnNewCollectionWhenNotRegistered(): void
{
$coll = $this->mapper->unregistered;
$this->assertInstanceOf(Collection::class, $coll);
$this->assertEquals('unregistered', $coll->getName());
}
}
8 changes: 8 additions & 0 deletions tests/CollectionIteratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,12 @@ public function getChildrenUseCollectionNext(): void
$iterator = new CollectionIterator($coll);
$this->assertTrue($iterator->hasChildren());
}

#[Test]
public function recursiveTraversalShouldVisitNextChain(): void
{
$coll = Collection::foo()->bar->baz;
$items = iterator_to_array(CollectionIterator::recursive($coll));
$this->assertCount(3, $items);
}
}
81 changes: 81 additions & 0 deletions tests/Collections/CollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -297,4 +297,85 @@ public function fetchAllOnCollectionShouldExceptionIfMapperDontExist(): void
$this->expectException(RuntimeException::class);
Collection::foo()->fetchAll();
}

#[Test]
public function usingShouldCreateCollectionWithCondition(): void
{
$coll = Collection::using(42);
$this->assertInstanceOf(Collection::class, $coll);
$this->assertEquals(42, $coll->getCondition());
}

#[Test]
public function haveShouldReturnFalseForMissingExtra(): void
{
$coll = new Collection('foo');
$this->assertFalse($coll->have('nonexistent'));
}

#[Test]
public function haveShouldReturnTrueForExistingExtra(): void
{
$coll = new Collection('foo');
$coll->extra('key', 'value');
$this->assertTrue($coll->have('key'));
}

#[Test]
public function getExtraShouldReturnNullForMissingExtra(): void
{
$coll = new Collection('foo');
$this->assertNull($coll->getExtra('nonexistent'));
}

#[Test]
public function getExtraShouldReturnValueForExistingExtra(): void
{
$coll = new Collection('foo');
$coll->extra('key', 'value');
$this->assertEquals('value', $coll->getExtra('key'));
}

#[Test]
public function extraShouldReturnSelf(): void
{
$coll = new Collection('foo');
$result = $coll->extra('key', 'value');
$this->assertSame($coll, $result);
}

#[Test]
public function getParentNameShouldReturnNullWhenNoParent(): void
{
$coll = new Collection('foo');
$this->assertNull($coll->getParentName());
}

#[Test]
public function getNextNameShouldReturnNullWhenNoNext(): void
{
$coll = new Collection('foo');
$this->assertNull($coll->getNextName());
}

#[Test]
public function hasNextShouldReturnFalseWhenNoNext(): void
{
$coll = new Collection('foo');
$this->assertFalse($coll->hasNext());
}

#[Test]
public function magicGetShouldUseRegisteredCollectionFromMapper(): void
{
$registered = Collection::bar();
$mapperMock = $this->createMock(AbstractMapper::class);
$mapperMock->method('__isset')->with('bar')->willReturn(true);
$mapperMock->method('__get')->with('bar')->willReturn($registered);

$coll = new Collection('foo');
$coll->setMapper($mapperMock);
$result = $coll->bar;
$this->assertEquals('bar', $result->getNextName());
}
}
9 changes: 9 additions & 0 deletions tests/Collections/FilteredTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,13 @@ public function collectionCanBeCreatedStaticallyWithChildren(): void
$this->assertEquals(['bar'], $children1->getExtra('filters'));
$this->assertEquals(['bat'], $children2->getExtra('filters'));
}

#[Test]
public function callStaticShouldCreateFilteredCollectionWithName(): void
{
$coll = Filtered::items();
$this->assertInstanceOf(Filtered::class, $coll);
$this->assertEquals('items', $coll->getName());
$this->assertEquals([], $coll->getExtra('filters'));
}
}
9 changes: 9 additions & 0 deletions tests/Collections/MixedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,13 @@ public function collectionCanBeCreatedStaticallyWithChildren(): void
$this->assertEquals(['foo' => ['bar']], $children1->getExtra('mixins'));
$this->assertEquals(['bat' => ['bar']], $children2->getExtra('mixins'));
}

#[Test]
public function callStaticShouldCreateMixCollectionWithName(): void
{
$coll = Mix::items();
$this->assertInstanceOf(Mix::class, $coll);
$this->assertEquals('items', $coll->getName());
$this->assertEquals([], $coll->getExtra('mixins'));
}
}
9 changes: 9 additions & 0 deletions tests/Collections/TypedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,13 @@ public function collectionCanBeCreatedStaticallyWithChildren(): void
$this->assertEquals('a', $children1->getExtra('type'));
$this->assertEquals('b', $children2->getExtra('type'));
}

#[Test]
public function callStaticShouldCreateTypedCollectionWithName(): void
{
$coll = Typed::items();
$this->assertInstanceOf(Typed::class, $coll);
$this->assertEquals('items', $coll->getName());
$this->assertEquals('', $coll->getExtra('type'));
}
}
12 changes: 12 additions & 0 deletions tests/Styles/AbstractStyleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,16 @@ public function testCamelCaseToSeparatorAndViceVersa(
$separatorToCamelCaseMethod->invoke($this->style, $separated, $separator),
);
}

public function testPluralToSingularReturnsUnchangedWhenNoMatch(): void
{
$method = new ReflectionMethod($this->style, 'pluralToSingular');
$this->assertEquals('fox', $method->invoke($this->style, 'fox'));
}

public function testSingularToPluralReturnsUnchangedWhenNoMatch(): void
{
$method = new ReflectionMethod($this->style, 'singularToPlural');
$this->assertEquals('news', $method->invoke($this->style, 'news'));
}
}
1 change: 0 additions & 1 deletion tests/Styles/CakePHP/CakePHPIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Respect\Data\InMemoryMapper;
use Respect\Data\Styles\CakePHP;

#[CoversClass(InMemoryMapper::class)]
#[CoversClass(CakePHP::class)]
class CakePHPIntegrationTest extends TestCase
{
Expand Down
1 change: 0 additions & 1 deletion tests/Styles/NorthWind/NorthWindIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Respect\Data\InMemoryMapper;
use Respect\Data\Styles\NorthWind;

#[CoversClass(InMemoryMapper::class)]
#[CoversClass(NorthWind::class)]
class NorthWindIntegrationTest extends TestCase
{
Expand Down
1 change: 0 additions & 1 deletion tests/Styles/Plural/PluralIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Respect\Data\InMemoryMapper;
use Respect\Data\Styles\Plural;

#[CoversClass(InMemoryMapper::class)]
#[CoversClass(Plural::class)]
class PluralIntegrationTest extends TestCase
{
Expand Down
1 change: 0 additions & 1 deletion tests/Styles/Sakila/SakilaIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Respect\Data\InMemoryMapper;
use Respect\Data\Styles\Sakila;

#[CoversClass(InMemoryMapper::class)]
#[CoversClass(Sakila::class)]
class SakilaIntegrationTest extends TestCase
{
Expand Down
Loading