test(Settings::class) ->assertSet('tab', 'installation') ->assertSee(__('admin_settings.update_title')); }); it('remembers the tab across a reload, because it is in the URL', function () { // The whole point of #[Url]. Without it a refresh — or the browser's back // button — lands on the first tab every time. Livewire::withQueryParams(['tab' => 'account']) ->actingAs(operator('Owner'), 'operator') ->test(Settings::class) ->assertSet('tab', 'account') ->assertSee(__('admin_settings.account_title')) ->assertDontSee(__('admin_settings.update_title')); }); it('falls back to the first tab when the URL names one that does not exist', function () { // A query string is a string a stranger typed. Anything unknown must not // render a settings page with no section on it at all. Livewire::withQueryParams(['tab' => 'wat']) ->actingAs(operator('Owner'), 'operator') ->test(Settings::class) ->assertSet('tab', 'installation') ->assertSee(__('admin_settings.update_title')); }); it('carries two-factor enrolment as a tab rather than a page of its own', function () { Livewire::withQueryParams(['tab' => 'two-factor']) ->actingAs(operator('Owner'), 'operator') ->test(Settings::class) ->assertSet('tab', 'two-factor') ->assertSee(__('two_factor_setup.title')); }); it('does not offer the team tab to somebody who cannot manage staff', function () { // A tab that raises a 403 is worse than one that is not there — and an // operator who reaches ?tab=team anyway lands on the first tab rather than // on a page with nothing on it. $support = operator('Support'); expect($support->can('staff.manage'))->toBeFalse(); Livewire::withQueryParams(['tab' => 'team']) ->actingAs($support, 'operator') ->test(Settings::class) ->assertSet('tab', 'installation') ->assertViewHas('tabs', fn (array $tabs) => ! in_array('team', $tabs, true)); }); it('lists every tab it can render, and renders every tab it lists', function () { // The const IS the schema: it validates the query string, builds the bar // and decides what renders. A tab in the list with no label is a button // that says "admin_settings.tab.x". foreach (Settings::TABS as $tab) { $key = 'admin_settings.tab.'.str_replace('-', '_', $tab); expect(__($key))->not->toBe($key); } }); it('moves the tunnel to System and drops the two-factor entry from the menu', function () { // VPN is not a task of the day's operations — it is how the console reaches // the estate at all, so it belongs beside the other things that have to // work first. Two-factor left the menu entirely: it is a tab now. $groups = collect(Navigation::console()); $routesIn = fn (string $label) => $groups ->firstWhere('label', __($label))['items'] ?? []; $system = collect($routesIn('admin.nav_group.system'))->pluck(0); $operations = collect($routesIn('admin.nav_group.operations'))->pluck(0); expect($system)->toContain('admin.vpn') ->and($operations)->not->toContain('admin.vpn') ->and($groups->pluck('items')->flatten(1)->pluck(0)) ->not->toContain('admin.two-factor-setup'); }); it('keeps the standalone enrolment route, because the gate sends people there', function () { // RequireOperatorTwoFactor redirects an operator who has not enrolled yet, // and that operator may not open anything else — the settings page // included. Folding the page away entirely would have locked them out. expect(route('admin.two-factor-setup'))->toBeString(); $this->actingAs(operator('Owner'), 'operator') ->get(route('admin.two-factor-setup')) ->assertOk() ->assertSee(__('two_factor_setup.title')); });