Skip to content

Commit

Permalink
testlib: make it possible to test double-clicks with discrete events
Browse files Browse the repository at this point in the history
The timestamp will no longer be incremented by 500ms after a mouse
release if the delay has been explicitly specified.

The default delay is 1 ms since f5010c4
but the running timestamp was unconditionally post-incremented by 500ms
after every mouse release, to prevent double-clicks, which were always
deemed as unintended (because we have a mouseDClick function for that).
Now, we do that 500ms increment only if the user has not provided a
delay value in the function argument at all. We have often found it
useful in our own tests to generate double-clicks "the hard way", by
sending indivdual events, so as to be able to check state in some target
object at each step, as shown in the new snippet.

[ChangeLog][QtTest] QTest::mouseRelease() and mouseClick() can now be
used to test double-clicks, by specifying a realistic timestamp delay.

Fixes: QTBUG-102441
Change-Id: I8e8d242061f79efb4c6e02638645e03661a9cd92
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
  • Loading branch information
ec1oud committed Oct 16, 2022
1 parent 5caf808 commit 0f94430
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 12 deletions.
13 changes: 13 additions & 0 deletions src/testlib/doc/snippets/code/src_qtestlib_qtestcase_snippet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,16 @@ char *toString(const MyType &t)
return repr;
}
//! [34]

//! [35]
QSignalSpy doubleClickSpy(target, &TargetClass::doubleClicked);
const QPoint p(1, 2);
QTest::mousePress(&myWindow, Qt::LeftButton, Qt::NoModifier, p);
QVERIFY(target.isPressed());
QTest::mouseRelease(&myWindow, Qt::LeftButton, Qt::NoModifier, p, 10);
QCOMPARE(target.isPressed(), false);
QTest::mousePress(&myWindow, Qt::LeftButton, Qt::NoModifier, p, 10);
QCOMPARE(target.pressCount(), 2);
QTest::mouseRelease(&myWindow, Qt::LeftButton, Qt::NoModifier, p, 10);
QCOMPARE(doubleClickSpy.count(), 1);
//! [35]
22 changes: 20 additions & 2 deletions src/testlib/qtestcase.qdoc
Original file line number Diff line number Diff line change
Expand Up @@ -1260,7 +1260,16 @@
on a \a widget. The position of the release is defined by \a pos;
the default position is the center of the widget. If \a delay is
specified, the test will wait for the specified amount of
milliseconds before releasing the button.
milliseconds before releasing the button; otherwise, it will wait for a
default amount of time (1 ms), which can be overridden via
\l {Testing Options}{command-line arguments}.

\note If you wish to test a double-click by sending events individually,
specify a short delay, greater than the default, on both mouse release events.
The total of the delays for the press, release, press and release must be
less than QStyleHints::mouseDoubleClickInterval(). But if you don't need
to check state between events, it's better to use QTest::mouseDClick().
\snippet code/src_qtestlib_qtestcase_snippet.cpp 35

\sa QTest::mousePress(), QTest::mouseClick()
*/
Expand All @@ -1273,7 +1282,16 @@
on a \a window. The position of the release is defined by \a pos;
the default position is the center of the window. If \a delay is
specified, the test will wait for the specified amount of
milliseconds before releasing the button.
milliseconds before releasing the button; otherwise, it will wait for a
default amount of time (1 ms), which can be overridden via
\l {Testing Options}{command-line arguments}.

\note If you wish to test a double-click by sending events individually,
specify a short delay, greater than the default, on both mouse release events.
The total of the delays for the press, release, press and release must be
less than QStyleHints::mouseDoubleClickInterval(). But if you don't need
to check state between events, it's better to use QTest::mouseDClick().
\snippet code/src_qtestlib_qtestcase_snippet.cpp 35

\sa QTest::mousePress(), QTest::mouseClick()
*/
Expand Down
14 changes: 10 additions & 4 deletions src/testlib/qtestmouse.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ namespace QTest
event. We expect all event-handling code to rely on the event
timestamps, not the system clock; therefore tests can be run faster
than real-time.
If \a delay is not given, a default minimum mouse delay is used, and
unintended double-click events are prevented by incrementing the
timestamp by 500ms after each mouse release. Therefore, to test
double-clicks, it's necessary to give a realistic \a delay value (for
example, 10ms).
*/
static void mouseEvent(MouseAction action, QWindow *window, Qt::MouseButton button,
Qt::KeyboardModifiers stateKey, QPoint pos, int delay=-1)
Expand All @@ -71,9 +77,8 @@ namespace QTest
pos.x(), pos.y(), windowSize.width(), windowSize.height());
}

if (delay == -1 || delay < defaultMouseDelay())
delay = defaultMouseDelay();
lastMouseTimestamp += qMax(1, delay);
int actualDelay = (delay == -1 || delay < defaultMouseDelay()) ? defaultMouseDelay() : delay;
lastMouseTimestamp += qMax(1, actualDelay);

if (pos.isNull())
pos = QPoint(window->width() / 2, window->height() / 2);
Expand Down Expand Up @@ -108,7 +113,8 @@ namespace QTest
qtestMouseButtons.setFlag(button, false);
qt_handleMouseEvent(w, pos, global, qtestMouseButtons, button, QEvent::MouseButtonRelease,
stateKey, lastMouseTimestamp);
lastMouseTimestamp += mouseDoubleClickInterval; // avoid double clicks being generated
if (delay == -1)
lastMouseTimestamp += mouseDoubleClickInterval; // avoid double clicks being generated
break;
case MouseMove:
qt_handleMouseEvent(w, pos, global, qtestMouseButtons, Qt::NoButton, QEvent::MouseMove,
Expand Down
3 changes: 2 additions & 1 deletion tests/auto/testlib/selftests/expected_mouse.junitxml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<testsuite name="tst_Mouse" timestamp="@TEST_START_TIME@" hostname="@HOSTNAME@" tests="13" failures="0" errors="0" skipped="3" time="@TEST_DURATION@">
<testsuite name="tst_Mouse" timestamp="@TEST_START_TIME@" hostname="@HOSTNAME@" tests="14" failures="0" errors="0" skipped="3" time="@TEST_DURATION@">
<properties>
<property name="QTestVersion" value="@INSERT_QT_VERSION_HERE@"/>
<property name="QtVersion" value="@INSERT_QT_VERSION_HERE@"/>
Expand All @@ -23,5 +23,6 @@
<testcase name="deterministicEvents(first&#x002D;run&#x002D;false)" classname="tst_Mouse" time="@TEST_DURATION@">
<skipped message="Not implemented!"/>
</testcase>
<testcase name="doubleClick" classname="tst_Mouse" time="@TEST_DURATION@"/>
<testcase name="cleanupTestCase" classname="tst_Mouse" time="@TEST_DURATION@"/>
</testsuite>
4 changes: 4 additions & 0 deletions tests/auto/testlib/selftests/expected_mouse.lightxml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
</Incident>
<Duration msecs="0"/>
</TestFunction>
<TestFunction name="doubleClick">
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
</TestFunction>
<TestFunction name="cleanupTestCase">
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
Expand Down
9 changes: 5 additions & 4 deletions tests/auto/testlib/selftests/expected_mouse.tap
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ ok 9 - stateHandlingPart1(dummy-2)
ok 10 - stateHandlingPart2() # SKIP Not implemented beyond this point!
ok 11 - deterministicEvents(first-run-true) # SKIP Not implemented!
ok 12 - deterministicEvents(first-run-false) # SKIP Not implemented!
ok 13 - cleanupTestCase()
1..13
# tests 13
# pass 10
ok 13 - doubleClick()
ok 14 - cleanupTestCase()
1..14
# tests 14
# pass 11
# fail 0
2 changes: 2 additions & 0 deletions tests/auto/testlib/selftests/expected_mouse.teamcity
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
##teamcity[testStarted name='deterministicEvents(first-run-false)' flowId='tst_Mouse']
##teamcity[testIgnored name='deterministicEvents(first-run-false)' message='Not implemented! |[Loc: qtbase/tests/auto/testlib/selftests/mouse/tst_mouse.cpp(0)|]' flowId='tst_Mouse']
##teamcity[testFinished name='deterministicEvents(first-run-false)' flowId='tst_Mouse']
##teamcity[testStarted name='doubleClick()' flowId='tst_Mouse']
##teamcity[testFinished name='doubleClick()' flowId='tst_Mouse']
##teamcity[testStarted name='cleanupTestCase()' flowId='tst_Mouse']
##teamcity[testFinished name='cleanupTestCase()' flowId='tst_Mouse']
##teamcity[testSuiteFinished name='tst_Mouse' flowId='tst_Mouse']
3 changes: 2 additions & 1 deletion tests/auto/testlib/selftests/expected_mouse.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ SKIP : tst_Mouse::deterministicEvents(first-run-true) Not implemented!
Loc: [qtbase/tests/auto/testlib/selftests/mouse/tst_mouse.cpp(0)]
SKIP : tst_Mouse::deterministicEvents(first-run-false) Not implemented!
Loc: [qtbase/tests/auto/testlib/selftests/mouse/tst_mouse.cpp(0)]
PASS : tst_Mouse::doubleClick()
PASS : tst_Mouse::cleanupTestCase()
Totals: 10 passed, 0 failed, 3 skipped, 0 blacklisted, 0ms
Totals: 11 passed, 0 failed, 3 skipped, 0 blacklisted, 0ms
********* Finished testing of tst_Mouse *********
4 changes: 4 additions & 0 deletions tests/auto/testlib/selftests/expected_mouse.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
</Incident>
<Duration msecs="0"/>
</TestFunction>
<TestFunction name="doubleClick">
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
</TestFunction>
<TestFunction name="cleanupTestCase">
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>
Expand Down
49 changes: 49 additions & 0 deletions tests/auto/testlib/selftests/mouse/tst_mouse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ private slots:
void stateHandlingPart2();
void deterministicEvents_data();
void deterministicEvents();
void doubleClick();
};

class MouseWindow : public QWindow
Expand Down Expand Up @@ -251,5 +252,53 @@ void tst_Mouse::deterministicEvents()
QCOMPARE(w.moveCount, 1);
}

void tst_Mouse::doubleClick()
{
MouseWindow w;
w.show();
w.setGeometry(100, 100, 200, 200);
QVERIFY(QTest::qWaitForWindowActive(&w));

// click
QPoint point(10, 10);
QCOMPARE(w.pressCount, 0);
QTest::mousePress(&w, Qt::LeftButton, { }, point);
QCOMPARE(w.pressCount, 1);
// give a delay of 10ms
auto ts = w.lastTimeStamp;
QTest::mouseRelease(&w, Qt::LeftButton, { }, point, 10);
QCOMPARE(w.lastTimeStamp, ts + 10);
QCOMPARE(w.doubleClickCount, 0);

// click again within a short time to generate double-click
QTest::mousePress(&w, Qt::LeftButton, { }, point, 10);
QCOMPARE(w.pressCount, 2);
QCOMPARE(w.lastTimeStamp, ts + 20);
// this time, let some virtual time elapse, because we're going to test double-click again afterwards
QTest::mouseRelease(&w, Qt::LeftButton, { }, point);
QCOMPARE_GT(w.lastTimeStamp, ts + 20);
QCOMPARE(w.doubleClickCount, 1);

// use the mouseClick function to generate another double-click
ts = w.lastTimeStamp;
QTest::mouseClick(&w, Qt::LeftButton, {}, point, 10);
QCOMPARE_GE(w.lastTimeStamp, ts + 500); // because the last release had a default delay
QTest::mouseClick(&w, Qt::LeftButton, {}, point);
QCOMPARE(w.doubleClickCount, 2);

// use the mouseDClick function to generate another double-click
ts = w.lastTimeStamp;
QTest::mouseDClick(&w, Qt::LeftButton, {}, point);
QCOMPARE_GE(w.lastTimeStamp, ts + 500); // because the last release had a default delay
QCOMPARE(w.doubleClickCount, 3);

// use the mouseClick function with default delay to avoid double-click
ts = w.lastTimeStamp;
QTest::mouseClick(&w, Qt::LeftButton, {}, point);
QCOMPARE_GE(w.lastTimeStamp, ts + 500); // because the last release had a default delay
QTest::mouseClick(&w, Qt::LeftButton, {}, point);
QCOMPARE(w.doubleClickCount, 3);
}

QTEST_MAIN(tst_Mouse)
#include "tst_mouse.moc"

0 comments on commit 0f94430

Please sign in to comment.