MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - ui - SetupWizard.cpp (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 81.9 % 166 136
Test Date: 2026-07-27 17:53:44 Functions: 94.7 % 19 18
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 40.9 % 394 161

             Branch data     Line data    Source code
       1                 :             : #include "SetupWizard.h"
       2                 :             : 
       3                 :             : #include <QLabel>
       4                 :             : #include <QLoggingCategory>
       5                 :             : #include <QMessageBox>
       6                 :             : #include <QProgressBar>
       7                 :             : #include <QPushButton>
       8                 :             : #include <QScrollArea>
       9                 :             : #include <QTimer>
      10                 :             : #include <QVBoxLayout>
      11                 :             : 
      12                 :             : #include "data/AccountConfig.h"
      13                 :             : #include "service/ImapService.h"
      14                 :             : #include "ui/AccountFormWidget.h"
      15                 :             : #include "ui/PlainTextMessageBox.h"
      16                 :             : #include "ui/ThemeManager.h"
      17                 :             : #include <QEvent>
      18                 :             : 
      19   [ +  -  +  -  :           1 : Q_LOGGING_CATEGORY(lcWizard, "mailjd.wizard")
             +  -  -  - ]
      20                 :             : 
      21                 :             : // --- SetupWizard ---
      22                 :             : 
      23                 :          10 : SetupWizard::SetupWizard(QWidget *parent)
      24   [ +  -  +  - ]:          10 :     : QWizard(parent), m_configDir(AccountConfigLoader::defaultConfigDir()) {
      25   [ +  -  +  - ]:          10 :   setWindowTitle(tr("MailJD – Initial Setup"));
      26         [ +  - ]:          10 :   setMinimumSize(650, 700);
      27         [ +  - ]:          10 :   setWizardStyle(QWizard::ModernStyle);
      28                 :             : 
      29   [ +  -  +  -  :          10 :   addPage(new WelcomePage(this));
          +  -  -  +  -  
                      - ]
      30   [ +  -  +  -  :          10 :   addPage(new AccountPage(this));
          +  -  -  +  -  
                      - ]
      31   [ +  -  +  -  :          10 :   addPage(new TestPage(this));
          +  -  -  +  -  
                      - ]
      32                 :             : 
      33                 :          10 : }
      34                 :             : 
      35                 :           6 : void SetupWizard::setConfigDir(const QString &dir) { m_configDir = dir; }
      36                 :             : 
      37                 :           2 : AccountConfig SetupWizard::configuredAccount() const {
      38                 :           2 :   auto *page = qobject_cast<AccountPage *>(this->page(1));
      39         [ +  - ]:           2 :   if (page) {
      40                 :           2 :     return page->form()->config();
      41                 :             :   }
      42                 :           0 :   return {};
      43   [ #  #  #  #  :           0 : }
          #  #  #  #  #  
                      # ]
      44                 :             : 
      45                 :           1 : void SetupWizard::accept() {
      46         [ +  - ]:           1 :   auto account = configuredAccount();
      47   [ +  -  -  + ]:           2 :   if (account.name.isEmpty() ||
      48   [ +  -  -  + ]:           1 :       !AccountConfigLoader::save(account, m_configDir)) {
      49         [ #  # ]:           0 :     PlainTextMessageBox::warning(
      50         [ #  # ]:           0 :         this, tr("Account Storage Error"),
      51         [ #  # ]:           0 :         tr("The account could not be stored securely. Check the keyring and "
      52                 :             :            "configuration directory, then try again."));
      53                 :           0 :     return;
      54                 :             :   }
      55   [ +  -  +  -  :           2 :   qCInfo(lcWizard) << "Wizard completed, saved account:" << account.name;
          +  -  +  -  +  
                      + ]
      56         [ +  - ]:           1 :   QWizard::accept();
      57         [ +  - ]:           1 : }
      58                 :             : 
      59                 :             : // --- WelcomePage ---
      60                 :             : 
      61                 :          10 : WelcomePage::WelcomePage(QWidget *parent) : QWizardPage(parent) {
      62   [ +  -  +  - ]:          10 :   setTitle(tr("Welcome to MailJD"));
      63   [ +  -  +  - ]:          10 :   setSubTitle(tr("Set up your first email account now."));
      64                 :             : 
      65   [ +  -  +  -  :          10 :   auto *layout = new QVBoxLayout(this);
             -  +  -  - ]
      66                 :             : 
      67                 :             :   auto *label = new QLabel(
      68                 :             :       "<p>MailJD ist ein schneller, tastaturgesteuerter Mail-Client für "
      69                 :             :       "Linux.</p>"
      70                 :             :       "<p>Im nächsten Schritt kannst du deinen IMAP/SMTP-Server konfigurieren. "
      71                 :             :       "Du brauchst folgende Informationen:</p>"
      72                 :             :       "<ul>"
      73                 :             :       "<li>IMAP-Server und Port (z.B. imap.example.com:993)</li>"
      74                 :             :       "<li>SMTP-Server und Port (z.B. smtp.example.com:587)</li>"
      75                 :             :       "<li>Dein Benutzername und Passwort</li>"
      76                 :             :       "</ul>",
      77   [ +  -  +  -  :          10 :       this);
          +  -  -  +  -  
                      - ]
      78         [ +  - ]:          10 :   label->setWordWrap(true);
      79         [ +  - ]:          10 :   layout->addWidget(label);
      80         [ +  - ]:          10 :   layout->addStretch();
      81                 :          10 : }
      82                 :             : 
      83                 :             : // --- AccountPage ---
      84                 :             : 
      85                 :          10 : AccountPage::AccountPage(QWidget *parent) : QWizardPage(parent) {
      86   [ +  -  +  - ]:          10 :   setTitle(tr("Account Setup"));
      87   [ +  -  +  - ]:          10 :   setSubTitle(tr("Enter your email account server details."));
      88                 :             : 
      89   [ +  -  +  -  :          10 :   auto *layout = new QVBoxLayout(this);
             -  +  -  - ]
      90                 :             : 
      91   [ +  -  +  -  :          10 :   auto *scrollArea = new QScrollArea(this);
             -  +  -  - ]
      92         [ +  - ]:          10 :   scrollArea->setWidgetResizable(true);
      93         [ +  - ]:          10 :   scrollArea->setFrameShape(QFrame::NoFrame);
      94                 :             : 
      95   [ +  -  +  -  :          10 :   m_form = new AccountFormWidget(this);
             -  +  -  - ]
      96         [ +  - ]:          10 :   scrollArea->setWidget(m_form);
      97                 :             : 
      98         [ +  - ]:          10 :   layout->addWidget(scrollArea);
      99                 :          10 : }
     100                 :             : 
     101                 :           2 : bool AccountPage::validatePage() {
     102         [ +  - ]:           2 :   auto errors = m_form->validate();
     103         [ +  + ]:           2 :   if (!errors.isEmpty()) {
     104         [ +  - ]:           1 :     PlainTextMessageBox::warning(
     105         [ +  - ]:           2 :         this, tr("Missing Information"),
     106   [ +  -  +  -  :           2 :         "Bitte korrigiere folgende Fehler:\n\n• " + errors.join("\n• "));
                   +  - ]
     107                 :           1 :     return false;
     108                 :             :   }
     109   [ +  -  +  - ]:           1 :   const auto *setupWizard = qobject_cast<const SetupWizard *>(wizard());
     110                 :             :   const QString configDir = setupWizard
     111         [ +  - ]:           1 :                                 ? setupWizard->configDir()
     112         [ -  - ]:           1 :                                 : AccountConfigLoader::defaultConfigDir();
     113                 :             :   const QString uniquenessError = AccountConfigLoader::checkNameUniqueness(
     114   [ +  -  +  - ]:           1 :       m_form->config(), configDir);
     115         [ -  + ]:           1 :   if (!uniquenessError.isEmpty()) {
     116   [ #  #  #  # ]:           0 :     PlainTextMessageBox::warning(this, tr("Account Name Conflict"),
     117                 :             :                                  uniquenessError);
     118                 :           0 :     return false;
     119                 :             :   }
     120                 :           1 :   return true;
     121                 :           2 : }
     122                 :             : 
     123                 :             : // --- TestPage ---
     124                 :             : 
     125                 :          10 : TestPage::TestPage(QWidget *parent) : QWizardPage(parent) {
     126   [ +  -  +  - ]:          10 :   setTitle(tr("Connection Test"));
     127   [ +  -  +  - ]:          10 :   setSubTitle(tr("Your IMAP server connection is being tested."));
     128                 :             : 
     129   [ +  -  +  -  :          10 :   auto *layout = new QVBoxLayout(this);
             -  +  -  - ]
     130                 :             : 
     131   [ +  -  +  -  :          10 :   m_summaryLabel = new QLabel(this);
             -  +  -  - ]
     132         [ +  - ]:          20 :   m_summaryLabel->setObjectName(QStringLiteral("setupAccountSummary"));
     133         [ +  - ]:          10 :   m_summaryLabel->setTextFormat(Qt::PlainText);
     134         [ +  - ]:          10 :   m_summaryLabel->setWordWrap(true);
     135         [ +  - ]:          10 :   layout->addWidget(m_summaryLabel);
     136                 :             : 
     137   [ +  -  +  -  :          10 :   m_progressBar = new QProgressBar(this);
             -  +  -  - ]
     138         [ +  - ]:          10 :   m_progressBar->setRange(0, 0); // Indeterminate
     139         [ +  - ]:          10 :   layout->addWidget(m_progressBar);
     140                 :             : 
     141   [ +  -  +  -  :          10 :   m_statusLabel = new QLabel(tr("Testing connection..."), this);
          +  -  -  +  -  
                      - ]
     142         [ +  - ]:          20 :   m_statusLabel->setObjectName(QStringLiteral("setupConnectionStatus"));
     143         [ +  - ]:          10 :   m_statusLabel->setTextFormat(Qt::PlainText);
     144         [ +  - ]:          10 :   m_statusLabel->setWordWrap(true);
     145         [ +  - ]:          10 :   layout->addWidget(m_statusLabel);
     146                 :             : 
     147   [ +  -  +  -  :          10 :   m_retryButton = new QPushButton("Erneut testen", this);
          +  -  -  +  -  
                      - ]
     148         [ +  - ]:          10 :   m_retryButton->setVisible(false);
     149         [ +  - ]:          10 :   connect(m_retryButton, &QPushButton::clicked, this, &TestPage::runTest);
     150         [ +  - ]:          10 :   layout->addWidget(m_retryButton);
     151                 :             : 
     152         [ +  - ]:          10 :   layout->addStretch();
     153                 :          10 : }
     154                 :             : 
     155                 :           2 : void TestPage::initializePage() {
     156                 :             :   // Get account info from the previous page
     157   [ +  -  +  -  :           2 :   auto *accountPage = qobject_cast<AccountPage *>(wizard()->page(1));
                   +  - ]
     158         [ -  + ]:           2 :   if (!accountPage)
     159                 :           0 :     return;
     160                 :             : 
     161         [ +  - ]:           2 :   auto cfg = accountPage->form()->config();
     162         [ +  - ]:           2 :   m_summaryLabel->setText(QString("Account: %1\n"
     163                 :             :                                   "E-Mail: %2\n"
     164         [ +  - ]:           2 :                                   "IMAP: %3:%4 (%5)")
     165         [ +  - ]:           4 :                               .arg(cfg.name, cfg.email, cfg.imap.host,
     166         [ +  - ]:           4 :                                    QString::number(cfg.imap.port),
     167         [ +  - ]:           4 :                                    cfg.imap.security.toUpper()));
     168                 :             : 
     169                 :           2 :   m_testComplete = false;
     170         [ +  - ]:           2 :   emit completeChanged();
     171                 :             : 
     172         [ +  - ]:           2 :   runTest();
     173                 :           2 : }
     174                 :             : 
     175                 :          10 : bool TestPage::isComplete() const {
     176                 :             :   // Always allow "Finish" – even if the test failed.
     177                 :             :   // The user may want to save the config and fix connection later.
     178                 :          10 :   return m_testComplete;
     179                 :             : }
     180                 :             : 
     181                 :           2 : void TestPage::runTest() {
     182         [ +  - ]:           2 :   m_progressBar->setVisible(true);
     183         [ +  - ]:           2 :   m_progressBar->setRange(0, 0);
     184   [ +  -  +  - ]:           2 :   m_statusLabel->setText("Verbindung wird getestet...");
     185   [ +  -  +  - ]:           2 :   m_statusLabel->setStyleSheet("");
     186         [ +  - ]:           2 :   m_retryButton->setVisible(false);
     187                 :             : 
     188                 :             :   // Clean up previous
     189         [ -  + ]:           2 :   if (m_testService) {
     190         [ #  # ]:           0 :     m_testService->disconnect();
     191         [ #  # ]:           0 :     m_testService->deleteLater();
     192                 :             :   }
     193                 :             : 
     194   [ +  -  +  -  :           2 :   m_testService = new ImapService(this);
             -  +  -  - ]
     195                 :             : 
     196   [ +  -  +  -  :           2 :   auto *accountPage = qobject_cast<AccountPage *>(wizard()->page(1));
                   +  - ]
     197         [ -  + ]:           2 :   if (!accountPage)
     198                 :           0 :     return;
     199                 :             : 
     200         [ +  - ]:           2 :   auto cfg = accountPage->form()->config();
     201                 :             : 
     202                 :           2 :   connect(m_testService, &ImapService::stateChanged, this,
     203         [ +  - ]:           2 :           [this](ImapService::State state) {
     204         [ -  + ]:          10 :             if (state == ImapService::State::Authenticated) {
     205                 :           0 :               onTestSuccess();
     206                 :             :             }
     207                 :          10 :           });
     208                 :             : 
     209                 :           2 :   connect(m_testService, &ImapService::errorOccurred, this,
     210         [ +  - ]:           5 :           [this](const QString &error) { onTestFailed(error); });
     211                 :             : 
     212                 :             :   // Timeout: 10s
     213         [ +  - ]:           2 :   QTimer::singleShot(10000, this, [this]() {
     214   [ #  #  #  # ]:           0 :     if (m_testService && !m_testComplete) {
     215   [ #  #  #  # ]:           0 :       onTestFailed("Zeitüberschreitung (10s)");
     216                 :             :     }
     217                 :           0 :   });
     218                 :             : 
     219         [ +  - ]:           2 :   m_testService->connectToServer(cfg.imap);
     220                 :           2 : }
     221                 :             : 
     222                 :           0 : void TestPage::onTestSuccess() {
     223                 :           0 :   m_progressBar->setVisible(false);
     224   [ #  #  #  # ]:           0 :   m_statusLabel->setText(
     225                 :             :       "✅ Verbindung erfolgreich! Der Account kann gespeichert werden.");
     226                 :             :   // Sprint 69: @success token instead of CSS named color.
     227         [ #  # ]:           0 :   m_statusLabel->setStyleSheet(
     228                 :           0 :       QStringLiteral("color: %1; font-weight: bold;")
     229   [ #  #  #  #  :           0 :           .arg(ThemeManager::instance().color(QLatin1String("@success"))));
             #  #  #  # ]
     230                 :           0 :   m_retryButton->setVisible(false);
     231                 :             : 
     232                 :           0 :   m_testComplete = true;
     233                 :           0 :   emit completeChanged();
     234                 :             : 
     235         [ #  # ]:           0 :   if (m_testService) {
     236                 :           0 :     m_testService->disconnect();
     237                 :           0 :     m_testService->deleteLater();
     238                 :           0 :     m_testService = nullptr;
     239                 :             :   }
     240                 :           0 : }
     241                 :             : 
     242                 :           3 : void TestPage::onTestFailed(const QString &error) {
     243                 :           3 :   m_progressBar->setVisible(false);
     244   [ +  -  +  - ]:           3 :   m_statusLabel->setText("❌ Verbindungstest fehlgeschlagen: " + error);
     245                 :             :   // Sprint 69: @danger token instead of CSS named color.
     246         [ +  - ]:           6 :   m_statusLabel->setStyleSheet(
     247                 :           6 :       QStringLiteral("color: %1;")
     248   [ +  -  +  -  :           6 :           .arg(ThemeManager::instance().color(QLatin1String("@danger"))));
             +  -  +  - ]
     249                 :           3 :   m_retryButton->setVisible(true);
     250                 :             : 
     251                 :             :   // Allow finish even on failure
     252                 :           3 :   m_testComplete = true;
     253                 :           3 :   emit completeChanged();
     254                 :             : 
     255         [ +  + ]:           3 :   if (m_testService) {
     256                 :           2 :     m_testService->disconnect();
     257                 :           2 :     m_testService->deleteLater();
     258                 :           2 :     m_testService = nullptr;
     259                 :             :   }
     260                 :           3 : }
     261                 :             : 
     262                 :             : // T-304: Runtime language switching
     263                 :          19 : void SetupWizard::changeEvent(QEvent *event) {
     264         [ +  + ]:          19 :   if (event->type() == QEvent::LanguageChange)
     265                 :           1 :     retranslateUi();
     266                 :          19 :   QWizard::changeEvent(event);
     267                 :          19 : }
     268                 :             : 
     269                 :           1 : void SetupWizard::retranslateUi() {
     270   [ +  -  +  - ]:           1 :   setWindowTitle(tr("MailJD \u2013 Initial Setup"));
     271                 :           1 : }
        

Generated by: LCOV version 2.0-1