diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-08 11:17:29 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-08 11:17:29 +0200 |
| commit | caa34f1f4616b6c3e7a11295e7b07fe303b183fb (patch) | |
| tree | b03f10b8fa60ee1a0683d9c1896dfb3ddef25d2d | |
| parent | a00d35452fcef8c8bc723ba7045fcae38fe8f315 (diff) | |
| download | qtmaildir-caa34f1f4616b6c3e7a11295e7b07fe303b183fb.tar.gz qtmaildir-caa34f1f4616b6c3e7a11295e7b07fe303b183fb.zip | |
feat: make Sync follow the account being looked at
Item 101. The sync run was already account-aware for pending edits and
consulted the account dropdown for nothing, so looking at one account and
pressing Sync collected every one of them.
pendingSyncChannels() now reads the dropdown as well as m_editedAccounts.
The two are a UNION rather than one replacing the other, which is the whole
safety property: looking at one account while having edited another is
ordinary, and a run that dropped the edited account's channel would strand
that write with nothing on screen to say so. All accounts is the empty key
and narrows nothing, so a full fetch stays what an unselected window asks
for. The existing fallback is untouched: an account whose section names no
channel still widens the run rather than being silently skipped.
syncStartedText() is the visibility half. A run opened with "Syncing..."
whether it covered one account or all of them; it now names what it covers.
The exit paths overwrite the label with their own wording immediately, so
the parameter is defaulted rather than threaded through them.
Four tests. The narrowing one asserts the channel list is EMPTY before the
gesture, since empty is what a full fetch looks like and a test starting
from a narrowed state could not tell the fix from a window that had never
widened. Mutation-checked twice: ignoring the selection fails three of
them, and replacing the pending set instead of unioning with it fails the
union test alone, which is the version that looks correct and loses a
write.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KphFXTc2QajxXsHWyvGJ4R
| -rw-r--r-- | CHANGELOG.md | 7 | ||||
| -rw-r--r-- | README.md | 15 | ||||
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md | 46 | ||||
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 45 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 55 | ||||
| -rw-r--r-- | src/mainwindow.h | 9 | ||||
| -rw-r--r-- | tests/test_mainwindow.cpp | 196 | ||||
| -rw-r--r-- | translations/qtmaildir_it_IT.ts | 12 |
8 files changed, 322 insertions, 63 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e1378fc..32c1a34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,13 @@ point at which they are stable. themes draw as an exclamation mark; that was invisible while it only ever appeared beside its own label, and became an info glyph the moment it reached the icon-only message bar. +- **Sync follows the account you are looking at.** With an account selected in + the dropdown, Sync collects that account rather than every one; with All + accounts selected it collects everything, as before. Any account with + unsynced edits is always included, whichever one is on screen, so choosing a + narrower view cannot leave a change stranded. The status line now names what + a run covers instead of saying only "Syncing...", so a narrowed run is + visible as one. ### Fixed @@ -655,11 +655,16 @@ Two things any replacement has to get right, both learned the hard way: ### Per-account sync -When tag changes are outstanding, a sync passes only the affected accounts' -channel names to the command. When nothing is outstanding the run is a plain -fetch and no names are passed, so every account is synced: narrowing a fetch to -wherever the last edit happened to be would quietly stop collecting mail -everywhere else. +A sync covers the account selected in the dropdown, plus any account with +outstanding tag changes. With **All accounts** selected and nothing +outstanding, no names are passed and the run is a plain fetch over everything: +narrowing a fetch to wherever the last edit happened to be would quietly stop +collecting mail everywhere else. + +The two are a union rather than a choice, so an edit is never left behind by +looking somewhere else. Editing mail in `personal` and then switching the +dropdown to `work` syncs both: `work` because it is on screen, `personal` +because it owes a write. The status line names what a run covers as it starts. The name passed is the **mbsync channel**, which is not always the account's section key. `[account.mail-first.last]` may well be the channel diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md index 6613059..bcb6431 100644 --- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md +++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md @@ -10232,3 +10232,49 @@ sends, and asserts the row arrives with no further gesture: no second against the defect. Mutation-checked by disabling the connection, which fails on the row count. +## 101. Sync is account-aware for edits but not for the account the user is looking at + +**Done 2026-09-08.** The user chose BOTH halves the entry offered, and then +narrowed the first one: not a second action beside Sync, but the existing Sync +made account-aware. "If 'All Accounts' is selected, Sync works as today, if +'work' is selected, hitting Sync works only for 'work'." + +**What was built.** `pendingSyncChannels()` now reads the account dropdown as +well as `m_editedAccounts`, and the two are a UNION rather than one replacing +the other. That union is the whole safety property and is the constraint the +original entry named: looking at one account while having edited another is +ordinary, and a run that dropped the edited account's channel would strand that +write with nothing on screen to say so. All accounts is the empty key and +narrows nothing, so a full fetch is still what an unselected window asks for, +which is what keeps item 49's reasoning intact. The existing fallback survives +untouched: an account whose section names no channel still widens the run to +everything rather than being silently skipped. + +The visibility half is `syncStartedText()`, called from `setSyncBusy()`. A run +used to open with "Syncing..." whether it covered one account or all of them, +which is precisely the invisibility the entry complained about; it now reads +"Syncing all accounts..." or "Syncing work-channel, personal-channel...". The +exit paths pass no channels and overwrite the label with their own wording +immediately, so the parameter is defaulted rather than threaded through them. + +**Four tests**, three on the channel resolution and one on the generated +string. `syncNarrowsToTheSelectedAccount` asserts the list is EMPTY before the +gesture, which matters more than it looks: empty is what a full fetch looks +like, so a test starting from a narrowed state could not tell the fix from a +window that had never widened. `aNarrowedSyncStillCarriesAnEditFromAnotherAccount` +is the union, and it is the one that fails under the plausible wrong +implementation. `theStatusLineNamesWhatASyncWillCover` asserts on the generated +string rather than on a label after a real run, which would be a test of +QProcess timing. + +Mutation-checked twice: ignoring the selection fails all three channel tests, +and replacing the pending set instead of unioning with it fails the third one +alone. That second mutation is the one worth having, since it is the version +that looks correct and loses a write. + +**One thing deliberately not done.** The separator in the channel list is a +plain `", "`, not a translated string. `lupdate` picked it up as translatable +when it was written with `tr()`, and a comma joins a list the same way in +Italian; a translatable separator is a string for a translator to get wrong for +no gain. + diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md index 681200e..47c46fb 100644 --- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md +++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md @@ -167,7 +167,7 @@ taking that too literally. | 98 | "Important" adds the tag but cannot remove it, unlike every other toggle | defect | XS | **done** 2026-08-17, unreleased. Calls `everySelectedRowHasTag()`, as the entry required. Its reply test needed THREE different states (list-first thread, the reply's own thread, the reply) before it could tell the two wrong answers apart; with the reply defaulted to its thread's state the item 105 mutation stayed green, measured | | 99 | The unread action is labelled "Toggle unread" whichever way it will go | presentation | S | **done 2026-08-25**, unreleased, with 112: the user's note is ONE design across both. The label names the direction it will go, and the entry is hidden on a selection with no single state. `refreshUnreadAction()` reads the new three-valued `selectionTagPresence()` | | 100 | The message pane offers Back, Forward, Reload and Save page, none of which mean anything | defect | XS | **done** 2026-08-17, unreleased. `MessageView::removeBrowserActions()` filters the standard menu by `pageAction()` POINTER, never by text; `ViewSource` went with them, and stranded separators are swept | -| 101 | Sync is account-aware for edits but not for the account the user is looking at | workflow | S | open; item 49 built the edit half deliberately. Needs a decision, see the entry | +| 101 | Sync is account-aware for edits but not for the account the user is looking at | workflow | S | **done 2026-09-08**, unreleased. The user chose both halves the entry offered and narrowed the first: no second action, the existing Sync reads the account dropdown. Selected account narrows the run, All accounts is a full fetch as before, and the edited accounts are a UNION with the selection so a narrowed run cannot strand a write. The status line names what a run covers. See the closed entry | | 102 | The rules table shows no note, so the field explaining a rule is invisible until it is opened | workflow | XS | **done** 2026-08-17, unreleased. A Note column before `ColumnCount`, so the appended Matches column stays last. Found a second defect on the way: `restoreState` REFUSES a header state with a different column count, and the sized flags were being set regardless | | 103 | What Delete does to mail on the server is undocumented and unverified | clarification | S+M | done; Delete moves to the account trash, with Restore and a stranded-mail cleanup. Section in the closed file | | 104 | Mail visible in Thunderbird never reaches qtmaildir | defect | XS | **done 2026-08-25**, hand-tested. The worker never reopened its read-only notmuch handle, so no query saw mail indexed after startup. Confirmed on a sync run from the application that added 20 messages: they appeared without a restart | @@ -484,49 +484,6 @@ literals `lupdate` can see; a string built by concatenation is not translatable. **Size: S.** Mostly the mixed-selection and toolbar decisions, not the code. -## 101. Sync is account-aware for edits but not for the account the user is looking at - -**Observed (user, from the notes):** "sync button should be account-aware." - -**Cause (verified in the code).** `MainWindow::pendingSyncChannels()` -(`src/mainwindow.cpp:3550`) resolves channels from `m_editedAccounts`, the set of -accounts the user has made EDITS in, and from nothing else. The account dropdown -is not consulted. With nothing pending it returns empty on purpose, and -`mailsync.sh` turns that into `mbsync -a`, every channel. - -**Item 49 built exactly this and the reasoning still holds.** The comment states -it: with nothing pending the run is a FETCH, and narrowing a fetch to wherever -the last edit happened would "quietly stop collecting mail everywhere else". -Fetching is global by nature; carrying edits is not. - -**So this needs a decision, not a fix.** The note does not say which of two -things the user means, and they are different features: - -*Sync only the selected account, on demand.* A deliberate "sync this account" -that ignores the pending set, presumably beside the existing Sync rather than -replacing it. Useful when one account is slow and the user wants their mail from -another one now. The risk is the one item 49 named: a button that looks like -Sync and quietly does not collect the rest of the mail. - -*Show which accounts a sync will cover.* No behaviour change at all, just making -the existing account-awareness visible, since today the user cannot tell whether -a run is narrowed or full. The status bar already names each channel as mbsync -reaches it (item 42), so most of this exists. - -**Constraints.** - -- The account dropdown is a VIEW filter. Making it also steer sync couples two - things the user may reasonably want apart: looking at one account while - fetching all of them is the normal case, not an edge case. -- Whatever narrows a run must still carry every pending edit, or an edit is - stranded with nothing on screen to say so. `pendingSyncChannels()` already - falls back to a full sync when it cannot resolve a channel for an edited - account, and that safety must survive. -- An account with no `[account.<key>]` section, or one whose section names no - channel, has no channel to sync. The fallback covers it today. - -**Size: S** for the on-demand button, XS for the visibility half. Ask which. - ## 113. No way to see a message's HTML source **Observed (user, 2026-08-17):** reviewing item 100's removals, "view source diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 41dbe9c..e7cbf33 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5452,7 +5452,7 @@ void MainWindow::feedSyncPhase(const QString &chunk) m_statusLabel->setText(m_syncPhase.statusText()); } -void MainWindow::setSyncBusy(bool busy) +void MainWindow::setSyncBusy(bool busy, const QStringList &channels) { m_localSyncBusy = busy; updateSyncControls(); @@ -5462,7 +5462,22 @@ void MainWindow::setSyncBusy(bool busy) // output by then. Setting the label is still right, since the tracker has // nothing to say until a line it recognises arrives. if (busy && m_syncPhase.statusText().isEmpty()) - m_statusLabel->setText(tr("Syncing...")); + m_statusLabel->setText(syncStartedText(channels)); +} + +QString MainWindow::syncStartedText(const QStringList &channels) const +{ + // Item 101's visibility half. The run is already account-aware and the user + // could not tell: the same three dots appeared whether mbsync was about to + // collect one account or all of them. Naming the channels makes a narrowed + // run legible at the moment it starts, which is what the item asked for. + // + // Empty means every channel, since that is exactly what pendingSyncChannels() + // returns for a full fetch and what mailsync.sh turns into `mbsync -a`. + if (channels.isEmpty()) + return tr("Syncing all accounts..."); + + return tr("Syncing %1...").arg(channels.join(QStringLiteral(", "))); } void MainWindow::updateSyncControls() @@ -5515,11 +5530,15 @@ void MainWindow::startSync() m_syncPhase.reset(); m_syncLineBuffer.clear(); - if (!m_sync->start(pendingSyncChannels())) { + // Read once and passed on, rather than asked for twice: the label must + // describe the run that actually started, and a second call could disagree + // with the first if the selection moved in between. + const QStringList channels = pendingSyncChannels(); + if (!m_sync->start(channels)) { showTransientStatus(tr("Sync already running")); return; } - setSyncBusy(true); + setSyncBusy(true, channels); } void MainWindow::scheduleAutoSync() @@ -5607,16 +5626,34 @@ void MainWindow::recordPendingEdit(const QString &messageId, const QString &tag, QStringList MainWindow::pendingSyncChannels() const { - // Nothing pending means this run is a FETCH, and a fetch must cover every - // account: narrowing it to wherever the last edit happened to be would + // The account the user is LOOKING at narrows the run, per item 101 and the + // user's decision on it: one Sync action, steered by the dropdown. All + // accounts is the empty key and narrows nothing, which is what keeps item + // 49's property that a fetch collects everywhere by default. + // + // This is deliberately a UNION with the pending set below, never a + // replacement for it. Looking at one account while having edited another is + // ordinary rather than an edge case, and a run that dropped the edited + // account's channel would strand that write with nothing on screen to say + // so. + const QString selected = m_accountBox + ? m_accountBox->currentData().toString() + : QString(); + + QSet<QString> wanted = m_editedAccounts; + if (!selected.isEmpty()) + wanted.insert(selected); + + // Nothing pending and nothing selected means this run is a FETCH over + // everything: narrowing it to wherever the last edit happened to be would // quietly stop collecting mail everywhere else. Empty is the signal for // that, and MailSync::start() appends nothing. - if (m_editedAccounts.isEmpty()) + if (wanted.isEmpty()) return {}; QStringList channels; for (const Account &account : m_config.accounts()) { - if (m_editedAccounts.contains(account.key)) + if (wanted.contains(account.key)) channels.append(account.syncChannel()); } @@ -5624,7 +5661,7 @@ QStringList MainWindow::pendingSyncChannels() const // channel, and syncing a subset that omits it would leave its edits behind // with nothing to say so. Fall back to a full sync, which is correct if // wasteful; the alternative is silently stranding an edit. - if (channels.size() != m_editedAccounts.size()) + if (channels.size() != wanted.size()) return {}; // Stable order so a run is reproducible and the log reads the same way diff --git a/src/mainwindow.h b/src/mainwindow.h index 3829ae2..55d67ae 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1012,7 +1012,14 @@ private: /// the script's output is unstructured, so a bar that filled from left to /// right would be inventing a fraction nobody knows. An indeterminate one /// says "working, duration unknown", which is the truth. - void setSyncBusy(bool busy); + /// \p channels is the list the run was started with, used only for the + /// opening status line: empty means every account. Defaulted because the + /// exit paths overwrite the label with their own wording immediately after. + void setSyncBusy(bool busy, const QStringList &channels = {}); + + /// The opening status line for a run over \p channels. Item 101's + /// visibility half: a narrowed run must say so. + Q_INVOKABLE QString syncStartedText(const QStringList &channels) const; /// Reassembles lines from a sync output chunk and updates the status label /// when the phase or its detail changes. diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index 1c54ebb..b7f7846 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -438,6 +438,10 @@ private slots: void anUnknownExternalStateClearsNothing(); void aSuccessfulCronSyncDrainsTheEditedAccounts(); void aCronSyncDoesNotClearAnEditMadeWhileItRan(); + void syncNarrowsToTheSelectedAccount(); + void syncCoversEveryAccountWhenAllIsSelected(); + void aNarrowedSyncStillCarriesAnEditFromAnotherAccount(); + void theStatusLineNamesWhatASyncWillCover(); void everyActionCarriesAnIcon(); void everyActionIsReachableFromAMenu(); @@ -7496,6 +7500,198 @@ void TestMainWindow::aCronSyncDoesNotClearAnEditMadeWhileItRan() "an edit made after the sync ended was swallowed by it"); } +// Item 101. + +namespace { + +/// Writes a config with two accounts, each naming its own sync channel, so a +/// narrowed run can be told from a full one by the channel list alone. +void loadTwoAccountConfig(Config &config, const QTemporaryDir &dir, + const QString &logPath) +{ + const QString path = dir.filePath(QStringLiteral("qtmaildir.conf")); + QFile file(path); + QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); + file.write(QStringLiteral("[sync]\nlog=%1\nstatus=%2\n\n" + "[account.work]\n" + "maildir=work-mail\n" + "channel=work-channel\n\n" + "[account.personal]\n" + "maildir=personal-mail\n" + "channel=personal-channel\n") + .arg(logPath, dir.filePath(QStringLiteral("no-status.json"))) + .toUtf8()); + file.close(); + + config.load(path); + QCOMPARE(config.accounts().size(), 2); +} + +/// Records one edit against \p accountKey, by giving the model a thread tagged +/// for that account and sending a tag change over it. This is the route a real +/// edit takes, so m_editedAccounts is populated the way production populates it. +void recordEditForAccount(MainWindow &window, const QString &threadId, + const QString &accountKey) +{ + auto *model = window.findChild<ThreadListModel *>(); + QVERIFY(model); + ThreadSummary thread; + thread.threadId = threadId; + thread.subject = QStringLiteral("Subject"); + thread.totalCount = 1; + thread.firstMessageId = threadId + QStringLiteral("-m1"); + thread.tags = { QStringLiteral("account-") + accountKey, + QStringLiteral("inbox") }; + model->appendBatch({ thread }); + QCOMPARE(model->accountKeysForThread(threadId), QStringList{ accountKey }); + + QVERIFY(QMetaObject::invokeMethod( + &window, "sendThreadTagChange", + Q_ARG(QStringList, QStringList{ threadId }), + Q_ARG(QStringList, QStringList{ QStringLiteral("flagged") }), + Q_ARG(QStringList, QStringList{}), + Q_ARG(QString, QStringLiteral("Flag")))); +} + +/// QVERIFY2 expands to a bare `return`, so the invoke check cannot live in a +/// value-returning helper. Out-param instead, called through the macro below. +void readSyncChannels(MainWindow &window, QStringList &channels) +{ + QVERIFY2(QMetaObject::invokeMethod(&window, "pendingSyncChannels", + Q_RETURN_ARG(QStringList, channels)), + "pendingSyncChannels() could not be invoked"); +} + +#define SYNC_CHANNELS(window, out) \ + QStringList out; \ + readSyncChannels((window), (out)) + +} // namespace + +void TestMainWindow::syncNarrowsToTheSelectedAccount() +{ + // The user's decision on item 101: one Sync action, steered by the account + // dropdown. Selecting an account and pressing Sync collects that account + // only, rather than every channel. + QTemporaryDir dir; + QVERIFY(dir.isValid()); + const QString logPath = dir.filePath(QStringLiteral("mailsync.log")); + writeSyncLog(logPath, QStringLiteral("OK")); + + Config config; + loadTwoAccountConfig(config, dir, logPath); + MainWindow window(config); + + // The guard, and it matters: an empty list is what a FULL sync looks like, + // so a test starting from a narrowed state could not tell the fix from a + // window that had never widened. Nothing is selected and nothing is edited, + // so this must be the full-fetch signal before the gesture. + SYNC_CHANNELS(window, before); + QVERIFY2(before.isEmpty(), + "a fresh window with All accounts selected did not ask for a " + "full fetch"); + + window.selectAccountForTesting(QStringLiteral("work")); + + SYNC_CHANNELS(window, narrowed); + QCOMPARE(narrowed, QStringList{ QStringLiteral("work-channel") }); +} + +void TestMainWindow::syncCoversEveryAccountWhenAllIsSelected() +{ + // The other half of the same rule, and the one that keeps item 49's + // property: All accounts means a full fetch, which mailsync.sh turns into + // mbsync -a. Asserted after a narrowing so it proves the selection is read + // each time rather than latched once. + QTemporaryDir dir; + QVERIFY(dir.isValid()); + const QString logPath = dir.filePath(QStringLiteral("mailsync.log")); + writeSyncLog(logPath, QStringLiteral("OK")); + + Config config; + loadTwoAccountConfig(config, dir, logPath); + MainWindow window(config); + + window.selectAccountForTesting(QStringLiteral("work")); + SYNC_CHANNELS(window, narrowed); + QCOMPARE(narrowed, QStringList{ QStringLiteral("work-channel") }); + + window.selectAccountForTesting(QString()); + + SYNC_CHANNELS(window, widened); + QVERIFY2(widened.isEmpty(), + "going back to All accounts left the run narrowed"); +} + +void TestMainWindow::aNarrowedSyncStillCarriesAnEditFromAnotherAccount() +{ + // Item 101's standing constraint, and the reason the selection is a UNION + // with the pending set rather than a replacement for it. Looking at one + // account while having edited another is ordinary, and a run that dropped + // the other account's channel would strand that edit with nothing on + // screen to say so. + QTemporaryDir dir; + QVERIFY(dir.isValid()); + const QString logPath = dir.filePath(QStringLiteral("mailsync.log")); + writeSyncLog(logPath, QStringLiteral("OK")); + + Config config; + loadTwoAccountConfig(config, dir, logPath); + MainWindow window(config); + + // Edit in personal, then go and look at work. + recordEditForAccount(window, QStringLiteral("t1"), + QStringLiteral("personal")); + SYNC_CHANNELS(window, edited); + QCOMPARE(edited, QStringList{ QStringLiteral("personal-channel") }); + + window.selectAccountForTesting(QStringLiteral("work")); + + // Both: work because it is on screen, personal because it owes a write. + const QStringList expected{ QStringLiteral("personal-channel"), + QStringLiteral("work-channel") }; + SYNC_CHANNELS(window, both); + QCOMPARE(both, expected); +} + +void TestMainWindow::theStatusLineNamesWhatASyncWillCover() +{ + // Item 101's visibility half. The run was already account-aware before this + // item and the user could not tell, since a narrowed run and a full one + // showed the same three dots. Asserted on the generated STRING, which has a + // right answer, rather than on the label after a real run, which would be a + // test of QProcess timing. + QTemporaryDir dir; + QVERIFY(dir.isValid()); + const QString logPath = dir.filePath(QStringLiteral("mailsync.log")); + writeSyncLog(logPath, QStringLiteral("OK")); + + Config config; + loadTwoAccountConfig(config, dir, logPath); + MainWindow window(config); + + QString text; + + // Empty is the full-fetch signal, so it must not read as a channel list. + QVERIFY(QMetaObject::invokeMethod(&window, "syncStartedText", + Q_RETURN_ARG(QString, text), + Q_ARG(QStringList, QStringList{}))); + QCOMPARE(text, QStringLiteral("Syncing all accounts...")); + + QVERIFY(QMetaObject::invokeMethod( + &window, "syncStartedText", Q_RETURN_ARG(QString, text), + Q_ARG(QStringList, QStringList{ QStringLiteral("work-channel") }))); + QCOMPARE(text, QStringLiteral("Syncing work-channel...")); + + const QStringList two{ QStringLiteral("personal-channel"), + QStringLiteral("work-channel") }; + QVERIFY(QMetaObject::invokeMethod(&window, "syncStartedText", + Q_RETURN_ARG(QString, text), + Q_ARG(QStringList, two))); + QCOMPARE(text, + QStringLiteral("Syncing personal-channel, work-channel...")); +} + // Items 56 and 57. void TestMainWindow::everyActionIsReachableFromAMenu() diff --git a/translations/qtmaildir_it_IT.ts b/translations/qtmaildir_it_IT.ts index 4daea21..c0e336d 100644 --- a/translations/qtmaildir_it_IT.ts +++ b/translations/qtmaildir_it_IT.ts @@ -739,6 +739,14 @@ Il messaggio È stato inviato. Non inviarlo di nuovo.</translation> <translation>&Elimina conversazione</translation> </message> <message> + <source>Syncing all accounts...</source> + <translation>Sincronizzazione di tutti gli account...</translation> + </message> + <message> + <source>Syncing %1...</source> + <translation>Sincronizzazione di %1...</translation> + </message> + <message> <source>Undelete thread</source> <translation>Ripristina conversazione</translation> </message> @@ -1409,10 +1417,6 @@ Il messaggio È stato inviato. Non inviarlo di nuovo.</translation> <source>%1, waiting for the running sync to finish</source> <translation>%1, in attesa che termini la sincronizzazione in corso</translation> </message> - <message> - <source>Syncing...</source> - <translation>Sincronizzazione in corso...</translation> - </message> <message numerus="yes"> <source>%n unsynced change(s)</source> <translation> |
