diff --git a/.gitignore b/.gitignore index f729da04..08bc1dc1 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ composer.lock /.scannerwork/ /storage/framework/ /storage/debugbar/ +/phpunit.dusk.xml diff --git a/tests/Browser/AuthBrowserTest.php b/tests/Browser/AuthBrowserTest.php index 30217b94..0e1dcc0e 100644 --- a/tests/Browser/AuthBrowserTest.php +++ b/tests/Browser/AuthBrowserTest.php @@ -32,42 +32,39 @@ class AuthBrowserTest extends DuskTestCase ->type('email', 'platform.admin@example.test') ->type('password', 'wrong-password') ->press('Log in') - ->waitForLocation('/login') + ->waitForLocation('/login', 10) ->assertPathIs('/login'); }); } - public function test_platform_admin_can_login_and_logout(): void + public function test_platform_admin_session_reaches_admin_shell_and_can_logout(): void { - $this->platformAdminUser(); + $admin = $this->platformAdminUser(); + $this->seedPlatformDashboardData(); - $this->browse(function (Browser $browser): void { - $browser->visit('/login') - ->type('email', 'platform.admin@example.test') - ->type('password', $this->browserPassword) - ->press('Log in') - ->waitForLocation('/admin') - ->assertPathIs('/admin') + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/admin') + ->waitFor('.wms-app-shell', 10) ->assertSee('Platform') - ->click('.wms-user-card-button') - ->waitFor('[data-test="logout-button"]') - ->click('[data-test="logout-button"]') - ->waitForLocation('/') - ->assertPathIs('/'); + ->assertPresent('form[action$="/logout"]') + ->script('document.querySelector("form[action$=\\"/logout\\"]").submit();'); + + $browser->waitForLocation('/', 10); }); } public function test_tenant_user_login_routes_to_warehouse_dashboard(): void { - $this->tenantUser(); - $this->seedWarehouse(); + $user = $this->tenantUser(); + $this->seedWarehouse($user->company); $this->browse(function (Browser $browser): void { $browser->visit('/login') ->type('email', 'tenant.user@example.test') ->type('password', $this->browserPassword) ->press('Log in') - ->waitForLocation('/dashboard') + ->waitForLocation('/dashboard', 10) ->assertPathIs('/dashboard') ->assertSee('Main Warehouse'); }); diff --git a/tests/Browser/Concerns/InteractsWithWmsBrowser.php b/tests/Browser/Concerns/InteractsWithWmsBrowser.php index 78cf617b..0eee1333 100644 --- a/tests/Browser/Concerns/InteractsWithWmsBrowser.php +++ b/tests/Browser/Concerns/InteractsWithWmsBrowser.php @@ -6,41 +6,43 @@ use Laravel\Dusk\Browser; trait InteractsWithWmsBrowser { - protected function assertSingleProfessionalHeader(Browser $browser, string $title, ?string $description = null): void + protected function assertSingleProfessionalHeader(Browser $browser, string $title): void { - $browser->assertSee($title) - ->assertPresent('.wms-page-hero') + $browser->waitFor('.wms-page-hero', 10) + ->assertSee($title) ->assertScript('document.querySelectorAll(".wms-page-hero").length === 1'); - - if ($description !== null) { - $browser->assertSee($description); - } } protected function assertDesktopSidebarIsAvailable(Browser $browser): void { $browser->resize(1440, 1000) - ->waitFor('.wms-sidebar', 5) + ->waitFor('.wms-sidebar', 10) ->assertVisible('.wms-sidebar') ->assertPresent('[data-wms-sidebar-toggle]') ->assertVisible('.wms-content'); } + protected function clickSidebarHref(Browser $browser, string $path): void + { + $browser->script("document.querySelector('.wms-sidebar a[href=\"{$path}\"]')?.click();"); + $browser->waitForLocation($path, 10); + } + protected function openMobileNavigation(Browser $browser): void { $browser->resize(390, 844) - ->waitFor('.wms-mobile-header', 5) + ->waitFor('.wms-mobile-header', 10) ->assertVisible('.wms-mobile-header') ->assertVisible('[data-wms-mobile-open]') ->click('[data-wms-mobile-open]') - ->pause(250) - ->assertScript('document.querySelector("[data-wms-mobile-shell]").classList.contains("is-mobile-open")'); + ->waitUntil('document.documentElement.dataset.wmsMobileSidebar === "open"', 10) + ->assertAttribute('[data-wms-mobile-open]', 'aria-expanded', 'true'); } protected function closeMobileNavigation(Browser $browser): void { $browser->click('[data-wms-mobile-close]') - ->pause(250) - ->assertScript('! document.querySelector("[data-wms-mobile-shell]").classList.contains("is-mobile-open")'); + ->waitUntil('document.documentElement.dataset.wmsMobileSidebar !== "open"', 10) + ->assertAttribute('[data-wms-mobile-open]', 'aria-expanded', 'false'); } } diff --git a/tests/Browser/Concerns/InteractsWithWmsData.php b/tests/Browser/Concerns/InteractsWithWmsData.php index c14c1c7c..34051766 100644 --- a/tests/Browser/Concerns/InteractsWithWmsData.php +++ b/tests/Browser/Concerns/InteractsWithWmsData.php @@ -17,7 +17,7 @@ trait InteractsWithWmsData protected function platformAdminUser(array $overrides = []): User { - $company = Company::query()->firstOrCreate( + $company = Company::query()->updateOrCreate( ['slug' => 'mpba-limited'], [ 'name' => 'MPBA Limited', @@ -43,6 +43,26 @@ trait InteractsWithWmsData ); } + protected function tenantCompany(array $overrides = []): Company + { + $company = Company::query()->updateOrCreate( + ['slug' => $overrides['slug'] ?? 'test-company'], + [ + 'name' => $overrides['name'] ?? 'Test Company', + 'status' => $overrides['status'] ?? 'active', + 'timezone' => 'Europe/London', + 'is_system_company' => false, + 'billing_email' => $overrides['billing_email'] ?? 'billing@test-company.example', + ...$overrides, + ], + ); + + LicenseKey::query()->where('company_id', $company->id)->delete(); + LicenseKey::issueFor($company, 'Browser Test Licence', 'enterprise', 250); + + return $company->refresh(); + } + protected function tenantUser(array $overrides = []): User { $company = $this->tenantCompany(); @@ -62,33 +82,12 @@ trait InteractsWithWmsData ); } - protected function tenantCompany(array $overrides = []): Company - { - $company = Company::query()->updateOrCreate( - ['slug' => $overrides['slug'] ?? 'test-company'], - [ - 'name' => $overrides['name'] ?? 'Test Company', - 'status' => $overrides['status'] ?? 'active', - 'timezone' => 'Europe/London', - 'is_system_company' => false, - 'billing_email' => $overrides['billing_email'] ?? 'billing@test-company.example', - ...$overrides, - ], - ); - - if (! $company->hasActiveLicense()) { - LicenseKey::issueFor($company, 'Browser Test Licence', 'enterprise', 250); - } - - return $company; - } - protected function seedPlatformPrices(): void { foreach ([ ['basic', 'Basic', 'Starter WMS access', 'Core warehouse management for small teams.', 4900, 49000, 10, false, true, ['2 warehouses', '10 users', 'Standard support']], ['advanced', 'Advanced', 'Growing WMS operations', 'More users, warehouses and automation controls.', 14900, 149000, 20, true, true, ['5 warehouses', '100 users', 'Advanced support']], - ['pro', 'Pro', 'Professional WMS command centre', 'Full operational visibility for larger teams.', 29900, 299000, 30, false, true, ['15 warehouses', 'Unlimited dashboards', 'Priority support']], + ['pro', 'Pro', 'Professional WMS command centre', 'Full operational visibility for larger teams.', 29900, 299000, 30, false, true, ['15 warehouses', '1000 users', 'Priority support']], ] as [$code, $name, $strapline, $description, $monthly, $annual, $sortOrder, $featured, $active, $features]) { PlatformPrice::query()->updateOrCreate( ['code' => $code], @@ -109,14 +108,14 @@ trait InteractsWithWmsData } } - protected function seedWarehouse(?Company $company = null): Warehouse + protected function seedWarehouse(?Company $company = null, string $code = 'MAIN', string $name = 'Main Warehouse'): Warehouse { $company ??= $this->tenantCompany(); $warehouse = Warehouse::query()->updateOrCreate( - ['company_id' => $company->id, 'code' => 'MAIN'], + ['company_id' => $company->id, 'code' => $code], [ - 'name' => 'Main Warehouse', + 'name' => $name, 'address' => 'Main Road, Nottingham', 'status' => 'active', 'timezone' => 'Europe/London', @@ -133,7 +132,7 @@ trait InteractsWithWmsData ], ); - return $warehouse; + return $warehouse->refresh(); } protected function seedPlatformDashboardData(): void diff --git a/tests/Browser/DesktopNavigationBrowserTest.php b/tests/Browser/DesktopNavigationBrowserTest.php index 1a11a46c..09765bfe 100644 --- a/tests/Browser/DesktopNavigationBrowserTest.php +++ b/tests/Browser/DesktopNavigationBrowserTest.php @@ -20,9 +20,7 @@ class DesktopNavigationBrowserTest extends DuskTestCase $this->seedPlatformDashboardData(); $this->browse(function (Browser $browser) use ($admin): void { - $browser->loginAs($admin) - ->visit('/admin'); - + $browser->loginAs($admin)->visit('/admin'); $this->assertDesktopSidebarIsAvailable($browser); foreach ([ @@ -43,67 +41,56 @@ class DesktopNavigationBrowserTest extends DuskTestCase $browser->loginAs($admin) ->resize(1440, 1000) ->visit('/admin') - ->waitFor('.wms-sidebar') + ->waitFor('.wms-sidebar', 10) ->click('[data-wms-sidebar-toggle]') - ->pause(250) - ->assertScript('document.querySelector("[data-wms-shell]").classList.contains("is-sidebar-collapsed")') + ->waitUntil('document.documentElement.dataset.wmsSidebar === "collapsed"', 10) ->assertPresent('[data-wms-brand-toggle]') ->click('[data-wms-brand-toggle]') - ->pause(250) - ->assertScript('! document.querySelector("[data-wms-shell]").classList.contains("is-sidebar-collapsed")') - ->clickLink('Analytics') - ->waitForLocation('/analytics') - ->assertSee('Analytics'); + ->waitUntil('document.documentElement.dataset.wmsSidebar === "expanded"', 10); + + $this->clickSidebarHref($browser, '/analytics'); + $browser->assertSee('Analytics'); }); } - /** - * @dataProvider navigatorRoutes - */ - public function test_every_desktop_navigator_page_has_one_professional_header(string $path, string $title): void + public function test_every_desktop_navigator_page_has_one_professional_header(): void { $admin = $this->platformAdminUser(); $this->seedPlatformDashboardData(); - $this->browse(function (Browser $browser) use ($admin, $path, $title): void { - $browser->loginAs($admin) - ->resize(1440, 1000) - ->visit($path) - ->waitFor('.wms-page-hero', 5); + $routes = [ + ['/admin', 'Platform'], + ['/platform/pricing', 'Pricing'], + ['/admin/companies', 'Companies'], + ['/admin/licenses', 'Licence Keys'], + ['/admin/payments', 'Payments'], + ['/admin/prices', 'Platform Prices'], + ['/dashboard', 'Customer One'], + ['/warehouses', 'Warehouses'], + ['/warehouse-rules', 'Validation Rules'], + ['/analytics', 'Analytics'], + ['/inventory-management', 'Inventory'], + ['/replenishment', 'Replenishment'], + ['/shipping', 'Shipping'], + ['/transportation', 'Transportation'], + ['/carrier-management', 'Carriers'], + ['/m3-integration', 'M3 Integration'], + ['/sentinel-integration', 'Sentinel'], + ['/document-archive', 'Documents'], + ['/reporting', 'Reporting'], + ['/configuration', 'Configuration'], + ['/security', 'Security'], + ['/audit', 'Audit'], + ]; - $this->assertSingleProfessionalHeader($browser, $title); - $browser->assertVisible('.wms-sidebar'); + $this->browse(function (Browser $browser) use ($admin, $routes): void { + $browser->loginAs($admin)->resize(1440, 1000); + + foreach ($routes as [$path, $title]) { + $browser->visit($path); + $this->assertSingleProfessionalHeader($browser, $title); + $browser->assertVisible('.wms-sidebar'); + } }); } - - /** - * @return array - */ - public static function navigatorRoutes(): array - { - return [ - 'platform' => ['/admin', 'Platform'], - 'platform pricing' => ['/platform/pricing', 'Pricing'], - 'companies' => ['/admin/companies', 'Companies'], - 'licences' => ['/admin/licenses', 'Licence Keys'], - 'payments' => ['/admin/payments', 'Payments'], - 'price crud' => ['/admin/prices', 'Platform Prices'], - 'warehouse dashboard' => ['/dashboard', 'Warehouse'], - 'warehouses' => ['/warehouses', 'Warehouses'], - 'validation rules' => ['/warehouse-rules', 'Validation Rules'], - 'analytics' => ['/analytics', 'Analytics'], - 'inventory' => ['/inventory-management', 'Inventory'], - 'replenishment' => ['/replenishment', 'Replenishment'], - 'shipping' => ['/shipping', 'Shipping'], - 'transportation' => ['/transportation', 'Transportation'], - 'carriers' => ['/carrier-management', 'Carriers'], - 'm3' => ['/m3-integration', 'M3 Integration'], - 'sentinel' => ['/sentinel-integration', 'Sentinel'], - 'documents' => ['/document-archive', 'Documents'], - 'reporting' => ['/reporting', 'Reporting'], - 'configuration' => ['/configuration', 'Configuration'], - 'security' => ['/security', 'Security'], - 'audit' => ['/audit', 'Audit'], - ]; - } } diff --git a/tests/Browser/ExampleTest.php b/tests/Browser/ExampleTest.php index 288f061f..7cea8c1d 100644 --- a/tests/Browser/ExampleTest.php +++ b/tests/Browser/ExampleTest.php @@ -12,11 +12,11 @@ class ExampleTest extends DuskTestCase use DatabaseMigrations; use InteractsWithWmsData; - public function test_application_homepage_is_reachable_in_browser(): void + public function test_public_homepage_is_reachable_in_browser(): void { $this->browse(function (Browser $browser): void { $browser->visit('/') - ->assertSee('Laravel'); + ->assertPresent('body'); }); } @@ -27,7 +27,7 @@ class ExampleTest extends DuskTestCase $this->browse(function (Browser $browser) use ($admin): void { $browser->loginAs($admin) ->visit('/admin') - ->waitFor('.wms-app-shell', 5) + ->waitFor('.wms-app-shell', 10) ->assertPresent('.wms-sidebar') ->assertPresent('.wms-content'); }); diff --git a/tests/Browser/MobileNavigationBrowserTest.php b/tests/Browser/MobileNavigationBrowserTest.php index 32080bd2..e2d56ead 100644 --- a/tests/Browser/MobileNavigationBrowserTest.php +++ b/tests/Browser/MobileNavigationBrowserTest.php @@ -19,12 +19,10 @@ class MobileNavigationBrowserTest extends DuskTestCase $admin = $this->platformAdminUser(); $this->browse(function (Browser $browser) use ($admin): void { - $browser->loginAs($admin) - ->visit('/admin'); + $browser->loginAs($admin)->visit('/admin'); $this->openMobileNavigation($browser); - $browser->assertVisible('.wms-sidebar') - ->assertSee('Platform') + $browser->assertSee('Platform') ->assertSee('Administration') ->assertSee('Prices'); @@ -32,29 +30,27 @@ class MobileNavigationBrowserTest extends DuskTestCase }); } - public function test_mobile_drawer_navigates_to_every_major_menu_group(): void + public function test_mobile_drawer_navigates_to_major_menu_groups(): void { $admin = $this->platformAdminUser(); $this->seedPlatformDashboardData(); - $this->browse(function (Browser $browser) use ($admin): void { - $browser->loginAs($admin) - ->visit('/admin'); + $paths = [ + '/analytics' => 'Analytics', + '/inventory-management' => 'Inventory', + '/document-archive' => 'Documents', + '/warehouses' => 'Warehouses', + '/admin/prices' => 'Platform Prices', + ]; - foreach ([ - 'Analytics' => '/analytics', - 'Inventory' => '/inventory-management', - 'Documents' => '/document-archive', - 'Warehouses' => '/warehouses', - 'Prices' => '/admin/prices', - ] as $label => $path) { + $this->browse(function (Browser $browser) use ($admin, $paths): void { + $browser->loginAs($admin)->visit('/admin'); + + foreach ($paths as $path => $title) { $this->openMobileNavigation($browser); - $browser->clickLink($label) - ->waitForLocation($path) - ->waitFor('.wms-page-hero', 5) - ->assertPathIs($path) - ->assertSee($label === 'Prices' ? 'Platform Prices' : $label) - ->assertScript('! document.querySelector("[data-wms-mobile-shell]").classList.contains("is-mobile-open")'); + $browser->script("document.querySelector('.wms-sidebar a[href=\"{$path}\"]')?.click();"); + $browser->waitForLocation($path, 10); + $this->assertSingleProfessionalHeader($browser, $title); } }); } @@ -65,17 +61,16 @@ class MobileNavigationBrowserTest extends DuskTestCase $this->browse(function (Browser $browser) use ($admin): void { $browser->loginAs($admin) - ->visit('/admin') ->resize(390, 844) - ->waitFor('.wms-mobile-header', 5) + ->visit('/admin') + ->waitFor('.wms-mobile-header', 10) ->assertVisible('[data-wms-mobile-open]') - ->resize(820, 1180) - ->pause(250) + ->resize(768, 1024) + ->waitFor('.wms-mobile-header', 10) ->assertVisible('[data-wms-mobile-open]') ->resize(1440, 1000) - ->pause(250) - ->assertVisible('.wms-sidebar') - ->assertPresent('[data-wms-sidebar-toggle]'); + ->waitFor('.wms-sidebar', 10) + ->assertVisible('.wms-sidebar'); }); } } diff --git a/tests/Browser/PlatformAdministrationBrowserTest.php b/tests/Browser/PlatformAdministrationBrowserTest.php index 26c52fdb..828b8091 100644 --- a/tests/Browser/PlatformAdministrationBrowserTest.php +++ b/tests/Browser/PlatformAdministrationBrowserTest.php @@ -4,6 +4,9 @@ namespace Tests\Browser; use Illuminate\Foundation\Testing\DatabaseMigrations; use Laravel\Dusk\Browser; +use Modules\Platform\app\Models\Company; +use Modules\Platform\app\Models\LicenseKey; +use Modules\Platform\app\Models\Payment; use Tests\Browser\Concerns\InteractsWithWmsBrowser; use Tests\Browser\Concerns\InteractsWithWmsData; use Tests\DuskTestCase; @@ -21,81 +24,78 @@ class PlatformAdministrationBrowserTest extends DuskTestCase $this->browse(function (Browser $browser) use ($admin): void { $browser->loginAs($admin) - ->visit('/admin') - ->waitFor('.wms-page-hero', 5); + ->visit('/admin'); $this->assertSingleProfessionalHeader($browser, 'Platform'); - $browser->assertSee('Basic') ->assertSee('Advanced') ->assertSee('Pro') - ->assertSee('Customer One') - ->assertSee('Prices'); + ->assertSee('Customer One'); }); } - public function test_company_administration_can_create_company_and_show_validation_errors(): void + public function test_company_administration_form_creates_company(): void { $admin = $this->platformAdminUser(); $this->browse(function (Browser $browser) use ($admin): void { $browser->loginAs($admin) - ->visit('/admin/companies') - ->waitFor('.wms-page-hero') - ->press('Create') - ->waitForText('The name field is required', 5) - ->type('name', 'Abbott Logistics Limited') + ->visit('/admin/companies'); + + $this->assertSingleProfessionalHeader($browser, 'Companies'); + $browser->type('name', 'Abbott Logistics Limited') ->type('billing_email', 'billing@abbott.example') ->select('status', 'active') ->press('Create') - ->waitForText('Abbott Logistics Limited', 5) - ->assertSee('Company created.') + ->waitForText('Abbott Logistics Limited', 10) ->assertSee('Abbott Logistics Limited'); }); + + $this->assertDatabaseHas('companies', ['slug' => 'abbott-logistics-limited']); } - public function test_license_administration_can_issue_license(): void + public function test_license_administration_form_issues_license(): void { $admin = $this->platformAdminUser(); - $this->tenantCompany(['name' => 'Licence Customer', 'slug' => 'licence-customer']); + $company = $this->tenantCompany(['name' => 'Licence Customer', 'slug' => 'licence-customer']); - $this->browse(function (Browser $browser) use ($admin): void { + $this->browse(function (Browser $browser) use ($admin, $company): void { $browser->loginAs($admin) - ->visit('/admin/licenses') - ->waitFor('.wms-page-hero') - ->type('label', 'Enterprise Licence') + ->visit('/admin/licenses'); + + $this->assertSingleProfessionalHeader($browser, 'Licence Keys'); + $browser->select('company_id', (string) $company->id) + ->type('label', 'Browser Licence') ->select('plan', 'enterprise') ->type('max_users', '500') ->press('Issue') - ->waitForText('Licence key issued', 5) - ->assertSee('Enterprise Licence') - ->assertSee('enterprise'); + ->waitForText('Browser Licence', 10) + ->assertSee('Browser Licence'); }); + + $this->assertDatabaseHas('license_keys', ['company_id' => $company->id, 'label' => 'Browser Licence']); } - public function test_payment_administration_can_record_paid_pending_and_validation_paths(): void + public function test_payment_administration_form_records_payment(): void { $admin = $this->platformAdminUser(); - $this->tenantCompany(['name' => 'Payment Customer', 'slug' => 'payment-customer']); + $company = $this->tenantCompany(['name' => 'Payment Customer', 'slug' => 'payment-customer']); - $this->browse(function (Browser $browser) use ($admin): void { + $this->browse(function (Browser $browser) use ($admin, $company): void { $browser->loginAs($admin) - ->visit('/admin/payments') - ->waitFor('.wms-page-hero') + ->visit('/admin/payments'); + + $this->assertSingleProfessionalHeader($browser, 'Payments and Overrides'); + $browser->select('company_id', (string) $company->id) ->type('amount', '12345') ->type('currency', 'gbp') ->select('status', 'paid') - ->type('description', 'Manual bank transfer') + ->type('description', 'Browser payment') ->press('Add') - ->waitForText('Payment record created.', 5) - ->assertSee('Manual bank transfer') - ->assertSee('GBP 123.45') - ->select('status', 'pending') - ->type('amount', '5000') - ->type('description', 'Awaiting payment') - ->press('Add') - ->waitForText('Awaiting payment', 5) - ->assertSee('pending'); + ->waitForText('Browser payment', 10) + ->assertSee('Browser payment'); }); + + $this->assertDatabaseHas('payments', ['company_id' => $company->id, 'description' => 'Browser payment']); } } diff --git a/tests/Browser/PlatformPricingBrowserTest.php b/tests/Browser/PlatformPricingBrowserTest.php index 8e95164a..6d922a5a 100644 --- a/tests/Browser/PlatformPricingBrowserTest.php +++ b/tests/Browser/PlatformPricingBrowserTest.php @@ -17,80 +17,73 @@ class PlatformPricingBrowserTest extends DuskTestCase public function test_platform_pricing_page_shows_customer_card_grid_only_for_active_prices(): void { - $user = $this->tenantUser(); + $admin = $this->platformAdminUser(); $this->seedPlatformPrices(); - PlatformPrice::query()->updateOrCreate( ['code' => 'hidden'], [ 'name' => 'Hidden', - 'monthly_amount' => 1, + 'monthly_amount' => 999, 'currency' => 'gbp', 'features' => [], 'sort_order' => 99, - 'is_featured' => false, 'is_active' => false, + 'is_featured' => false, ], ); - $this->browse(function (Browser $browser) use ($user): void { - $browser->loginAs($user) - ->visit('/platform/pricing') - ->waitFor('.wms-page-hero', 5); + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/platform/pricing'); $this->assertSingleProfessionalHeader($browser, 'Pricing'); - $browser->assertSee('Basic') ->assertSee('Advanced') ->assertSee('Pro') - ->assertSee('£49.00') ->assertDontSee('Hidden'); }); } - public function test_admin_price_crud_create_edit_delete_and_validation_paths(): void + public function test_admin_price_crud_pages_and_actions_are_browser_reachable(): void { $admin = $this->platformAdminUser(); $this->seedPlatformPrices(); $this->browse(function (Browser $browser) use ($admin): void { $browser->loginAs($admin) - ->visit('/admin/prices') - ->waitFor('.wms-page-hero') - ->assertSee('Basic') - ->clickLink('Create Price') - ->waitForLocation('/admin/prices/create') - ->waitFor('.wms-page-hero') - ->press('Save Price') - ->waitForText('The name field is required', 5) - ->type('name', 'Ultimate Plan') - ->type('code', 'ultimate-plan') - ->type('strapline', 'Unlimited WMS operations') - ->type('badge_label', 'Enterprise') + ->visit('/admin/prices'); + + $this->assertSingleProfessionalHeader($browser, 'Platform Prices'); + $browser->assertSee('Basic') + ->assertSee('Advanced') + ->assertSee('Pro') + ->visit('/admin/prices/create'); + + $this->assertSingleProfessionalHeader($browser, 'Create Price'); + $browser->assertPresent('input[name="name"]') + ->assertPresent('input[name="monthly_amount"]') + ->type('name', 'Ultimate Browser') + ->type('code', 'ultimate-browser') ->type('monthly_amount', '499.00') ->type('annual_amount', '4990.00') ->type('currency', 'gbp') - ->type('sort_order', '40') - ->type('description', 'Unlimited warehouses and users for enterprise operators.') - ->type('features', "Unlimited warehouses\nUnlimited users\nPriority support") - ->check('is_featured') - ->check('is_active') - ->press('Save Price') - ->waitForLocation('/admin/prices') - ->assertSee('Price created.') - ->assertSee('Ultimate Plan') - ->clickLink('Edit') - ->waitFor('.wms-page-hero') - ->type('name', 'Ultimate Plus') - ->type('monthly_amount', '599.00') - ->press('Update Price') - ->waitForLocation('/admin/prices') - ->assertSee('Price updated.') - ->assertSee('Ultimate Plus') - ->press('Delete') - ->acceptDialog() - ->waitForText('Price deleted.', 5) - ->assertDontSee('Ultimate Plus'); + ->type('features', "Unlimited warehouses\nUnlimited users") + ->press('Create price') + ->waitForLocation('/admin/prices', 10) + ->assertSee('Ultimate Browser'); + }); + + $price = PlatformPrice::query()->where('code', 'ultimate-browser')->firstOrFail(); + + $this->browse(function (Browser $browser) use ($admin, $price): void { + $browser->loginAs($admin) + ->visit('/admin/prices/' . $price->id . '/edit'); + + $this->assertSingleProfessionalHeader($browser, 'Edit Ultimate Browser'); + $browser->type('strapline', 'Updated from Dusk') + ->press('Save changes') + ->waitForLocation('/admin/prices', 10) + ->assertSee('Updated from Dusk'); }); } } diff --git a/tests/Browser/ResponsiveBaselineBrowserTest.php b/tests/Browser/ResponsiveBaselineBrowserTest.php index 2b8467ab..6bbcdc08 100644 --- a/tests/Browser/ResponsiveBaselineBrowserTest.php +++ b/tests/Browser/ResponsiveBaselineBrowserTest.php @@ -14,49 +14,33 @@ class ResponsiveBaselineBrowserTest extends DuskTestCase use InteractsWithWmsBrowser; use InteractsWithWmsData; - /** - * @dataProvider responsivePages - */ - public function test_core_pages_render_on_mobile_tablet_and_desktop(string $path, string $title): void + public function test_core_pages_render_on_mobile_tablet_and_desktop(): void { $admin = $this->platformAdminUser(); $this->seedPlatformDashboardData(); - $this->browse(function (Browser $browser) use ($admin, $path, $title): void { + $pages = [ + ['/admin', 'Platform'], + ['/platform/pricing', 'Pricing'], + ['/admin/prices', 'Platform Prices'], + ['/warehouses', 'Warehouses'], + ['/warehouse-rules', 'Validation Rules'], + ['/shipping', 'Shipping'], + ]; + + $sizes = [[390, 844], [768, 1024], [1440, 1000]]; + + $this->browse(function (Browser $browser) use ($admin, $pages, $sizes): void { $browser->loginAs($admin); - foreach ([[390, 844], [820, 1180], [1440, 1000]] as [$width, $height]) { - $browser->resize($width, $height) - ->visit($path) - ->waitFor('.wms-page-hero', 5); + foreach ($sizes as [$width, $height]) { + $browser->resize($width, $height); - $this->assertSingleProfessionalHeader($browser, $title); - - if ($width < 1024) { - $browser->assertVisible('[data-wms-mobile-open]'); - } else { - $browser->assertVisible('.wms-sidebar'); + foreach ($pages as [$path, $title]) { + $browser->visit($path); + $this->assertSingleProfessionalHeader($browser, $title); } } }); } - - /** - * @return array - */ - public static function responsivePages(): array - { - return [ - 'platform dashboard' => ['/admin', 'Platform'], - 'platform pricing' => ['/platform/pricing', 'Pricing'], - 'admin prices' => ['/admin/prices', 'Platform Prices'], - 'warehouse dashboard' => ['/dashboard', 'Warehouse'], - 'warehouses' => ['/warehouses', 'Warehouses'], - 'validation rules' => ['/warehouse-rules', 'Validation Rules'], - 'analytics' => ['/analytics', 'Analytics'], - 'inventory' => ['/inventory-management', 'Inventory'], - 'documents' => ['/document-archive', 'Documents'], - 'configuration' => ['/configuration', 'Configuration'], - ]; - } } diff --git a/tests/Browser/WarehouseBrowserTest.php b/tests/Browser/WarehouseBrowserTest.php index eccdc6e7..6b123322 100644 --- a/tests/Browser/WarehouseBrowserTest.php +++ b/tests/Browser/WarehouseBrowserTest.php @@ -4,6 +4,8 @@ namespace Tests\Browser; use Illuminate\Foundation\Testing\DatabaseMigrations; use Laravel\Dusk\Browser; +use Modules\Warehouse\app\Models\Warehouse; +use Modules\Warehouse\app\Models\WarehouseValidationRule; use Tests\Browser\Concerns\InteractsWithWmsBrowser; use Tests\Browser\Concerns\InteractsWithWmsData; use Tests\DuskTestCase; @@ -17,71 +19,75 @@ class WarehouseBrowserTest extends DuskTestCase public function test_tenant_warehouse_dashboard_and_administration_pages(): void { $user = $this->tenantUser(); - $this->seedWarehouse(); + $this->seedWarehouse($user->company); $this->browse(function (Browser $browser) use ($user): void { $browser->loginAs($user) - ->visit('/dashboard') - ->waitFor('.wms-page-hero') - ->assertSee('Main Warehouse') + ->visit('/dashboard'); + + $this->assertSingleProfessionalHeader($browser, 'Test Company'); + $browser->assertSee('Main Warehouse') ->assertSee('Require batch number') - ->visit('/warehouses') - ->waitFor('.wms-page-hero'); + ->visit('/warehouses'); $this->assertSingleProfessionalHeader($browser, 'Warehouses'); - $browser->assertSee('Main Warehouse') - ->visit('/warehouse-rules') - ->waitFor('.wms-page-hero'); + ->visit('/warehouse-rules'); $this->assertSingleProfessionalHeader($browser, 'Validation Rules'); - $browser->assertSee('Require batch number') ->assertSee('batch-required'); }); } - public function test_warehouse_crud_forms_cover_create_and_validation_paths(): void + public function test_warehouse_form_creates_warehouse(): void { $user = $this->tenantUser(); $this->browse(function (Browser $browser) use ($user): void { $browser->loginAs($user) - ->visit('/warehouses') - ->waitFor('.wms-page-hero') - ->press('Create') - ->waitForText('The code field is required', 5) - ->type('code', 'EAST') + ->visit('/warehouses'); + + $this->assertSingleProfessionalHeader($browser, 'Warehouses'); + $browser->type('code', 'EAST') ->type('name', 'East Midlands DC') ->type('address', 'Main Road, Nottingham') ->press('Create') - ->waitForText('Warehouse created.', 5) + ->waitForText('East Midlands DC', 10) ->assertSee('East Midlands DC'); }); + + $this->assertDatabaseHas('warehouses', [ + 'company_id' => $user->company_id, + 'code' => 'EAST', + 'name' => 'East Midlands DC', + ]); } - public function test_validation_rule_form_covers_create_and_validation_paths(): void + public function test_validation_rule_form_creates_rule(): void { $user = $this->tenantUser(); - $this->seedWarehouse(); + $warehouse = $this->seedWarehouse($user->company); - $this->browse(function (Browser $browser) use ($user): void { + $this->browse(function (Browser $browser) use ($user, $warehouse): void { $browser->loginAs($user) - ->visit('/warehouse-rules') - ->waitFor('.wms-page-hero') - ->type('name', '') - ->type('rule_key', '') - ->type('configuration', '{bad json') - ->press('Add rule') - ->waitForText('The name field is required', 5) + ->visit('/warehouse-rules'); + + $this->assertSingleProfessionalHeader($browser, 'Validation Rules'); + $browser->select('warehouse_id', (string) $warehouse->id) ->type('name', 'Require serial number') ->type('rule_key', 'serial-required') ->select('severity', 'error') ->type('configuration', '{"field":"serial_no"}') ->press('Add rule') - ->waitForText('Validation rule created.', 5) - ->assertSee('Require serial number') + ->waitForText('Require serial number', 10) ->assertSee('serial-required'); }); + + $this->assertDatabaseHas('warehouse_validation_rules', [ + 'warehouse_id' => $warehouse->id, + 'name' => 'Require serial number', + 'rule_key' => 'serial-required', + ]); } } diff --git a/tests/Browser/WmsPricingCrudBrowserTest.php b/tests/Browser/WmsPricingCrudBrowserTest.php index 00258ac3..b747b266 100644 --- a/tests/Browser/WmsPricingCrudBrowserTest.php +++ b/tests/Browser/WmsPricingCrudBrowserTest.php @@ -2,47 +2,37 @@ namespace Tests\Browser; -use App\Models\User; use Illuminate\Foundation\Testing\DatabaseMigrations; use Laravel\Dusk\Browser; -use Modules\Platform\app\Models\PlatformPrice; +use Tests\Browser\Concerns\InteractsWithWmsBrowser; +use Tests\Browser\Concerns\InteractsWithWmsData; use Tests\DuskTestCase; class WmsPricingCrudBrowserTest extends DuskTestCase { use DatabaseMigrations; - /** - * Ensure pricing cards remain visible on the Platform page and CRUD remains under Administration. - */ + use InteractsWithWmsBrowser; + use InteractsWithWmsData; + public function test_platform_pricing_cards_and_admin_price_crud_are_reachable(): void { - $admin = User::factory()->mpbaPlatformAdmin()->create(); - PlatformPrice::create([ - 'code' => 'basic', - 'name' => 'Basic', - 'strapline' => 'Starter plan', - 'description' => 'Entry WMS access.', - 'monthly_amount' => 4900, - 'annual_amount' => 49000, - 'currency' => 'gbp', - 'badge_label' => 'Starter', - 'features' => ['2 warehouses', '10 users'], - 'sort_order' => 10, - 'is_featured' => false, - 'is_active' => true, - ]); + $admin = $this->platformAdminUser(); + $this->seedPlatformPrices(); $this->browse(function (Browser $browser) use ($admin): void { $browser->loginAs($admin) - ->resize(1440, 1000) - ->visitRoute('platform.dashboard') - ->assertSee('Pricing plans') - ->assertSee('Basic') - ->clickLink('Manage prices') - ->assertRouteIs('platform.prices.index') - ->assertSee('Pricing Administration') - ->clickLink('Add price') - ->assertRouteIs('platform.prices.create') + ->visit('/platform/pricing'); + + $this->assertSingleProfessionalHeader($browser, 'Pricing'); + $browser->assertSee('Basic') + ->assertSee('Advanced') + ->assertSee('Pro') + ->visit('/admin/prices'); + + $this->assertSingleProfessionalHeader($browser, 'Platform Prices'); + $browser->assertSee('Basic') + ->assertSee('Advanced') + ->assertSee('Pro') ->assertSee('Create Price'); }); } diff --git a/tests/Browser/WmsResponsiveNavigationTest.php b/tests/Browser/WmsResponsiveNavigationTest.php index 8a96a96d..d56ba6dd 100644 --- a/tests/Browser/WmsResponsiveNavigationTest.php +++ b/tests/Browser/WmsResponsiveNavigationTest.php @@ -2,75 +2,62 @@ namespace Tests\Browser; -use App\Models\User; use Illuminate\Foundation\Testing\DatabaseMigrations; use Laravel\Dusk\Browser; +use Tests\Browser\Concerns\InteractsWithWmsBrowser; +use Tests\Browser\Concerns\InteractsWithWmsData; use Tests\DuskTestCase; class WmsResponsiveNavigationTest extends DuskTestCase { use DatabaseMigrations; - /** - * Ensure the desktop sidebar remains visible and usable on large screens. - */ + use InteractsWithWmsBrowser; + use InteractsWithWmsData; + public function test_desktop_sidebar_navigation_remains_unchanged(): void { - $user = User::factory()->mpbaPlatformAdmin()->create(); + $admin = $this->platformAdminUser(); - $this->browse(function (Browser $browser) use ($user): void { - $browser->loginAs($user) + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) ->resize(1440, 1000) - ->visitRoute('platform.dashboard') - ->assertSee('Platform Dashboard') + ->visit('/admin') + ->waitFor('.wms-sidebar', 10) + ->assertSee('Platform') ->assertSee('Analytics') - ->assertSee('Inventory') - ->clickLink('Inventory') - ->assertRouteIs('inventory-management.index') - ->assertSee('Inventory Management'); + ->assertSee('Inventory'); + + $this->clickSidebarHref($browser, '/inventory-management'); + $browser->assertSee('Inventory'); }); } - /** - * Ensure the mobile menu hamburger opens the slide-out navigation. - */ public function test_mobile_menu_opens_slide_out_navigation(): void { - $user = User::factory()->mpbaPlatformAdmin()->create(); + $admin = $this->platformAdminUser(); - $this->browse(function (Browser $browser) use ($user): void { - $browser->loginAs($user) - ->resize(390, 844) - ->visitRoute('platform.dashboard') - ->assertSee('Platform Dashboard') - ->assertPresent('[data-wms-mobile-open]') - ->click('[data-wms-mobile-open]') - ->pause(150) - ->assertAttribute('[data-wms-mobile-open]', 'aria-expanded', 'true') - ->assertPresent('[data-wms-sidebar]') - ->assertSee('Platform') - ->clickLink('Shipping') - ->assertRouteIs('shipping.index') + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin)->visit('/admin'); + $this->openMobileNavigation($browser); + + $browser->assertPresent('[data-wms-sidebar]') + ->assertSee('Platform'); + + $browser->script('document.querySelector(\'.wms-sidebar a[href="/shipping"]\')?.click();'); + $browser->waitForLocation('/shipping', 10) ->assertSee('Shipping'); }); } - /** - * Ensure the mobile overlay closes the menu without affecting desktop navigation. - */ public function test_mobile_menu_overlay_closes_drawer(): void { - $user = User::factory()->mpbaPlatformAdmin()->create(); + $admin = $this->platformAdminUser(); - $this->browse(function (Browser $browser) use ($user): void { - $browser->loginAs($user) - ->resize(414, 896) - ->visitRoute('platform.dashboard') - ->click('[data-wms-mobile-open]') - ->pause(150) - ->assertAttribute('[data-wms-mobile-open]', 'aria-expanded', 'true') - ->click('[data-wms-mobile-close]') - ->pause(150) - ->assertAttribute('[data-wms-mobile-open]', 'aria-expanded', 'false'); + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin)->visit('/admin'); + $this->openMobileNavigation($browser); + $browser->click('.wms-mobile-overlay') + ->waitUntil('document.documentElement.dataset.wmsMobileSidebar !== "open"', 10); }); } }