mudlet/test/TKeySequenceEditTest.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

238 lines
11 KiB
C++
Raw Permalink Normal View History

/***************************************************************************
* Copyright (C) 2026 by Andrew Johnson - andrew@johnson5.net *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "TKeySequenceEdit.h"
#include "utils.h"
#include <QLineEdit>
#include <QSignalSpy>
#include <QVBoxLayout>
#include <QtTest/QtTest>
infrastructure: fix milestone assignment, unbreak the key sequence tests (#9679) #### Brief overview of PR changes/additions - `add-milestone` resolves the milestone by exact title first and then by version prefix, so `5.0.0` finds `5.0.0 next release` again, and fails loudly instead of assigning nothing. 191 PRs merged since 4.22.0 have no milestone. - `TKeySequenceEditTest`'s two focus traversal cases no longer fail under a bare Xvfb: ctest pins the offscreen platform on X11, and a direct run without a window manager skips with a message instead of burning the activation timeout twice. #### Motivation for adding to Mudlet Both were failing silently. The milestone step matched a title that no longer exists and exited 0; and the traversal tests were the one red mark in an otherwise green local suite, which everybody had to re-derive as environmental. The milestone lookup is now a script under `.github/scripts/`, covered by a new `MilestoneResolutionTest` that runs it against a stubbed `gh`. Re-introducing the original bug makes that test fail. Worth knowing: `add-milestone` on this PR still assigned nothing, because `pull_request_target` runs the copy of the workflow that is on the base branch. It takes effect for pull requests opened after this merges. #### Other info (issues closed, discussion etc) - Closes #9671 - CI: add-milestone silently assigns nothing - metadata says "4.23.0" but the milestone is titled "4.23.0 next release" - Closes #9575 - TKeySequenceEditTest: two traversal tests fail under bare Xvfb (no window manager) Test case: `ctest -R 'MilestoneResolutionTest|TKeySequenceEditTest'`, plus `xvfb-run --auto-servernum ctest -R TKeySequenceEditTest` for the case #9575 is about. Assisted-by: Claude:claude-opus-5
2026-08-06 06:07:10 +02:00
static constexpr const char* activationUnavailableMessage = "the window never became active, so focus traversal cannot be exercised - this "
"display has no window manager. Run the suite through ctest, or set "
"QT_QPA_PLATFORM=offscreen, or start a window manager such as openbox.";
// Shared by the two traversal cases, because QSKIP and QFAIL only work from the
// test function itself and the two must not drift apart. ctest sets
// MUDLET_REQUIRE_WINDOW_ACTIVATION because every display it runs against can
// activate a window, so a skip there would be hiding a regression rather than
// reporting an environment - the same floor MUDLET_MEDIA_TESTS_REQUIRE_PLAYBACK
// puts under the media tests.
#define REQUIRE_WINDOW_ACTIVATION(window) \
do { \
if (!QTest::qWaitForWindowActive(&(window))) { \
if (qEnvironmentVariableIsSet("MUDLET_REQUIRE_WINDOW_ACTIVATION")) { \
QFAIL(activationUnavailableMessage); \
} \
QSKIP(activationUnavailableMessage); \
} \
} while (false)
// Pins the accessibility behaviour that TKeySequenceEdit adds on top of the
// stock QKeySequenceEdit (#8873). Key events are sent to the inner QLineEdit
// because that is where keyboard focus lives via the focus proxy.
class TKeySequenceEditTest : public QObject
{
Q_OBJECT
static QLineEdit* innerLineEdit(TKeySequenceEdit& edit) { return edit.findChild<QLineEdit*>(); }
private slots:
void focusIsRoutedToInnerLineEdit()
{
TKeySequenceEdit edit(QKeySequence(qsl("Ctrl+O")), qsl("Open file"));
auto* lineEdit = innerLineEdit(edit);
QVERIFY(lineEdit);
QCOMPARE(edit.focusProxy(), lineEdit);
QVERIFY(!lineEdit->focusProxy());
}
void accessibleNameIsOnInnerFieldOnly()
{
const QString label = qsl("Open file");
TKeySequenceEdit edit(QKeySequence(), label);
// The inner field carries the accessible name; the wrapper is left
// unnamed so a screen reader does not announce the label twice - once
// for the wrapper's grouping and once for the focused field (#9322):
QCOMPARE(innerLineEdit(edit)->accessibleName(), label);
QVERIFY(edit.accessibleName().isEmpty());
}
void populatedBindingIsAnnouncedOnlyAsTheFieldValue()
{
const QKeySequence sequence(qsl("Ctrl+O"));
TKeySequenceEdit edit(sequence, qsl("Open file"));
auto* lineEdit = innerLineEdit(edit);
// The binding is shown as the field's text, which screen readers
// announce as its value, so it must not also appear in the accessible
// description of the field or the wrapper - otherwise the reader speaks
// the combination several times over (#9322):
QCOMPARE(edit.keySequence(), sequence);
QVERIFY(!lineEdit->text().isEmpty());
QVERIFY(lineEdit->accessibleDescription().isEmpty());
QVERIFY(edit.accessibleDescription().isEmpty());
}
void emptyBindingIsDescribedAsUnset()
{
TKeySequenceEdit edit(QKeySequence(), qsl("Open file"));
auto* lineEdit = innerLineEdit(edit);
// With no value to announce, the empty state is conveyed by the field's
// description instead - but still only once: the wrapper never carries
// it (#9322):
QVERIFY(!lineEdit->accessibleDescription().isEmpty());
QVERIFY(edit.accessibleDescription().isEmpty());
}
void accessibleDescriptionUpdatesOnSequenceChange()
{
TKeySequenceEdit edit(QKeySequence(), qsl("Open file"));
auto* lineEdit = innerLineEdit(edit);
const QString unsetDescription = lineEdit->accessibleDescription();
QVERIFY(!unsetDescription.isEmpty());
// Once there is a value to announce, the description is cleared so the
// binding is not read out twice:
edit.setKeySequence(QKeySequence(qsl("Alt+F4")));
QVERIFY(lineEdit->accessibleDescription().isEmpty());
edit.clear();
QCOMPARE(lineEdit->accessibleDescription(), unsetDescription);
}
void lineEditRejectsDirectEditing()
{
TKeySequenceEdit edit(QKeySequence(), qsl("Open file"));
auto* lineEdit = innerLineEdit(edit);
QVERIFY(lineEdit->isReadOnly());
QCOMPARE(lineEdit->contextMenuPolicy(), Qt::NoContextMenu);
QVERIFY(!lineEdit->testAttribute(Qt::WA_InputMethodEnabled));
}
void captureStillRecordsKeyCombination()
{
TKeySequenceEdit edit(QKeySequence(), qsl("Open file"));
QTest::keyClick(innerLineEdit(edit), Qt::Key_P, Qt::ControlModifier);
QCOMPARE(edit.keySequence(), QKeySequence(Qt::CTRL | Qt::Key_P));
}
void bareModifierPressDoesNotClearBinding()
{
const QKeySequence sequence(qsl("Ctrl+O"));
TKeySequenceEdit edit(sequence, qsl("Open file"));
auto* lineEdit = innerLineEdit(edit);
QTest::keyPress(lineEdit, Qt::Key_Shift);
QCOMPARE(edit.keySequence(), sequence);
QTest::keyRelease(lineEdit, Qt::Key_Shift);
QCOMPARE(edit.keySequence(), sequence);
QTest::keyClick(lineEdit, Qt::Key_Control);
QCOMPARE(edit.keySequence(), sequence);
QTest::keyClick(lineEdit, Qt::Key_Alt);
QCOMPARE(edit.keySequence(), sequence);
QTest::keyClick(lineEdit, Qt::Key_Meta);
QCOMPARE(edit.keySequence(), sequence);
}
void shiftTabTraversalDoesNotChangeBinding()
{
const QKeySequence sequence(qsl("Ctrl+O"));
TKeySequenceEdit edit(sequence, qsl("Open file"));
auto* lineEdit = innerLineEdit(edit);
// Shift+Tab arrives from the platform as Backtab with the Shift
// modifier still set:
QTest::keyClick(lineEdit, Qt::Key_Backtab, Qt::ShiftModifier);
QCOMPARE(edit.keySequence(), sequence);
QTest::keyClick(lineEdit, Qt::Key_Tab, Qt::ShiftModifier);
QCOMPARE(edit.keySequence(), sequence);
}
// The traversal tests need real focus movement: the capture is committed
// by the focus-out that the traversal causes, mirroring how the stock
infrastructure: fix milestone assignment, unbreak the key sequence tests (#9679) #### Brief overview of PR changes/additions - `add-milestone` resolves the milestone by exact title first and then by version prefix, so `5.0.0` finds `5.0.0 next release` again, and fails loudly instead of assigning nothing. 191 PRs merged since 4.22.0 have no milestone. - `TKeySequenceEditTest`'s two focus traversal cases no longer fail under a bare Xvfb: ctest pins the offscreen platform on X11, and a direct run without a window manager skips with a message instead of burning the activation timeout twice. #### Motivation for adding to Mudlet Both were failing silently. The milestone step matched a title that no longer exists and exited 0; and the traversal tests were the one red mark in an otherwise green local suite, which everybody had to re-derive as environmental. The milestone lookup is now a script under `.github/scripts/`, covered by a new `MilestoneResolutionTest` that runs it against a stubbed `gh`. Re-introducing the original bug makes that test fail. Worth knowing: `add-milestone` on this PR still assigned nothing, because `pull_request_target` runs the copy of the workflow that is on the base branch. It takes effect for pull requests opened after this merges. #### Other info (issues closed, discussion etc) - Closes #9671 - CI: add-milestone silently assigns nothing - metadata says "4.23.0" but the milestone is titled "4.23.0 next release" - Closes #9575 - TKeySequenceEditTest: two traversal tests fail under bare Xvfb (no window manager) Test case: `ctest -R 'MilestoneResolutionTest|TKeySequenceEditTest'`, plus `xvfb-run --auto-servernum ctest -R TKeySequenceEditTest` for the case #9575 is about. Assisted-by: Claude:claude-opus-5
2026-08-06 06:07:10 +02:00
// widget commits in focusOutEvent() when Tab moves focus away. Qt only
// delivers those focus events while the window is active, and nothing
// activates a window on an X server without a window manager, so on such a
// display these two cases are skipped rather than failed (#9575). Under
// ctest they never get that far: the offscreen platform is pinned there,
// and it synthesises activation.
void shiftBacktabCommitsCaptureAndMovesFocusBackwards()
{
QWidget window;
auto* layout = new QVBoxLayout(&window);
auto* neighbour = new QLineEdit(&window);
auto* edit = new TKeySequenceEdit(QKeySequence(), qsl("Open file"), &window);
layout->addWidget(neighbour);
layout->addWidget(edit);
window.show();
infrastructure: fix milestone assignment, unbreak the key sequence tests (#9679) #### Brief overview of PR changes/additions - `add-milestone` resolves the milestone by exact title first and then by version prefix, so `5.0.0` finds `5.0.0 next release` again, and fails loudly instead of assigning nothing. 191 PRs merged since 4.22.0 have no milestone. - `TKeySequenceEditTest`'s two focus traversal cases no longer fail under a bare Xvfb: ctest pins the offscreen platform on X11, and a direct run without a window manager skips with a message instead of burning the activation timeout twice. #### Motivation for adding to Mudlet Both were failing silently. The milestone step matched a title that no longer exists and exited 0; and the traversal tests were the one red mark in an otherwise green local suite, which everybody had to re-derive as environmental. The milestone lookup is now a script under `.github/scripts/`, covered by a new `MilestoneResolutionTest` that runs it against a stubbed `gh`. Re-introducing the original bug makes that test fail. Worth knowing: `add-milestone` on this PR still assigned nothing, because `pull_request_target` runs the copy of the workflow that is on the base branch. It takes effect for pull requests opened after this merges. #### Other info (issues closed, discussion etc) - Closes #9671 - CI: add-milestone silently assigns nothing - metadata says "4.23.0" but the milestone is titled "4.23.0 next release" - Closes #9575 - TKeySequenceEditTest: two traversal tests fail under bare Xvfb (no window manager) Test case: `ctest -R 'MilestoneResolutionTest|TKeySequenceEditTest'`, plus `xvfb-run --auto-servernum ctest -R TKeySequenceEditTest` for the case #9575 is about. Assisted-by: Claude:claude-opus-5
2026-08-06 06:07:10 +02:00
REQUIRE_WINDOW_ACTIVATION(window);
edit->setFocus();
auto* lineEdit = edit->findChild<QLineEdit*>();
QTRY_VERIFY(lineEdit->hasFocus());
QSignalSpy spy(edit, &QKeySequenceEdit::editingFinished);
const QKeySequence captured(Qt::CTRL | Qt::Key_P);
QTest::keyClick(lineEdit, Qt::Key_P, Qt::ControlModifier);
QCOMPARE(edit->keySequence(), captured);
QTest::keyClick(lineEdit, Qt::Key_Backtab, Qt::ShiftModifier);
QCOMPARE(edit->keySequence(), captured);
QCOMPARE(spy.count(), 1);
QTRY_VERIFY(neighbour->hasFocus());
}
void plainTabCommitsCaptureAndMovesFocusForwards()
{
QWidget window;
auto* layout = new QVBoxLayout(&window);
auto* edit = new TKeySequenceEdit(QKeySequence(), qsl("Open file"), &window);
auto* neighbour = new QLineEdit(&window);
layout->addWidget(edit);
layout->addWidget(neighbour);
window.show();
infrastructure: fix milestone assignment, unbreak the key sequence tests (#9679) #### Brief overview of PR changes/additions - `add-milestone` resolves the milestone by exact title first and then by version prefix, so `5.0.0` finds `5.0.0 next release` again, and fails loudly instead of assigning nothing. 191 PRs merged since 4.22.0 have no milestone. - `TKeySequenceEditTest`'s two focus traversal cases no longer fail under a bare Xvfb: ctest pins the offscreen platform on X11, and a direct run without a window manager skips with a message instead of burning the activation timeout twice. #### Motivation for adding to Mudlet Both were failing silently. The milestone step matched a title that no longer exists and exited 0; and the traversal tests were the one red mark in an otherwise green local suite, which everybody had to re-derive as environmental. The milestone lookup is now a script under `.github/scripts/`, covered by a new `MilestoneResolutionTest` that runs it against a stubbed `gh`. Re-introducing the original bug makes that test fail. Worth knowing: `add-milestone` on this PR still assigned nothing, because `pull_request_target` runs the copy of the workflow that is on the base branch. It takes effect for pull requests opened after this merges. #### Other info (issues closed, discussion etc) - Closes #9671 - CI: add-milestone silently assigns nothing - metadata says "4.23.0" but the milestone is titled "4.23.0 next release" - Closes #9575 - TKeySequenceEditTest: two traversal tests fail under bare Xvfb (no window manager) Test case: `ctest -R 'MilestoneResolutionTest|TKeySequenceEditTest'`, plus `xvfb-run --auto-servernum ctest -R TKeySequenceEditTest` for the case #9575 is about. Assisted-by: Claude:claude-opus-5
2026-08-06 06:07:10 +02:00
REQUIRE_WINDOW_ACTIVATION(window);
edit->setFocus();
auto* lineEdit = edit->findChild<QLineEdit*>();
QTRY_VERIFY(lineEdit->hasFocus());
QSignalSpy spy(edit, &QKeySequenceEdit::editingFinished);
const QKeySequence captured(Qt::CTRL | Qt::Key_P);
QTest::keyClick(lineEdit, Qt::Key_P, Qt::ControlModifier);
QTest::keyClick(lineEdit, Qt::Key_Tab);
QCOMPARE(edit->keySequence(), captured);
QCOMPARE(spy.count(), 1);
QTRY_VERIFY(neighbour->hasFocus());
}
};
#include "TKeySequenceEditTest.moc"
QTEST_MAIN(TKeySequenceEditTest)