Browse Source

Fix nav context back-chain behavior

Alexander Musikhin 21 hours ago
parent
commit
a5cdc69f8c
28 changed files with 304 additions and 59 deletions
  1. 1 2
      app/Http/Controllers/Admin/AdminAreaController.php
  2. 1 2
      app/Http/Controllers/Admin/AdminDistrictController.php
  3. 1 2
      app/Http/Controllers/ContractController.php
  4. 8 0
      app/Http/Controllers/Controller.php
  5. 1 2
      app/Http/Controllers/ImportController.php
  6. 1 2
      app/Http/Controllers/MafOrderController.php
  7. 2 4
      app/Http/Controllers/OrderController.php
  8. 1 2
      app/Http/Controllers/ProductController.php
  9. 1 2
      app/Http/Controllers/ProductSKUController.php
  10. 1 2
      app/Http/Controllers/ReclamationController.php
  11. 1 2
      app/Http/Controllers/ResponsibleController.php
  12. 1 2
      app/Http/Controllers/SparePartController.php
  13. 1 2
      app/Http/Controllers/SparePartOrderController.php
  14. 1 2
      app/Http/Controllers/UserController.php
  15. 70 8
      app/Services/NavigationContextService.php
  16. 1 0
      resources/views/orders/show.blade.php
  17. 1 1
      tests/Feature/AdminAreaControllerTest.php
  18. 1 1
      tests/Feature/AdminDistrictControllerTest.php
  19. 1 1
      tests/Feature/ContractControllerTest.php
  20. 24 2
      tests/Feature/OrderControllerTest.php
  21. 2 0
      tests/Feature/ProductControllerTest.php
  22. 2 0
      tests/Feature/ProductSKUControllerTest.php
  23. 131 0
      tests/Feature/ReclamationControllerTest.php
  24. 1 1
      tests/Feature/ResponsibleControllerTest.php
  25. 2 2
      tests/Feature/SparePartControllerTest.php
  26. 2 2
      tests/Feature/SparePartOrderControllerTest.php
  27. 1 1
      tests/Feature/UserControllerTest.php
  28. 43 12
      tests/Unit/Services/NavigationContextServiceTest.php

+ 1 - 2
app/Http/Controllers/Admin/AdminAreaController.php

@@ -25,8 +25,7 @@ class AdminAreaController extends Controller
 
     public function index(Request $request): View
     {
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
         $allowedSortFields = ['id', 'name', 'district_name'];
         $sortBy = in_array($request->get('sortBy'), $allowedSortFields, true) ? $request->get('sortBy') : 'name';
         $orderBy = $request->get('order') === 'desc' ? 'desc' : 'asc';

+ 1 - 2
app/Http/Controllers/Admin/AdminDistrictController.php

@@ -25,8 +25,7 @@ class AdminDistrictController extends Controller
 
     public function index(Request $request): View
     {
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
         $allowedSortFields = ['id', 'shortname', 'name', 'areas_count'];
         $sortBy = in_array($request->get('sortBy'), $allowedSortFields, true) ? $request->get('sortBy') : 'id';
         $orderBy = $request->get('order') === 'desc' ? 'desc' : 'asc';

+ 1 - 2
app/Http/Controllers/ContractController.php

@@ -27,8 +27,7 @@ class ContractController extends Controller
     public function index(Request $request)
     {
         session(['gp_contracts' => $request->query()]);
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
 
         $model = new Contract();
         // fill filters

+ 8 - 0
app/Http/Controllers/Controller.php

@@ -28,6 +28,14 @@ class Controller extends BaseController
         return app(NavigationContextService::class)->getOrCreateToken($request);
     }
 
+    protected function startNavigationContext(Request $request): string
+    {
+        $token = app(NavigationContextService::class)->createToken();
+        $this->rememberNavigation($request, $token);
+
+        return $token;
+    }
+
     protected function rememberNavigation(Request $request, string $token): void
     {
         app(NavigationContextService::class)->rememberCurrentPage($request, $token);

+ 1 - 2
app/Http/Controllers/ImportController.php

@@ -35,8 +35,7 @@ class ImportController extends Controller
     public function index(Request $request)
     {
         session(['gp_import' => $request->all()]);
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
         $model = new Import;
         // fill filters
         $this->data['ranges'] = [];

+ 1 - 2
app/Http/Controllers/MafOrderController.php

@@ -39,8 +39,7 @@ class MafOrderController extends Controller
     {
         $model = new MafOrdersView;
         session(['gp_maf_order' => $request->all()]);
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
         $this->createDateFilters($model, 'created_at');
         $this->createFilters($model, 'user_name');
         $this->createRangeFilters($model, 'in_stock');

+ 2 - 4
app/Http/Controllers/OrderController.php

@@ -92,8 +92,7 @@ class OrderController extends Controller
     public function index(Request $request)
     {
         session(['gp_orders' => $request->all()]);
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
         $model = new OrderView;
         // fill filters
         $this->createFilters($model, 'user_name', 'district_name', 'area_name', 'object_type_name', 'brigadier_name', 'order_status_name', 'ready_to_mount');
@@ -137,8 +136,7 @@ class OrderController extends Controller
      */
     public function create(Request $request)
     {
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
         $this->data['nav'] = $nav;
         $this->data['back_url'] = $this->navigationBackUrl(
             $request,

+ 1 - 2
app/Http/Controllers/ProductController.php

@@ -60,8 +60,7 @@ class ProductController extends Controller
     public function index(Request $request)
     {
         session(['gp_products' => $request->query()]);
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
         $model = new Product;
         // fill filters
         $this->createFilters($model, 'type_tz', 'type', 'certificate_id');

+ 1 - 2
app/Http/Controllers/ProductSKUController.php

@@ -57,8 +57,7 @@ class ProductSKUController extends Controller
     public function index(Request $request)
     {
         session(['gp_product_sku' => $request->all()]);
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
         $model = new MafView;
 
         $fil = array_keys($this->data['header']);

+ 1 - 2
app/Http/Controllers/ReclamationController.php

@@ -68,8 +68,7 @@ class ReclamationController extends Controller
     public function index(Request $request)
     {
         session(['gp_reclamations' => $request->all()]);
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
         $model = new ReclamationView();
         // fill filters
         $this->createFilters($model, 'user_name', 'status_name');

+ 1 - 2
app/Http/Controllers/ResponsibleController.php

@@ -51,8 +51,7 @@ class ResponsibleController extends Controller
     public function index(Request $request)
     {
         session(['gp_responsibles' => $request->query()]);
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
 
         $model = new ResponsibleView;
         $this->createFilters($model, 'area_name');

+ 1 - 2
app/Http/Controllers/SparePartController.php

@@ -51,8 +51,7 @@ class SparePartController extends Controller
     public function index(Request $request)
     {
         session(['gp_spare_parts' => $request->query()]);
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
         $model = new SparePartsView();
 
         // Для админа добавляем колонку цены закупки

+ 1 - 2
app/Http/Controllers/SparePartOrderController.php

@@ -48,8 +48,7 @@ class SparePartOrderController extends Controller
     public function index(Request $request)
     {
         session(['gp_spare_part_orders' => $request->query()]);
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
         $model = new SparePartOrdersView();
 
         // Фильтры

+ 1 - 2
app/Http/Controllers/UserController.php

@@ -46,8 +46,7 @@ class UserController extends Controller
     public function index(Request $request)
     {
         session(['gp_users' => $request->query()]);
-        $nav = $this->resolveNavToken($request);
-        $this->rememberNavigation($request, $nav);
+        $nav = $this->startNavigationContext($request);
 
         $model = new User;
         $this->createFilters($model, 'role');

+ 70 - 8
app/Services/NavigationContextService.php

@@ -21,6 +21,19 @@ class NavigationContextService
             return $token;
         }
 
+        $token = $this->createToken();
+        $this->saveContext($token, [
+            'updated_at' => time(),
+            'stack' => [],
+        ]);
+
+        return $token;
+    }
+
+    public function createToken(): string
+    {
+        $this->pruneExpired();
+
         $token = bin2hex(random_bytes(8));
         $this->saveContext($token, [
             'updated_at' => time(),
@@ -45,7 +58,7 @@ class NavigationContextService
         $stack = $context['stack'] ?? [];
 
         if (empty($stack)) {
-            return $fallback;
+            return $this->appendNav($fallback, $token);
         }
 
         $currentUrl = $this->normalizeUrl($request->fullUrl());
@@ -53,10 +66,10 @@ class NavigationContextService
 
         if ($lastUrl === $currentUrl) {
             $previousUrl = prev($stack);
-            return $previousUrl !== false ? $previousUrl : $fallback;
+            return $this->appendNav($previousUrl !== false ? $previousUrl : $fallback, $token);
         }
 
-        return $lastUrl ?: $fallback;
+        return $this->appendNav($lastUrl ?: $fallback, $token);
     }
 
     public function parentUrl(string $token, ?string $fallback = null): ?string
@@ -65,15 +78,15 @@ class NavigationContextService
         $stack = $context['stack'] ?? [];
 
         if (empty($stack)) {
-            return $fallback;
+            return $this->appendNav($fallback, $token);
         }
 
         $lastUrl = array_pop($stack);
         if (empty($stack)) {
-            return $fallback ?? $lastUrl;
+            return $this->appendNav($fallback ?? $lastUrl, $token);
         }
 
-        return end($stack) ?: $fallback;
+        return $this->appendNav(end($stack) ?: $fallback, $token);
     }
 
     public function routeParams(array $params, string $token): array
@@ -121,8 +134,12 @@ class NavigationContextService
         $context = $this->context($token);
         $stack = $context['stack'] ?? [];
 
-        $stack = array_values(array_filter($stack, static fn (string $storedUrl) => $storedUrl !== $normalizedUrl));
-        $stack[] = $normalizedUrl;
+        $existingIndex = array_search($normalizedUrl, $stack, true);
+        if ($existingIndex !== false) {
+            $stack = array_slice($stack, 0, $existingIndex + 1);
+        } else {
+            $stack[] = $normalizedUrl;
+        }
 
         if (count($stack) > self::MAX_STACK_SIZE) {
             $stack = array_slice($stack, -self::MAX_STACK_SIZE);
@@ -177,6 +194,51 @@ class NavigationContextService
         return $normalizedUrl;
     }
 
+    private function appendNav(?string $url, string $token): ?string
+    {
+        if (empty($url)) {
+            return $url;
+        }
+
+        $parts = parse_url($url);
+        if ($parts === false || empty($parts['path'])) {
+            return $url;
+        }
+
+        $query = [];
+        if (!empty($parts['query'])) {
+            parse_str($parts['query'], $query);
+        }
+
+        $query['nav'] = $token;
+
+        $rebuiltUrl = '';
+        if (!empty($parts['scheme'])) {
+            $rebuiltUrl .= $parts['scheme'] . '://';
+        }
+
+        if (!empty($parts['user'])) {
+            $rebuiltUrl .= $parts['user'];
+            if (!empty($parts['pass'])) {
+                $rebuiltUrl .= ':' . $parts['pass'];
+            }
+            $rebuiltUrl .= '@';
+        }
+
+        if (!empty($parts['host'])) {
+            $rebuiltUrl .= $parts['host'];
+        }
+
+        if (!empty($parts['port'])) {
+            $rebuiltUrl .= ':' . $parts['port'];
+        }
+
+        $rebuiltUrl .= $parts['path'];
+        $rebuiltUrl .= '?' . http_build_query($query);
+
+        return $rebuiltUrl;
+    }
+
     private function isGetPage(Request $request): bool
     {
         return strtoupper($request->method()) === 'GET';

+ 1 - 0
resources/views/orders/show.blade.php

@@ -333,6 +333,7 @@
                                 <form action="{{ route('reclamations.create', $order) }}" method="post"
                                       class="visually-hidden" id="create-reclamation-form">
                                     @csrf
+                                    <input type="hidden" name="nav" value="{{ $nav ?? '' }}">
                                 </form>
                                 <br class="d-md-none">
                             @endif

+ 1 - 1
tests/Feature/AdminAreaControllerTest.php

@@ -126,7 +126,7 @@ class AdminAreaControllerTest extends TestCase
 
         $response->assertOk();
         $response->assertViewHas('nav', 'area-nav');
-        $response->assertViewHas('back_url', route('admin.area.index', ['page' => 2]));
+        $response->assertViewHas('back_url', route('admin.area.index', ['page' => 2, 'nav' => 'area-nav']));
     }
 
     public function test_manager_cannot_view_area_edit_form(): void

+ 1 - 1
tests/Feature/AdminDistrictControllerTest.php

@@ -112,7 +112,7 @@ class AdminDistrictControllerTest extends TestCase
 
         $response->assertOk();
         $response->assertViewHas('nav', 'district-nav');
-        $response->assertViewHas('back_url', route('admin.district.index', ['page' => 2]));
+        $response->assertViewHas('back_url', route('admin.district.index', ['page' => 2, 'nav' => 'district-nav']));
     }
 
     public function test_manager_cannot_view_district_edit_form(): void

+ 1 - 1
tests/Feature/ContractControllerTest.php

@@ -127,7 +127,7 @@ class ContractControllerTest extends TestCase
 
         $response->assertOk();
         $response->assertViewHas('nav', 'contract-nav');
-        $response->assertViewHas('back_url', route('contract.index', ['page' => 2]));
+        $response->assertViewHas('back_url', route('contract.index', ['page' => 2, 'nav' => 'contract-nav']));
     }
 
     // ==================== Store ====================

+ 24 - 2
tests/Feature/OrderControllerTest.php

@@ -215,7 +215,7 @@ class OrderControllerTest extends TestCase
 
         $indexResponse = $this->actingAs($this->managerUser)
             ->get(route('order.index', [
-                'filters' => ['object_address' => 'Тестовый адрес'],
+                'filters' => ['object_address' => 'Test address'],
             ]));
 
         $nav = $indexResponse->viewData('nav');
@@ -236,7 +236,29 @@ class OrderControllerTest extends TestCase
             $query = parse_url($backUrl, PHP_URL_QUERY);
             parse_str((string) $query, $params);
 
-            return ($params['filters']['object_address'] ?? null) === 'Тестовый адрес';
+            return ($params['filters']['object_address'] ?? null) === 'Test address';
+        });
+    }
+
+    public function test_order_index_starts_new_navigation_context(): void
+    {
+        $response = $this->actingAs($this->managerUser)
+            ->withSession([
+                'navigation' => [
+                    'existing-card-nav' => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('order.show', Order::factory()->create()),
+                            route('reclamations.index'),
+                        ],
+                    ],
+                ],
+            ])
+            ->get(route('order.index', ['nav' => 'existing-card-nav']));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', function (string $nav): bool {
+            return $nav !== 'existing-card-nav';
         });
     }
 

+ 2 - 0
tests/Feature/ProductControllerTest.php

@@ -169,6 +169,7 @@ class ProductControllerTest extends TestCase
         $response->assertViewHas('nav', $nav);
         $response->assertViewHas('back_url', route('reclamations.show', [
             'reclamation' => $reclamation,
+            'nav' => $nav,
         ]));
     }
 
@@ -264,6 +265,7 @@ class ProductControllerTest extends TestCase
 
         $response->assertRedirect(route('reclamations.show', [
             'reclamation' => $reclamation,
+            'nav' => 'catalog-nav-token',
         ]));
     }
 

+ 2 - 0
tests/Feature/ProductSKUControllerTest.php

@@ -237,6 +237,7 @@ class ProductSKUControllerTest extends TestCase
         $response->assertViewHas('nav', $nav);
         $response->assertViewHas('back_url', route('reclamations.show', [
             'reclamation' => $reclamation,
+            'nav' => $nav,
         ]));
     }
 
@@ -323,6 +324,7 @@ class ProductSKUControllerTest extends TestCase
 
         $response->assertRedirect(route('reclamations.show', [
             'reclamation' => $reclamation,
+            'nav' => 'product-sku-nav-token',
         ]));
     }
 

+ 131 - 0
tests/Feature/ReclamationControllerTest.php

@@ -121,6 +121,37 @@ class ReclamationControllerTest extends TestCase
         ]);
     }
 
+    public function test_creating_reclamation_from_order_preserves_nav_token(): void
+    {
+        $order = Order::factory()->create();
+        $product = Product::factory()->create();
+        $productSku = ProductSKU::factory()->create([
+            'order_id' => $order->id,
+            'product_id' => $product->id,
+        ]);
+
+        $response = $this->actingAs($this->managerUser)
+            ->withSession([
+                'navigation' => [
+                    'order-nav-token' => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('order.index'),
+                            route('order.show', $order),
+                        ],
+                    ],
+                ],
+            ])
+            ->post(route('reclamations.create', ['order' => $order, 'nav' => 'order-nav-token']), [
+                'skus' => [$productSku->id],
+                'nav' => 'order-nav-token',
+            ]);
+
+        $location = $response->headers->get('Location');
+        $this->assertNotNull($location);
+        $this->assertStringContainsString('nav=order-nav-token', $location);
+    }
+
     public function test_creating_reclamation_attaches_skus(): void
     {
         $order = Order::factory()->create();
@@ -187,6 +218,106 @@ class ReclamationControllerTest extends TestCase
         });
     }
 
+    public function test_reclamations_index_starts_new_navigation_context(): void
+    {
+        $response = $this->actingAs($this->managerUser)
+            ->withSession([
+                'navigation' => [
+                    'existing-card-nav' => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('order.show', Order::factory()->create()),
+                            route('reclamations.show', Reclamation::factory()->create()),
+                        ],
+                    ],
+                ],
+            ])
+            ->get(route('reclamations.index', ['nav' => 'existing-card-nav']));
+
+        $response->assertOk();
+        $response->assertViewHas('nav', function (string $nav): bool {
+            return $nav !== 'existing-card-nav';
+        });
+    }
+
+    public function test_reclamation_show_returns_to_order_after_coming_back_from_catalog(): void
+    {
+        $order = Order::factory()->create();
+        $reclamation = Reclamation::factory()->create([
+            'order_id' => $order->id,
+            'user_id' => $this->managerUser->id,
+        ]);
+
+        $response = $this->actingAs($this->managerUser)
+            ->withSession([
+                'navigation' => [
+                    'order-reclamation-nav' => [
+                        'updated_at' => time(),
+                        'stack' => [
+                            route('order.show', $order),
+                            route('reclamations.show', $reclamation),
+                            route('catalog.show', Product::factory()->create()),
+                        ],
+                    ],
+                ],
+            ])
+            ->get(route('reclamations.show', [
+                'reclamation' => $reclamation,
+                'nav' => 'order-reclamation-nav',
+            ]));
+
+        $response->assertOk();
+        $response->assertViewHas('back_url', route('order.show', [
+            'order' => $order,
+            'nav' => 'order-reclamation-nav',
+        ]));
+    }
+
+    public function test_existing_reclamation_opened_from_order_keeps_order_as_back_target_after_catalog(): void
+    {
+        $order = Order::factory()->create([
+            'object_address' => 'ул. Навигационная, д. 7',
+        ]);
+        $product = Product::factory()->create();
+        $productSku = ProductSKU::factory()->create([
+            'order_id' => $order->id,
+            'product_id' => $product->id,
+        ]);
+        $reclamation = Reclamation::factory()->create([
+            'order_id' => $order->id,
+            'user_id' => $this->managerUser->id,
+        ]);
+        $reclamation->skus()->attach($productSku->id);
+
+        $indexResponse = $this->actingAs($this->managerUser)
+            ->get(route('order.index'));
+        $nav = $indexResponse->viewData('nav');
+
+        $orderResponse = $this->actingAs($this->managerUser)
+            ->get(route('order.show', ['order' => $order, 'nav' => $nav]));
+        $orderResponse->assertOk();
+
+        $reclamationResponse = $this->actingAs($this->managerUser)
+            ->get(route('reclamations.show', ['reclamation' => $reclamation, 'nav' => $nav]));
+        $reclamationResponse->assertOk();
+        $reclamationResponse->assertViewHas('back_url', route('order.show', [
+            'order' => $order,
+            'nav' => $nav,
+        ]));
+
+        $catalogResponse = $this->actingAs($this->managerUser)
+            ->get(route('catalog.show', ['product' => $product, 'nav' => $nav]));
+        $catalogResponse->assertOk();
+
+        $reclamationBackResponse = $this->actingAs($this->managerUser)
+            ->get(route('reclamations.show', ['reclamation' => $reclamation, 'nav' => $nav]));
+        $reclamationBackResponse->assertOk();
+        $reclamationBackResponse->assertViewHas('back_url', route('order.show', [
+            'order' => $order,
+            'nav' => $nav,
+        ]));
+    }
+
     public function test_reclamation_details_show_spare_part_note_in_input_instead_of_used_in_maf(): void
     {
         $sparePart = \App\Models\SparePart::factory()->create([

+ 1 - 1
tests/Feature/ResponsibleControllerTest.php

@@ -107,6 +107,6 @@ class ResponsibleControllerTest extends TestCase
 
         $response->assertOk();
         $response->assertViewHas('nav', 'responsible-nav');
-        $response->assertViewHas('back_url', route('responsible.index', ['page' => 3]));
+        $response->assertViewHas('back_url', route('responsible.index', ['page' => 3, 'nav' => 'responsible-nav']));
     }
 }

+ 2 - 2
tests/Feature/SparePartControllerTest.php

@@ -158,7 +158,7 @@ class SparePartControllerTest extends TestCase
                 'customer_price' => 250.00,
             ]);
 
-        $response->assertRedirect(route('spare_parts.index'));
+        $response->assertRedirect(route('spare_parts.index', ['nav' => 'spare-part-nav-token']));
     }
 
     public function test_store_requires_article(): void
@@ -234,7 +234,7 @@ class SparePartControllerTest extends TestCase
                 'min_stock' => 9,
             ]);
 
-        $response->assertRedirect(route('spare_parts.index'));
+        $response->assertRedirect(route('spare_parts.index', ['nav' => 'spare-part-update-token']));
     }
 
     public function test_manager_cannot_update_spare_part(): void

+ 2 - 2
tests/Feature/SparePartOrderControllerTest.php

@@ -253,7 +253,7 @@ class SparePartOrderControllerTest extends TestCase
                 'source_text' => 'Test Supplier',
             ]);
 
-        $response->assertRedirect(route('spare_part_orders.index'));
+        $response->assertRedirect(route('spare_part_orders.index', ['nav' => 'spare-part-order-nav-token']));
     }
 
     public function test_manager_can_create_spare_part_order(): void
@@ -341,7 +341,7 @@ class SparePartOrderControllerTest extends TestCase
                 'ordered_quantity' => 21,
             ]);
 
-        $response->assertRedirect(route('spare_part_orders.index'));
+        $response->assertRedirect(route('spare_part_orders.index', ['nav' => 'spare-part-order-update-token']));
     }
 
     public function test_manager_can_update_spare_part_order(): void

+ 1 - 1
tests/Feature/UserControllerTest.php

@@ -147,7 +147,7 @@ class UserControllerTest extends TestCase
 
         $response->assertOk();
         $response->assertViewHas('nav', 'user-nav');
-        $response->assertViewHas('back_url', route('user.index', ['page' => 2]));
+        $response->assertViewHas('back_url', route('user.index', ['page' => 2, 'nav' => 'user-nav']));
     }
 
     // ==================== Store - create new user ====================

+ 43 - 12
tests/Unit/Services/NavigationContextServiceTest.php

@@ -23,29 +23,31 @@ class NavigationContextServiceTest extends TestCase
         $backUrl = $service->backUrl($showRequest, $token, 'https://crm.test/reclamations');
         $parentUrl = $service->parentUrl($token, 'https://crm.test/reclamations');
 
-        $this->assertSame('https://crm.test/reclamations?filters%5Bcomment%5D=abc', $backUrl);
-        $this->assertSame('https://crm.test/reclamations?filters%5Bcomment%5D=abc', $parentUrl);
+        $this->assertSame('https://crm.test/reclamations?filters%5Bcomment%5D=abc&nav=' . $token, $backUrl);
+        $this->assertSame('https://crm.test/reclamations?filters%5Bcomment%5D=abc&nav=' . $token, $parentUrl);
     }
 
-    public function test_remember_current_page_moves_duplicate_url_to_stack_end_without_duplication(): void
+    public function test_remember_current_page_truncates_stack_when_returning_to_previous_url(): void
     {
         session()->start();
 
         $service = app(NavigationContextService::class);
-        $indexRequest = Request::create('https://crm.test/catalog?page=2', 'GET');
-        $showRequest = Request::create('https://crm.test/catalog/15?nav=test-token', 'GET');
+        $orderRequest = Request::create('https://crm.test/order/show/10', 'GET');
+        $reclamationRequest = Request::create('https://crm.test/reclamations/show/20?nav=test-token', 'GET');
+        $catalogRequest = Request::create('https://crm.test/catalog/15?nav=test-token', 'GET');
 
-        $token = $service->getOrCreateToken($indexRequest);
-        $service->rememberCurrentPage($indexRequest, $token);
-        $service->rememberCurrentPage($showRequest, $token);
-        $service->rememberCurrentPage($indexRequest, $token);
+        $token = $service->getOrCreateToken($orderRequest);
+        $service->rememberCurrentPage($orderRequest, $token);
+        $service->rememberCurrentPage($reclamationRequest, $token);
+        $service->rememberCurrentPage($catalogRequest, $token);
+        $service->rememberCurrentPage($reclamationRequest, $token);
 
         $contexts = session('navigation');
         $stack = $contexts[$token]['stack'] ?? [];
 
         $this->assertSame([
-            'https://crm.test/catalog/15',
-            'https://crm.test/catalog?page=2',
+            'https://crm.test/order/show/10',
+            'https://crm.test/reclamations/show/20',
         ], $stack);
     }
 
@@ -59,6 +61,35 @@ class NavigationContextServiceTest extends TestCase
         $token = $service->getOrCreateToken($request);
         $service->rememberCurrentPage($request, $token);
 
-        $this->assertSame('https://crm.test/product_sku', $service->parentUrl($token, 'https://crm.test/product_sku'));
+        $this->assertSame('https://crm.test/product_sku?nav=' . $token, $service->parentUrl($token, 'https://crm.test/product_sku'));
+    }
+
+    public function test_back_url_preserves_nav_token_for_previous_card(): void
+    {
+        session()->start();
+
+        $service = app(NavigationContextService::class);
+        $catalogRequest = Request::create('https://crm.test/catalog/15?nav=test-token', 'GET');
+
+        $token = $service->getOrCreateToken(Request::create('https://crm.test/reclamations', 'GET', ['nav' => 'test-token']));
+        session([
+            'navigation.' . $token => [
+                'updated_at' => time(),
+                'stack' => [
+                    'https://crm.test/order/show/10',
+                    'https://crm.test/reclamations/show/20',
+                    'https://crm.test/catalog/15',
+                ],
+            ],
+        ]);
+
+        $this->assertSame(
+            'https://crm.test/reclamations/show/20?nav=' . $token,
+            $service->backUrl($catalogRequest, $token, 'https://crm.test/catalog')
+        );
+        $this->assertSame(
+            'https://crm.test/reclamations/show/20?nav=' . $token,
+            $service->parentUrl($token, 'https://crm.test/catalog')
+        );
     }
 }