diff --git a/docs/topics/UserInterface.adoc b/docs/topics/UserInterface.adoc index b60b28a66d..456c09ea63 100644 --- a/docs/topics/UserInterface.adoc +++ b/docs/topics/UserInterface.adoc @@ -86,7 +86,7 @@ Additionally, the following environment variables may be useful when running the |KPXC_CONFIG | Override default path to roaming configuration file |KPXC_CONFIG_LOCAL | Override default path to local configuration file |KPXC_INITIAL_DIR | Override initial location picking for databases -|SSH_AUTH_SOCKET | Path of the unix file socket that the agent uses for communication with other processes (SSH Agent) +|SSH_AUTH_SOCK | Path of the unix file socket that the agent uses for communication with other processes (SSH Agent) |QT_SCALE_FACTOR [numeric] | Defines a global scale factor for the whole application, including point-sized fonts. |QT_SCREEN_SCALE_FACTORS [list] | Specifies scale factors for each screen. See https://doc.qt.io/qt-5/highdpi.html#high-dpi-support-in-qt |QT_SCALE_FACTOR_ROUNDING_POLICY | Control device pixel ratio rounding to the nearest integer. See https://doc.qt.io/qt-5/highdpi.html#high-dpi-support-in-qt diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index 4427ca4882..884c34d9e9 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -3468,10 +3468,6 @@ Supported extensions are: %1. Unable to fetch favicon. - - You can enable the DuckDuckGo website icon service under Tools -> Settings -> Security - - Existing icon selected. @@ -3513,6 +3509,10 @@ Supported extensions are: %1. + + You can enable the DuckDuckGo website icon service under Application Settings -> Security + + EditWidgetProperties diff --git a/share/windows/wix-template.xml b/share/windows/wix-template.xml index ae937ce709..add2af2974 100644 --- a/share/windows/wix-template.xml +++ b/share/windows/wix-template.xml @@ -92,6 +92,9 @@ + + + @@ -116,12 +119,17 @@ - - - + + + + + + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 07534bbd7f..c0b62f8586 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,6 +59,7 @@ set(keepassx_SOURCES core/TimeDelta.cpp core/TimeInfo.cpp core/Tools.cpp + core/Totp.cpp core/Translator.cpp core/UrlTools.cpp cli/Utils.cpp @@ -193,8 +194,7 @@ set(keepassx_SOURCES streams/LayeredStream.cpp streams/qtiocompressor.cpp streams/StoreDataStream.cpp - streams/SymmetricCipherStream.cpp - totp/totp.cpp) + streams/SymmetricCipherStream.cpp) if(APPLE) set(keepassx_SOURCES ${keepassx_SOURCES} diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 04120e90b2..e237ae53d5 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -24,7 +24,7 @@ #include "core/Metadata.h" #include "core/PasswordHealth.h" #include "core/Tools.h" -#include "totp/totp.h" +#include "core/Totp.h" #include #include @@ -566,7 +566,7 @@ void Entry::setTotp(QSharedPointer settings) m_attributes->remove(Totp::ATTRIBUTE_SEED); m_attributes->remove(Totp::ATTRIBUTE_SETTINGS); - if (settings->key.isEmpty()) { + if (!settings || settings->key.isEmpty()) { m_data.totpSettings.reset(); } else { m_data.totpSettings = std::move(settings); @@ -1279,11 +1279,11 @@ void Entry::setGroup(Group* group, bool trackPrevious) } } + QObject::setParent(group); + m_group = group; group->addEntry(this); - QObject::setParent(group); - if (m_updateTimeinfo) { m_data.timeInfo.setLocationChanged(Clock::currentDateTimeUtc()); } diff --git a/src/core/Merger.cpp b/src/core/Merger.cpp index fd30da7aa4..0ffb94b9e6 100644 --- a/src/core/Merger.cpp +++ b/src/core/Merger.cpp @@ -338,6 +338,7 @@ Merger::ChangeList Merger::resolveEntryConflict_MergeHistories(const MergeContex const int comparison = compare(targetEntry->timeInfo().lastModificationTime(), sourceEntry->timeInfo().lastModificationTime(), CompareItemIgnoreMilliseconds); + const int maxItems = targetEntry->database()->metadata()->historyMaxItems(); if (comparison < 0) { Group* currentGroup = targetEntry->group(); Entry* clonedEntry = sourceEntry->clone(Entry::CloneIncludeHistory); @@ -346,15 +347,15 @@ Merger::ChangeList Merger::resolveEntryConflict_MergeHistories(const MergeContex qPrintable(sourceEntry->title()), qPrintable(currentGroup->name())); changes << tr("Synchronizing from newer source %1 [%2]").arg(targetEntry->title(), targetEntry->uuidToHex()); - moveEntry(clonedEntry, currentGroup); - mergeHistory(targetEntry, clonedEntry, mergeMethod); + mergeHistory(targetEntry, clonedEntry, mergeMethod, maxItems); eraseEntry(targetEntry); + moveEntry(clonedEntry, currentGroup); } else { qDebug("Merge %s/%s with local on top/under %s", qPrintable(targetEntry->title()), qPrintable(sourceEntry->title()), qPrintable(targetEntry->group()->name())); - const bool changed = mergeHistory(sourceEntry, targetEntry, mergeMethod); + const bool changed = mergeHistory(sourceEntry, targetEntry, mergeMethod, maxItems); if (changed) { changes << tr("Synchronizing from older source %1 [%2]").arg(targetEntry->title(), targetEntry->uuidToHex()); @@ -400,7 +401,10 @@ Merger::resolveEntryConflict(const MergeContext& context, const Entry* sourceEnt return changes; } -bool Merger::mergeHistory(const Entry* sourceEntry, Entry* targetEntry, Group::MergeMode mergeMethod) +bool Merger::mergeHistory(const Entry* sourceEntry, + Entry* targetEntry, + Group::MergeMode mergeMethod, + const int maxItems) { Q_UNUSED(mergeMethod); const auto targetHistoryItems = targetEntry->historyItems(); @@ -473,7 +477,6 @@ bool Merger::mergeHistory(const Entry* sourceEntry, Entry* targetEntry, Group::M } bool changed = false; - const int maxItems = targetEntry->database()->metadata()->historyMaxItems(); const auto updatedHistoryItems = merged.values(); for (int i = 0; i < maxItems; ++i) { const Entry* oldEntry = targetHistoryItems.value(targetHistoryItems.count() - i); diff --git a/src/core/Merger.h b/src/core/Merger.h index 4b277f9561..ea45a6d141 100644 --- a/src/core/Merger.h +++ b/src/core/Merger.h @@ -50,7 +50,7 @@ class Merger : public QObject ChangeList mergeDeletions(const MergeContext& context); ChangeList mergeMetadata(const MergeContext& context); bool markOlderEntry(Entry* entry); - bool mergeHistory(const Entry* sourceEntry, Entry* targetEntry, Group::MergeMode mergeMethod); + bool mergeHistory(const Entry* sourceEntry, Entry* targetEntry, Group::MergeMode mergeMethod, const int maxItems); void moveEntry(Entry* entry, Group* targetGroup); void moveGroup(Group* group, Group* targetGroup); // remove an entry without a trace in the deletedObjects - needed for elemination cloned entries diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp index 01641bc802..aa0f3e7172 100644 --- a/src/core/PasswordGenerator.cpp +++ b/src/core/PasswordGenerator.cpp @@ -261,8 +261,10 @@ QVector PasswordGenerator::passwordGroups() const if (!m_custom.isEmpty()) { PasswordGroup group; - for (auto ch : m_custom) { - group.append(ch); + for (const auto& ch : m_custom) { + if (!group.contains(ch)) { + group.append(ch); + } } passwordGroups.append(group); diff --git a/src/totp/totp.cpp b/src/core/Totp.cpp similarity index 97% rename from src/totp/totp.cpp rename to src/core/Totp.cpp index dc58f158db..f55312a9db 100644 --- a/src/totp/totp.cpp +++ b/src/core/Totp.cpp @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -#include "totp.h" +#include "Totp.h" #include "core/Base32.h" #include "core/Clock.h" @@ -59,6 +59,11 @@ static QString getNameForHashType(const Totp::Algorithm hashType) QSharedPointer Totp::parseSettings(const QString& rawSettings, const QString& key) { + // Early out if both strings are empty + if (rawSettings.isEmpty() && key.isEmpty()) { + return {}; + } + // Create default settings auto settings = createSettings(key, DEFAULT_DIGITS, DEFAULT_STEP); @@ -96,6 +101,11 @@ QSharedPointer Totp::parseSettings(const QString& rawSettings, c settings->algorithm = getHashTypeByName(query.queryItemValue("otpHashMode")); } } else { + if (settings->key.isEmpty()) { + // Legacy format cannot work with an empty key + return {}; + } + // Parse semi-colon separated values ([step];[digits|S]) settings->format = StorageFormat::LEGACY; auto vars = rawSettings.split(";"); diff --git a/src/totp/totp.h b/src/core/Totp.h similarity index 100% rename from src/totp/totp.h rename to src/core/Totp.h diff --git a/src/fdosecrets/objects/Collection.cpp b/src/fdosecrets/objects/Collection.cpp index 4cc6ca537d..be452d4299 100644 --- a/src/fdosecrets/objects/Collection.cpp +++ b/src/fdosecrets/objects/Collection.cpp @@ -495,6 +495,10 @@ namespace FdoSecrets } auto item = Item::Create(this, entry); + if (!item) { + return; + } + m_items << item; m_entryToItem[entry] = item; diff --git a/src/format/OpVaultReaderSections.cpp b/src/format/OpVaultReaderSections.cpp index 661b9d6c3e..d05f8fca12 100644 --- a/src/format/OpVaultReaderSections.cpp +++ b/src/format/OpVaultReaderSections.cpp @@ -18,7 +18,7 @@ #include "OpVaultReader.h" #include "core/Entry.h" -#include "totp/totp.h" +#include "core/Totp.h" #include #include diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp index f3adeee29b..fcda80e43b 100644 --- a/src/gui/DatabaseOpenWidget.cpp +++ b/src/gui/DatabaseOpenWidget.cpp @@ -513,12 +513,12 @@ void DatabaseOpenWidget::hardwareKeyResponse(bool found) void DatabaseOpenWidget::openHardwareKeyHelp() { - QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs#faq-cat-yubikey")); + QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs/#faq-yubikey-2fa")); } void DatabaseOpenWidget::openKeyFileHelp() { - QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs#faq-cat-keyfile")); + QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs/#faq-keyfile-howto")); } void DatabaseOpenWidget::setUserInteractionLock(bool state) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 94fa2fbe75..deeec3662f 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -493,6 +493,10 @@ void DatabaseWidget::setupTotp() auto setupTotpDialog = new TotpSetupDialog(this, currentEntry); connect(setupTotpDialog, SIGNAL(totpUpdated()), SIGNAL(entrySelectionChanged())); + if (currentWidget() == m_editEntryWidget) { + // Entry is being edited, tell it when we are finished updating TOTP + connect(setupTotpDialog, SIGNAL(totpUpdated()), m_editEntryWidget, SLOT(updateTotp())); + } connect(this, &DatabaseWidget::databaseLockRequested, setupTotpDialog, &TotpSetupDialog::close); setupTotpDialog->open(); } @@ -2151,7 +2155,7 @@ bool DatabaseWidget::performSave(QString& errorMessage, const QString& fileName) m_groupView->setDisabled(false); m_tagView->setDisabled(false); - if (focusWidget) { + if (focusWidget && focusWidget->isVisible()) { focusWidget->setFocus(); } diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 25542730cc..c91974c4c4 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -222,7 +222,7 @@ void EditWidgetIcons::iconReceived(const QString& url, const QImage& icon) QString message(tr("Unable to fetch favicon.")); if (!config()->get(Config::Security_IconDownloadFallback).toBool()) { message.append("\n").append( - tr("You can enable the DuckDuckGo website icon service under Tools -> Settings -> Security")); + tr("You can enable the DuckDuckGo website icon service under Application Settings -> Security")); } emit messageEditEntry(message, MessageWidget::Error); return; diff --git a/src/gui/EntryPreviewWidget.cpp b/src/gui/EntryPreviewWidget.cpp index 7d7151c0db..b7c8ca1bcc 100644 --- a/src/gui/EntryPreviewWidget.cpp +++ b/src/gui/EntryPreviewWidget.cpp @@ -21,10 +21,10 @@ #include "Application.h" #include "core/Config.h" +#include "core/Totp.h" #include "gui/Clipboard.h" #include "gui/Font.h" #include "gui/Icons.h" -#include "totp/totp.h" #if defined(WITH_XC_KEESHARE) #include "keeshare/KeeShare.h" #include "keeshare/KeeShareSettings.h" diff --git a/src/gui/TotpDialog.cpp b/src/gui/TotpDialog.cpp index e856f5d6a4..577fa10563 100644 --- a/src/gui/TotpDialog.cpp +++ b/src/gui/TotpDialog.cpp @@ -20,9 +20,9 @@ #include "ui_TotpDialog.h" #include "core/Clock.h" +#include "core/Totp.h" #include "gui/Clipboard.h" #include "gui/MainWindow.h" -#include "totp/totp.h" #include #include diff --git a/src/gui/TotpExportSettingsDialog.cpp b/src/gui/TotpExportSettingsDialog.cpp index 1ac5231d40..8e56d5d2ec 100644 --- a/src/gui/TotpExportSettingsDialog.cpp +++ b/src/gui/TotpExportSettingsDialog.cpp @@ -17,11 +17,11 @@ #include "TotpExportSettingsDialog.h" +#include "core/Totp.h" #include "gui/Clipboard.h" #include "gui/MainWindow.h" #include "gui/SquareSvgWidget.h" #include "qrcode/QrCode.h" -#include "totp/totp.h" #include #include diff --git a/src/gui/TotpSetupDialog.cpp b/src/gui/TotpSetupDialog.cpp index d33d9ce398..e796e1d5e3 100644 --- a/src/gui/TotpSetupDialog.cpp +++ b/src/gui/TotpSetupDialog.cpp @@ -19,8 +19,8 @@ #include "ui_TotpSetupDialog.h" #include "core/Base32.h" +#include "core/Totp.h" #include "gui/MessageBox.h" -#include "totp/totp.h" TotpSetupDialog::TotpSetupDialog(QWidget* parent, Entry* entry) : QDialog(parent) diff --git a/src/gui/csvImport/CsvImportWidget.cpp b/src/gui/csvImport/CsvImportWidget.cpp index a3a30e4c33..08f6d6589c 100644 --- a/src/gui/csvImport/CsvImportWidget.cpp +++ b/src/gui/csvImport/CsvImportWidget.cpp @@ -22,9 +22,9 @@ #include #include "core/Clock.h" +#include "core/Totp.h" #include "format/KeePass2Writer.h" #include "gui/MessageBox.h" -#include "totp/totp.h" // I wanted to make the CSV import GUI future-proof, so if one day you need a new field, // all you have to do is add a field to m_columnHeader, and the GUI will follow: @@ -208,7 +208,7 @@ void CsvImportWidget::writeDatabase() auto otpString = m_parserModel->data(m_parserModel->index(r, 6)); if (otpString.isValid() && !otpString.toString().isEmpty()) { auto totp = Totp::parseSettings(otpString.toString()); - if (totp->key.isEmpty()) { + if (!totp || totp->key.isEmpty()) { // Bare secret, use default TOTP settings totp = Totp::parseSettings({}, otpString.toString()); } diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index a1297d4c0c..b84fa2488f 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -114,6 +114,7 @@ EditEntryWidget::EditEntryWidget(QWidget* parent) m_entryModifiedTimer.setSingleShot(true); m_entryModifiedTimer.setInterval(0); connect(&m_entryModifiedTimer, &QTimer::timeout, this, [this] { + // TODO: Upon refactor of this widget, this needs to merge unsaved changes in the UI if (isVisible() && m_entry) { setForms(m_entry); } @@ -704,6 +705,13 @@ void EditEntryWidget::toKeeAgentSettings(KeeAgentSettings& settings) const settings.setSaveAttachmentToTempFile(m_sshAgentSettings.saveAttachmentToTempFile()); } +void EditEntryWidget::updateTotp() +{ + if (m_entry) { + m_attributesModel->setEntryAttributes(m_entry->attributes()); + } +} + void EditEntryWidget::browsePrivateKey() { auto fileName = fileDialog()->getOpenFileName(this, tr("Select private key"), FileDialog::getLastDir("sshagent")); @@ -828,8 +836,6 @@ void EditEntryWidget::loadEntry(Entry* entry, m_create = create; m_history = history; - connect(m_entry, &Entry::modified, this, [this] { m_entryModifiedTimer.start(); }); - if (history) { setHeadline(QString("%1 \u2022 %2").arg(parentName, tr("Entry history"))); } else { @@ -837,6 +843,8 @@ void EditEntryWidget::loadEntry(Entry* entry, setHeadline(QString("%1 \u2022 %2").arg(parentName, tr("Add entry"))); } else { setHeadline(QString("%1 \u2022 %2 \u2022 %3").arg(parentName, entry->title(), tr("Edit entry"))); + // Reload entry details if changed outside of the edit dialog + connect(m_entry, &Entry::modified, this, [this] { m_entryModifiedTimer.start(); }); } } @@ -1143,6 +1151,7 @@ bool EditEntryWidget::commitEntry() } m_historyModel->setEntries(m_entry->historyItems(), m_entry); + setPageHidden(m_historyWidget, m_history || m_entry->historyItems().count() < 1); m_advancedUi->attachmentsWidget->linkAttachments(m_entry->attachments()); showMessage(tr("Entry updated successfully."), MessageWidget::Positive); diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h index 3bd67032f3..fddf64eda8 100644 --- a/src/gui/entry/EditEntryWidget.h +++ b/src/gui/entry/EditEntryWidget.h @@ -118,6 +118,7 @@ private slots: void updateSSHAgentAttachment(); void updateSSHAgentAttachments(); void updateSSHAgentKeyInfo(); + void updateTotp(); void browsePrivateKey(); void addKeyToAgent(); void removeKeyFromAgent(); diff --git a/src/gui/entry/EntryHistoryModel.cpp b/src/gui/entry/EntryHistoryModel.cpp index acde63cb52..618f4328f0 100644 --- a/src/gui/entry/EntryHistoryModel.cpp +++ b/src/gui/entry/EntryHistoryModel.cpp @@ -158,9 +158,10 @@ void EntryHistoryModel::deleteIndex(QModelIndex index) { auto entry = entryFromIndex(index); if (entry) { - beginRemoveRows(QModelIndex(), m_historyEntries.indexOf(entry), m_historyEntries.indexOf(entry)); + beginRemoveRows(QModelIndex(), index.row(), index.row()); m_historyEntries.removeAll(entry); m_deletedHistoryEntries << entry; + m_historyModifications.erase(m_historyModifications.begin() + index.row()); endRemoveRows(); } } diff --git a/src/gui/entry/EntryView.cpp b/src/gui/entry/EntryView.cpp index 868250a666..419a08d753 100644 --- a/src/gui/entry/EntryView.cpp +++ b/src/gui/entry/EntryView.cpp @@ -219,11 +219,12 @@ void EntryView::displaySearch(const QList& entries) m_model->setEntries(entries); header()->showSection(EntryModel::ParentGroup); + setFirstEntryActive(); + // Reset sort column to 'Group', overrides DatabaseWidgetStateSync m_sortModel->sort(EntryModel::ParentGroup, Qt::AscendingOrder); sortByColumn(EntryModel::ParentGroup, Qt::AscendingOrder); - setFirstEntryActive(); m_inSearchMode = true; } @@ -545,6 +546,8 @@ void EntryView::startDrag(Qt::DropActions supportedActions) listWidget.addItem(item); } + listWidget.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + listWidget.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listWidget.setStyleSheet("QListWidget { background-color: palette(highlight); border: 1px solid palette(dark); " "padding: 4px; color: palette(highlighted-text); }"); auto width = listWidget.sizeHintForColumn(0) + 2 * listWidget.frameWidth(); diff --git a/src/gui/styles/base/classicstyle.qss b/src/gui/styles/base/classicstyle.qss index f7d3c0fb47..d0ab2b88fc 100644 --- a/src/gui/styles/base/classicstyle.qss +++ b/src/gui/styles/base/classicstyle.qss @@ -9,8 +9,9 @@ QToolTip { DatabaseWidget #SearchBanner, DatabaseWidget #KeeShareBanner { font-weight: bold; - background-color: rgb(94, 161, 14); - border: 1px solid rgb(190, 190, 190); + background-color: palette(highlight); + color: palette(highlighted-text); + border: 1px solid palette(dark); padding: 2px; } diff --git a/tests/TestCsvExporter.cpp b/tests/TestCsvExporter.cpp index 4854da1116..c4c937e5d8 100644 --- a/tests/TestCsvExporter.cpp +++ b/tests/TestCsvExporter.cpp @@ -22,9 +22,9 @@ #include #include "core/Group.h" +#include "core/Totp.h" #include "crypto/Crypto.h" #include "format/CsvExporter.h" -#include "totp/totp.h" QTEST_GUILESS_MAIN(TestCsvExporter) diff --git a/tests/TestOpVaultReader.cpp b/tests/TestOpVaultReader.cpp index a5d05e9b07..4899d335fb 100644 --- a/tests/TestOpVaultReader.cpp +++ b/tests/TestOpVaultReader.cpp @@ -20,9 +20,9 @@ #include "config-keepassx-tests.h" #include "core/Group.h" #include "core/Metadata.h" +#include "core/Totp.h" #include "crypto/Crypto.h" #include "format/OpVaultReader.h" -#include "totp/totp.h" #include #include diff --git a/tests/TestTotp.cpp b/tests/TestTotp.cpp index bb3b55dbd8..f2bb3d47a3 100644 --- a/tests/TestTotp.cpp +++ b/tests/TestTotp.cpp @@ -19,8 +19,8 @@ #include "TestTotp.h" #include "core/Entry.h" +#include "core/Totp.h" #include "crypto/Crypto.h" -#include "totp/totp.h" #include @@ -97,6 +97,14 @@ void TestTotp::testParseSecret() QCOMPARE(settings->digits, 6u); QCOMPARE(settings->step, 30u); QCOMPARE(settings->algorithm, Totp::Algorithm::Sha1); + + // Blank settings (expected failure) + settings = Totp::parseSettings("", ""); + QVERIFY(settings.isNull()); + + // TOTP Settings with blank secret (expected failure) + settings = Totp::parseSettings("30;8", ""); + QVERIFY(settings.isNull()); } void TestTotp::testTotpCode() @@ -164,4 +172,8 @@ void TestTotp::testEntryHistory() entry.setTotp(settings); QCOMPARE(entry.historyItems().size(), 2); QCOMPARE(entry.totpSettings()->key, QString("foo")); + // Nullptr Settings (expected reset of TOTP) + entry.setTotp(nullptr); + QVERIFY(!entry.hasTotp()); + QCOMPARE(entry.historyItems().size(), 3); } diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 178402d7eb..dbc3c11c52 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -1078,6 +1078,13 @@ void TestGui::testSearch() QCOMPARE(groupView->currentGroup(), m_db->rootGroup()); QVERIFY(!m_dbWidget->isSearchActive()); + // check if first entry is selected after search + QTest::keyClicks(searchTextEdit, "some"); + QTRY_VERIFY(m_dbWidget->isSearchActive()); + QTRY_COMPARE(entryView->selectedEntries().length(), 1); + QModelIndex index_current = entryView->indexFromEntry(entryView->currentEntry()); + QTRY_COMPARE(index_current.row(), 0); + // Try to edit the first entry from the search view // Refocus back to search edit QTest::mouseClick(searchTextEdit, Qt::LeftButton);