diff --git a/app/qml/Main.qml b/app/qml/Main.qml index 2f8dc9a..c5c8f09 100644 --- a/app/qml/Main.qml +++ b/app/qml/Main.qml @@ -374,6 +374,7 @@ ApplicationWindow { ChatView { id: chat + objectName: "chatView" Layout.fillWidth: true Layout.fillHeight: true model: transcript diff --git a/app/src/ui/UiTest.cpp b/app/src/ui/UiTest.cpp index 48bbec0..d7c5f7d 100644 --- a/app/src/ui/UiTest.cpp +++ b/app/src/ui/UiTest.cpp @@ -146,6 +146,27 @@ QPoint centre(QQuickItem *it) { return p.toPoint(); } +/* Is this item's centre really reachable? isVisible() answers whether hiding + * was asked for, not whether a clipping ancestor (a scrolling view) has moved + * it out of sight -- and an event goes to whatever is at that point instead. */ +bool onScreen(QQuickItem *it) { + if (!it || !it->isVisible() || it->width() <= 0 || it->height() <= 0) + return false; + QQuickWindow *win = it->window(); + if (!win) + return false; + const QPointF p = it->mapToScene(QPointF(it->width() / 2, it->height() / 2)); + if (!QRectF(0, 0, win->width(), win->height()).contains(p)) + return false; + for (QQuickItem *a = it->parentItem(); a; a = a->parentItem()) { + if (!a->clip()) + continue; + if (!a->mapRectToScene(QRectF(0, 0, a->width(), a->height())).contains(p)) + return false; + } + return true; +} + void clickMouse(QQuickWindow *win, const QString &name) { QQuickItem *it = item(win, name); if (!it || !it->isVisible()) { @@ -437,19 +458,59 @@ int runUiTest(AgentCore &core, const QString &size) { item(win, QStringLiteral("fileShare0")) != nullptr, QStringLiteral("the card carries open and share")); win->setProperty("statusText", QString()); + /* A click goes to whatever is at that point, and the transcript replays + * from its bottom: on a phone-sized window the card can be above the + * visible part, where isVisible() still says yes (it does not know about + * the view's clipping) and the click lands on the row that happens to be + * there. Scroll it in first, which is what a person does -- and stop the + * view following its tail (ChatView.stickBottom), or the scroll is undone + * by the next content change. */ + if (list && !onScreen(item(win, QStringLiteral("fileOpen0")))) { + QQuickItem *button = item(win, QStringLiteral("fileOpen0")); + QQuickItem *chat = item(win, QStringLiteral("chatView")); + if (chat) + chat->setProperty("stickBottom", false); + const double contentY = list->property("contentY").toDouble(); + const double y = button->mapToItem(list, QPointF(0, 0)).y() + contentY; + const double lowest = + list->property("contentHeight").toDouble() - list->height(); + note(QStringLiteral("the card is out of the viewport (contentY %1, card " + "at %2 of %3): scrolling to it") + .arg(contentY) + .arg(y) + .arg(list->property("contentHeight").toDouble())); + list->setProperty("contentY", qBound(0.0, y - list->height() / 2, lowest)); + QTest::qWait(150); + } clickMouse(win, QStringLiteral("fileOpen0")); - /* This button lives in the scrolling transcript, and on the NX789J the - * synthesized mouse click does not reach a button there -- the same click - * lands in the header and in the sidebar. What a phone has is a finger, so - * try that too, and say in the message which one it took: neither is a - * failure to hide. */ + /* What the card itself hands the engine ("rel" is the name the store keeps + * the file under), and whether it is really on screen: a card outside the + * viewport takes the events meant for it, and the card's own line is where + * the engine puts its refusal. */ + QStringList rels; + QString cardSays; + QList strips; + collectNamed(win->contentItem(), QStringLiteral("imageStrip"), &strips); + for (QQuickItem *s : strips) { + const QVariantList files = s->property("files").toList(); + for (const QVariant &f : files) + rels << f.toMap().value(QStringLiteral("rel")).toString(); + if (cardSays.isEmpty()) + cardSays = s->property("errorText").toString(); + } const bool byMouse = !win->property("statusText").toString().isEmpty(); if (!byMouse) tapTouch(win, touch, QStringLiteral("fileOpen0")); const QString opened = win->property("statusText").toString(); check(opened.startsWith(QLatin1String("opened ")), - QStringLiteral("Open hands the file to the system (%1%2)") - .arg(opened, byMouse ? QString() : QStringLiteral(" after a tap"))); + QStringLiteral("Open hands the file to the system (%1%2; cards hold '%3', " + "%4, card says '%5')") + .arg(opened, byMouse ? QString() : QStringLiteral(" after a tap"), + rels.join(QLatin1String(", ")), + onScreen(item(win, QStringLiteral("fileOpen0"))) + ? QStringLiteral("on screen") + : QStringLiteral("out of the viewport"), + cardSays)); win->setProperty("statusText", QString()); clickMouse(win, QStringLiteral("fileShare0")); const bool shareByMouse = !win->property("statusText").toString().isEmpty(); @@ -457,8 +518,9 @@ int runUiTest(AgentCore &core, const QString &size) { tapTouch(win, touch, QStringLiteral("fileShare0")); const QString shared = win->property("statusText").toString(); check(shared.contains(QLatin1String("report.md")), - QStringLiteral("Share names the file (%1%2)") - .arg(shared, shareByMouse ? QString() : QStringLiteral(" after a tap"))); + QStringLiteral("Share names the file (%1%2; card says '%3')") + .arg(shared, shareByMouse ? QString() : QStringLiteral(" after a tap"), + cardSays)); const QString refused = core.openArtifact(QStringLiteral("art-not-one-of-ours.md")); check(refused.contains(QLatin1String("no such file")),