├── .github
└── workflows
│ └── run-tests.yml
├── .gitignore
├── .styleci.yml
├── LICENSE.md
├── README.md
├── composer.json
├── phpunit.xml.dist
├── resources
└── views
│ ├── bootstrap3.blade.php
│ ├── bootstrap4.blade.php
│ ├── bulma.blade.php
│ └── foundation6.blade.php
├── src
├── Breadcrumb.php
├── BreadcrumbLink.php
├── BreadcrumbLinkFactory.php
├── BreadcrumbServiceProvider.php
├── Facades
│ └── Breadcrumb.php
└── RouteParameterBinder.php
└── tests
├── BreadcrumbTest.php
├── TemplateTest.php
├── TestCase.php
└── database
└── migrations
├── 2018_12_05_000000_create_foos_table.php
└── 2018_12_05_000001_create_bars_table.php
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: run-tests
2 |
3 | on:
4 | push:
5 | pull_request:
6 | schedule:
7 | - cron: '0 12 * * *'
8 |
9 | jobs:
10 | tests:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | fail-fast: true
15 | matrix:
16 | php: [7.4, 8.0]
17 | laravel: [^8.0]
18 | dependency-version: [prefer-lowest, prefer-stable]
19 |
20 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
21 |
22 | steps:
23 | - name: Checkout code
24 | uses: actions/checkout@v2
25 |
26 | - name: Composer cache
27 | uses: actions/cache@v2
28 | with:
29 | path: ~/.composer/cache/files
30 | key: composer-php-${{ matrix.php }}-laravel-${{ matrix.laravel }}-composer-${{ hashFiles('composer.json') }}
31 |
32 | - name: Setup PHP
33 | uses: shivammathur/setup-php@v2
34 | with:
35 | php-version: ${{ matrix.php }}
36 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, exif, iconv
37 | coverage: none
38 |
39 | - name: Install dependencies
40 | run: |
41 | composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update
42 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
43 |
44 | - name: Execute tests
45 | run: vendor/bin/phpunit
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /.vscode
3 | /vendor
4 | composer.lock
5 | .DS_Store
6 | Thumbs.db
7 | .phpunit.result.cache
8 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: laravel
2 | enabled:
3 | - length_ordered_imports
4 |
5 | disabled:
6 | - alpha_ordered_imports
7 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Kevin Pohl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Add breadcrumbs to your routes
2 |
3 | [](https://github.com/fragkp/laravel-route-breadcrumb/releases)
4 | [](https://packagist.org/packages/fragkp/laravel-route-breadcrumb)
5 |
6 | This package tries to give a simple solution for breadcrumbs. Add breadcrumbs direct to your routes and display them in your views.
7 |
8 | ## Installation
9 |
10 | You can install the package via composer:
11 |
12 | ```bash
13 | composer require fragkp/laravel-route-breadcrumb
14 | ```
15 |
16 | If you want also use the facade to access the main breadcrumb class, add this line to your facades array in config/app.php:
17 |
18 | ```php
19 | 'Breadcrumb' => Fragkp\LaravelRouteBreadcrumb\Facades\Breadcrumb::class,
20 | ```
21 |
22 | This package contains some pre-built views for the most active css-frameworks:
23 | - [Bootstrap 3](https://github.com/fragkp/laravel-route-breadcrumb/tree/master/resources/views/bootstrap3.blade.php)
24 | - [Bootstrap 4](https://github.com/fragkp/laravel-route-breadcrumb/tree/master/resources/views/bootstrap4.blade.php)
25 | - [Bulma](https://github.com/fragkp/laravel-route-breadcrumb/tree/master/resources/views/bulma.blade.php)
26 | - [Foundation 6](https://github.com/fragkp/laravel-route-breadcrumb/tree/master/resources/views/foundation6.blade.php)
27 |
28 | If you want to use one of these views, include it in this way:
29 |
30 | ```php
31 | @include('laravel-breadcrumb::bootstrap3')
32 | ```
33 |
34 | To customize the pre-built views, run this command:
35 |
36 | ```bash
37 | php artisan vendor:publish Fragkp\LaravelRouteBreadcrumb\BreadcrumbServiceProvider --tag=views
38 | ```
39 | > Note: You could also create your own [custom view](#view-example) to display breadcrumb links.
40 |
41 | ## Usage
42 |
43 | ### Defining breadcrumbs
44 |
45 | #### Basic
46 |
47 | To add a breadcrumb title to your route, call the `breadcrumb` method and pass your title.
48 | ```php
49 | Route::get('/')->breadcrumb('Your custom title');
50 | ```
51 |
52 | #### Index
53 |
54 | On some websites, you wish to have always an index inside your breadcrumbs. Use the `breadcrumbIndex` method.
55 | **This method should only be used once.**
56 | > Note: `breadcrumbIndex` sets also the breadcrumb title for this route.
57 | ```php
58 | Route::get('/')->breadcrumbIndex('Start');
59 |
60 | Route::get('/foo')->breadcrumb('Your custom title');
61 | ```
62 |
63 | #### Inside groups
64 |
65 | The `breadcrumb` method will also work inside route groups.
66 | ```php
67 | Route::get('/')->breadcrumbIndex('Start');
68 |
69 | Route::prefix('/foo')->group(function () {
70 | Route::get('/bar')->breadcrumb('Your custom title');
71 | });
72 | ```
73 |
74 | #### Group index
75 |
76 | Also, it is possible to specify a group index title by calling `breadcrumbGroup`.
77 | **This method should only be used once inside a group.**
78 | > Note: `breadcrumbGroup` sets also the breadcrumb title for this route.
79 | ```php
80 | Route::get('/')->breadcrumbIndex('Start');
81 |
82 | Route::prefix('/foo')->group(function () {
83 | Route::get('/')->breadcrumbGroup('Foo group index');
84 |
85 | Route::get('/bar')->breadcrumb('Your custom title');
86 | });
87 | ```
88 |
89 | #### Custom title resolver
90 |
91 | If you want to customize your breadcrumb title, you could pass a closure to all breadcrumb methods.
92 | ```php
93 | Route::get('/')->breadcrumb(function () {
94 | return 'Your custom title';
95 | });
96 | ```
97 |
98 | You could also pass a fully qualified class name. This will invoke your class.
99 | ```php
100 | Route::get('/')->breadcrumb(YourCustomTitleResolver::class);
101 |
102 | class YourCustomTitleResolver
103 | {
104 | public function __invoke()
105 | {
106 | return 'Your custom title';
107 | }
108 | }
109 | ```
110 |
111 | You may also pass a callable.
112 | ```php
113 | Route::get('/foo/{id}')->breadcrumb([app('my_breadcrumb_resolver'), 'resolve']);
114 |
115 | // my_breadcrumb_resolver
116 | class MyBreadcrumbResolver
117 | {
118 | public function resolve($id)
119 | {
120 | $title = $this->repo->findById($id);
121 |
122 | return $title->getName();
123 | }
124 | }
125 | ```
126 |
127 | ##### Route parameters
128 |
129 | All route parameters will be passed to your resolver. Route model binding is also supported.
130 | ```php
131 | Route::get('/{foo}')->breadcrumb(YourCustomTitleResolver::class);
132 |
133 | class YourCustomTitleResolver
134 | {
135 | public function __invoke(Foo $foo)
136 | {
137 | return "Title: {$foo->title}";
138 | }
139 | }
140 | ```
141 |
142 | ### Accessing breadcrumb
143 |
144 | #### Links
145 |
146 | The `links` method will return a `Collection` of `BreadcrumbLink`.
147 | > Note: The array is indexed by the uri.
148 | ```php
149 | app(Breadcrumb::class)->links(); // or use here the facade
150 | ```
151 | Example result:
152 | ```php
153 | Illuminate\Support\Collection {#266
154 | #items: array:2 [
155 | "/" => Fragkp\LaravelRouteBreadcrumb\BreadcrumbLink {#41
156 | +uri: "/"
157 | +title: "Start"
158 | }
159 | "foo" => Fragkp\LaravelRouteBreadcrumb\BreadcrumbLink {#262
160 | +uri: "foo"
161 | +title: "Your custom title"
162 | }
163 | ]
164 | }
165 | ```
166 |
167 | #### Index
168 |
169 | The `index` method will return a single instance of `BreadcrumbLink`. If you haven't defined any index, null is returned.
170 | ```php
171 | app(Breadcrumb::class)->index(); // or use here the facade
172 | ```
173 | Example result:
174 | ```php
175 | Fragkp\LaravelRouteBreadcrumb\BreadcrumbLink {#36
176 | +uri: "/"
177 | +title: "Start"
178 | }
179 | ```
180 |
181 | #### Current
182 |
183 | The `current` method will return a single instance of `BreadcrumbLink`. If no route is provided (e.g. on errors), null is returned.
184 | ```php
185 | app(Breadcrumb::class)->current(); // or use here the facade
186 | ```
187 | Example result:
188 | ```php
189 | Fragkp\LaravelRouteBreadcrumb\BreadcrumbLink {#36
190 | +uri: "/"
191 | +title: "Your custom title"
192 | }
193 | ```
194 |
195 | #### View example
196 |
197 | A good way to access the breadcrumb inside your views is to bound it via a View Composer.
198 | > For more information about View Composers, have a look at the [Laravel docs](https://laravel.com/docs/5.6/views#view-composers).
199 | ```php
200 | // app/Providers/AppServiceProvider.php
201 |
202 | class AppServiceProvider extends ServiceProvider
203 | {
204 | public function boot()
205 | {
206 | View::composer('your-view', function ($view) {
207 | $view->with('breadcrumb', app(Breadcrumb::class)->links());
208 | });
209 | }
210 | }
211 | ```
212 | ```php
213 | // resources/views/breadcrumb.blade.php
214 |
215 |
216 | @foreach ($breadcrumb as $link)
217 | -
218 | {{ $link->title }}
219 |
220 | @endforeach
221 |
222 | ```
223 |
224 | ## Testing
225 |
226 | ``` bash
227 | ./vendor/bin/phpunit
228 | ```
229 |
230 | ## License
231 |
232 | MIT License (MIT). Please see [License File](LICENSE.md) for more information.
233 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fragkp/laravel-route-breadcrumb",
3 | "description": "",
4 | "keywords": [
5 | "laravel",
6 | "route",
7 | "breadcrumb"
8 | ],
9 | "license": "MIT",
10 | "authors": [
11 | {
12 | "name": "Kevin Pohl"
13 | }
14 | ],
15 | "require": {
16 | "php": "^7.4|^8.0",
17 | "laravel/framework": "^8.0"
18 | },
19 | "require-dev": {
20 | "orchestra/testbench": "^6.0",
21 | "orchestra/database": "^6.0",
22 | "phpunit/phpunit": "^9.4",
23 | "mockery/mockery": "^1.4"
24 | },
25 | "autoload": {
26 | "psr-4": {
27 | "Fragkp\\LaravelRouteBreadcrumb\\": "src"
28 | }
29 | },
30 | "autoload-dev": {
31 | "psr-4": {
32 | "Fragkp\\LaravelRouteBreadcrumb\\Tests\\": "tests"
33 | }
34 | },
35 | "scripts": {
36 | "test": "vendor/bin/phpunit",
37 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage"
38 | },
39 | "config": {
40 | "sort-packages": true
41 | },
42 | "minimum-stability": "dev",
43 | "prefer-stable": true,
44 | "extra": {
45 | "laravel": {
46 | "providers": [
47 | "Fragkp\\LaravelRouteBreadcrumb\\BreadcrumbServiceProvider"
48 | ],
49 | "aliases": {
50 | "Breadcrumb": "Fragkp\\LaravelRouteBreadcrumb\\Facades\\Breadcrumb"
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | ./tests
15 |
16 |
17 |
18 |
19 | ./src
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/resources/views/bootstrap3.blade.php:
--------------------------------------------------------------------------------
1 | @if ($breadcrumb->isNotEmpty())
2 |
3 | @foreach ($breadcrumb as $link)
4 | @if ($loop->last && ! $loop->first)
5 | - {{ $link->title }}
6 | @else
7 | -
8 | {{ $link->title }}
9 |
10 | @endif
11 | @endforeach
12 |
13 | @endif
14 |
--------------------------------------------------------------------------------
/resources/views/bootstrap4.blade.php:
--------------------------------------------------------------------------------
1 | @if ($breadcrumb->isNotEmpty())
2 |
15 | @endif
16 |
--------------------------------------------------------------------------------
/resources/views/bulma.blade.php:
--------------------------------------------------------------------------------
1 | @if ($breadcrumb->isNotEmpty())
2 |
17 | @endif
18 |
--------------------------------------------------------------------------------
/resources/views/foundation6.blade.php:
--------------------------------------------------------------------------------
1 | @if ($breadcrumb->isNotEmpty())
2 |
17 | @endif
18 |
--------------------------------------------------------------------------------
/src/Breadcrumb.php:
--------------------------------------------------------------------------------
1 | router = $router;
35 | $this->request = $request;
36 | }
37 |
38 | /**
39 | * @return \Fragkp\LaravelRouteBreadcrumb\BreadcrumbLink|null
40 | */
41 | public function index()
42 | {
43 | $indexRoute = $this->routes()->first(function (Route $route) {
44 | return $route->getAction('breadcrumbIndex');
45 | });
46 |
47 | if (! $indexRoute) {
48 | return;
49 | }
50 |
51 | return app(BreadcrumbLinkFactory::class)->create($indexRoute->uri(), $indexRoute);
52 | }
53 |
54 | /**
55 | * @return \Illuminate\Support\Collection
56 | */
57 | public function links()
58 | {
59 | $links = $this->groupLinks();
60 |
61 | if ($indexLink = $this->index()) {
62 | $links->prepend($indexLink, $indexLink->uri);
63 | }
64 |
65 | if ($currentLink = $this->current()) {
66 | $links->put($currentLink->uri, $currentLink);
67 | }
68 |
69 | if ($indexLink && is_null($currentLink)) {
70 | return Collection::make([$indexLink->uri => $indexLink]);
71 | }
72 |
73 | return $links;
74 | }
75 |
76 | /**
77 | * @return \Illuminate\Support\Collection
78 | */
79 | protected function groupLinks()
80 | {
81 | $pathPrefixes = $this->groupPrefixes(
82 | $this->request->path()
83 | );
84 |
85 | $routeUriPrefixes = $this->groupPrefixes(
86 | optional($this->request->route())->uri() ?? ''
87 | );
88 |
89 | return $this->routes()
90 | ->filter(function (Route $route) use ($routeUriPrefixes) {
91 | return in_array($route->uri(), $routeUriPrefixes, true);
92 | })
93 | ->filter(function (Route $route) {
94 | return $route->getAction('breadcrumb') && $route->getAction('breadcrumbGroup');
95 | })
96 | ->mapWithKeys(function (Route $route) use ($pathPrefixes) {
97 | $routeUri = $pathPrefixes[substr_count($route->uri(), '/') + 1];
98 |
99 | return [$routeUri => app(BreadcrumbLinkFactory::class)->create($routeUri, $route)];
100 | });
101 | }
102 |
103 | /**
104 | * @return \Fragkp\LaravelRouteBreadcrumb\BreadcrumbLink|null
105 | */
106 | public function current()
107 | {
108 | $route = $this->request->route();
109 |
110 | if (! $route || ! $route->getAction('breadcrumb')) {
111 | return;
112 | }
113 |
114 | return app(BreadcrumbLinkFactory::class)->create($this->request->path(), $route);
115 | }
116 |
117 | /**
118 | * @return \Illuminate\Support\Collection|\Illuminate\Routing\Route[]
119 | */
120 | protected function routes()
121 | {
122 | $breadcrumbCollection = ($route = $this->request->route())
123 | ? $route->getAction('breadcrumbCollection')
124 | : null;
125 |
126 | if ($this->routes) {
127 | return $this->routes;
128 | }
129 |
130 | $routes = Collection::make($this->router->getRoutes()->getRoutes());
131 |
132 | if (! is_null($breadcrumbCollection)) {
133 | $routes = $routes->filter(function (Route $route) use ($breadcrumbCollection) {
134 | return $route->getAction('breadcrumbCollection') === $breadcrumbCollection;
135 | });
136 | }
137 |
138 | return $this->routes = $routes;
139 | }
140 |
141 | /**
142 | * @param string $currentPath
143 | * @return array
144 | */
145 | protected function groupPrefixes(string $currentPath)
146 | {
147 | $prefixes = explode('/', $currentPath);
148 |
149 | $prefixes = array_map(function ($prefix) use ($prefixes) {
150 | $startPrefixes = implode('/', array_slice($prefixes, 0, array_search($prefix, $prefixes)));
151 |
152 | return ltrim("{$startPrefixes}/{$prefix}", '/');
153 | }, $prefixes);
154 |
155 | $prefixes = array_filter($prefixes);
156 |
157 | return Arr::prepend($prefixes, '/');
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/BreadcrumbLink.php:
--------------------------------------------------------------------------------
1 | uri = $uri;
24 | $this->title = $title;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/BreadcrumbLinkFactory.php:
--------------------------------------------------------------------------------
1 | request = $request;
23 | }
24 |
25 | /**
26 | * @param string $uri
27 | * @param \Illuminate\Routing\Route $route
28 | * @return \Fragkp\LaravelRouteBreadcrumb\BreadcrumbLink|null
29 | */
30 | public function create(string $uri, Route $route)
31 | {
32 | try {
33 | $route = RouteParameterBinder::bind($this->request, $route);
34 |
35 | $resolvedTitle = static::resolveTitle(
36 | $route->getAction('breadcrumb'),
37 | static::routeParameters($route)
38 | );
39 | } catch (Throwable $error) {
40 | return;
41 | }
42 |
43 | return new BreadcrumbLink($uri, $resolvedTitle);
44 | }
45 |
46 | /**
47 | * @param \Illuminate\Routing\Route $route
48 | * @return array
49 | */
50 | protected static function routeParameters(Route $route)
51 | {
52 | return $route->hasParameters()
53 | ? array_values($route->parameters())
54 | : [];
55 | }
56 |
57 | /**
58 | * @param string|callable|\Closure $title
59 | * @param array $parameters
60 | * @return string
61 | */
62 | protected static function resolveTitle($title, array $parameters)
63 | {
64 | if ($title instanceof Closure) {
65 | return $title(...$parameters);
66 | }
67 |
68 | if (is_string($title) && class_exists($title)) {
69 | return app($title)(...$parameters);
70 | }
71 |
72 | if (is_array($title) && is_callable($title)) {
73 | return $title(...$parameters);
74 | }
75 |
76 | return $title;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/BreadcrumbServiceProvider.php:
--------------------------------------------------------------------------------
1 | loadViewsFrom(__DIR__.'/../resources/views/', 'laravel-breadcrumb');
19 |
20 | $this->publishes([
21 | __DIR__.'/../resources/views' => base_path('resources/views/vendor/laravel-breadcrumb'),
22 | ], 'views');
23 |
24 | $this->app->singleton(Breadcrumb::class);
25 |
26 | View::composer('laravel-breadcrumb::*', function (\Illuminate\View\View $view) {
27 | $view->with('breadcrumb', $this->app->make(Breadcrumb::class)->links());
28 | });
29 | }
30 |
31 | /**
32 | * Register the application services.
33 | *
34 | * @return void
35 | */
36 | public function register()
37 | {
38 | if (! Route::hasMacro('breadcrumb')) {
39 | Route::macro('breadcrumb', function ($title) {
40 | $this->action['breadcrumb'] = $title;
41 |
42 | return $this;
43 | });
44 | }
45 |
46 | if (! Route::hasMacro('breadcrumbIndex')) {
47 | Route::macro('breadcrumbIndex', function ($title) {
48 | $this->action['breadcrumbIndex'] = true;
49 | $this->action['breadcrumb'] = $title;
50 |
51 | return $this;
52 | });
53 | }
54 |
55 | if (! Route::hasMacro('breadcrumbGroup')) {
56 | Route::macro('breadcrumbGroup', function ($title) {
57 | $this->action['breadcrumbGroup'] = true;
58 | $this->action['breadcrumb'] = $title;
59 |
60 | return $this;
61 | });
62 | }
63 |
64 | if (! Route::hasMacro('breadcrumbCollection')) {
65 | Route::macro('breadcrumbCollection', function (string $collection) {
66 | $this->action['breadcrumbCollection'] = $collection;
67 |
68 | return $this;
69 | });
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Facades/Breadcrumb.php:
--------------------------------------------------------------------------------
1 | getCompiled())) {
19 | return $route;
20 | }
21 |
22 | $compiledRouteParameters = $compiledRoute->getVariables();
23 |
24 | if (! empty($compiledRouteParameters)) {
25 | $currentParameters = $request->route()->parameters();
26 |
27 | $route->bind(new Request);
28 |
29 | foreach (Arr::only($currentParameters, $compiledRouteParameters) as $name => $parameter) {
30 | $route->setParameter($name, $parameter);
31 | }
32 | }
33 |
34 | return $route;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/BreadcrumbTest.php:
--------------------------------------------------------------------------------
1 | get('/foo')->assertSuccessful();
20 |
21 | $breadcrumbLinks = app(Breadcrumb::class)->links();
22 |
23 | $this->assertCount(0, $breadcrumbLinks);
24 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
25 | $this->assertEmpty($breadcrumbLinks);
26 |
27 | $this->assertNull(app(Breadcrumb::class)->current());
28 | $this->assertNull(app(Breadcrumb::class)->index());
29 | }
30 |
31 | /** @test */
32 | public function it_is_empty_when_no_route_is_found()
33 | {
34 | $this->get('/foo')->assertStatus(404);
35 |
36 | $breadcrumbLinks = app(Breadcrumb::class)->links();
37 |
38 | $this->assertCount(0, $breadcrumbLinks);
39 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
40 | $this->assertEmpty($breadcrumbLinks);
41 |
42 | $this->assertNull(app(Breadcrumb::class)->current());
43 | $this->assertNull(app(Breadcrumb::class)->index());
44 | }
45 |
46 | /** @test */
47 | public function it_is_empty_when_an_error_occurs()
48 | {
49 | Route::get('/foo', function () {
50 | throw new \Exception;
51 | });
52 |
53 | $this->get('/foo')->assertStatus(500);
54 |
55 | $breadcrumbLinks = app(Breadcrumb::class)->links();
56 |
57 | $this->assertCount(0, $breadcrumbLinks);
58 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
59 | $this->assertEmpty($breadcrumbLinks);
60 |
61 | $this->assertNull(app(Breadcrumb::class)->current());
62 | $this->assertNull(app(Breadcrumb::class)->index());
63 | }
64 |
65 | /** @test */
66 | public function it_returns_always_the_breadcrumb_index()
67 | {
68 | Route::get('/', TestBreadcrumbController::class)->breadcrumbIndex('Start');
69 |
70 | Route::get('/foo', function () {
71 | throw new \Exception;
72 | });
73 |
74 | $this->get('/foo')->assertStatus(500);
75 |
76 | $breadcrumbLinks = app(Breadcrumb::class)->links();
77 |
78 | $this->assertCount(1, $breadcrumbLinks);
79 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
80 | $this->assertEquals(new Collection([
81 | '/' => new BreadcrumbLink('/', 'Start'),
82 | ]), $breadcrumbLinks);
83 |
84 | $this->assertEquals(new BreadcrumbLink('/', 'Start'), app(Breadcrumb::class)->index());
85 |
86 | $this->assertNull(app(Breadcrumb::class)->current());
87 | }
88 |
89 | /** @test */
90 | public function it_returns_only_the_matched_breadcrumb()
91 | {
92 | Route::get('/foo', TestBreadcrumbController::class)->breadcrumb('Foo');
93 | Route::get('/bar/camp', TestBreadcrumbController::class)->breadcrumb('Bar');
94 | Route::get('/zoo/deep/crew', TestBreadcrumbController::class)->breadcrumb('Zoo');
95 | Route::get('/baz', TestBreadcrumbController::class)->breadcrumb('Baz');
96 |
97 | $this->get('/zoo/deep/crew')->assertSuccessful();
98 |
99 | $breadcrumbLinks = app(Breadcrumb::class)->links();
100 |
101 | $this->assertCount(1, $breadcrumbLinks);
102 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
103 | $this->assertEquals(new Collection([
104 | 'zoo/deep/crew' => new BreadcrumbLink('zoo/deep/crew', 'Zoo'),
105 | ]), $breadcrumbLinks);
106 |
107 | $this->assertNull(app(Breadcrumb::class)->index());
108 |
109 | $this->assertEquals(new BreadcrumbLink('zoo/deep/crew', 'Zoo'), app(Breadcrumb::class)->current());
110 | }
111 |
112 | /** @test */
113 | public function it_returns_only_the_matched_and_defined_index_breadcrumbs()
114 | {
115 | Route::get('/', TestBreadcrumbController::class)->breadcrumbIndex('Start');
116 | Route::get('/foo', TestBreadcrumbController::class)->breadcrumb('First');
117 |
118 | $this->get('/foo')->assertSuccessful();
119 |
120 | $breadcrumbLinks = app(Breadcrumb::class)->links();
121 |
122 | $this->assertCount(2, $breadcrumbLinks);
123 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
124 | $this->assertEquals(new Collection([
125 | '/' => new BreadcrumbLink('/', 'Start'),
126 | 'foo' => new BreadcrumbLink('foo', 'First'),
127 | ]), $breadcrumbLinks);
128 |
129 | $this->assertEquals(new BreadcrumbLink('/', 'Start'), app(Breadcrumb::class)->index());
130 |
131 | $this->assertEquals(new BreadcrumbLink('foo', 'First'), app(Breadcrumb::class)->current());
132 | }
133 |
134 | /** @test */
135 | public function it_returns_always_the_first_index()
136 | {
137 | Route::get('/bar', TestBreadcrumbController::class)->breadcrumbIndex('Start first');
138 | Route::get('/', TestBreadcrumbController::class)->breadcrumbIndex('Start second');
139 | Route::get('/zoo', TestBreadcrumbController::class)->breadcrumbIndex('Start third');
140 | Route::get('/foo', TestBreadcrumbController::class)->breadcrumb('First');
141 |
142 | $this->get('/foo')->assertSuccessful();
143 |
144 | $breadcrumbLinks = app(Breadcrumb::class)->links();
145 |
146 | $this->assertCount(2, $breadcrumbLinks);
147 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
148 | $this->assertEquals(new Collection([
149 | 'bar' => new BreadcrumbLink('bar', 'Start first'),
150 | 'foo' => new BreadcrumbLink('foo', 'First'),
151 | ]), $breadcrumbLinks);
152 |
153 | $this->assertEquals(new BreadcrumbLink('bar', 'Start first'), app(Breadcrumb::class)->index());
154 |
155 | $this->assertEquals(new BreadcrumbLink('foo', 'First'), app(Breadcrumb::class)->current());
156 | }
157 |
158 | /** @test */
159 | public function it_handles_same_uri_groups_as_different_breadcrumbs()
160 | {
161 | Route::get('/', TestBreadcrumbController::class)->breadcrumbIndex('Start');
162 | Route::get('/bar', TestBreadcrumbController::class)->breadcrumbGroup('bar');
163 | Route::get('/bar/bar', TestBreadcrumbController::class)->breadcrumb('bar bar');
164 |
165 | $this->get('/bar/bar')->assertSuccessful();
166 |
167 | $breadcrumbLinks = app(Breadcrumb::class)->links();
168 |
169 | $this->assertCount(3, $breadcrumbLinks);
170 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
171 | $this->assertEquals(new Collection([
172 | '/' => new BreadcrumbLink('/', 'Start'),
173 | 'bar' => new BreadcrumbLink('bar', 'bar'),
174 | 'bar/bar' => new BreadcrumbLink('bar/bar', 'bar bar'),
175 | ]), $breadcrumbLinks);
176 |
177 | $this->assertEquals(new BreadcrumbLink('/', 'Start'), app(Breadcrumb::class)->index());
178 |
179 | $this->assertEquals(new BreadcrumbLink('bar/bar', 'bar bar'), app(Breadcrumb::class)->current());
180 | }
181 |
182 | /** @test */
183 | public function it_handles_route_collection()
184 | {
185 | Route::get('/', TestBreadcrumbController::class)->breadcrumbIndex('Start');
186 | Route::get('/bar', TestBreadcrumbController::class)->breadcrumbGroup('bar');
187 | Route::get('/bar/bar', TestBreadcrumbController::class)->breadcrumb('bar bar');
188 |
189 | $this->get('/bar/bar')->assertSuccessful();
190 |
191 | $breadcrumbLinks = app(Breadcrumb::class)->links();
192 |
193 | $this->assertCount(3, $breadcrumbLinks);
194 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
195 | $this->assertEquals(new Collection([
196 | '/' => new BreadcrumbLink('/', 'Start'),
197 | 'bar' => new BreadcrumbLink('bar', 'bar'),
198 | 'bar/bar' => new BreadcrumbLink('bar/bar', 'bar bar'),
199 | ]), $breadcrumbLinks);
200 |
201 | $this->assertEquals(new BreadcrumbLink('/', 'Start'), app(Breadcrumb::class)->index());
202 |
203 | $this->assertEquals(new BreadcrumbLink('bar/bar', 'bar bar'), app(Breadcrumb::class)->current());
204 | }
205 |
206 | /** @test */
207 | public function it_can_handle_the_breadcrumb_title_by_closure()
208 | {
209 | Route::get('/foo', TestBreadcrumbController::class)->breadcrumb(function () {
210 | return 'Closure title';
211 | });
212 |
213 | $this->get('/foo')->assertSuccessful();
214 |
215 | $breadcrumbLinks = app(Breadcrumb::class)->links();
216 |
217 | $this->assertCount(1, $breadcrumbLinks);
218 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
219 | $this->assertEquals(new Collection([
220 | 'foo' => new BreadcrumbLink('foo', 'Closure title'),
221 | ]), $breadcrumbLinks);
222 |
223 | $this->assertEquals(new BreadcrumbLink('foo', 'Closure title'), app(Breadcrumb::class)->current());
224 | }
225 |
226 | /** @test */
227 | public function it_can_handle_the_breadcrumb_title_by_callable()
228 | {
229 | $resolver = new CustomTitleResolver();
230 |
231 | Route::get('/foo', TestBreadcrumbController::class)->breadcrumb([$resolver, 'getTitle']);
232 |
233 | $this->get('/foo')->assertSuccessful();
234 |
235 | $breadcrumbLinks = app(Breadcrumb::class)->links();
236 |
237 | $this->assertCount(1, $breadcrumbLinks);
238 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
239 | $this->assertEquals(new Collection([
240 | 'foo' => new BreadcrumbLink('foo', 'Custom title'),
241 | ]), $breadcrumbLinks);
242 |
243 | $this->assertEquals(new BreadcrumbLink('foo', 'Custom title'), app(Breadcrumb::class)->current());
244 | }
245 |
246 | /** @test */
247 | public function it_can_handle_the_breadcrumb_title_by_callable_with_parameters()
248 | {
249 | $resolver = new CustomTitleResolver();
250 |
251 | Route::get('/products/{category}/{name}', TestBreadcrumbController::class)->breadcrumb([$resolver, 'getTitleWithParameters']);
252 |
253 | $this->get('/products/shirts/foo')->assertSuccessful();
254 |
255 | $breadcrumbLinks = app(Breadcrumb::class)->links();
256 |
257 | $this->assertCount(1, $breadcrumbLinks);
258 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
259 | $this->assertEquals(new Collection([
260 | 'products/shirts/foo' => new BreadcrumbLink('products/shirts/foo', 'Custom title: shirts > foo'),
261 | ]), $breadcrumbLinks);
262 |
263 | $this->assertEquals(new BreadcrumbLink('products/shirts/foo', 'Custom title: shirts > foo'), app(Breadcrumb::class)->current());
264 | }
265 |
266 | /** @test */
267 | public function it_can_handle_the_breadcrumb_title_by_custom_class()
268 | {
269 | Route::get('/foo', TestBreadcrumbController::class)->breadcrumb(CustomTitleResolver::class);
270 |
271 | $this->get('/foo')->assertSuccessful();
272 |
273 | $breadcrumbLinks = app(Breadcrumb::class)->links();
274 |
275 | $this->assertCount(1, $breadcrumbLinks);
276 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
277 | $this->assertEquals(new Collection([
278 | 'foo' => new BreadcrumbLink('foo', 'Class title'),
279 | ]), $breadcrumbLinks);
280 |
281 | $this->assertEquals(new BreadcrumbLink('foo', 'Class title'), app(Breadcrumb::class)->current());
282 | }
283 |
284 | /** @test */
285 | public function it_returns_the_breadcrumb_inside_a_group()
286 | {
287 | Route::get('/', TestBreadcrumbController::class)->breadcrumbIndex('Start');
288 |
289 | Route::prefix('foo')->group(function () {
290 | Route::get('/bar', TestBreadcrumbController::class)->breadcrumb('Inside group');
291 | });
292 |
293 | $this->get('/foo/bar')->assertSuccessful();
294 |
295 | $breadcrumbLinks = app(Breadcrumb::class)->links();
296 |
297 | $this->assertCount(2, $breadcrumbLinks);
298 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
299 | $this->assertEquals(new Collection([
300 | '/' => new BreadcrumbLink('/', 'Start'),
301 | 'foo/bar' => new BreadcrumbLink('foo/bar', 'Inside group'),
302 | ]), $breadcrumbLinks);
303 |
304 | $this->assertEquals(new BreadcrumbLink('/', 'Start'), app(Breadcrumb::class)->index());
305 |
306 | $this->assertEquals(new BreadcrumbLink('foo/bar', 'Inside group'), app(Breadcrumb::class)->current());
307 | }
308 |
309 | /** @test */
310 | public function it_returns_the_breadcrumb_inside_a_group_with_the_group_index()
311 | {
312 | Route::get('/', TestBreadcrumbController::class)->breadcrumbIndex('Start');
313 |
314 | Route::prefix('foo')->group(function () {
315 | Route::get('/', TestBreadcrumbController::class)->breadcrumbGroup('Inside group - index');
316 | Route::get('/bar', TestBreadcrumbController::class)->breadcrumb('Inside group - bar');
317 | });
318 |
319 | $this->get('/foo/bar')->assertSuccessful();
320 |
321 | $breadcrumbLinks = app(Breadcrumb::class)->links();
322 |
323 | $this->assertCount(3, $breadcrumbLinks);
324 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
325 | $this->assertEquals(new Collection([
326 | '/' => new BreadcrumbLink('/', 'Start'),
327 | 'foo' => new BreadcrumbLink('foo', 'Inside group - index'),
328 | 'foo/bar' => new BreadcrumbLink('foo/bar', 'Inside group - bar'),
329 | ]), $breadcrumbLinks);
330 |
331 | $this->assertEquals(new BreadcrumbLink('/', 'Start'), app(Breadcrumb::class)->index());
332 |
333 | $this->assertEquals(new BreadcrumbLink('foo/bar', 'Inside group - bar'), app(Breadcrumb::class)->current());
334 | }
335 |
336 | /** @test */
337 | public function it_can_handle_multiple_nested_groups()
338 | {
339 | Route::get('/', TestBreadcrumbController::class)->breadcrumbIndex('Start');
340 |
341 | Route::prefix('foo')->group(function () {
342 | Route::get('/', TestBreadcrumbController::class)->breadcrumbGroup('Inside group - index');
343 | Route::get('/bar', TestBreadcrumbController::class)->breadcrumb('Inside group - bar');
344 |
345 | Route::prefix('baz')->group(function () {
346 | Route::get('/zoo', TestBreadcrumbController::class)->breadcrumb('Inside nested group - zoo');
347 |
348 | Route::prefix('too')->group(function () {
349 | Route::get('/', TestBreadcrumbController::class)->breadcrumbGroup('Inside nested group - group - index');
350 | Route::get('/crew', TestBreadcrumbController::class)->breadcrumb('Inside nested group - group - crew');
351 | });
352 | });
353 | });
354 |
355 | $this->get('/foo/baz/too/crew')->assertSuccessful();
356 |
357 | $breadcrumbLinks = app(Breadcrumb::class)->links();
358 |
359 | $this->assertCount(4, $breadcrumbLinks);
360 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
361 | $this->assertEquals(new Collection([
362 | '/' => new BreadcrumbLink('/', 'Start'),
363 | 'foo' => new BreadcrumbLink('foo', 'Inside group - index'),
364 | 'foo/baz/too' => new BreadcrumbLink('foo/baz/too', 'Inside nested group - group - index'),
365 | 'foo/baz/too/crew' => new BreadcrumbLink('foo/baz/too/crew', 'Inside nested group - group - crew'),
366 | ]), $breadcrumbLinks);
367 |
368 | $this->assertEquals(new BreadcrumbLink('/', 'Start'), app(Breadcrumb::class)->index());
369 |
370 | $this->assertEquals(new BreadcrumbLink('foo/baz/too/crew', 'Inside nested group - group - crew'), app(Breadcrumb::class)->current());
371 | }
372 |
373 | /** @test */
374 | public function it_can_handle_route_model_binding()
375 | {
376 | $this->migrate();
377 |
378 | Foo::create();
379 |
380 | Route::middleware(SubstituteBindings::class)->get('/binding/{foo}', function (Foo $foo) {
381 | return $foo->id;
382 | })->breadcrumb('First');
383 |
384 | $this->get('/binding/1')->assertSuccessful()->assertSee('1');
385 |
386 | $breadcrumbLinks = app(Breadcrumb::class)->links();
387 |
388 | $this->assertCount(1, $breadcrumbLinks);
389 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
390 | $this->assertEquals(new Collection([
391 | 'binding/1' => new BreadcrumbLink('binding/1', 'First'),
392 | ]), $breadcrumbLinks);
393 |
394 | $this->assertEquals(new BreadcrumbLink('binding/1', 'First'), app(Breadcrumb::class)->current());
395 | }
396 |
397 | /** @test */
398 | public function it_can_handle_route_model_binding_and_resolves_title_by_closure()
399 | {
400 | $this->migrate();
401 |
402 | Foo::create();
403 |
404 | Route::middleware(SubstituteBindings::class)->get('/binding/{foo}', function (Foo $foo) {
405 | return $foo->id;
406 | })->breadcrumb(function (Foo $foo) {
407 | return "Id: {$foo->id}";
408 | });
409 |
410 | $this->get('/binding/1')->assertSuccessful()->assertSee('1');
411 |
412 | $breadcrumbLinks = app(Breadcrumb::class)->links();
413 |
414 | $this->assertCount(1, $breadcrumbLinks);
415 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
416 | $this->assertEquals(new Collection([
417 | 'binding/1' => new BreadcrumbLink('binding/1', 'Id: 1'),
418 | ]), $breadcrumbLinks);
419 |
420 | $this->assertEquals(new BreadcrumbLink('binding/1', 'Id: 1'), app(Breadcrumb::class)->current());
421 | }
422 |
423 | /** @test */
424 | public function it_can_handle_route_model_binding_and_resolves_title_by_callable()
425 | {
426 | $this->migrate();
427 |
428 | Foo::create();
429 |
430 | $resolver = new CustomTitleResolver();
431 |
432 | Route::middleware(SubstituteBindings::class)->get('/binding/{foo}', function (Foo $foo) {
433 | return $foo->id;
434 | })->breadcrumb([$resolver, 'getTitleFromModel']);
435 |
436 | $this->get('/binding/1')->assertSuccessful()->assertSee('1');
437 |
438 | $breadcrumbLinks = app(Breadcrumb::class)->links();
439 |
440 | $this->assertCount(1, $breadcrumbLinks);
441 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
442 | $this->assertEquals(new Collection([
443 | 'binding/1' => new BreadcrumbLink('binding/1', 'Custom title: 1'),
444 | ]), $breadcrumbLinks);
445 |
446 | $this->assertEquals(new BreadcrumbLink('binding/1', 'Custom title: 1'), app(Breadcrumb::class)->current());
447 | }
448 |
449 | /** @test */
450 | public function it_can_handle_route_model_binding_and_resolves_title_by_custom_class()
451 | {
452 | $this->migrate();
453 |
454 | Foo::create();
455 | Foo::create();
456 |
457 | Route::middleware(SubstituteBindings::class)->get('/binding/{foo}', function (Foo $foo) {
458 | return $foo->id;
459 | })->breadcrumb(CustomRouteModelBindingTitleResolver::class);
460 |
461 | $this->get('/binding/2')->assertSuccessful()->assertSee('2');
462 |
463 | $breadcrumbLinks = app(Breadcrumb::class)->links();
464 |
465 | $this->assertCount(1, $breadcrumbLinks);
466 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
467 | $this->assertEquals(new Collection([
468 | 'binding/2' => new BreadcrumbLink('binding/2', 'Id: 2'),
469 | ]), $breadcrumbLinks);
470 |
471 | $this->assertEquals(new BreadcrumbLink('binding/2', 'Id: 2'), app(Breadcrumb::class)->current());
472 | }
473 |
474 | /** @test */
475 | public function it_can_handle_multiple_route_model_bindings_inside_groups()
476 | {
477 | $this->migrate();
478 |
479 | Foo::create();
480 | Bar::create();
481 | Bar::create();
482 | Bar::create();
483 | Bar::create();
484 | Bar::create();
485 |
486 | Route::get('/', TestBreadcrumbController::class)->breadcrumbIndex('Start');
487 |
488 | Route::prefix('first-group')->group(function () {
489 | Route::get('/')->breadcrumbGroup('Inside first group');
490 |
491 | Route::prefix('second-group')->group(function () {
492 | Route::get('/')->breadcrumbGroup('Inside second group');
493 |
494 | Route::middleware(SubstituteBindings::class)->get('/{foo}/{bar}', function (Foo $foo, Bar $bar) {
495 | return $foo->id.'-'.$bar->id;
496 | })->breadcrumb('Binding');
497 | });
498 | });
499 |
500 | $this->get('/first-group/second-group/1/5')->assertSuccessful()->assertSee('1-5');
501 |
502 | $breadcrumbLinks = app(Breadcrumb::class)->links();
503 |
504 | $this->assertCount(4, $breadcrumbLinks);
505 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
506 | $this->assertEquals(new Collection([
507 | '/' => new BreadcrumbLink('/', 'Start'),
508 | 'first-group' => new BreadcrumbLink('first-group', 'Inside first group'),
509 | 'first-group/second-group' => new BreadcrumbLink('first-group/second-group', 'Inside second group'),
510 | 'first-group/second-group/1/5' => new BreadcrumbLink('first-group/second-group/1/5', 'Binding'),
511 | ]), $breadcrumbLinks);
512 |
513 | $this->assertEquals(new BreadcrumbLink('/', 'Start'), app(Breadcrumb::class)->index());
514 |
515 | $this->assertEquals(new BreadcrumbLink('first-group/second-group/1/5', 'Binding'), app(Breadcrumb::class)->current());
516 | }
517 |
518 | /** @test */
519 | public function it_can_handle_custom_route_model_binding()
520 | {
521 | Route::bind('customBinding', function ($value) {
522 | return new CustomBinding($value);
523 | });
524 |
525 | Route::middleware(SubstituteBindings::class)->get('/binding/{customBinding}', function (CustomBinding $customBinding) {
526 | return $customBinding->value;
527 | })->breadcrumb('First');
528 |
529 | $this->get('/binding/foo')->assertSuccessful()->assertSee('foo');
530 |
531 | $breadcrumbLinks = app(Breadcrumb::class)->links();
532 |
533 | $this->assertCount(1, $breadcrumbLinks);
534 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
535 | $this->assertEquals(new Collection([
536 | 'binding/foo' => new BreadcrumbLink('binding/foo', 'First'),
537 | ]), $breadcrumbLinks);
538 |
539 | $this->assertEquals(new BreadcrumbLink('binding/foo', 'First'), app(Breadcrumb::class)->current());
540 | }
541 |
542 | /** @test */
543 | public function it_can_handle_nested_route_parameters_with_route_model_binding_for_group_index()
544 | {
545 | Route::bind('customBinding', function ($value) {
546 | return new CustomBinding($value);
547 | });
548 |
549 | Route::bind('secondBinding', function ($value) {
550 | return new SecondBinding($value);
551 | });
552 |
553 | Route::middleware(SubstituteBindings::class)->group(function () {
554 | Route::get('/binding/{customBinding}', function (CustomBinding $customBinding) {
555 | return $customBinding->value;
556 | })->breadcrumbGroup(function (CustomBinding $customBinding) {
557 | return $customBinding->value;
558 | });
559 |
560 | Route::get('/binding/{customBinding}/{secondBinding}', function (CustomBinding $customBinding, SecondBinding $secondBinding) {
561 | return $customBinding->value.'-'.$secondBinding->value;
562 | })->breadcrumb(function (CustomBinding $customBinding, SecondBinding $secondBinding) {
563 | return $customBinding->value.'-'.$secondBinding->value;
564 | });
565 | });
566 |
567 | $this->get('/binding/foo/bar')->assertSuccessful()->assertSee('foo-bar');
568 |
569 | $breadcrumbLinks = app(Breadcrumb::class)->links();
570 |
571 | $this->assertCount(2, $breadcrumbLinks);
572 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
573 | $this->assertEquals(new Collection([
574 | 'binding/foo' => new BreadcrumbLink('binding/foo', 'foo'),
575 | 'binding/foo/bar' => new BreadcrumbLink('binding/foo/bar', 'foo-bar'),
576 | ]), $breadcrumbLinks);
577 |
578 | $this->assertEquals(new BreadcrumbLink('binding/foo/bar', 'foo-bar'), app(Breadcrumb::class)->current());
579 | }
580 |
581 | /** @test */
582 | public function it_returns_the_index_when_nested_routes_with_parameters_doesnt_match_route_binding_current()
583 | {
584 | Route::bind('customBinding', function ($value) {
585 | return new CustomBinding($value);
586 | });
587 |
588 | Route::bind('secondBinding', function () {
589 | return abort(404);
590 | });
591 |
592 | Route::get('/', TestBreadcrumbController::class)->breadcrumbIndex('Start');
593 |
594 | Route::middleware(SubstituteBindings::class)->group(function () {
595 | Route::get('/binding/{customBinding}', function (CustomBinding $customBinding) {
596 | return $customBinding->value;
597 | })->breadcrumbGroup(function (CustomBinding $customBinding) {
598 | return $customBinding->value;
599 | });
600 |
601 | Route::get('/binding/{customBinding}/{parameter}', function (CustomBinding $customBinding, $parameters) {
602 | return $customBinding->value.'-'.$parameters;
603 | })->breadcrumb(function (CustomBinding $customBinding, $parameters) {
604 | return $customBinding->value.'-'.$parameters;
605 | });
606 |
607 | Route::get('/binding/{customBinding}/{parameter}/{secondBinding}', function (CustomBinding $customBinding, int $parameter, SecondBinding $secondBinding) {
608 | return $customBinding->value.'-'.$parameter.'-'.$secondBinding->value;
609 | })->breadcrumb(function (CustomBinding $customBinding, int $parameter, SecondBinding $secondBinding) {
610 | return $customBinding->value.'-'.$parameter.'-'.$secondBinding->value;
611 | });
612 | });
613 |
614 | $this->get('/binding/foo/bar/baz')->assertStatus(404);
615 |
616 | $breadcrumbLinks = app(Breadcrumb::class)->links();
617 |
618 | $this->assertCount(1, $breadcrumbLinks);
619 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
620 | $this->assertEquals(new Collection([
621 | '/' => new BreadcrumbLink('/', 'Start'),
622 | ]), $breadcrumbLinks);
623 |
624 | $this->assertNull(app(Breadcrumb::class)->current());
625 |
626 | $this->assertEquals(new BreadcrumbLink('/', 'Start'), app(Breadcrumb::class)->index());
627 | }
628 |
629 | /** @test */
630 | public function it_returns_the_index_when_nested_routes_with_parameters_doesnt_match_route_binding_links()
631 | {
632 | Route::bind('customBinding', function ($value) {
633 | return new CustomBinding($value);
634 | });
635 |
636 | Route::bind('secondBinding', function ($value) {
637 | return abort(404);
638 | });
639 |
640 | Route::get('/', TestBreadcrumbController::class)->breadcrumbIndex('Start');
641 |
642 | Route::middleware(SubstituteBindings::class)->group(function () {
643 | Route::get('/binding/{customBinding}', function (CustomBinding $customBinding) {
644 | return $customBinding->value;
645 | })->breadcrumbGroup(function (CustomBinding $customBinding) {
646 | return $customBinding->value;
647 | });
648 |
649 | Route::get('/binding/{customBinding}/{secondBinding}', function (CustomBinding $customBinding, SecondBinding $secondBinding) {
650 | return $customBinding->value.'-'.$secondBinding->value;
651 | })->breadcrumb(function (CustomBinding $customBinding, SecondBinding $secondBinding) {
652 | return $customBinding->value.'-'.$secondBinding->value;
653 | });
654 |
655 | Route::get('/binding/{customBinding}/{secondBinding}/{parameter}', function (CustomBinding $customBinding, SecondBinding $secondBinding, int $parameter) {
656 | return $customBinding->value.'-'.$secondBinding->value.'-'.$parameter;
657 | })->breadcrumb(function (CustomBinding $customBinding, SecondBinding $secondBinding, int $parameter) {
658 | return $customBinding->value.'-'.$secondBinding->value.'-'.$parameter;
659 | });
660 | });
661 |
662 | $this->get('/binding/foo/bar/baz')->assertStatus(404);
663 |
664 | $breadcrumbLinks = app(Breadcrumb::class)->links();
665 |
666 | $this->assertCount(1, $breadcrumbLinks);
667 | $this->assertInstanceOf(Collection::class, $breadcrumbLinks);
668 | $this->assertEquals(new Collection([
669 | '/' => new BreadcrumbLink('/', 'Start'),
670 | ]), $breadcrumbLinks);
671 |
672 | $this->assertNull(app(Breadcrumb::class)->current());
673 |
674 | $this->assertEquals(new BreadcrumbLink('/', 'Start'), app(Breadcrumb::class)->index());
675 | }
676 | }
677 |
678 | class TestBreadcrumbController
679 | {
680 | public function __invoke()
681 | {
682 | //
683 | }
684 | }
685 |
686 | class Foo extends Model
687 | {
688 | //
689 | }
690 |
691 | class Bar extends Model
692 | {
693 | //
694 | }
695 |
696 | class CustomBinding
697 | {
698 | public function __construct($value)
699 | {
700 | $this->value = $value;
701 | }
702 | }
703 |
704 | class SecondBinding
705 | {
706 | public function __construct($value)
707 | {
708 | $this->value = $value;
709 | }
710 | }
711 |
712 | class CustomTitleResolver
713 | {
714 | public function __invoke()
715 | {
716 | return 'Class title';
717 | }
718 |
719 | public function getTitle()
720 | {
721 | return 'Custom title';
722 | }
723 |
724 | public function getTitleWithParameters(...$params)
725 | {
726 | return 'Custom title: '.\implode(' > ', $params);
727 | }
728 |
729 | public function getTitleFromModel(Foo $foo)
730 | {
731 | return "Custom title: {$foo->id}";
732 | }
733 | }
734 |
735 | class CustomRouteModelBindingTitleResolver
736 | {
737 | public function __invoke(Foo $foo)
738 | {
739 | return "Id: {$foo->id}";
740 | }
741 | }
742 |
--------------------------------------------------------------------------------
/tests/TemplateTest.php:
--------------------------------------------------------------------------------
1 | get('/foo')->assertStatus(404);
13 |
14 | $html = (string) view('laravel-breadcrumb::bootstrap3');
15 |
16 | $this->assertEmpty($html);
17 | }
18 |
19 | /** @test */
20 | public function it_returns_the_correct_bootstrap_3_template()
21 | {
22 | Route::get('/', TestTemplateController::class)->breadcrumbIndex('Start');
23 | Route::get('/foo', TestTemplateController::class)->breadcrumb('First');
24 |
25 | $this->get('/foo')->assertStatus(200);
26 |
27 | $html = (string) view('laravel-breadcrumb::bootstrap3');
28 |
29 | $this->assertXmlStringEqualsXmlString('
30 |
31 | -
32 | Start
33 |
34 |
35 | - First
36 |
37 | ', $html);
38 | }
39 |
40 | /** @test */
41 | public function it_returns_an_empty_bootstrap_4_template()
42 | {
43 | $this->get('/foo')->assertStatus(404);
44 |
45 | $html = (string) view('laravel-breadcrumb::bootstrap4');
46 |
47 | $this->assertEmpty($html);
48 | }
49 |
50 | /** @test */
51 | public function it_returns_the_correct_bootstrap_4_template()
52 | {
53 | Route::get('/', TestTemplateController::class)->breadcrumbIndex('Start');
54 | Route::get('/foo', TestTemplateController::class)->breadcrumb('First');
55 |
56 | $this->get('/foo')->assertStatus(200);
57 |
58 | $html = (string) view('laravel-breadcrumb::bootstrap4');
59 |
60 | $this->assertXmlStringEqualsXmlString('
61 |
70 | ', $html);
71 | }
72 |
73 | /** @test */
74 | public function it_returns_an_empty_bulma_template()
75 | {
76 | $this->get('/foo')->assertStatus(404);
77 |
78 | $html = (string) view('laravel-breadcrumb::bulma');
79 |
80 | $this->assertEmpty($html);
81 | }
82 |
83 | /** @test */
84 | public function it_returns_the_correct_bulma_template()
85 | {
86 | Route::get('/', TestTemplateController::class)->breadcrumbIndex('Start');
87 | Route::get('/foo', TestTemplateController::class)->breadcrumb('First');
88 |
89 | $this->get('/foo')->assertStatus(200);
90 |
91 | $html = (string) view('laravel-breadcrumb::bulma');
92 |
93 | $this->assertXmlStringEqualsXmlString('
94 |
105 | ', $html);
106 | }
107 |
108 | /** @test */
109 | public function it_returns_an_empty_foundation_6_template()
110 | {
111 | $this->get('/foo')->assertStatus(404);
112 |
113 | $html = (string) view('laravel-breadcrumb::foundation6');
114 |
115 | $this->assertEmpty($html);
116 | }
117 |
118 | /** @test */
119 | public function it_returns_the_correct_foundation_6_template()
120 | {
121 | Route::get('/', TestTemplateController::class)->breadcrumbIndex('Start');
122 | Route::get('/foo', TestTemplateController::class)->breadcrumb('First');
123 |
124 | $this->get('/foo')->assertStatus(200);
125 |
126 | $html = (string) view('laravel-breadcrumb::foundation6');
127 |
128 | $this->assertXmlStringEqualsXmlString('
129 |
140 | ', $html);
141 | }
142 | }
143 |
144 | class TestTemplateController
145 | {
146 | public function __invoke()
147 | {
148 | //
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | set('database.default', 'testbench');
20 | $app['config']->set('database.connections.testbench', [
21 | 'driver' => 'sqlite',
22 | 'database' => ':memory:',
23 | 'prefix' => '',
24 | ]);
25 | }
26 |
27 | protected function migrate()
28 | {
29 | $this->loadMigrationsFrom(realpath(__DIR__.'/database/migrations'));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/database/migrations/2018_12_05_000000_create_foos_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->timestamps();
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function down()
28 | {
29 | Schema::dropIfExists('foos');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/database/migrations/2018_12_05_000001_create_bars_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->timestamps();
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function down()
28 | {
29 | Schema::dropIfExists('bars');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------