aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-04 10:10:19 +0200
committerDanilo M. <danix@danix.xyz>2026-08-04 12:54:35 +0200
commitcf75b8ecf7d27fda217023507b8ed4df1f198613 (patch)
treee75025c35540d9422488fe51c3dcd2f53e9c8ff4 /tests
parent4df7edfc259fb3bdb3814ffe6549ecd3ee448578 (diff)
downloadqtmaildir-cf75b8ecf7d27fda217023507b8ed4df1f198613.tar.gz
qtmaildir-cf75b8ecf7d27fda217023507b8ed4df1f198613.zip
feat(completion): size the description column by need, not by half
The delegate split each row evenly between the value and its description. That reads as fair but spends the width on the wrong column: the values are short, the longest prefix being "attachment:", while the descriptions are ordinary prose. Measured against the built-in vocabulary, ten of fifteen descriptions elided at a 400px popup and the longest, "directory below the Maildir root", needed a ~650px popup to appear in full, all while the value's half of the row sat mostly empty. The descriptions are the whole reason the popup teaches the query language, so a half they cannot use is a half wasted. Measure the value and lend the description the remainder, capped at 65% of the row. Every built-in description now fits at ~500px instead of ~700px. The cap is what keeps a genuinely long value legible, and the value's own reservation is capped in turn so that "application/vnd.oasis.opendocument.text" elides itself rather than claiming the row and silencing the column that explains what it is. The test renders the real popup and reads the pixels back, since the delegate is private to the .cpp and legibility is a painting question. It asserts the painted width against the width the text needs unelided, not merely that something was drawn: a blank-or-not check passes against the very rule this replaces. 550px is chosen deliberately, being a width where the new rule paints the longest description in full and an even split cannot; against the old code it fails with "painted 261px of the 285px it needs". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/test_querycompleter.cpp95
1 files changed, 95 insertions, 0 deletions
diff --git a/tests/test_querycompleter.cpp b/tests/test_querycompleter.cpp
index 09eedc3..6cf9651 100644
--- a/tests/test_querycompleter.cpp
+++ b/tests/test_querycompleter.cpp
@@ -20,7 +20,9 @@
#include <QTemporaryDir>
#include <QLineEdit>
+#include <QImage>
#include <QListView>
+#include <QPixmap>
#include "config.h"
#include "querycompleter.h"
@@ -81,6 +83,7 @@ private slots:
void returnRunsTheQueryOnceCompletionIsDone();
void returnRunsTheQueryAfterAMouseAccept();
void returnRunsTheQueryWhenThePopupMatchesNothing();
+ void theDescriptionSurvivesAModestPopupWidth();
};
// Copied from tests/test_config.cpp rather than shared, so the two test files
@@ -781,5 +784,97 @@ void TestQueryCompleter::returnRunsTheQueryWhenThePopupMatchesNothing()
QVERIFY(ran);
}
+void TestQueryCompleter::theDescriptionSurvivesAModestPopupWidth()
+{
+ // The delegate lends the description whatever the value does not need, up
+ // to 65% of the row. Under the previous even split the longest built-in
+ // description needed a ~650px popup to survive; it now needs ~500px, which
+ // is the difference between the column working at an ordinary window size
+ // and being decorative.
+ //
+ // 550px is chosen to sit inside that band: the current rule paints the text
+ // in full there, an even split cannot. A width outside the band would pass
+ // against both rules and prove nothing.
+ //
+ // The delegate is private to the .cpp, so this renders the real popup and
+ // reads the pixels back rather than reaching for the class: whether the text
+ // is legible on screen is a painting question, not an arithmetic one.
+ Config config;
+ QLineEdit edit;
+ edit.resize(550, edit.sizeHint().height());
+ edit.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&edit));
+ edit.setFocus();
+ QueryCompleter completer(&edit, config);
+
+ QTest::keyClicks(&edit, QStringLiteral("path"));
+ QListView *popup = findPopup();
+ QVERIFY(popup && popup->isVisible());
+ QVERIFY(QTest::qWaitForWindowExposed(popup));
+
+ // "path:" carries the longest built-in description, so it is the row that
+ // fails first if the column is starved.
+ const QModelIndex row = popup->model()->index(0, 0);
+ QVERIFY(row.isValid());
+ QCOMPARE(row.data(Qt::DisplayRole).toString(), QStringLiteral("path:"));
+
+ const QRect rect = popup->visualRect(row);
+ QVERIFY(rect.isValid());
+ QPixmap shot = popup->viewport()->grab(rect);
+ QVERIFY(!shot.isNull());
+ const QImage image = shot.toImage();
+
+ // Measure how much of the row carries ink, then compare that against the
+ // width the description needs when it is NOT elided.
+ //
+ // "Something was drawn" is too weak a check: the previous even-split rule
+ // also drew the description, just cut down to an ellipsis, so a blank-or-not
+ // test passes against the very code this replaces. What distinguishes the
+ // two is whether the full text fits, which is a width comparison.
+ const QRgb background = image.pixel(image.width() - 2, image.height() / 2);
+ int rightmostInk = -1;
+ int leftmostInkAfterValue = image.width();
+ for (int x = 0; x < image.width(); ++x) {
+ for (int y = 0; y < image.height(); ++y) {
+ if (image.pixel(x, y) != background) {
+ rightmostInk = qMax(rightmostInk, x);
+ break;
+ }
+ }
+ }
+ QVERIFY2(rightmostInk >= 0, "the row rendered entirely blank");
+
+ // The description is right-aligned, so the ink running to the right edge is
+ // the description itself. Walk left from there over the contiguous run to
+ // find how wide it was actually painted.
+ int x = rightmostInk;
+ int gapRun = 0;
+ while (x > 0 && gapRun < 8) {
+ bool column = false;
+ for (int y = 0; y < image.height(); ++y) {
+ if (image.pixel(x, y) != background) {
+ column = true;
+ break;
+ }
+ }
+ gapRun = column ? 0 : gapRun + 1;
+ if (column)
+ leftmostInkAfterValue = x;
+ --x;
+ }
+
+ const QFontMetrics metrics(popup->font());
+ const QString description = popup->model()->index(0, 1).data().toString();
+ QCOMPARE(description, QStringLiteral("directory below the Maildir root"));
+ const int painted = rightmostInk - leftmostInkAfterValue;
+ const int needed = metrics.horizontalAdvance(description);
+
+ // Allow a little slack for antialiasing at the glyph edges.
+ QVERIFY2(painted >= needed - 4,
+ qPrintable(QStringLiteral("description elided: painted %1px of the "
+ "%2px it needs")
+ .arg(painted).arg(needed)));
+}
+
QTEST_MAIN(TestQueryCompleter)
#include "test_querycompleter.moc"