UPDATED TESTS
This commit is contained in:
+14
-1
@@ -12,11 +12,24 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
|
||||
$middleware->trustProxies(
|
||||
|
||||
at: '*',
|
||||
|
||||
headers: Request::HEADER_X_FORWARDED_FOR
|
||||
|
||||
| Request::HEADER_X_FORWARDED_HOST
|
||||
|
||||
| Request::HEADER_X_FORWARDED_PORT
|
||||
|
||||
| Request::HEADER_X_FORWARDED_PROTO
|
||||
|
||||
);
|
||||
$environment = $_ENV['APP_ENV']
|
||||
?? $_SERVER['APP_ENV']
|
||||
?? getenv('APP_ENV')
|
||||
?: null;
|
||||
|
||||
if ($environment === 'testing') {
|
||||
$middleware->validateCsrfTokens(except: ['*']);
|
||||
}
|
||||
|
||||
+5
-2
@@ -7,17 +7,20 @@
|
||||
"dev": "vite"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^7.1.0",
|
||||
"@laravel/passkeys": "^0.2.0",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-vite-plugin": "^3.1",
|
||||
"tailwindcss": "^4.0.7",
|
||||
"vite": "^8.0.0",
|
||||
"@fortawesome/fontawesome-free": "^7.1.0"
|
||||
"vite": "^8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-linux-x64-gnu": "4.9.5",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "^4.0.1",
|
||||
"lightningcss-linux-x64-gnu": "^1.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chokidar": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsData;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class AuthBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
use InteractsWithWmsData;
|
||||
|
||||
public function test_login_page_renders_professional_auth_form(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser): void {
|
||||
$browser->visit('/login')
|
||||
->assertSee('Log in')
|
||||
->assertPresent('input[name="email"]')
|
||||
->assertPresent('input[name="password"]')
|
||||
->assertPresent('[data-test="login-button"]');
|
||||
});
|
||||
}
|
||||
|
||||
public function test_invalid_login_stays_on_login_page(): void
|
||||
{
|
||||
$this->platformAdminUser();
|
||||
|
||||
$this->browse(function (Browser $browser): void {
|
||||
$browser->visit('/login')
|
||||
->type('email', 'platform.admin@example.test')
|
||||
->type('password', 'wrong-password')
|
||||
->press('Log in')
|
||||
->waitForLocation('/login')
|
||||
->assertPathIs('/login');
|
||||
});
|
||||
}
|
||||
|
||||
public function test_platform_admin_can_login_and_logout(): void
|
||||
{
|
||||
$this->platformAdminUser();
|
||||
|
||||
$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')
|
||||
->assertSee('Platform')
|
||||
->click('.wms-user-card-button')
|
||||
->waitFor('[data-test="logout-button"]')
|
||||
->click('[data-test="logout-button"]')
|
||||
->waitForLocation('/')
|
||||
->assertPathIs('/');
|
||||
});
|
||||
}
|
||||
|
||||
public function test_tenant_user_login_routes_to_warehouse_dashboard(): void
|
||||
{
|
||||
$this->tenantUser();
|
||||
$this->seedWarehouse();
|
||||
|
||||
$this->browse(function (Browser $browser): void {
|
||||
$browser->visit('/login')
|
||||
->type('email', 'tenant.user@example.test')
|
||||
->type('password', $this->browserPassword)
|
||||
->press('Log in')
|
||||
->waitForLocation('/dashboard')
|
||||
->assertPathIs('/dashboard')
|
||||
->assertSee('Main Warehouse');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser\Concerns;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
|
||||
trait InteractsWithWmsBrowser
|
||||
{
|
||||
protected function assertSingleProfessionalHeader(Browser $browser, string $title, ?string $description = null): void
|
||||
{
|
||||
$browser->assertSee($title)
|
||||
->assertPresent('.wms-page-hero')
|
||||
->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)
|
||||
->assertVisible('.wms-sidebar')
|
||||
->assertPresent('[data-wms-sidebar-toggle]')
|
||||
->assertVisible('.wms-content');
|
||||
}
|
||||
|
||||
protected function openMobileNavigation(Browser $browser): void
|
||||
{
|
||||
$browser->resize(390, 844)
|
||||
->waitFor('.wms-mobile-header', 5)
|
||||
->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")');
|
||||
}
|
||||
|
||||
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")');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser\Concerns;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Modules\Platform\app\Models\Company;
|
||||
use Modules\Platform\app\Models\LicenseKey;
|
||||
use Modules\Platform\app\Models\Payment;
|
||||
use Modules\Platform\app\Models\PlatformPrice;
|
||||
use Modules\Warehouse\app\Models\Warehouse;
|
||||
use Modules\Warehouse\app\Models\WarehouseValidationRule;
|
||||
|
||||
trait InteractsWithWmsData
|
||||
{
|
||||
protected string $browserPassword = 'Password1!';
|
||||
|
||||
protected function platformAdminUser(array $overrides = []): User
|
||||
{
|
||||
$company = Company::query()->firstOrCreate(
|
||||
['slug' => 'mpba-limited'],
|
||||
[
|
||||
'name' => 'MPBA Limited',
|
||||
'status' => 'active',
|
||||
'timezone' => 'Europe/London',
|
||||
'is_system_company' => true,
|
||||
'billing_email' => 'mark.gregory@mpba.co.uk',
|
||||
],
|
||||
);
|
||||
|
||||
return User::query()->updateOrCreate(
|
||||
['email' => $overrides['email'] ?? 'platform.admin@example.test'],
|
||||
[
|
||||
'company_id' => $company->id,
|
||||
'name' => $overrides['name'] ?? 'Platform Admin',
|
||||
'password' => Hash::make($this->browserPassword),
|
||||
'email_verified_at' => now(),
|
||||
'is_platform_admin' => true,
|
||||
'is_company_admin' => true,
|
||||
'status' => 'active',
|
||||
...$overrides,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
protected function tenantUser(array $overrides = []): User
|
||||
{
|
||||
$company = $this->tenantCompany();
|
||||
|
||||
return User::query()->updateOrCreate(
|
||||
['email' => $overrides['email'] ?? 'tenant.user@example.test'],
|
||||
[
|
||||
'company_id' => $company->id,
|
||||
'name' => $overrides['name'] ?? 'Tenant User',
|
||||
'password' => Hash::make($this->browserPassword),
|
||||
'email_verified_at' => now(),
|
||||
'is_platform_admin' => false,
|
||||
'is_company_admin' => true,
|
||||
'status' => 'active',
|
||||
...$overrides,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
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']],
|
||||
] as [$code, $name, $strapline, $description, $monthly, $annual, $sortOrder, $featured, $active, $features]) {
|
||||
PlatformPrice::query()->updateOrCreate(
|
||||
['code' => $code],
|
||||
[
|
||||
'name' => $name,
|
||||
'strapline' => $strapline,
|
||||
'description' => $description,
|
||||
'monthly_amount' => $monthly,
|
||||
'annual_amount' => $annual,
|
||||
'currency' => 'gbp',
|
||||
'badge_label' => $featured ? 'Popular' : null,
|
||||
'features' => $features,
|
||||
'sort_order' => $sortOrder,
|
||||
'is_featured' => $featured,
|
||||
'is_active' => $active,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function seedWarehouse(?Company $company = null): Warehouse
|
||||
{
|
||||
$company ??= $this->tenantCompany();
|
||||
|
||||
$warehouse = Warehouse::query()->updateOrCreate(
|
||||
['company_id' => $company->id, 'code' => 'MAIN'],
|
||||
[
|
||||
'name' => 'Main Warehouse',
|
||||
'address' => 'Main Road, Nottingham',
|
||||
'status' => 'active',
|
||||
'timezone' => 'Europe/London',
|
||||
],
|
||||
);
|
||||
|
||||
WarehouseValidationRule::query()->updateOrCreate(
|
||||
['warehouse_id' => $warehouse->id, 'rule_key' => 'batch-required'],
|
||||
[
|
||||
'name' => 'Require batch number',
|
||||
'severity' => 'blocker',
|
||||
'is_active' => true,
|
||||
'configuration' => ['field' => 'batch_no'],
|
||||
],
|
||||
);
|
||||
|
||||
return $warehouse;
|
||||
}
|
||||
|
||||
protected function seedPlatformDashboardData(): void
|
||||
{
|
||||
$company = $this->tenantCompany(['name' => 'Customer One', 'slug' => 'customer-one']);
|
||||
$this->seedWarehouse($company);
|
||||
$this->seedPlatformPrices();
|
||||
|
||||
Payment::query()->updateOrCreate(
|
||||
['stripe_payment_intent_id' => 'pi_browser_paid'],
|
||||
[
|
||||
'company_id' => $company->id,
|
||||
'amount' => 12345,
|
||||
'currency' => 'gbp',
|
||||
'status' => 'paid',
|
||||
'description' => 'Browser test payment',
|
||||
'paid_at' => now(),
|
||||
'metadata' => ['source' => 'dusk'],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsBrowser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsData;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class DesktopNavigationBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
use InteractsWithWmsBrowser;
|
||||
use InteractsWithWmsData;
|
||||
|
||||
public function test_desktop_sidebar_lists_every_navigator_option(): void
|
||||
{
|
||||
$admin = $this->platformAdminUser();
|
||||
$this->seedPlatformDashboardData();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin): void {
|
||||
$browser->loginAs($admin)
|
||||
->visit('/admin');
|
||||
|
||||
$this->assertDesktopSidebarIsAvailable($browser);
|
||||
|
||||
foreach ([
|
||||
'Platform', 'Analytics', 'Inventory', 'Replenishment', 'Shipping', 'Transportation', 'Carriers',
|
||||
'M3 Integration', 'Sentinel', 'Documents', 'Reporting', 'Warehouses', 'Validation Rules',
|
||||
'Pricing', 'Configuration', 'Security', 'Audit', 'Companies', 'Licences', 'Payments', 'Prices',
|
||||
] as $label) {
|
||||
$browser->assertSee($label);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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')
|
||||
->click('[data-wms-sidebar-toggle]')
|
||||
->pause(250)
|
||||
->assertScript('document.querySelector("[data-wms-shell]").classList.contains("is-sidebar-collapsed")')
|
||||
->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');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider navigatorRoutes
|
||||
*/
|
||||
public function test_every_desktop_navigator_page_has_one_professional_header(string $path, string $title): 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);
|
||||
|
||||
$this->assertSingleProfessionalHeader($browser, $title);
|
||||
$browser->assertVisible('.wms-sidebar');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{0: string, 1: string}>
|
||||
*/
|
||||
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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,32 @@ namespace Tests\Browser;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsData;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class ExampleTest extends DuskTestCase
|
||||
{
|
||||
/**
|
||||
* A basic browser test example.
|
||||
*/
|
||||
public function test_basic_example(): void
|
||||
use DatabaseMigrations;
|
||||
use InteractsWithWmsData;
|
||||
|
||||
public function test_application_homepage_is_reachable_in_browser(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$this->browse(function (Browser $browser): void {
|
||||
$browser->visit('/')
|
||||
->assertSee('Laravel');
|
||||
});
|
||||
}
|
||||
|
||||
public function test_authenticated_application_shell_is_reachable_in_browser(): void
|
||||
{
|
||||
$admin = $this->platformAdminUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin): void {
|
||||
$browser->loginAs($admin)
|
||||
->visit('/admin')
|
||||
->waitFor('.wms-app-shell', 5)
|
||||
->assertPresent('.wms-sidebar')
|
||||
->assertPresent('.wms-content');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsBrowser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsData;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class MobileNavigationBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
use InteractsWithWmsBrowser;
|
||||
use InteractsWithWmsData;
|
||||
|
||||
public function test_mobile_hamburger_opens_and_closes_professional_drawer(): void
|
||||
{
|
||||
$admin = $this->platformAdminUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin): void {
|
||||
$browser->loginAs($admin)
|
||||
->visit('/admin');
|
||||
|
||||
$this->openMobileNavigation($browser);
|
||||
$browser->assertVisible('.wms-sidebar')
|
||||
->assertSee('Platform')
|
||||
->assertSee('Administration')
|
||||
->assertSee('Prices');
|
||||
|
||||
$this->closeMobileNavigation($browser);
|
||||
});
|
||||
}
|
||||
|
||||
public function test_mobile_drawer_navigates_to_every_major_menu_group(): void
|
||||
{
|
||||
$admin = $this->platformAdminUser();
|
||||
$this->seedPlatformDashboardData();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin): void {
|
||||
$browser->loginAs($admin)
|
||||
->visit('/admin');
|
||||
|
||||
foreach ([
|
||||
'Analytics' => '/analytics',
|
||||
'Inventory' => '/inventory-management',
|
||||
'Documents' => '/document-archive',
|
||||
'Warehouses' => '/warehouses',
|
||||
'Prices' => '/admin/prices',
|
||||
] as $label => $path) {
|
||||
$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")');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function test_mobile_tablet_and_desktop_breakpoints_keep_navigation_accessible(): void
|
||||
{
|
||||
$admin = $this->platformAdminUser();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin): void {
|
||||
$browser->loginAs($admin)
|
||||
->visit('/admin')
|
||||
->resize(390, 844)
|
||||
->waitFor('.wms-mobile-header', 5)
|
||||
->assertVisible('[data-wms-mobile-open]')
|
||||
->resize(820, 1180)
|
||||
->pause(250)
|
||||
->assertVisible('[data-wms-mobile-open]')
|
||||
->resize(1440, 1000)
|
||||
->pause(250)
|
||||
->assertVisible('.wms-sidebar')
|
||||
->assertPresent('[data-wms-sidebar-toggle]');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsBrowser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsData;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class PlatformAdministrationBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
use InteractsWithWmsBrowser;
|
||||
use InteractsWithWmsData;
|
||||
|
||||
public function test_platform_dashboard_displays_counts_pricing_cards_and_recent_companies(): void
|
||||
{
|
||||
$admin = $this->platformAdminUser();
|
||||
$this->seedPlatformDashboardData();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin): void {
|
||||
$browser->loginAs($admin)
|
||||
->visit('/admin')
|
||||
->waitFor('.wms-page-hero', 5);
|
||||
|
||||
$this->assertSingleProfessionalHeader($browser, 'Platform');
|
||||
|
||||
$browser->assertSee('Basic')
|
||||
->assertSee('Advanced')
|
||||
->assertSee('Pro')
|
||||
->assertSee('Customer One')
|
||||
->assertSee('Prices');
|
||||
});
|
||||
}
|
||||
|
||||
public function test_company_administration_can_create_company_and_show_validation_errors(): 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')
|
||||
->type('billing_email', 'billing@abbott.example')
|
||||
->select('status', 'active')
|
||||
->press('Create')
|
||||
->waitForText('Abbott Logistics Limited', 5)
|
||||
->assertSee('Company created.')
|
||||
->assertSee('Abbott Logistics Limited');
|
||||
});
|
||||
}
|
||||
|
||||
public function test_license_administration_can_issue_license(): void
|
||||
{
|
||||
$admin = $this->platformAdminUser();
|
||||
$this->tenantCompany(['name' => 'Licence Customer', 'slug' => 'licence-customer']);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin): void {
|
||||
$browser->loginAs($admin)
|
||||
->visit('/admin/licenses')
|
||||
->waitFor('.wms-page-hero')
|
||||
->type('label', 'Enterprise Licence')
|
||||
->select('plan', 'enterprise')
|
||||
->type('max_users', '500')
|
||||
->press('Issue')
|
||||
->waitForText('Licence key issued', 5)
|
||||
->assertSee('Enterprise Licence')
|
||||
->assertSee('enterprise');
|
||||
});
|
||||
}
|
||||
|
||||
public function test_payment_administration_can_record_paid_pending_and_validation_paths(): void
|
||||
{
|
||||
$admin = $this->platformAdminUser();
|
||||
$this->tenantCompany(['name' => 'Payment Customer', 'slug' => 'payment-customer']);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin): void {
|
||||
$browser->loginAs($admin)
|
||||
->visit('/admin/payments')
|
||||
->waitFor('.wms-page-hero')
|
||||
->type('amount', '12345')
|
||||
->type('currency', 'gbp')
|
||||
->select('status', 'paid')
|
||||
->type('description', 'Manual bank transfer')
|
||||
->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');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
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 PlatformPricingBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
use InteractsWithWmsBrowser;
|
||||
use InteractsWithWmsData;
|
||||
|
||||
public function test_platform_pricing_page_shows_customer_card_grid_only_for_active_prices(): void
|
||||
{
|
||||
$user = $this->tenantUser();
|
||||
$this->seedPlatformPrices();
|
||||
|
||||
PlatformPrice::query()->updateOrCreate(
|
||||
['code' => 'hidden'],
|
||||
[
|
||||
'name' => 'Hidden',
|
||||
'monthly_amount' => 1,
|
||||
'currency' => 'gbp',
|
||||
'features' => [],
|
||||
'sort_order' => 99,
|
||||
'is_featured' => false,
|
||||
'is_active' => false,
|
||||
],
|
||||
);
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user): void {
|
||||
$browser->loginAs($user)
|
||||
->visit('/platform/pricing')
|
||||
->waitFor('.wms-page-hero', 5);
|
||||
|
||||
$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
|
||||
{
|
||||
$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')
|
||||
->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');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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
|
||||
```
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsBrowser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsData;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class ResponsiveBaselineBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
use InteractsWithWmsBrowser;
|
||||
use InteractsWithWmsData;
|
||||
|
||||
/**
|
||||
* @dataProvider responsivePages
|
||||
*/
|
||||
public function test_core_pages_render_on_mobile_tablet_and_desktop(string $path, string $title): void
|
||||
{
|
||||
$admin = $this->platformAdminUser();
|
||||
$this->seedPlatformDashboardData();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($admin, $path, $title): 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);
|
||||
|
||||
$this->assertSingleProfessionalHeader($browser, $title);
|
||||
|
||||
if ($width < 1024) {
|
||||
$browser->assertVisible('[data-wms-mobile-open]');
|
||||
} else {
|
||||
$browser->assertVisible('.wms-sidebar');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{0: string, 1: string}>
|
||||
*/
|
||||
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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsBrowser;
|
||||
use Tests\Browser\Concerns\InteractsWithWmsData;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class WarehouseBrowserTest extends DuskTestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
use InteractsWithWmsBrowser;
|
||||
use InteractsWithWmsData;
|
||||
|
||||
public function test_tenant_warehouse_dashboard_and_administration_pages(): void
|
||||
{
|
||||
$user = $this->tenantUser();
|
||||
$this->seedWarehouse();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user): void {
|
||||
$browser->loginAs($user)
|
||||
->visit('/dashboard')
|
||||
->waitFor('.wms-page-hero')
|
||||
->assertSee('Main Warehouse')
|
||||
->assertSee('Require batch number')
|
||||
->visit('/warehouses')
|
||||
->waitFor('.wms-page-hero');
|
||||
|
||||
$this->assertSingleProfessionalHeader($browser, 'Warehouses');
|
||||
|
||||
$browser->assertSee('Main Warehouse')
|
||||
->visit('/warehouse-rules')
|
||||
->waitFor('.wms-page-hero');
|
||||
|
||||
$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
|
||||
{
|
||||
$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')
|
||||
->type('name', 'East Midlands DC')
|
||||
->type('address', 'Main Road, Nottingham')
|
||||
->press('Create')
|
||||
->waitForText('Warehouse created.', 5)
|
||||
->assertSee('East Midlands DC');
|
||||
});
|
||||
}
|
||||
|
||||
public function test_validation_rule_form_covers_create_and_validation_paths(): void
|
||||
{
|
||||
$user = $this->tenantUser();
|
||||
$this->seedWarehouse();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($user): 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)
|
||||
->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')
|
||||
->assertSee('serial-required');
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user