diff --git a/Modules/Helpdesk/Resources/views/components/layouts/app.blade.php b/Modules/Helpdesk/Resources/views/components/layouts/app.blade.php
new file mode 100644
index 00000000..2e7ed892
--- /dev/null
+++ b/Modules/Helpdesk/Resources/views/components/layouts/app.blade.php
@@ -0,0 +1,1048 @@
+
+
+@include('partials.head')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $slot }}
+
+
+
+
+@fluxScripts
+
+
+
+
diff --git a/Modules/Helpdesk/Resources/views/layouts/app/sidebar.blade.php b/Modules/Helpdesk/Resources/views/layouts/app/sidebar.blade.php
new file mode 100644
index 00000000..026260c3
--- /dev/null
+++ b/Modules/Helpdesk/Resources/views/layouts/app/sidebar.blade.php
@@ -0,0 +1,1171 @@
+
+
+@include('partials.head')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $slot }}
+
+
+
+
+
+
+
+
+@fluxScripts
+
+
+
+
diff --git a/tests/Browser/DesktopNavigationBrowserTest.php b/tests/Browser/DesktopNavigationBrowserTest.php
index 95a9209b..001e3b1d 100644
--- a/tests/Browser/DesktopNavigationBrowserTest.php
+++ b/tests/Browser/DesktopNavigationBrowserTest.php
@@ -26,7 +26,7 @@ class DesktopNavigationBrowserTest extends DuskTestCase
'M3 Integration', 'Sentinel', 'Documents', 'Reporting', 'Warehouses', 'Validation Rules',
'Pricing', 'Configuration', 'Security', 'Audit', 'Companies', 'Licences', 'Payments', 'Prices',
] as $label) {
- $browser->assertSee($label);
+ $browser->assertSourceHas($label);
}
});
}
diff --git a/tests/Browser/MobileNavigationBrowserTest.php b/tests/Browser/MobileNavigationBrowserTest.php
index ffaf714f..0dff1ff0 100644
--- a/tests/Browser/MobileNavigationBrowserTest.php
+++ b/tests/Browser/MobileNavigationBrowserTest.php
@@ -21,7 +21,7 @@ class MobileNavigationBrowserTest extends DuskTestCase
$this->openMobileNavigation($browser);
$browser->assertSee('Platform')
- ->assertSee('Prices');
+ ->assertSourceHas('Prices');
$this->closeMobileNavigation($browser);
});
diff --git a/tests/Browser/README_DUSK_WMS.md b/tests/Browser/README_DUSK_WMS.md
deleted file mode 100644
index a976792b..00000000
--- a/tests/Browser/README_DUSK_WMS.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# WMS Dusk coverage pack
-
-This pack adds browser coverage for the current WMS UI baseline:
-
-- Authentication browser flows.
-- Desktop sidebar navigation and collapse / expand behaviour.
-- Mobile hamburger menu and slide-out drawer behaviour.
-- Single professional page hero/header on navigator pages.
-- Platform dashboard and pricing card display.
-- Platform pricing customer grid.
-- Administration pricing CRUD.
-- Company, licence and payment administration forms.
-- Warehouse dashboard, warehouse form and validation rule form.
-- Mobile, tablet and desktop responsive rendering checks.
-
-Run with:
-
-```bash
-php artisan dusk
-```
-
-Recommended local setup before running Dusk:
-
-```bash
-php artisan optimize:clear
-php artisan migrate
-php artisan dusk:chrome-driver --detect
-```
-
-If the app uses database sessions, make sure the sessions table exists:
-
-```bash
-php artisan session:table
-php artisan migrate
-```
diff --git a/tests/Browser/WmsResponsiveNavigationTest.php b/tests/Browser/WmsResponsiveNavigationTest.php
index c62793db..0ea161ba 100644
--- a/tests/Browser/WmsResponsiveNavigationTest.php
+++ b/tests/Browser/WmsResponsiveNavigationTest.php
@@ -22,11 +22,11 @@ class WmsResponsiveNavigationTest extends DuskTestCase
->visit('/admin')
->waitFor('.wms-sidebar', 10)
->assertSee('Platform')
- ->assertSee('Analytics')
- ->assertSee('Inventory');
+ ->assertSourceHas('Analytics')
+ ->assertSourceHas('Inventory');
$this->clickSidebarHref($browser, '/inventory-management');
- $browser->assertSee('Inventory');
+ $browser->assertSourceHas('Inventory');
});
}
diff --git a/tests/tests/Browser/Concerns/InteractsWithWmsNavigation.php b/tests/tests/Browser/Concerns/InteractsWithWmsNavigation.php
new file mode 100644
index 00000000..f9da0f4a
--- /dev/null
+++ b/tests/tests/Browser/Concerns/InteractsWithWmsNavigation.php
@@ -0,0 +1,157 @@
+ 'MPBA Limited',
+ ];
+
+ $companyValues = [];
+
+ foreach ([
+ 'slug' => 'mpba-limited',
+ 'timezone' => 'Europe/London',
+ 'billing_email' => 'platform-admin@mpba.co.uk',
+ 'status' => 'active',
+ 'is_system_company' => true,
+ ] as $column => $value) {
+ if (Schema::hasColumn('companies', $column)) {
+ $companyValues[$column] = $value;
+ }
+ }
+
+ $company = $companyClass::query()->updateOrCreate($companyAttributes, $companyValues);
+ $companyId = $company->getKey();
+ }
+
+ $userAttributes = array_merge([
+ 'name' => 'Mark Gregory',
+ 'email' => 'mark.gregory@mpba.co.uk',
+ 'password' => Hash::make('password'),
+ 'email_verified_at' => now(),
+ ], $attributes);
+
+ if ($companyId !== null && Schema::hasColumn('users', 'company_id')) {
+ $userAttributes['company_id'] = $companyId;
+ }
+
+ /** @var User $user */
+ $user = User::factory()->create($userAttributes);
+
+ foreach ([
+ 'is_platform_admin' => true,
+ 'is_mpba_platform_admin' => true,
+ 'platform_admin' => true,
+ ] as $column => $value) {
+ if (Schema::hasColumn('users', $column)) {
+ $user->forceFill([$column => $value]);
+ }
+ }
+
+ $user->save();
+
+ if (method_exists($user, 'assignRole')) {
+ try {
+ $user->assignRole('platform-admin');
+ } catch (\Throwable) {
+ // Some test databases do not seed Spatie roles for Dusk; the MPBA email/company is enough for this app helper.
+ }
+ }
+
+ return $user;
+ }
+
+ protected function resetNavigatorState(Browser $browser): Browser
+ {
+ $browser->script(<<<'JS'
+ localStorage.setItem('wms-sidebar-state', 'expanded');
+ localStorage.removeItem('wms-platform-menu-opened');
+ document.documentElement.dataset.wmsSidebar = 'expanded';
+ document.documentElement.dataset.wmsMobileSidebar = 'closed';
+ document.body.classList.remove('wms-mobile-nav-open');
+ JS);
+
+ return $browser->pause(150);
+ }
+
+ protected function openSidebarGroup(Browser $browser, string $group): Browser
+ {
+ $browser->script(<<pause(150);
+ }
+
+ protected function assertSidebarGroupContains(Browser $browser, string $group, array $labels): Browser
+ {
+ $this->openSidebarGroup($browser, $group);
+
+ foreach ($labels as $label) {
+ $browser->assertSee($label);
+ }
+
+ return $browser;
+ }
+
+ protected function openMobileNavigation(Browser $browser): Browser
+ {
+ $browser->script(<<<'JS'
+ const button = document.querySelector('[data-wms-mobile-open]');
+ if (button) {
+ button.click();
+ }
+ JS);
+
+ return $browser->pause(250);
+ }
+
+ protected function closeMobileNavigation(Browser $browser): Browser
+ {
+ $browser->script(<<<'JS'
+ const button = document.querySelector('[data-wms-mobile-close]');
+ if (button) {
+ button.click();
+ }
+ JS);
+
+ return $browser->pause(200);
+ }
+
+ protected function clickSidebarHref(Browser $browser, string $href): Browser
+ {
+ $browser->script(<<pause(300);
+ }
+}
diff --git a/tests/tests/Browser/DesktopNavigationBrowserTest.php b/tests/tests/Browser/DesktopNavigationBrowserTest.php
new file mode 100644
index 00000000..a30d58b0
--- /dev/null
+++ b/tests/tests/Browser/DesktopNavigationBrowserTest.php
@@ -0,0 +1,133 @@
+platformAdminUser();
+
+ $this->browse(function (Browser $browser) use ($admin): void {
+ $browser->loginAs($admin)
+ ->resize(1440, 1000)
+ ->visit('/admin')
+ ->waitFor('.wms-sidebar', 10);
+
+ $this->resetNavigatorState($browser);
+
+ $this->assertSidebarGroupContains($browser, 'platform', [
+ 'Platform',
+ ]);
+
+ $this->assertSidebarGroupContains($browser, 'operations', [
+ 'Analytics',
+ 'Inventory',
+ 'Replenishment',
+ 'Shipping',
+ 'Transportation',
+ 'Carriers',
+ ]);
+
+ $this->assertSidebarGroupContains($browser, 'integration-reporting', [
+ 'M3 Integration',
+ 'Sentinel',
+ 'Documents',
+ 'Reporting',
+ ]);
+
+ $this->assertSidebarGroupContains($browser, 'administration', [
+ 'Warehouses',
+ 'Validation Rules',
+ 'Prices',
+ 'Configuration',
+ 'Security',
+ 'Audit',
+ ]);
+
+ $this->assertSidebarGroupContains($browser, 'platform-administration', [
+ 'Companies',
+ 'Licences',
+ 'Payments',
+ ]);
+ });
+ }
+
+ public function test_desktop_sidebar_collapse_and_brand_expand_preserve_navigation(): void
+ {
+ $admin = $this->platformAdminUser();
+
+ $this->browse(function (Browser $browser) use ($admin): void {
+ $browser->loginAs($admin)
+ ->resize(1440, 1000)
+ ->visit('/admin')
+ ->waitFor('.wms-sidebar', 10);
+
+ $this->resetNavigatorState($browser);
+
+ $browser->script("document.querySelector('[data-wms-sidebar-toggle]')?.click();");
+ $browser->pause(200)
+ ->assertPresent('.wms-sidebar');
+
+ $browser->script("document.querySelector('[data-wms-brand-toggle]')?.click();");
+ $browser->pause(200);
+
+ $this->assertSidebarGroupContains($browser, 'operations', [
+ 'Analytics',
+ 'Inventory',
+ ]);
+ });
+ }
+
+ public function test_every_desktop_navigator_page_has_one_professional_header(): void
+ {
+ $admin = $this->platformAdminUser();
+
+ $paths = [
+ '/admin',
+ '/analytics',
+ '/inventory-management',
+ '/replenishment',
+ '/shipping',
+ '/transportation',
+ '/carrier-management',
+ '/m3-integration',
+ '/sentinel-integration',
+ '/document-archive',
+ '/reporting',
+ '/warehouses',
+ '/warehouse-rules',
+ '/platform/prices',
+ '/configuration',
+ '/security',
+ '/audit',
+ '/platform/companies',
+ '/platform/licenses',
+ '/platform/payments',
+ ];
+
+ $this->browse(function (Browser $browser) use ($admin, $paths): void {
+ $browser->loginAs($admin)->resize(1440, 1000);
+
+ foreach ($paths as $path) {
+ $browser->visit($path)
+ ->waitFor('.wms-page-hero', 10)
+ ->assertPresent('.wms-page-hero');
+
+ $heroCount = $browser->script("return document.querySelectorAll('.wms-page-hero').length;")[0] ?? 0;
+
+ $this->assertSame(1, (int) $heroCount, "Expected one professional page hero on [{$path}].");
+ }
+ });
+ }
+}
diff --git a/tests/tests/Browser/MobileNavigationBrowserTest.php b/tests/tests/Browser/MobileNavigationBrowserTest.php
new file mode 100644
index 00000000..2bc6ff8c
--- /dev/null
+++ b/tests/tests/Browser/MobileNavigationBrowserTest.php
@@ -0,0 +1,84 @@
+platformAdminUser();
+
+ $this->browse(function (Browser $browser) use ($admin): void {
+ $browser->loginAs($admin)
+ ->resize(390, 844)
+ ->visit('/admin')
+ ->waitFor('[data-wms-mobile-open]', 10);
+
+ $this->openMobileNavigation($browser);
+
+ $browser->assertSee('Platform');
+
+ $this->assertSidebarGroupContains($browser, 'administration', [
+ 'Prices',
+ ]);
+
+ $this->closeMobileNavigation($browser);
+
+ $browser->assertPresent('[data-wms-mobile-open]');
+ });
+ }
+
+ public function test_mobile_drawer_navigates_to_major_menu_groups(): void
+ {
+ $admin = $this->platformAdminUser();
+
+ $this->browse(function (Browser $browser) use ($admin): void {
+ $browser->loginAs($admin)
+ ->resize(390, 844)
+ ->visit('/admin')
+ ->waitFor('[data-wms-mobile-open]', 10);
+
+ $this->openMobileNavigation($browser);
+ $this->assertSidebarGroupContains($browser, 'operations', ['Inventory']);
+ $this->clickSidebarHref($browser, '/inventory-management');
+ $browser->assertSee('Inventory');
+
+ $this->openMobileNavigation($browser);
+ $this->assertSidebarGroupContains($browser, 'administration', ['Warehouses']);
+ $this->clickSidebarHref($browser, '/warehouses');
+ $browser->assertSee('Warehouses');
+ });
+ }
+
+ public function test_mobile_tablet_and_desktop_breakpoints_keep_navigation_accessible(): void
+ {
+ $admin = $this->platformAdminUser();
+
+ $this->browse(function (Browser $browser) use ($admin): void {
+ foreach ([[390, 844], [834, 1112], [1440, 1000]] as [$width, $height]) {
+ $browser->loginAs($admin)
+ ->resize($width, $height)
+ ->visit('/admin')
+ ->waitFor('.wms-app-shell', 10)
+ ->assertPresent('.wms-app-shell');
+
+ if ($width < 1024) {
+ $browser->assertPresent('[data-wms-mobile-open]');
+ continue;
+ }
+
+ $browser->assertPresent('.wms-sidebar');
+ }
+ });
+ }
+}
diff --git a/tests/tests/Browser/WmsResponsiveNavigationTest.php b/tests/tests/Browser/WmsResponsiveNavigationTest.php
new file mode 100644
index 00000000..b7dbeebe
--- /dev/null
+++ b/tests/tests/Browser/WmsResponsiveNavigationTest.php
@@ -0,0 +1,74 @@
+platformAdminUser();
+
+ $this->browse(function (Browser $browser) use ($admin): void {
+ $browser->loginAs($admin)
+ ->resize(1440, 1000)
+ ->visit('/admin')
+ ->waitFor('.wms-sidebar', 10)
+ ->assertSee('Platform');
+
+ $this->resetNavigatorState($browser);
+ $this->assertSidebarGroupContains($browser, 'operations', [
+ 'Analytics',
+ 'Inventory',
+ ]);
+
+ $this->clickSidebarHref($browser, '/inventory-management');
+ $browser->assertSee('Inventory');
+ });
+ }
+
+ public function test_mobile_menu_opens_slide_out_navigation(): void
+ {
+ $admin = $this->platformAdminUser();
+
+ $this->browse(function (Browser $browser) use ($admin): void {
+ $browser->loginAs($admin)
+ ->resize(390, 844)
+ ->visit('/admin')
+ ->waitFor('[data-wms-mobile-open]', 10);
+
+ $this->openMobileNavigation($browser);
+
+ $browser->assertSee('Platform');
+ $this->assertSidebarGroupContains($browser, 'operations', ['Inventory']);
+ });
+ }
+
+ public function test_mobile_menu_overlay_closes_drawer(): void
+ {
+ $admin = $this->platformAdminUser();
+
+ $this->browse(function (Browser $browser) use ($admin): void {
+ $browser->loginAs($admin)
+ ->resize(390, 844)
+ ->visit('/admin')
+ ->waitFor('[data-wms-mobile-open]', 10);
+
+ $this->openMobileNavigation($browser);
+ $browser->assertSee('Platform');
+
+ $browser->script("document.querySelector('[data-wms-mobile-close]')?.click();");
+ $browser->pause(200)
+ ->assertPresent('[data-wms-mobile-open]');
+ });
+ }
+}