From 548f91c9d246fea502ad3dc3613347e68268b0f8 Mon Sep 17 00:00:00 2001 From: Thijs Kok Date: Thu, 9 Jul 2026 16:40:21 +0200 Subject: [PATCH 1/2] Add methods to navigate revisions: next, isFirstRevision, and isLastRevision --- src/Models/Revision.php | 31 +++++++++++ tests/RevisionNavigationTest.php | 88 ++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 tests/RevisionNavigationTest.php diff --git a/src/Models/Revision.php b/src/Models/Revision.php index 62e9d70..eb7989f 100644 --- a/src/Models/Revision.php +++ b/src/Models/Revision.php @@ -186,6 +186,37 @@ public function previous(): ?static ->first(); } + /** + * Return the revision that directly follows this one for the same model. + */ + public function next(): ?static + { + return static::query() + ->where([ + ['revisionable_type', $this->revisionable_type], + ['revisionable_id', $this->revisionable_id], + [$this->getKeyName(), '>', $this->getKey()], + ]) + ->oldest($this->getKeyName()) + ->first(); + } + + /** + * Determine whether this is the oldest revision for its model. + */ + public function isFirstRevision(): bool + { + return $this->getKey() === $this->revisionable->revisions->min($this->getKeyName()); + } + + /** + * Determine whether this is the most recent revision for its model. + */ + public function isLastRevision(): bool + { + return $this->getKey() === $this->revisionable->revisions->max($this->getKeyName()); + } + /** * Compare this revision against another revision or its predecessor. */ diff --git a/tests/RevisionNavigationTest.php b/tests/RevisionNavigationTest.php new file mode 100644 index 0000000..ebcd2ab --- /dev/null +++ b/tests/RevisionNavigationTest.php @@ -0,0 +1,88 @@ +createPost(); + + $this->modifyPost($post); + $this->modifyPost($post, ['name' => 'Yet another post name']); + + $revisions = $post->revisions()->orderBy('id')->get(); + + // When / Then + $this->assertNull($revisions->first()->previous()); + $this->assertTrue($revisions->last()->previous()->is($revisions->first())); + } + + #[Test] + public function it_returns_the_next_revision() + { + // Given + $post = $this->createPost(); + + $this->modifyPost($post); + $this->modifyPost($post, ['name' => 'Yet another post name']); + + $revisions = $post->revisions()->orderBy('id')->get(); + + // When / Then + $this->assertTrue($revisions->first()->next()->is($revisions->last())); + $this->assertNull($revisions->last()->next()); + } + + #[Test] + public function it_determines_whether_a_revision_is_the_first_revision() + { + // Given + $post = $this->createPost(); + + $this->modifyPost($post); + $this->modifyPost($post, ['name' => 'Yet another post name']); + + $revisions = $post->revisions()->orderBy('id')->get(); + + // When / Then + $this->assertTrue($revisions->first()->isFirstRevision()); + $this->assertFalse($revisions->last()->isFirstRevision()); + } + + #[Test] + public function it_determines_whether_a_revision_is_the_last_revision() + { + // Given + $post = $this->createPost(); + + $this->modifyPost($post); + $this->modifyPost($post, ['name' => 'Yet another post name']); + + $revisions = $post->revisions()->orderBy('id')->get(); + + // When / Then + $this->assertFalse($revisions->first()->isLastRevision()); + $this->assertTrue($revisions->last()->isLastRevision()); + } + + #[Test] + public function a_single_revision_is_both_the_first_and_the_last() + { + // Given + $post = $this->createPost(); + + $this->modifyPost($post); + + // When + $revision = $post->revisions()->firstOrFail(); + + // Then + $this->assertTrue($revision->isFirstRevision()); + $this->assertTrue($revision->isLastRevision()); + } +} From bdd081ade1bb02c1a2515dd88092dc1d0ea31740 Mon Sep 17 00:00:00 2001 From: Thijs Kok Date: Thu, 9 Jul 2026 18:36:36 +0200 Subject: [PATCH 2/2] Add methods to determine first and last revisions, and update tests for single and unsaved revisions --- src/Contracts/Revision.php | 2 ++ src/Models/Revision.php | 4 ++-- tests/RevisionNavigationTest.php | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Contracts/Revision.php b/src/Contracts/Revision.php index 4a1b201..244da03 100644 --- a/src/Contracts/Revision.php +++ b/src/Contracts/Revision.php @@ -15,6 +15,8 @@ public function revisionable(): MorphTo; public function previous(): ?static; + public function next(): ?static; + public function diff(?self $target = null): Diff; public function toModel(): Model; diff --git a/src/Models/Revision.php b/src/Models/Revision.php index eb7989f..1df8d53 100644 --- a/src/Models/Revision.php +++ b/src/Models/Revision.php @@ -206,7 +206,7 @@ public function next(): ?static */ public function isFirstRevision(): bool { - return $this->getKey() === $this->revisionable->revisions->min($this->getKeyName()); + return $this->exists && $this->previous() === null; } /** @@ -214,7 +214,7 @@ public function isFirstRevision(): bool */ public function isLastRevision(): bool { - return $this->getKey() === $this->revisionable->revisions->max($this->getKeyName()); + return $this->exists && $this->next() === null; } /** diff --git a/tests/RevisionNavigationTest.php b/tests/RevisionNavigationTest.php index ebcd2ab..9e969d8 100644 --- a/tests/RevisionNavigationTest.php +++ b/tests/RevisionNavigationTest.php @@ -3,6 +3,8 @@ namespace TestMonitor\Revisable\Tests; use PHPUnit\Framework\Attributes\Test; +use TestMonitor\Revisable\Models\Revision; +use TestMonitor\Revisable\Tests\Models\Post; class RevisionNavigationTest extends TestCase { @@ -71,7 +73,7 @@ public function it_determines_whether_a_revision_is_the_last_revision() } #[Test] - public function a_single_revision_is_both_the_first_and_the_last() + public function it_treats_a_single_revision_as_both_the_first_and_the_last() { // Given $post = $this->createPost(); @@ -85,4 +87,18 @@ public function a_single_revision_is_both_the_first_and_the_last() $this->assertTrue($revision->isFirstRevision()); $this->assertTrue($revision->isLastRevision()); } + + #[Test] + public function it_treats_an_unsaved_revision_as_neither_the_first_nor_the_last() + { + // Given + $revision = new Revision([ + 'revisionable_type' => Post::class, + 'revisionable_id' => 1, + ]); + + // When / Then + $this->assertFalse($revision->isFirstRevision()); + $this->assertFalse($revision->isLastRevision()); + } }