diff --git a/README.md b/README.md index f6a8bc4..b00c67e 100644 --- a/README.md +++ b/README.md @@ -102,3 +102,36 @@ Onboard::addStep('Step w/ custom attributes') $step->name; $step->shirt_color; ``` + + +## Example middlware + +If you want to ensure that your user is redirected to the next +unfinished onboarding step, whenever they access your web application, +you can use the following middleware as a starting point: + +```php +onboarding()->inProgress()) { + return redirect()->to( + Auth::user()->onboarding()->nextUnfinishedStep->link + ); + } + + return $next($request); + } +} +``` + +**Quick tip**: Don't add this middleware to routes that update the state +of the onboarding steps, your users will not be able to progress because they will be redirected back to the onboarding step. \ No newline at end of file diff --git a/src/OnboardingManager.php b/src/OnboardingManager.php index e2d876f..102c714 100644 --- a/src/OnboardingManager.php +++ b/src/OnboardingManager.php @@ -60,4 +60,16 @@ public function finished() // Report onboarding is finished if no incomplete steps remain. ->isEmpty(); } + + /** + * Get the next unfinished onboarding step, or null if already all steps are completed. + * + * @return null|OnboardingStep + */ + public function nextUnfinishedStep() + { + return collect($this->steps)->first(function ($step) { + return $step->incomplete(); + }); + } } diff --git a/tests/OnbaordTest.php b/tests/OnbaordTest.php deleted file mode 100644 index de86d11..0000000 --- a/tests/OnbaordTest.php +++ /dev/null @@ -1,89 +0,0 @@ -user = $this->getMock('User'); - } - - /** @test */ - public function steps_can_be_defined_and_configured() - { - $onboardingSteps = new OnboardingSteps; - - $onboardingSteps->addStep('Test Step') - ->link('/some/url') - ->cta('Test This!') - ->attributes(['another' => 'attribute']) - ->completeIf(function () { - return true; - }); - - $this->assertEquals(1, $onboardingSteps->steps(new stdClass())->count()); - - $step = $onboardingSteps->steps(new stdClass())->first(); - - $this->assertEquals('/some/url', $step->link); - $this->assertEquals('Test This!', $step->cta); - $this->assertEquals('Test Step', $step->title); - $this->assertEquals('attribute', $step->another); - } - - /** @test */ - public function is_in_progress_when_all_steps_are_incomplete() - { - $onboardingSteps = new OnboardingSteps; - $onboardingSteps->addStep('Test Step'); - $onboardingSteps->addStep('Another Test Step'); - - $onboarding = new OnboardingManager($this->user, $onboardingSteps); - - $this->assertTrue($onboarding->inProgress()); - $this->assertFalse($onboarding->finished()); - } - - /** @test */ - public function is_finished_when_all_steps_are_complete() - { - $onboardingSteps = new OnboardingSteps; - $onboardingSteps->addStep('Test Step') - ->completeIf(function() { - return true; - }); - - $onboarding = new OnboardingManager($this->user, $onboardingSteps); - - $this->assertTrue($onboarding->finished()); - $this->assertFalse($onboarding->inProgress()); - } - - /** @test */ - public function the_proper_object_gets_passed_into_completion_callback() - { - $user = $this->getMock('User', ['testMe']); - $user->expects($this->once())->method('testMe'); - - $onboardingSteps = new OnboardingSteps; - $onboardingSteps->addStep('Test Step') - ->completeIf(function($user) { - // if this gets called, it ensures that the right object was passed through. - $user->testMe(); - return true; - }); - - $onboarding = new OnboardingManager($user, $onboardingSteps); - - // Calling finished() will triger the completeIf callback. - $this->assertTrue($onboarding->finished()); - } -} diff --git a/tests/OnboardTest.php b/tests/OnboardTest.php new file mode 100644 index 0000000..dadcae7 --- /dev/null +++ b/tests/OnboardTest.php @@ -0,0 +1,146 @@ +user = $this->getMock('User'); + } + + /** @test */ + public function steps_can_be_defined_and_configured() + { + $onboardingSteps = new OnboardingSteps; + + $onboardingSteps->addStep('Test Step') + ->link('/some/url') + ->cta('Test This!') + ->attributes(['another' => 'attribute']) + ->completeIf(function () { + return true; + }); + + $this->assertEquals(1, $onboardingSteps->steps(new stdClass())->count()); + + $step = $onboardingSteps->steps(new stdClass())->first(); + + $this->assertEquals('/some/url', $step->link); + $this->assertEquals('Test This!', $step->cta); + $this->assertEquals('Test Step', $step->title); + $this->assertEquals('attribute', $step->another); + } + + /** @test */ + public function is_in_progress_when_all_steps_are_incomplete() + { + $onboardingSteps = new OnboardingSteps; + $onboardingSteps->addStep('Test Step'); + $onboardingSteps->addStep('Another Test Step'); + + $onboarding = new OnboardingManager($this->user, $onboardingSteps); + + $this->assertTrue($onboarding->inProgress()); + $this->assertFalse($onboarding->finished()); + } + + /** @test */ + public function is_finished_when_all_steps_are_complete() + { + $onboardingSteps = new OnboardingSteps; + $onboardingSteps->addStep('Test Step') + ->completeIf(function () { + return true; + }); + + $onboarding = new OnboardingManager($this->user, $onboardingSteps); + + $this->assertTrue($onboarding->finished()); + $this->assertFalse($onboarding->inProgress()); + } + + /** @test */ + public function it_returns_the_correct_next_unfinished_step() + { + $onboardingSteps = new OnboardingSteps; + $onboardingSteps->addStep('Step 1') + ->link("/step-1") + ->completeIf(function () { + return true; + }); + + $onboardingSteps->addStep('Step 2') + ->link("/step-2") + ->completeIf(function () { + return false; + }); + + $onboardingSteps->addStep('Step 3') + ->link("/step-3") + ->completeIf(function () { + return false; + }); + + $onboarding = new OnboardingManager($this->user, $onboardingSteps); + + $nextStep = $onboarding->nextUnfinishedStep(); + + $this->assertNotNull($nextStep); + $this->assertEquals("Step 2", $nextStep->title); + $this->assertEquals("/step-2", $nextStep->link); + } + + /** @test */ + public function nextUnfinishedStep_returns_null_if_all_steps_are_completed() + { + $onboardingSteps = new OnboardingSteps; + $onboardingSteps->addStep('Step 1') + ->completeIf(function () { + return true; + }); + + $onboardingSteps->addStep('Step 2') + ->completeIf(function () { + return true; + }); + + $onboardingSteps->addStep('Step 3') + ->completeIf(function () { + return true; + }); + + $onboarding = new OnboardingManager($this->user, $onboardingSteps); + + $nextStep = $onboarding->nextUnfinishedStep(); + + $this->assertNull($nextStep); + } + + /** @test */ + public function the_proper_object_gets_passed_into_completion_callback() + { + $user = $this->getMock('User', ['testMe']); + $user->expects($this->once())->method('testMe'); + + $onboardingSteps = new OnboardingSteps; + $onboardingSteps->addStep('Test Step') + ->completeIf(function ($user) { + // if this gets called, it ensures that the right object was passed through. + $user->testMe(); + return true; + }); + + $onboarding = new OnboardingManager($user, $onboardingSteps); + + // Calling finished() will triger the completeIf callback. + $this->assertTrue($onboarding->finished()); + } +}