204 lines
11 KiB
C++
204 lines
11 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#pragma once
|
|
|
|
#include "test/test_runner.h"
|
|
|
|
#include <QtCore/QPointer>
|
|
#include <QtGui/QImage>
|
|
#include <QtWidgets/QWidget>
|
|
|
|
#include <vector>
|
|
|
|
namespace Test {
|
|
|
|
// The one capture for a toast, and the only one whose saved frame cannot
|
|
// hold the product surface the toast was drawn over.
|
|
//
|
|
// Test::CaptureViaWindow grabs the toast's window cropped to the toast's
|
|
// rect mapped into it. That is the right answer to blankness and the wrong
|
|
// answer to hygiene. st::toastBg is #2c3033e5 (ui/colors.palette) - alpha
|
|
// 0xE5, about 90% opaque - so roughly a tenth of every pixel the product
|
|
// painted underneath composes into the saved frame, legibly enough to read
|
|
// text through it. The rect's corners are worse than that:
|
|
// Widget::paintBackground is _roundRect.paint(p, rect())
|
|
// (ui/toast/toast_widget.cpp), which fills the straight bands and draws
|
|
// four corner images (Ui::DrawRoundedRect in ui/round_rect.cpp) whose mask
|
|
// is filled with Qt::transparent and carries only a rounded rect
|
|
// (PrepareCornersMask in ui/image/image_prepare.cpp), so outside the arc
|
|
// the toast paints nothing at all and whatever is behind it shows there
|
|
// unblended.
|
|
//
|
|
// Test::GrabWidget and Test::GrabRect call Ui::GrabWidgetToImage(widget,
|
|
// rect, st::windowBg->c) (test_capture.cpp), which fills the result with
|
|
// the harness theme base and then renders only that target's own subtree
|
|
// (ui/ui_utility.cpp). A grab rooted at the toast is therefore hygienic by
|
|
// construction: every pixel the translucent toast leaves uncovered measures
|
|
// st::windowBg and never the product. That is the whole mechanism this
|
|
// module rests on, and it is why a root that is not the live toast is
|
|
// refused here rather than reframed.
|
|
//
|
|
// Ui::Toast::internal::Widget::_shownLevel is private (toast_widget.h), and
|
|
// while it is below 1 paintEvent draws the whole frame into a transparent
|
|
// proxy at that opacity and returns (toast_widget.cpp) - which is exactly
|
|
// where a toast-rooted grab reads blank. So settledness here is measured
|
|
// and not read: the settled background blend is derived from the live
|
|
// palette and one grab is sampled against it.
|
|
//
|
|
// Ui::Toast::internal::Manager exposes only instance() and addToast() and
|
|
// keeps its _toastByWidget map private (ui/toast/toast_manager.h), so no
|
|
// API enumerates live toasts and the honest resolution is the harness's own
|
|
// visible-widget walk over the top level widgets.
|
|
//
|
|
// This is its own module rather than part of test_capture.h because it
|
|
// appends Runner stages and test_runner.cpp already includes
|
|
// test_capture.h: the reverse include would invert the harness's layering
|
|
// and make its most-included module runner-aware, which is the rule
|
|
// the layering note above PopupMenuReading in test_menu.h states. It is
|
|
// not part of test_via_window.* either, because that module is the
|
|
// self-test of CaptureViaWindow and is this module's own
|
|
// unchanged-scenario control.
|
|
|
|
// |toast| is non-null exactly when |refusal| is empty at the moment the
|
|
// reading is taken: a caller cannot take the pointer without being handed
|
|
// the reason there is none. It is a QPointer<QWidget>, so a retained
|
|
// reading can only go from resolved to unresolved and never hands back a
|
|
// pointer into freed memory, while |frame|, |identity| and |refusal| are
|
|
// values that stay printable after the toast is gone.
|
|
// Test::WindowMappedCapture (test_capture.h) and
|
|
// Test::PaintingLayerRootResult carry the same type and the same contract.
|
|
struct ToastSubtreeReading {
|
|
QPointer<QWidget> toast;
|
|
QRect frame;
|
|
QString refusal;
|
|
QString identity;
|
|
|
|
[[nodiscard]] bool resolved() const {
|
|
return toast != nullptr;
|
|
}
|
|
};
|
|
|
|
// The visible-widget walk that answers which toasts are live now. It runs
|
|
// over every top level widget rather than one root, because a toast is
|
|
// parented wherever Ui::Toast::Show was called. FindLiveToast() answers
|
|
// that single toast, and nullptr when the walk finds zero of them or more
|
|
// than one - an ambiguous answer is refused rather than guessed.
|
|
[[nodiscard]] std::vector<QWidget*> FindLiveToasts();
|
|
[[nodiscard]] QWidget *FindLiveToast();
|
|
|
|
// ReadToastSubtree takes no grab. A root that is not a live
|
|
// Ui::Toast::internal::Widget is refused by name, and so is a child inside
|
|
// a toast, on purpose: the frame is rooted at the toast or nowhere,
|
|
// because everything above it paints the product and everything below it
|
|
// is not the toast. A non-empty |requested| that the toast's own rect does
|
|
// not contain is refused quoting both rects, never silently reframed.
|
|
[[nodiscard]] ToastSubtreeReading ReadToastSubtree(
|
|
QWidget *widget,
|
|
QRect requested = QRect());
|
|
|
|
// GrabToastSubtree, ToastSubtreeReady and ToastSubtreeDetails take exactly
|
|
// one grab each, so a poll costs one grab per tick - the budget
|
|
// test_capture.h's comment on WindowMappedCapture promises for the
|
|
// window-mapped family. A refused request answers a null image and never a
|
|
// reframed one, so a poll around a refused target ends in a named stage
|
|
// timeout rather than in a grab of something else. ToastSubtreeReady is the
|
|
// settled-show term: it is true once the sampled frame matches
|
|
// st::toastBg's own settled blend over the harness base, which no mid-fade
|
|
// frame does.
|
|
[[nodiscard]] QImage GrabToastSubtree(
|
|
QWidget *widget,
|
|
QRect requested = QRect());
|
|
[[nodiscard]] bool ToastSubtreeReady(QWidget *widget);
|
|
[[nodiscard]] QString ToastSubtreeDetails(QWidget *widget);
|
|
|
|
// Loud on a structural refusal, because no amount of waiting repairs one:
|
|
// no widget, a root that is not the live toast, an invisible or empty
|
|
// target, or a rect outside the toast's geometry. A Note and never a FAIL
|
|
// on an unsettled or blank frame, because the decisive oracle for a toast
|
|
// is textual and this capture corroborates it.
|
|
bool CaptureToastSubtree(
|
|
not_null<QWidget*> widget,
|
|
const QString &name,
|
|
QRect requested = QRect());
|
|
|
|
// The joined accessibilityName() of the toast's Ui::FlatLabels - the read
|
|
// the harness README already calls decisive - and its comparison, which
|
|
// goes through the shipped Test::CheckTextReads (test_text_reads.h) so that
|
|
// the space-class normalization and the both-verdicts printing are composed
|
|
// rather than re-derived. A widget that is not a live toast reads back an
|
|
// empty string.
|
|
[[nodiscard]] QString ReadToastText(QWidget *widget);
|
|
void CheckToastReads(
|
|
QWidget *widget,
|
|
const QString &expected,
|
|
const QString &what);
|
|
|
|
// This module measuring itself, in seven stages, over a synthetic
|
|
// sentinel-bearing surface it paints and a real Ui::Toast of its own.
|
|
//
|
|
// The surface is two horizontal bands of two high-contrast tones chosen so
|
|
// that nothing a toast-rooted frame can hold comes near either of them on
|
|
// any palette, and not merely on the one the tones were picked against.
|
|
// Such a frame carries st::windowBg, blends of st::toastBg over it and
|
|
// st::toastFg label ink, every one of which is a grey or within a few
|
|
// channel units of one, as is every antialiased mixture of them; and
|
|
// Test::ChannelDelta is the maximum absolute per-channel difference, so
|
|
// the nearest grey to a colour sits exactly half that colour's own channel
|
|
// span away. Both tones span nearly the whole range, which puts every grey
|
|
// at least 95 channel units from either and every near-grey at least about
|
|
// 92 - far outside the counter's 40-unit tolerance, whether the theme is
|
|
// light or dark. Stage 1 measures that instead of trusting it: a named
|
|
// fixture gate reports the separation from st::windowBg, from the settled
|
|
// toast blend and from st::toastFg, and FAILs if the smallest of them
|
|
// drops below twice the tolerance, so a palette that ever breaks the
|
|
// argument says so in the log rather than flipping the counter's verdict
|
|
// on a correct frame. The two tones also keep a window-mapped crop of the
|
|
// same rect clear of the blank threshold without resting on the toast's
|
|
// own paint - their lightness is 160 against 96 - which is the reason
|
|
// test_via_window.cpp's comment on PaintTwoTone gives for using two. No
|
|
// account fixture secret, wallet phrase, password or hint is read, painted
|
|
// or named anywhere: the only text the self-test paints is its own literal.
|
|
//
|
|
// Stage 1 reads the fade-in window in the one turn it exists, where the
|
|
// shown level is still 0, and shows the settled-show term answering false
|
|
// there and the capture declining with a Note that moves no failure count.
|
|
// Stage 2 waits on that same term as a real poll and then decides the
|
|
// module's claim twice: once by counting sentinel pixels in the two saved
|
|
// PNGs of the same toast in the same turn, and once - strictly stronger
|
|
// and independent of the corners - by hiding the surface beneath and
|
|
// showing that no pixel of the subtree frame moved while many pixels of
|
|
// the window-mapped frame did. It also reads the toast's phrase back and
|
|
// shows the same comparison declining a different phrase. Stage 3 shows a
|
|
// second toast beside the fixture and takes it down inside the same turn,
|
|
// where the walk answers two and FindLiveToast() refuses the ambiguity
|
|
// with nullptr; the fixture is shown single again there and a tick later.
|
|
// Stages 4 and 5 show a non-infinite toast with kDefaultDuration, wait on
|
|
// ToastSubtreeReady inside its lifetime, then wait until the product's
|
|
// own hide path has taken it out of the walk - quoting the live count,
|
|
// the texts and the elapsed time - so the fixture is single again before
|
|
// Stage 6 reads. Stage 6 refuses a null, an ancestor that paints the
|
|
// product and a rect larger than the toast, each by name and quoting both
|
|
// rects. Stage 7 is teardown, and it is where the other refusal is read:
|
|
// the control walk answers the fixture toast alone, this module's own
|
|
// Instance::hide() takes it down, and the same walk then answers an empty
|
|
// list that FindLiveToast() refuses with nullptr.
|
|
//
|
|
// It needs no session, no chats list, no network and no account fixture.
|
|
// The only thing it asks of the process is a primary window to parent the
|
|
// fixture to, and a missing one is reported as a named fixture gate
|
|
// instead of crashing. It appends its own teardown last, and it emits no
|
|
// deliberate failure: every refusal it demonstrates is observed through
|
|
// the pure FindLiveToasts, FindLiveToast, ReadToastSubtree,
|
|
// ToastSubtreeReady and ToastSubtreeDetails readings, which log nothing,
|
|
// and asserted as a passing Check whose details carry the refusal
|
|
// verbatim. test_text_reads.h's comment on AppendTextReadsSelfTest records
|
|
// that module as the harness's one self-test that emits deliberate
|
|
// failures, and this one does not become a second.
|
|
void AppendToastSubtreeCaptureSelfTest(not_null<Runner*> runner);
|
|
|
|
} // namespace Test
|