aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_avatar.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_avatar.cpp')
-rw-r--r--tests/test_avatar.cpp115
1 files changed, 115 insertions, 0 deletions
diff --git a/tests/test_avatar.cpp b/tests/test_avatar.cpp
index 72e5941..cea88b3 100644
--- a/tests/test_avatar.cpp
+++ b/tests/test_avatar.cpp
@@ -30,10 +30,14 @@ private slots:
void bareAddressTakesLocalAndDomain();
void nothingUsableFallsBackToTheAccountLabel();
void initialsAreAlwaysTwoLetters();
+ void aRawFromHeaderIsNotSplitOnItsBracket();
+ void aCommaJoinedAuthorListTakesTheFirstAuthor();
+ void aSeparatorIsNotAWord();
void aDisplayNameMeansAPerson();
void theListOverridesADisplayName();
void aColourIsStablePerAddress();
void aPixmapIsStableAndDiffersPerSeed();
+ void bothTwoToneHuesReachTheFace();
};
void TestAvatar::twoWordNameTakesOneLetterFromEach()
@@ -95,6 +99,61 @@ void TestAvatar::initialsAreAlwaysTwoLetters()
}
}
+void TestAvatar::aRawFromHeaderIsNotSplitOnItsBracket()
+{
+ // A reply row's first line is the RAW header, so a naive space split gave
+ // the name's first letter and a literal `<`.
+ QCOMPARE(Avatar::initialsFor(
+ QStringLiteral("tsujan <notifications@github.com>"),
+ QStringLiteral("notifications@github.com"),
+ QStringLiteral("Work")),
+ QStringLiteral("TS"));
+ // A bare address in the name's place is not a name: the address branch
+ // answers, rather than the local part's first two letters.
+ QCOMPARE(Avatar::initialsFor(QStringLiteral("info@moomhotel.com"),
+ QStringLiteral("info@moomhotel.com"),
+ QStringLiteral("Work")),
+ QStringLiteral("IM"));
+ // And the fill agrees: neither of those is a display name.
+ QCOMPARE(Avatar::fillFor(QStringLiteral("info@moomhotel.com"), false),
+ Avatar::Fill::TwoTone);
+}
+
+void TestAvatar::aCommaJoinedAuthorListTakesTheFirstAuthor()
+{
+ // notmuch's author summary joins participants with a comma, so one letter
+ // from each gave initials belonging to two different people.
+ QCOMPARE(Avatar::initialsFor(QStringLiteral("Standreas, tsujan"),
+ QStringLiteral("notifications@github.com"),
+ QStringLiteral("Work")),
+ QStringLiteral("ST"));
+ // A QUOTED name may legally contain a comma and must survive whole.
+ QCOMPARE(Avatar::initialsFor(QStringLiteral("\"Rossi, Mario\""),
+ QStringLiteral("m@example.org"),
+ QStringLiteral("Work")),
+ QStringLiteral("RM"));
+}
+
+void TestAvatar::aSeparatorIsNotAWord()
+{
+ // `INE - Expert IT Training` took the dash as its second word and drew
+ // `I-`. A word has to carry a letter or a digit.
+ QCOMPARE(Avatar::initialsFor(QStringLiteral("INE - Expert IT Training"),
+ QStringLiteral("news@example.org"),
+ QStringLiteral("Work")),
+ QStringLiteral("IE"));
+ // Leading punctuation is trimmed rather than disqualifying the word.
+ QCOMPARE(Avatar::initialsFor(QStringLiteral("(Acme) Support"),
+ QStringLiteral("s@example.org"),
+ QStringLiteral("Work")),
+ QStringLiteral("AS"));
+ // Punctuation ONLY is no name at all: the address answers.
+ QCOMPARE(Avatar::initialsFor(QStringLiteral("- ---"),
+ QStringLiteral("news@example.org"),
+ QStringLiteral("Work")),
+ QStringLiteral("NE"));
+}
+
void TestAvatar::aDisplayNameMeansAPerson()
{
// The case the user asked for by name: a corporate address that presents
@@ -149,5 +208,61 @@ void TestAvatar::aPixmapIsStableAndDiffersPerSeed()
QVERIFY(first.toImage() != twoTone.toImage());
}
+void TestAvatar::bothTwoToneHuesReachTheFace()
+{
+ // The split has to cross the squircle, not graze its edge. Building the
+ // gradient axis as a RADIUS from the centre put the 0.5 stop on the
+ // boundary, so one hue filled almost the whole face and the fill read as
+ // flat: reported against noreply@cofidis.it. A colour count is what
+ // distinguishes the two, since both versions paint every pixel.
+ //
+ // Several seeds, because one unlucky angle proves nothing either way.
+ const QStringList seeds { QStringLiteral("noreply@cofidis.it"),
+ QStringLiteral("a@example.org"),
+ QStringLiteral("b@example.org"),
+ QStringLiteral("c@example.org") };
+ for (const QString &seed : seeds) {
+ const QImage face =
+ Avatar::pixmapFor(seed, QStringLiteral("XX"),
+ Avatar::Fill::TwoTone, 64, QFont()).toImage();
+
+ // The two hues, as painted. Sampled by counting pixels of each rather
+ // than probing a corner: which corner gets which hue depends on the
+ // hashed angle.
+ const QColor base = Avatar::colourFor(seed);
+ const QColor dark = base.darker(135);
+ int light = 0, shade = 0;
+ for (int y = 0; y < face.height(); ++y) {
+ for (int x = 0; x < face.width(); ++x) {
+ const QColor pixel = face.pixelColor(x, y);
+ if (pixel.alpha() < 255)
+ continue; // The squircle's antialiased corners.
+ // Nearest of the two, not an exact match: the gradient
+ // interpolates in premultiplied space and the pixel format
+ // rounds, so an exact compare finds NEITHER hue and the probe
+ // reports 0 against 0 whatever the code does.
+ const int toBase = qAbs(pixel.red() - base.red())
+ + qAbs(pixel.green() - base.green())
+ + qAbs(pixel.blue() - base.blue());
+ const int toDark = qAbs(pixel.red() - dark.red())
+ + qAbs(pixel.green() - dark.green())
+ + qAbs(pixel.blue() - dark.blue());
+ if (toBase < toDark)
+ ++light;
+ else
+ ++shade;
+ }
+ }
+
+ // A fifth of the face each: enough that neither is a sliver, loose
+ // enough that the hashed angle is free to put the split anywhere.
+ const int fifth = face.width() * face.height() / 5;
+ QVERIFY2(light > fifth && shade > fifth,
+ qPrintable(QStringLiteral("%1: one hue took the face, %2 "
+ "light against %3 dark")
+ .arg(seed).arg(light).arg(shade)));
+ }
+}
+
QTEST_MAIN(TestAvatar)
#include "test_avatar.moc"