- HardwareKeys presses a key's unshifted keysym on touch and releases it on lift, through fake input; held keys repeat and combine as on a physical keyboard (games, Shift+click) - Shift/Ctrl/Alt/Super are plain held keys, caps lock is the system's, dead keys compose in the app - No long-press, flick, trackpad or rollover in this mode; nothing is left held when the mode is turned off or touches are cancelled - Settings switch; default stays the touch (virtual keyboard) mode Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
181 lines
6.5 KiB
C++
181 lines
6.5 KiB
C++
// End-to-end gesture tests: real Keyboard.qml, synthetic touch events.
|
|
// Set SLOPOSK_SNAPSHOT_DIR to also save what the keyboard looks like mid-gesture.
|
|
|
|
#include "FakeEngine.h"
|
|
#include "input/KeyboardController.h"
|
|
#include "input/KeyboardSurface.h"
|
|
#include "layout/LayoutModel.h"
|
|
#include "TestLayouts.h"
|
|
|
|
#include <QQmlComponent>
|
|
#include <QQmlEngine>
|
|
#include <QQmlExtensionPlugin>
|
|
#include <QQuickItem>
|
|
#include <QQuickWindow>
|
|
#include <QTest>
|
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
|
|
|
Q_IMPORT_QML_PLUGIN(SloposkPlugin)
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
namespace {
|
|
|
|
constexpr QPoint KeyboardOffset(0, 100); // room above for popups
|
|
constexpr int LongPressWaitMs = 600;
|
|
|
|
} // namespace
|
|
|
|
class TestKeyboardSurface : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private:
|
|
FakeEngine engine;
|
|
LayoutModel model;
|
|
std::unique_ptr<KeyboardController> controller;
|
|
QQmlEngine qml;
|
|
QQuickWindow window;
|
|
QQuickItem *keyboard = nullptr;
|
|
QPointingDevice *touchscreen = QTest::createTouchDevice();
|
|
|
|
// Window coordinates of the center of the key labelled `label`.
|
|
QPoint keyCenter(const QString &label, const QString &type = u"char"_s)
|
|
{
|
|
for (int i = 0; i < model.rowCount(); ++i) {
|
|
const QModelIndex idx = model.index(i);
|
|
if (idx.data(LayoutModel::KeyTypeRole) == type
|
|
&& (label.isEmpty() || idx.data(LayoutModel::LabelRole) == label))
|
|
return model.keyRect(i).center().toPoint() + KeyboardOffset;
|
|
}
|
|
qFatal("no key %s", qPrintable(label));
|
|
}
|
|
|
|
// Qt Quick delivers touch moves on the next frame; let one pass.
|
|
void settle() { QTest::qWait(50); }
|
|
|
|
void snapshot(const QString &name)
|
|
{
|
|
settle();
|
|
const QString dir = qEnvironmentVariable("SLOPOSK_SNAPSHOT_DIR");
|
|
if (!dir.isEmpty())
|
|
window.grabWindow().save(dir + u'/' + name + u".png"_s);
|
|
}
|
|
|
|
private Q_SLOTS:
|
|
void initTestCase()
|
|
{
|
|
model.setLayout(TestLayouts::build("us"));
|
|
controller = std::make_unique<KeyboardController>(engine, model);
|
|
|
|
QQmlComponent component(&qml, "Sloposk", "Keyboard");
|
|
keyboard = qobject_cast<QQuickItem *>(component.createWithInitialProperties({
|
|
{u"model"_s, QVariant::fromValue(&model)},
|
|
{u"controller"_s, QVariant::fromValue(controller.get())},
|
|
}));
|
|
QVERIFY2(keyboard, qPrintable(component.errorString()));
|
|
keyboard->setParentItem(window.contentItem());
|
|
keyboard->setPosition(KeyboardOffset);
|
|
keyboard->setSize({1000, 300});
|
|
window.setColor(QColor(u"#d1d3d9"_s));
|
|
window.resize(1000, 400);
|
|
window.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
|
}
|
|
|
|
void init()
|
|
{
|
|
engine = {};
|
|
}
|
|
|
|
void tapTypesLetter()
|
|
{
|
|
QTest::touchEvent(&window, touchscreen).press(0, keyCenter(u"q"_s));
|
|
QTest::touchEvent(&window, touchscreen).release(0, keyCenter(u"q"_s));
|
|
QCOMPARE(engine.output, u"q");
|
|
}
|
|
|
|
void flickDownTypesSecondarySymbol()
|
|
{
|
|
const QPoint w = keyCenter(u"w"_s);
|
|
QTest::touchEvent(&window, touchscreen).press(0, w);
|
|
QTest::touchEvent(&window, touchscreen).move(0, w + QPoint(0, 15));
|
|
snapshot(u"flick-halfway"_s);
|
|
const int wIndex = model.keyAt(w - KeyboardOffset);
|
|
QVERIFY(model.index(wIndex).data(LayoutModel::FlickProgressRole).toReal() > 0.3);
|
|
QTest::touchEvent(&window, touchscreen).move(0, w + QPoint(0, 40));
|
|
QTest::touchEvent(&window, touchscreen).release(0, w + QPoint(0, 40));
|
|
QCOMPARE(engine.output, u"2");
|
|
}
|
|
|
|
void longPressPicksAlternate()
|
|
{
|
|
const QPoint e = keyCenter(u"e"_s);
|
|
const qreal cell = model.keyRect(0).width();
|
|
QTest::touchEvent(&window, touchscreen).press(0, e);
|
|
QTest::qWait(LongPressWaitMs);
|
|
auto *surface = qobject_cast<KeyboardSurface *>(keyboard);
|
|
QVERIFY(surface->alternates()->isOpen());
|
|
|
|
QTest::touchEvent(&window, touchscreen).move(0, e + QPoint(int(cell), 0));
|
|
snapshot(u"alternates"_s);
|
|
QTest::touchEvent(&window, touchscreen).release(0, e + QPoint(int(cell), 0));
|
|
QCOMPARE(engine.output, u"è");
|
|
QVERIFY(!surface->alternates()->isOpen());
|
|
}
|
|
|
|
void secondFingerCommitsFirst()
|
|
{
|
|
const QPoint a = keyCenter(u"a"_s), s = keyCenter(u"s"_s);
|
|
QTest::touchEvent(&window, touchscreen).press(0, a);
|
|
QTest::touchEvent(&window, touchscreen).stationary(0).press(1, s);
|
|
QCOMPARE(engine.output, u"a");
|
|
QTest::touchEvent(&window, touchscreen).release(0, a).stationary(1);
|
|
QTest::touchEvent(&window, touchscreen).release(1, s);
|
|
QCOMPARE(engine.output, u"as");
|
|
}
|
|
|
|
void holdKeysKeepsEveryFingerDown()
|
|
{
|
|
controller->setOptions({.holdKeys = true});
|
|
const QPoint w = keyCenter(u"w"_s), e = keyCenter(u"e"_s);
|
|
QTest::touchEvent(&window, touchscreen).press(0, w);
|
|
QTest::touchEvent(&window, touchscreen).stationary(0).press(1, e);
|
|
QTest::qWait(600); // past the long-press delay: e has accents, but no popup
|
|
QCOMPARE(engine.held, (QSet<quint32>{XKB_KEY_w, XKB_KEY_e}));
|
|
QTest::touchEvent(&window, touchscreen).release(0, w).stationary(1);
|
|
QCOMPARE(engine.held, QSet<quint32>{XKB_KEY_e});
|
|
QTest::touchEvent(&window, touchscreen).release(1, e);
|
|
QVERIFY(engine.held.isEmpty());
|
|
QVERIFY(engine.output.isEmpty()); // key events only, no text
|
|
controller->setOptions({});
|
|
}
|
|
|
|
void slideFromNumbersKeyTypesAndReturns()
|
|
{
|
|
const QPoint layerKey = keyCenter(u".?123"_s, u"layer"_s);
|
|
QTest::touchEvent(&window, touchscreen).press(0, layerKey);
|
|
QCOMPARE(model.layerName(), u"numbers");
|
|
const QPoint five = keyCenter(u"5"_s);
|
|
QTest::touchEvent(&window, touchscreen).move(0, five);
|
|
QTest::touchEvent(&window, touchscreen).release(0, five);
|
|
QCOMPARE(engine.output, u"5");
|
|
QCOMPARE(model.layerName(), u"letters");
|
|
}
|
|
|
|
void spaceTrackpadMovesCursor()
|
|
{
|
|
const QPoint space = keyCenter({}, u"space"_s);
|
|
QTest::touchEvent(&window, touchscreen).press(0, space);
|
|
QTest::qWait(LongPressWaitMs);
|
|
QTest::touchEvent(&window, touchscreen).move(0, space + QPoint(45, 0));
|
|
snapshot(u"trackpad"_s);
|
|
QTest::touchEvent(&window, touchscreen).release(0, space + QPoint(45, 0));
|
|
QCOMPARE(engine.output, QString());
|
|
QCOMPARE(engine.keysyms, (QList<quint32>{XKB_KEY_Right, XKB_KEY_Right, XKB_KEY_Right}));
|
|
}
|
|
};
|
|
|
|
QTEST_MAIN(TestKeyboardSurface)
|
|
#include "tst_keyboardsurface.moc"
|