diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4598b49384d62..0961fd75964f5 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -240,6 +240,7 @@ set(client_SRCS activity/activitylistmodel.cpp tray/asyncimageresponse.cpp tray/trayimageprovider.cpp + tray/trayaccountmenupolicy.h tray/trayaccountappsmodel.h tray/trayaccountappsmodel.cpp tray/usermodel.h diff --git a/src/gui/macOS/trayaccountpopup/ncaccountactionspopup.h b/src/gui/macOS/trayaccountpopup/ncaccountactionspopup.h index 9a517374662cd..313e1d4c28833 100644 --- a/src/gui/macOS/trayaccountpopup/ncaccountactionspopup.h +++ b/src/gui/macOS/trayaccountpopup/ncaccountactionspopup.h @@ -14,9 +14,10 @@ /** * @brief The submenu shown for a single account. * - * Lists the user status, "Reveal in Finder", the Assistant, Search and Apps - * shortcuts, pending notifications and recent activity. Owns the apps and - * notification-actions sub-popups. + * Lists only "Reveal in Finder" and, where supported, the sign-in action while + * disconnected. Connected accounts also show user status, Assistant, Search + * and Apps shortcuts, pending notifications and recent activity. Owns the apps + * and notification-actions sub-popups. */ @interface NCAccountActionsPopup : NSPanel /** @brief Rebuilds the popup for the given account and refreshes its activity preview. */ @@ -28,7 +29,7 @@ */ - (void)populateForUserIndex:(int)userIndex owner:(NCTrayPopup *)owner refreshActivities:(BOOL)refreshActivities; /** @brief Whether the popup is currently visible and showing the given account. */ -- (BOOL)isShowingActivitiesForUserIndex:(int)userIndex; +- (BOOL)isShowingUserIndex:(int)userIndex; /** @brief Removes the persistent highlight from the row whose submenu is currently open. */ - (void)clearActiveSubmenuRow; /** @brief Hides the apps and notification-actions sub-popups and clears the active submenu row. */ diff --git a/src/gui/macOS/trayaccountpopup/ncaccountactionspopup.mm b/src/gui/macOS/trayaccountpopup/ncaccountactionspopup.mm index 1c3be0e5b6279..39b9859e00d29 100644 --- a/src/gui/macOS/trayaccountpopup/ncaccountactionspopup.mm +++ b/src/gui/macOS/trayaccountpopup/ncaccountactionspopup.mm @@ -18,6 +18,7 @@ #include "systray.h" #include "tray/trayaccountappsmodel.h" +#include "tray/trayaccountmenupolicy.h" #include "tray/usermodel.h" #include @@ -89,7 +90,7 @@ @implementation NCAccountActionsPopup { NCNotificationActionsPopup *_notificationActionsPopup; NCActionRow *_activeSubmenuRow; //!< The row whose sub-popup is currently shown, kept persistently highlighted. __unsafe_unretained NCTrayPopup *_owner; - QMetaObject::Connection _recentActivitiesConnection; //!< Rebuilds the popup in place when the model reports new activity or notifications. + QMetaObject::Connection _accountMenuDataConnection; //!< Rebuilds the popup when visible menu data or connectivity changes. int _userIndex; } @@ -112,13 +113,13 @@ - (void)dealloc { [_appsPopup release]; [_notificationActionsPopup release]; - if (_recentActivitiesConnection) { - QObject::disconnect(_recentActivitiesConnection); + if (_accountMenuDataConnection) { + QObject::disconnect(_accountMenuDataConnection); } [super dealloc]; } -- (BOOL)isShowingActivitiesForUserIndex:(int)userIndex +- (BOOL)isShowingUserIndex:(int)userIndex { return [self isVisible] && _userIndex == userIndex; } @@ -128,9 +129,9 @@ - (void)orderOut:(id)sender [_appsPopup orderOut:nil]; [_notificationActionsPopup orderOut:nil]; [self clearActiveSubmenuRow]; - if (_recentActivitiesConnection) { - QObject::disconnect(_recentActivitiesConnection); - _recentActivitiesConnection = {}; + if (_accountMenuDataConnection) { + QObject::disconnect(_accountMenuDataConnection); + _accountMenuDataConnection = {}; } _userIndex = -1; [super orderOut:sender]; @@ -190,6 +191,31 @@ - (void)populateForUserIndex:(int)userIndex owner:(NCTrayPopup *)owner [self populateForUserIndex:userIndex owner:owner refreshActivities:YES]; } +/** + * @brief Resizes the popup to its current rows while optionally retaining its top edge. + * @param preserveTopEdge Whether a visible popup should grow downwards. + * @param topEdge The previous top edge to retain when @p preserveTopEdge is YES. + */ +- (void)resizeToFitPreservingTopEdge:(BOOL)preserveTopEdge topEdge:(CGFloat)topEdge +{ + [self.contentView layoutSubtreeIfNeeded]; + NSRect frame = self.frame; + frame.size.width = kAccountActionsPopupWidth; + frame.size.height = _stack.fittingSize.height; + if (preserveTopEdge) { + frame.origin.y = topEdge - frame.size.height; + } + auto screen = self.screen; + if (!screen) { + screen = NSScreen.mainScreen ?: NSScreen.screens.firstObject; + } + if (screen) { + frame.origin = clampedPopupOrigin(frame.origin, frame.size, screen.visibleFrame); + } + [self setFrame:frame display:NO]; + [self invalidateShadow]; +} + - (void)populateForUserIndex:(int)userIndex owner:(NCTrayPopup *)owner refreshActivities:(BOOL)refreshActivities { const auto preserveTopEdge = [self isVisible]; @@ -197,15 +223,14 @@ - (void)populateForUserIndex:(int)userIndex owner:(NCTrayPopup *)owner refreshAc _owner = owner; _userIndex = userIndex; - [_appsPopup orderOut:nil]; - [self clearActiveSubmenuRow]; + [self hideAppsPopup]; clearStack(_stack); auto model = OCC::UserModel::instance(); - if (_recentActivitiesConnection) { - QObject::disconnect(_recentActivitiesConnection); - _recentActivitiesConnection = {}; + if (_accountMenuDataConnection) { + QObject::disconnect(_accountMenuDataConnection); + _accountMenuDataConnection = {}; } if (!model || userIndex < 0 || userIndex >= model->rowCount()) { return; @@ -213,9 +238,9 @@ - (void)populateForUserIndex:(int)userIndex owner:(NCTrayPopup *)owner refreshAc __unsafe_unretained NCTrayPopup *weakOwner = owner; __unsafe_unretained NCAccountActionsPopup *weakSelf = self; - _recentActivitiesConnection = QObject::connect(model, &QAbstractItemModel::dataChanged, model, + _accountMenuDataConnection = QObject::connect(model, &QAbstractItemModel::dataChanged, model, [weakSelf, weakOwner, userIndex](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles) { - if (!weakSelf || ![weakSelf isShowingActivitiesForUserIndex:userIndex]) { + if (!weakSelf || ![weakSelf isShowingUserIndex:userIndex]) { return; } if (userIndex < topLeft.row() || userIndex > bottomRight.row()) { @@ -223,7 +248,8 @@ - (void)populateForUserIndex:(int)userIndex owner:(NCTrayPopup *)owner refreshAc } if (!roles.isEmpty() && !roles.contains(OCC::UserModel::RecentActivitiesRole) - && !roles.contains(OCC::UserModel::TrayNotificationsRole)) { + && !roles.contains(OCC::UserModel::TrayNotificationsRole) + && !roles.contains(OCC::UserModel::IsConnectedRole)) { return; } @@ -231,15 +257,51 @@ - (void)populateForUserIndex:(int)userIndex owner:(NCTrayPopup *)owner refreshAc }); const auto userModelIndex = model->index(userIndex); + const auto policy = OCC::TrayAccountMenuPolicy{ + model->data(userModelIndex, OCC::UserModel::IsConnectedRole).toBool(), + model->data(userModelIndex, OCC::UserModel::CanLogoutRole).toBool(), + }; + if (!policy.showConnectedSections()) { + addOwnedArrangedSubview(_stack, [[NCSpacerView alloc] initWithHeight:kActionVerticalPadding width:kAccountActionsPopupWidth]); + for (const auto entry : policy.disconnectedEntries()) { + switch (entry) { + case OCC::TrayAccountMenuPolicy::Entry::LocalFolder: + addOwnedArrangedSubview(_stack, [[NCActionRow alloc] initWithTitle:QCoreApplication::translate("TrayFoldersMenuButton", "Reveal in Finder").toNSString() + width:kAccountActionsPopupWidth + enabled:YES + action:^{ + [weakOwner openLocalFolderForIndex:userIndex]; + } hoverAction:^(NSView *) { + [weakSelf hideAppsPopup]; + }]); + break; + case OCC::TrayAccountMenuPolicy::Entry::Separator: + [_stack addArrangedSubview:accountActionsSeparator()]; + break; + case OCC::TrayAccountMenuPolicy::Entry::Reconnect: + addOwnedArrangedSubview(_stack, [[NCActionRow alloc] initWithTitle:QCoreApplication::translate("OCC::AccountSettings", "Log in").toNSString() + width:kAccountActionsPopupWidth + enabled:YES + action:^{ + [weakOwner reconnectForIndex:userIndex]; + } hoverAction:^(NSView *) { + [weakSelf hideAppsPopup]; + }]); + break; + } + } + addOwnedArrangedSubview(_stack, [[NCSpacerView alloc] initWithHeight:kActionVerticalPadding width:kAccountActionsPopupWidth]); + [self resizeToFitPreservingTopEdge:preserveTopEdge topEdge:topEdge]; + return; + } + const auto serverHasUserStatus = model->data(userModelIndex, OCC::UserModel::ServerHasUserStatusRole).toBool(); - const auto onlineStatusEnabled = model->data(userModelIndex, OCC::UserModel::IsConnectedRole).toBool() - && serverHasUserStatus; + const auto onlineStatusEnabled = policy.showConnectedSections() && serverHasUserStatus; auto appsModel = OCC::TrayAccountAppsModel::instance(); appsModel->setUserId(userIndex); const auto appsEnabled = appsModel->rowCount() > 0; const auto assistantEnabled = model->data(userModelIndex, OCC::UserModel::AssistantEnabledRole).toBool(); - const auto searchEnabled = model->data(userModelIndex, OCC::UserModel::IsConnectedRole).toBool(); addOwnedArrangedSubview(_stack, [[NCSpacerView alloc] initWithHeight:kActionVerticalPadding width:kAccountActionsPopupWidth]); if (serverHasUserStatus) { const auto status = model->data(userModelIndex, OCC::UserModel::StatusRole).value(); @@ -278,7 +340,7 @@ - (void)populateForUserIndex:(int)userIndex owner:(NCTrayPopup *)owner refreshAc } addOwnedArrangedSubview(_stack, [[NCActionRow alloc] initWithTitle:QCoreApplication::translate("TrayAccountPopup", "Search").toNSString() width:kAccountActionsPopupWidth - enabled:searchEnabled + enabled:YES action:^{ [weakOwner openSearchForIndex:userIndex]; } hoverAction:^(NSView *) { @@ -370,24 +432,9 @@ - (void)populateForUserIndex:(int)userIndex owner:(NCTrayPopup *)owner refreshAc addOwnedArrangedSubview(_stack, [[NCSpacerView alloc] initWithHeight:kActionVerticalPadding width:kAccountActionsPopupWidth]); - [self.contentView layoutSubtreeIfNeeded]; - NSRect frame = self.frame; - frame.size.width = kAccountActionsPopupWidth; - frame.size.height = _stack.fittingSize.height; - if (preserveTopEdge) { - frame.origin.y = topEdge - frame.size.height; - } - auto screen = self.screen; - if (!screen) { - screen = NSScreen.mainScreen ?: NSScreen.screens.firstObject; - } - if (screen) { - frame.origin = clampedPopupOrigin(frame.origin, frame.size, screen.visibleFrame); - } - [self setFrame:frame display:NO]; - [self invalidateShadow]; + [self resizeToFitPreservingTopEdge:preserveTopEdge topEdge:topEdge]; - if (refreshActivities) { + if (refreshActivities && policy.fetchActivityPreview()) { model->fetchActivityPreview(userIndex); } } diff --git a/src/gui/macOS/trayaccountpopup/nctraypopup.h b/src/gui/macOS/trayaccountpopup/nctraypopup.h index cb538e66e387d..d93a7fc13ac7e 100644 --- a/src/gui/macOS/trayaccountpopup/nctraypopup.h +++ b/src/gui/macOS/trayaccountpopup/nctraypopup.h @@ -29,6 +29,8 @@ - (void)openActivitiesForIndex:(int)index; /** @brief Closes the popups and reveals the given account's local folder (or file provider domain) in Finder. */ - (void)openLocalFolderForIndex:(int)index; +/** @brief Closes the popups and starts the sign-in flow for the given account. */ +- (void)reconnectForIndex:(int)index; /** @brief Closes the popups and opens the Assistant window for the given account. */ - (void)openAssistantForIndex:(int)index; /** @brief Closes the popups and opens the Search window for the given account. */ diff --git a/src/gui/macOS/trayaccountpopup/nctraypopup.mm b/src/gui/macOS/trayaccountpopup/nctraypopup.mm index 10d1da9d41cd0..670e25359b764 100644 --- a/src/gui/macOS/trayaccountpopup/nctraypopup.mm +++ b/src/gui/macOS/trayaccountpopup/nctraypopup.mm @@ -296,6 +296,15 @@ - (void)openLocalFolderForIndex:(int)index #endif } +- (void)reconnectForIndex:(int)index +{ + [self closeAllPopups]; + + if (auto userModel = OCC::UserModel::instance()) { + userModel->login(index); + } +} + - (void)openAssistantForIndex:(int)index { [_accountActionsPopup orderOut:nil]; diff --git a/src/gui/tray/trayaccountmenupolicy.h b/src/gui/tray/trayaccountmenupolicy.h new file mode 100644 index 0000000000000..042952493e38b --- /dev/null +++ b/src/gui/tray/trayaccountmenupolicy.h @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include + +namespace OCC { + +/** + * @brief Defines which account-menu content is relevant for the connection state. + */ +class TrayAccountMenuPolicy +{ +public: + /** @brief Semantic entries used by the disconnected account menu. */ + enum class Entry { + LocalFolder, + Separator, + Reconnect, + }; + + /** @brief Account operation selected for the reconnect action. */ + enum class ReconnectMode { + None, + SignIn, + RetryConnection, + }; + + /** + * @brief Creates the menu policy for an account. + * @param canReconnect Whether the account supports signing in, which excludes public shares. + */ + explicit constexpr TrayAccountMenuPolicy(const bool isConnected, const bool canReconnect) + : _isConnected(isConnected) + , _canReconnect(canReconnect) + { + } + + /** @brief Whether server-backed account sections should be shown. */ + [[nodiscard]] constexpr bool showConnectedSections() const + { + return _isConnected; + } + + /** @brief Ordered entries to show instead of server-backed sections. */ + [[nodiscard]] constexpr std::span disconnectedEntries() const + { + if (_isConnected) { + return {}; + } + if (_canReconnect) { + return disconnectedReconnectEntries; + } + return disconnectedEntriesWithoutReconnect; + } + + /** @brief Whether opening the menu should request fresh server-backed previews. */ + [[nodiscard]] constexpr bool fetchActivityPreview() const + { + return _isConnected; + } + + /** @brief Selects the account operation behind the reconnect menu action. */ + [[nodiscard]] static constexpr ReconnectMode reconnectMode( + const bool isConnected, + const bool isSignedOut, + const bool canReconnect) + { + if (isConnected || !canReconnect) { + return ReconnectMode::None; + } + return isSignedOut + ? ReconnectMode::SignIn + : ReconnectMode::RetryConnection; + } + +private: + static constexpr std::array disconnectedReconnectEntries{ + Entry::LocalFolder, + Entry::Separator, + Entry::Reconnect, + }; + static constexpr std::array disconnectedEntriesWithoutReconnect{ + Entry::LocalFolder, + }; + + bool _isConnected; + bool _canReconnect; +}; + +} diff --git a/src/gui/tray/usermodel.cpp b/src/gui/tray/usermodel.cpp index 0ccccb571bc85..b6136e9557a11 100644 --- a/src/gui/tray/usermodel.cpp +++ b/src/gui/tray/usermodel.cpp @@ -4,6 +4,7 @@ */ #include "activity/notificationhandler.h" +#include "trayaccountmenupolicy.h" #include "usermodel.h" #include "common/filesystembase.h" @@ -1805,8 +1806,21 @@ void User::openFolderLocallyOrInBrowser(const QString &fullRemotePath) void User::login() const { - _account->account()->resetRejectedCertificates(); - _account->signIn(); + switch (TrayAccountMenuPolicy::reconnectMode( + _account->isConnected(), + _account->isSignedOut(), + !isPublicShareLink())) { + case TrayAccountMenuPolicy::ReconnectMode::None: + return; + case TrayAccountMenuPolicy::ReconnectMode::SignIn: + _account->account()->resetRejectedCertificates(); + _account->signIn(); + break; + case TrayAccountMenuPolicy::ReconnectMode::RetryConnection: + _account->account()->resetRejectedCertificates(); + _account->freshConnectionAttempt(); + break; + } } void User::logout() const diff --git a/src/gui/tray/usermodel.h b/src/gui/tray/usermodel.h index dbe29ea0f4ee6..a49ccde101da8 100644 --- a/src/gui/tray/usermodel.h +++ b/src/gui/tray/usermodel.h @@ -132,6 +132,7 @@ class User : public QObject [[nodiscard]] QColor headerTextColor() const; [[nodiscard]] AccountAppList appList() const; [[nodiscard]] QImage avatar() const; + /** @brief Signs in a signed-out account or retries another disconnected state. */ void login() const; void logout() const; void removeAccount() const; diff --git a/src/gui/trayaccountpopup_qt.cpp b/src/gui/trayaccountpopup_qt.cpp index 8b80b8dda14f1..8242b16fd2f41 100644 --- a/src/gui/trayaccountpopup_qt.cpp +++ b/src/gui/trayaccountpopup_qt.cpp @@ -10,6 +10,7 @@ #include "iconjob.h" #include "theme.h" #include "tray/trayaccountappsmodel.h" +#include "tray/trayaccountmenupolicy.h" #include "tray/trayimageutils.h" #include "tray/usermodel.h" @@ -509,6 +510,15 @@ void openLocalFolderForUser(const int userId) #endif } +void reconnectUser(const int userId) +{ + closeTrayPopup(); + + if (const auto userModel = UserModel::instance()) { + userModel->login(userId); + } +} + void openUserStatusForUser(const int userId) { closeTrayPopup(); @@ -699,16 +709,49 @@ void populateAccountMenu(QMenu *menu, const int userId, const bool fetchActivity return; } - if (fetchActivityPreview) { + const auto userModelIndex = userModel->index(userId); + const auto policy = TrayAccountMenuPolicy{ + userModel->data(userModelIndex, UserModel::IsConnectedRole).toBool(), + userModel->data(userModelIndex, UserModel::CanLogoutRole).toBool(), + }; + if (fetchActivityPreview && policy.fetchActivityPreview()) { userModel->fetchActivityPreview(userId); } - const auto userModelIndex = userModel->index(userId); const auto menuIconPalette = nativeMenuIconPalette(menu); const auto menuIconSize = nativeMenuIconSize(menu); - const auto serverHasUserStatus = userModel->data(userModelIndex, UserModel::ServerHasUserStatusRole).toBool(); - const auto onlineStatusEnabled = userModel->data(userModelIndex, UserModel::IsConnectedRole).toBool() && serverHasUserStatus; + if (!policy.showConnectedSections()) { + for (const auto entry : policy.disconnectedEntries()) { + switch (entry) { + case TrayAccountMenuPolicy::Entry::LocalFolder: { + const auto openFolderAction = addMenuAction(menu, + templateBlackThemeIcon(QStringLiteral("folder.svg"), menuIconSize, menuIconPalette), + QCoreApplication::translate("TrayFoldersMenuButton", "Local folder")); + QObject::connect(openFolderAction, &QAction::triggered, openFolderAction, [userId] { + openLocalFolderForUser(userId); + }); + break; + } + case TrayAccountMenuPolicy::Entry::Separator: + menu->addSeparator(); + break; + case TrayAccountMenuPolicy::Entry::Reconnect: { + const auto reconnectAction = addMenuAction(menu, + QIcon{}, + QCoreApplication::translate("OCC::AccountSettings", "Log in")); + QObject::connect(reconnectAction, &QAction::triggered, reconnectAction, [userId] { + reconnectUser(userId); + }); + break; + } + } + } + return; + } + + const auto serverHasUserStatus = userModel->data(userModelIndex, UserModel::ServerHasUserStatusRole).toBool(); + const auto onlineStatusEnabled = policy.showConnectedSections() && serverHasUserStatus; const auto accountAlert = userModel->data(userModelIndex, UserModel::AccountAlertRole).toMap(); const auto accountAlertTitle = accountAlert.value(QStringLiteral("title")).toString(); if (!accountAlertTitle.isEmpty()) { @@ -755,7 +798,6 @@ void populateAccountMenu(QMenu *menu, const int userId, const bool fetchActivity const auto searchAction = addMenuAction(menu, templateBlackThemeIcon(QStringLiteral("search.svg"), menuIconSize, menuIconPalette), QCoreApplication::translate("TrayAccountPopup", "Search")); - searchAction->setEnabled(userModel->data(userModelIndex, UserModel::IsConnectedRole).toBool()); QObject::connect(searchAction, &QAction::triggered, searchAction, [userId] { openSearchForUser(userId); }); @@ -812,7 +854,8 @@ void populateTrayMenu(QMenu *menu, Systray *systray) } if (!roles.isEmpty() && !roles.contains(UserModel::RecentActivitiesRole) - && !roles.contains(UserModel::TrayNotificationsRole)) { + && !roles.contains(UserModel::TrayNotificationsRole) + && !roles.contains(UserModel::IsConnectedRole)) { return; } populateAccountMenu(accountMenu, userId, false); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6092b21965af3..89549992e773c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -97,6 +97,7 @@ nextcloud_add_test(Theme) nextcloud_add_test(IconUtils) nextcloud_add_test(NotificationSoundPlayer) nextcloud_add_test(SetUserStatusDialog) +nextcloud_add_test(TrayAccountMenuPolicy) nextcloud_add_test(UnifiedSearchListmodel) nextcloud_add_test(ActivityListModel) nextcloud_add_test(SortedActivityListModel) diff --git a/test/testtrayaccountmenupolicy.cpp b/test/testtrayaccountmenupolicy.cpp new file mode 100644 index 0000000000000..b4f9094459d4e --- /dev/null +++ b/test/testtrayaccountmenupolicy.cpp @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: CC0-1.0 + * + * This software is in the public domain, furnished "as is", without technical + * support, and with no warranty, express or implied, as to its usefulness for + * any purpose. + */ + +#include "tray/trayaccountmenupolicy.h" + +#include + +#include + +using namespace OCC; + +class TestTrayAccountMenuPolicy : public QObject +{ + Q_OBJECT + +private slots: + void testConnectedAccountShowsServerBackedSections() + { + const auto policy = TrayAccountMenuPolicy{true, true}; + + QVERIFY(policy.showConnectedSections()); + QVERIFY(policy.disconnectedEntries().empty()); + QVERIFY(policy.fetchActivityPreview()); + } + + void testDisconnectedAccountShowsOnlyLocalFolderSeparatorAndReconnect() + { + const auto policy = TrayAccountMenuPolicy{false, true}; + const auto entries = policy.disconnectedEntries(); + + QVERIFY(!policy.showConnectedSections()); + QVERIFY(!policy.fetchActivityPreview()); + QCOMPARE_EQ(entries.size(), std::size_t{3}); + QVERIFY(entries[0] == TrayAccountMenuPolicy::Entry::LocalFolder); + QVERIFY(entries[1] == TrayAccountMenuPolicy::Entry::Separator); + QVERIFY(entries[2] == TrayAccountMenuPolicy::Entry::Reconnect); + } + + void testDisconnectedPublicShareShowsOnlyLocalFolder() + { + const auto policy = TrayAccountMenuPolicy{false, false}; + const auto entries = policy.disconnectedEntries(); + + QVERIFY(!policy.showConnectedSections()); + QVERIFY(!policy.fetchActivityPreview()); + QCOMPARE_EQ(entries.size(), std::size_t{1}); + QVERIFY(entries[0] == TrayAccountMenuPolicy::Entry::LocalFolder); + } + + void testReconnectMode() + { + QCOMPARE_EQ( + TrayAccountMenuPolicy::reconnectMode(false, true, true), + TrayAccountMenuPolicy::ReconnectMode::SignIn); + QCOMPARE_EQ( + TrayAccountMenuPolicy::reconnectMode(false, false, true), + TrayAccountMenuPolicy::ReconnectMode::RetryConnection); + QCOMPARE_EQ( + TrayAccountMenuPolicy::reconnectMode(true, false, true), + TrayAccountMenuPolicy::ReconnectMode::None); + QCOMPARE_EQ( + TrayAccountMenuPolicy::reconnectMode(false, true, false), + TrayAccountMenuPolicy::ReconnectMode::None); + } +}; + +QTEST_APPLESS_MAIN(TestTrayAccountMenuPolicy) +#include "testtrayaccountmenupolicy.moc"