From 3dec66aa858c2aa25c9943962b4069b9c261fbda Mon Sep 17 00:00:00 2001 From: kei Date: Thu, 11 Jun 2026 06:29:32 +0200 Subject: [PATCH 1/2] test(mail): cover link navigation click handling --- src/app/app.component.spec.ts | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/app/app.component.spec.ts diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts new file mode 100644 index 000000000..1ac039b39 --- /dev/null +++ b/src/app/app.component.spec.ts @@ -0,0 +1,72 @@ +// --------- BEGIN RUNBOX LICENSE --------- +// Copyright (C) 2016-2026 Runbox Solutions AS (runbox.com). +// +// This file is part of Runbox 7. +// +// Runbox 7 is free software: You can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// Runbox 7 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Runbox 7. If not, see . +// ---------- END RUNBOX LICENSE ---------- + +import { fakeAsync, tick } from '@angular/core/testing'; +import { MatSidenav } from '@angular/material/sidenav'; +import { AppComponent } from './app.component'; +import { MobileQueryService } from './mobile-query.service'; + +describe('AppComponent navigation links', () => { + function createComponent(matches = true): { component: AppComponent, close: jasmine.Spy } { + const component = Object.create(AppComponent.prototype) as AppComponent; + const close = jasmine.createSpy('close'); + + component.mobileQuery = { matches } as unknown as MobileQueryService; + component.sidemenu = { opened: true, close } as unknown as MatSidenav; + + return { component, close }; + } + + it('closes the mobile side menu after an app-handled navigation click', fakeAsync(() => { + const { component, close } = createComponent(); + + component.closeSideMenuAfterLocalNavigation(new MouseEvent('click')); + tick(); + + expect(close).toHaveBeenCalled(); + })); + + it('does not close the current tab side menu for browser-handled link clicks', fakeAsync(() => { + const browserHandledClicks = [ + new MouseEvent('click', { button: 1 }), + new MouseEvent('click', { ctrlKey: true }), + new MouseEvent('click', { metaKey: true }), + new MouseEvent('click', { shiftKey: true }), + new MouseEvent('click', { altKey: true }), + ]; + + for (const event of browserHandledClicks) { + const { component, close } = createComponent(); + + component.closeSideMenuAfterLocalNavigation(event); + tick(); + + expect(close).not.toHaveBeenCalled(); + } + })); + + it('leaves the side menu open on desktop navigation clicks', fakeAsync(() => { + const { component, close } = createComponent(false); + + component.closeSideMenuAfterLocalNavigation(new MouseEvent('click')); + tick(); + + expect(close).not.toHaveBeenCalled(); + })); +}); From 609ac6cc91ee29ba1080901296c563288dc26590 Mon Sep 17 00:00:00 2001 From: kei Date: Thu, 11 Jun 2026 06:29:33 +0200 Subject: [PATCH 2/2] fix(mail): make main navigation items linkable --- src/app/app.component.html | 16 ++++++++-------- src/app/app.component.ts | 33 ++++++++++++++++++++------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index ec21e8698..9c6ab63de 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -60,30 +60,30 @@ - + - + - + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 055e4c6ae..a78e1d46a 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -550,11 +550,24 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis public drafts() { this.router.navigate(['/compose']); - setTimeout(() => { - if (this.mobileQuery.matches && this.sidemenu.opened) { - this.sidemenu.close(); - } - }, 0); + this.closeSideMenuAfterLocalNavigation(); + } + + public closeSideMenuAfterLocalNavigation(event?: MouseEvent): void { + if (event && this.isBrowserHandledNavigation(event)) { + return; + } + setTimeout(() => this.closeSideMenuOnMobile(), 0); + } + + private isBrowserHandledNavigation(event: MouseEvent): boolean { + return event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey; + } + + private closeSideMenuOnMobile(): void { + if (this.mobileQuery.matches && this.sidemenu.opened) { + this.sidemenu.close(); + } } // folder-related stuff: perhaps move to its own service @@ -581,11 +594,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis public overview() { this.router.navigate(['/overview']); - setTimeout(() => { - if (this.mobileQuery.matches && this.sidemenu.opened) { - this.sidemenu.close(); - } - }, 0); + this.closeSideMenuAfterLocalNavigation(); } renameFolder(folder: RenameFolderEvent) { @@ -634,9 +643,7 @@ export class AppComponent implements OnInit, AfterViewInit, CanvasTableSelectLis } public compose() { - if (this.mobileQuery.matches && this.sidemenu.opened) { - this.sidemenu.close(); - } + this.closeSideMenuOnMobile(); this.router.navigate(['/compose'], {queryParams: {'new': true}}); }