-
Notifications
You must be signed in to change notification settings - Fork 979
fix(tray): scroll to top in the activity window on opening #10533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| /* | ||
| * 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 "account.h" | ||
|
Comment on lines
+1
to
+10
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's avoid writing C++ QTests for QML components. The window should be testable using a QtQuick Test |
||
| #include "accountmanager.h" | ||
| #include "accountstate.h" | ||
| #include "activity/activitylistmodel.h" | ||
| #include "activity/sortedactivitylistmodel.h" | ||
| #include "activity/syncstatussummary.h" | ||
| #include "activitylistmodeltestutils.h" | ||
| #include "systray.h" | ||
| #include "syncenginetestutils.h" | ||
| #include "wheelhandler.h" | ||
|
|
||
| #include <QQmlApplicationEngine> | ||
| #include <QQmlComponent> | ||
| #include <QPointer> | ||
| #include <QQuickWindow> | ||
| #include <QScopedPointer> | ||
| #include <QStandardPaths> | ||
| #include <QTest> | ||
|
|
||
| using namespace ActivityListModelTestUtils; | ||
|
|
||
| class TestActivitiesWindow : public QObject | ||
| { | ||
| Q_OBJECT | ||
|
|
||
| public: | ||
| TestActivitiesWindow() | ||
| { | ||
| Q_INIT_RESOURCE(resources); | ||
| Q_INIT_RESOURCE(theme); | ||
| } | ||
|
|
||
| ~TestActivitiesWindow() override | ||
| { | ||
| if (accountState) { | ||
| OCC::AccountManager::instance()->deleteAccount(accountState.data()); | ||
| } | ||
| } | ||
|
|
||
| private slots: | ||
| void initTestCase() | ||
| { | ||
| QStandardPaths::setTestModeEnabled(true); | ||
|
|
||
| qmlRegisterType<OCC::SyncStatusSummary>("com.nextcloud.desktopclient", 1, 0, "SyncStatusSummary"); | ||
| qmlRegisterType<OCC::SortedActivityListModel>("com.nextcloud.desktopclient", 1, 0, "SortedActivityListModel"); | ||
| qmlRegisterType<WheelHandler>("com.nextcloud.desktopclient", 1, 0, "WheelHandler"); | ||
| qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "Systray", OCC::Systray::instance()); | ||
|
|
||
| fakeQnam.reset(new FakeQNAM({})); | ||
| account = OCC::Account::create(); | ||
| account->setCredentials(new FakeCredentials{fakeQnam.data()}); | ||
| account->setUrl(QUrl(QStringLiteral("https://example.com"))); | ||
| accountState = OCC::AccountManager::instance()->addAccount(account); | ||
| QVERIFY(accountState); | ||
| } | ||
|
|
||
| void testOpeningResetsViewportWithoutInterruptingLiveUpdates() | ||
| { | ||
| TestingALM activityModel; | ||
| activityModel.setAccountState(accountState.data()); | ||
|
|
||
| const auto accountName = account->displayName(); | ||
| for (auto id = 1; id <= 40; ++id) { | ||
| const auto activity = exampleNotificationActivity(accountName, id); | ||
| activityModel.addNotificationToActivityList(activity); | ||
| } | ||
|
|
||
| QQmlApplicationEngine engine; | ||
| engine.addImportPath(QStringLiteral("qrc:/qml/theme")); | ||
|
|
||
| QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/qml/src/gui/activity/qml/ActivitiesWindow.qml"))); | ||
| QVERIFY2(component.isReady(), qPrintable(component.errorString())); | ||
|
|
||
| QScopedPointer<QObject> object(component.createWithInitialProperties({ | ||
| {QStringLiteral("activityModel"), QVariant::fromValue(&activityModel)}, | ||
| })); | ||
| QVERIFY2(object, qPrintable(component.errorString())); | ||
|
|
||
| const auto window = qobject_cast<QQuickWindow *>(object.data()); | ||
| QVERIFY(window); | ||
|
|
||
| window->show(); | ||
| QTRY_VERIFY(window->isVisible()); | ||
|
|
||
| const auto activityList = window->findChild<QObject *>(QStringLiteral("activityList")); | ||
| const auto activityListView = window->findChild<QObject *>(QStringLiteral("activityListView")); | ||
| const auto newActivitiesButtonLoader = window->findChild<QObject *>(QStringLiteral("newActivitiesButtonLoader")); | ||
| QVERIFY(activityList); | ||
| QVERIFY(activityListView); | ||
| QVERIFY(newActivitiesButtonLoader); | ||
| QTRY_VERIFY(activityListView->property("contentHeight").toReal() > activityListView->property("height").toReal()); | ||
|
|
||
| QVERIFY(QMetaObject::invokeMethod(activityListView, "positionViewAtEnd")); | ||
| QTRY_VERIFY(!activityList->property("atYBeginning").toBool()); | ||
|
|
||
| window->hide(); | ||
| QTRY_VERIFY(!window->isVisible()); | ||
| window->show(); | ||
| QTRY_VERIFY(window->isVisible()); | ||
| QTRY_VERIFY(activityList->property("atYBeginning").toBool()); | ||
|
|
||
| QVERIFY(QMetaObject::invokeMethod(activityListView, "positionViewAtEnd")); | ||
| QTRY_VERIFY(!activityList->property("atYBeginning").toBool()); | ||
|
|
||
| auto interactiveActivity = exampleNotificationActivity(accountName, 41); | ||
| OCC::ActivityLink action; | ||
| action._verb = QByteArrayLiteral("POST"); | ||
| interactiveActivity._links.push_back(action); | ||
| activityModel.addNotificationToActivityList(interactiveActivity); | ||
|
|
||
| QTRY_VERIFY(!activityList->property("atYBeginning").toBool()); | ||
| QTRY_VERIFY(newActivitiesButtonLoader->property("active").toBool()); | ||
| } | ||
|
|
||
| private: | ||
| QScopedPointer<FakeQNAM> fakeQnam; | ||
| OCC::AccountPtr account; | ||
| QPointer<OCC::AccountState> accountState; | ||
| }; | ||
|
|
||
| QTEST_MAIN(TestActivitiesWindow) | ||
|
Check warning on line 131 in test/testactivitieswindow.cpp
|
||
| #include "testactivitieswindow.moc" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This reset only fires from
Component.onCompletedor whenvisiblechanges. In the cached-window path ofSystray::showActivitiesWindow(), the tray action callsshow(),raise(), andrequestActivate()on the existingQQuickWindow; when that window is already visible but behind another window,visibledoes not change, so the list keeps its old scroll offset and new items at the top can still be missed. Please triggerresetActivityList()from the existing-window open path or another activation hook rather than relying only onvisibleChanged.Useful? React with 👍 / 👎.