mudlet/test/functional_tests/TelnetTextDisplayedTest.cpp

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

208 lines
8 KiB
C++
Raw Permalink Normal View History

/***************************************************************************
* Copyright (C) 2025 by Nicolas Keita - nicolaskeita2@@gmail.com *
* *
* 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 <QtTest/QtTest>
infrastructure: use std::chrono literals for time durations (#9493) #### Brief overview of PR changes/additions Convert raw millisecond integer literals at time-duration call sites to `std::chrono` literals, and add `#include <chrono>` to each touched translation unit. Examples: - `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)` - `mpTimerReplay->setInterval(1000)` → `setInterval(1s)` - `mPendingTimer.start(60000)` → `start(1min)` - `QObject::startTimer(50)` → `startTimer(50ms)` - `QTest::qWait(100)` → `QTest::qWait(100ms)` - `QThread::msleep(10)` → `QThread::sleep(10ms)` This is a semantics-preserving refactor - every duration is kept exactly equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes `1min`). No behavioural change. #### Motivation for adding to Mudlet Chrono literals make time durations self-documenting and type-safe. `1s` / `100ms` read unambiguously where a bare `1000` / `100` forces the reader to remember each API's unit, and the compiler now rejects unit mismatches. Only genuine duration arguments were converted - loop counts, scroll-line counts, sizes, ports and the like were deliberately left as plain integers. All targeted APIs provide `std::chrono` overloads in the minimum supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8), `QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)` (6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7). #### Other info (issues closed, discussion etc) Test case: the full application builds cleanly and the entire functional `ctest` suite passes. The only failing test is the known, pre-existing `PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is unrelated to this change. Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
#include <chrono>
Infrastructure: Swap out QtConcurrent module header for sub-module ones (#9246) #### Brief overview of PR changes/additions The Qt documentation for `QtConcurrent` points out: > If you include the `<QtConcurrent>` header, the entire Qt Concurrent module with the entire Qt Core module will be included, which may increase compilation times and binary sizes. To use individual functions from the QtConcurrent namespace, you can include more specific headers. > > The table below lists the functions in the QtConcurrent namespace and their corresponding headers: |Function|Header| |--------|------| |`QtConcurrent::run()`|`<QtConcurrentRun>`| |`QtConcurrent::task()`| `<QtConcurrentTask>`| |`QtConcurrent::filter()`,<br>`QtConcurrent::filtered()`,<br>`QtConcurrent::filteredReduced()`|`<QtConcurrentFilter>`| |`QtConcurrent::map()`,<br>`QtConcurrent::mapped()`,<br>`QtConcurrent::mappedReduced()`|`<QtConcurrentMap>`| #### Motivation for adding to Mudlet To speed up the build a little by removing stuff that isn't needed. #### Other info (issues closed, discussion etc) In doing this I happened to start cleaning up a couple of header files `T2DMap.h` and then `mudlet.h`, I then got into converting some `#include`s into forward declarations in a "include-what-you-use" move. This then rippled through into a (more than 10!) number of files but should "improve" things. Note that the ordering of `#include` in many files seems to be rather haphazard and is due for some serious overhaul - I suggest that we should actually declare an "official" style for this project so that everyone knows what it is. **During the CI/CB process I discovered that Linux and then MacOS builds were failing because the file referred to by the `#include <QtConcurrentTask>` header file was missing, yet was present on my local PC when I was using the Qt framework from the On-line installer. Initially I suspected a Debian (and then Devuan - as the packaged version on my own machine also had this defect AND Ubuntu) package problem; however it now seems to be an upstream Qt issue as the various Qt versions & OS combinations suggest that Qt themselves fixed it for Qt 6.10:** | OS | QtVersion | Missing header | |--------|-----------------------|----------------| | Windows| 6.11.0 package | No | | Devuan | 6.8.2 package | Yes | | Devuan | 6.10.0 online install | No | | Ubuntu | 6.9.0 package | Yes | | Debian | 6.8.2 package | Yes | | MacOS | 6.9.0 package | Yes | **To fix this I reverted to an `#include <qtconcurrenttask.h>` for Linux and MacOS builds - although it would probably have been better to make it conditional on the Qt Version instead...** *I have reported this upstream to Debian - see: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1135197* --------- Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2026-04-29 13:35:29 +01:00
#include <cstdlib>
Infrastructure: Swap out QtConcurrent module header for sub-module ones (#9246) #### Brief overview of PR changes/additions The Qt documentation for `QtConcurrent` points out: > If you include the `<QtConcurrent>` header, the entire Qt Concurrent module with the entire Qt Core module will be included, which may increase compilation times and binary sizes. To use individual functions from the QtConcurrent namespace, you can include more specific headers. > > The table below lists the functions in the QtConcurrent namespace and their corresponding headers: |Function|Header| |--------|------| |`QtConcurrent::run()`|`<QtConcurrentRun>`| |`QtConcurrent::task()`| `<QtConcurrentTask>`| |`QtConcurrent::filter()`,<br>`QtConcurrent::filtered()`,<br>`QtConcurrent::filteredReduced()`|`<QtConcurrentFilter>`| |`QtConcurrent::map()`,<br>`QtConcurrent::mapped()`,<br>`QtConcurrent::mappedReduced()`|`<QtConcurrentMap>`| #### Motivation for adding to Mudlet To speed up the build a little by removing stuff that isn't needed. #### Other info (issues closed, discussion etc) In doing this I happened to start cleaning up a couple of header files `T2DMap.h` and then `mudlet.h`, I then got into converting some `#include`s into forward declarations in a "include-what-you-use" move. This then rippled through into a (more than 10!) number of files but should "improve" things. Note that the ordering of `#include` in many files seems to be rather haphazard and is due for some serious overhaul - I suggest that we should actually declare an "official" style for this project so that everyone knows what it is. **During the CI/CB process I discovered that Linux and then MacOS builds were failing because the file referred to by the `#include <QtConcurrentTask>` header file was missing, yet was present on my local PC when I was using the Qt framework from the On-line installer. Initially I suspected a Debian (and then Devuan - as the packaged version on my own machine also had this defect AND Ubuntu) package problem; however it now seems to be an upstream Qt issue as the various Qt versions & OS combinations suggest that Qt themselves fixed it for Qt 6.10:** | OS | QtVersion | Missing header | |--------|-----------------------|----------------| | Windows| 6.11.0 package | No | | Devuan | 6.8.2 package | Yes | | Devuan | 6.10.0 online install | No | | Ubuntu | 6.9.0 package | Yes | | Debian | 6.8.2 package | Yes | | MacOS | 6.9.0 package | Yes | **To fix this I reverted to an `#include <qtconcurrenttask.h>` for Linux and MacOS builds - although it would probably have been better to make it conditional on the Qt Version instead...** *I have reported this upstream to Debian - see: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1135197* --------- Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2026-04-29 13:35:29 +01:00
#include "MudletInstanceCoordinator.h"
#include "TelnetServerStub.h"
#include "ctelnet.h"
#include "dlgConnectionProfiles.h"
#include "mudlet.h"
infrastructure: use std::chrono literals for time durations (#9493) #### Brief overview of PR changes/additions Convert raw millisecond integer literals at time-duration call sites to `std::chrono` literals, and add `#include <chrono>` to each touched translation unit. Examples: - `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)` - `mpTimerReplay->setInterval(1000)` → `setInterval(1s)` - `mPendingTimer.start(60000)` → `start(1min)` - `QObject::startTimer(50)` → `startTimer(50ms)` - `QTest::qWait(100)` → `QTest::qWait(100ms)` - `QThread::msleep(10)` → `QThread::sleep(10ms)` This is a semantics-preserving refactor - every duration is kept exactly equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes `1min`). No behavioural change. #### Motivation for adding to Mudlet Chrono literals make time durations self-documenting and type-safe. `1s` / `100ms` read unambiguously where a bare `1000` / `100` forces the reader to remember each API's unit, and the compiler now rejects unit mismatches. Only genuine duration arguments were converted - loop counts, scroll-line counts, sizes, ports and the like were deliberately left as plain integers. All targeted APIs provide `std::chrono` overloads in the minimum supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8), `QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)` (6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7). #### Other info (issues closed, discussion etc) Test case: the full application builds cleanly and the entire functional `ctest` suite passes. The only failing test is the known, pre-existing `PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is unrelated to this change. Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
using namespace std::chrono_literals;
extern void qInitResources_mudlet();
extern void qInitResources_qm();
extern void qInitResources_additional_splash_screens();
extern void qInitResources_mudlet_fonts_common();
extern void qInitResources_mudlet_fonts_posix();
void initializeQRCResources();
class TelnetTextDisplayedTest : public QObject
{
Q_OBJECT
private:
TelnetServerStub* mpServer = nullptr;
const QString mpHostname = "Test-Telnet";
QString mpPort; // assigned the stub's actual ephemeral port in init()
const QString mpLocalhost = "localhost";
private slots:
void initTestCase() { initializeQRCResources(); }
void init()
{
mpServer = new TelnetServerStub(qApp);
mpServer->start(mpLocalhost, 0); // ephemeral OS-assigned port avoids collisions across concurrent test runs
mpPort = QString::number(mpServer->serverPort());
mudlet::start();
mudlet::self()->setupConfig();
mudlet::self()->takeOwnershipOfInstanceCoordinator(std::make_unique<MudletInstanceCoordinator>("MudletInstanceCoordinator"));
mudlet::self()->init();
mudlet::self()->setStorePasswordsSecurely(false);
deleteProfileDirectory(mpHostname);
}
void test_TelnetTextDisplayed()
{
QString messageFromTheMud("\x1B[1z<B>Greetings < hunters & sorcerers</B>\x1B[7z");
QString messageToExpect("Greetings < hunters & sorcerers");
mpServer->setWelcomeMessage(messageFromTheMud);
startProfile(mpHostname, mpLocalhost, mpPort);
QVERIFY2(waitForTextInBuffer(messageToExpect), qPrintable(qsl("Expected text '%1' not found in console buffer").arg(messageToExpect)));
}
fix: non-English game text no longer garbles after a stray "&" (#9489) #### Brief overview of the PR When a game sends a bare `&` that runs straight into non-English (multi-byte) text - which is not a valid MXP entity - Mudlet was re-encoding the recovered bytes, mangling the following characters into mojibake. For example `&Ф` rendered as `&Ð�`, and `Käse&Brötchen` lost its umlaut. This restores the raw-byte passthrough for these malformed/unknown entities while keeping the correct session-encoding handling for custom `<!ENTITY>` values. Non-English text now displays intact. #### Motivation for adding to Mudlet #9439 fixed custom MXP entities that carry non-Latin1 values, but the same code path also handles malformed and unknown entities, whose value is raw, still-undecoded bytes (one Latin1 code unit per byte). Encoding those bytes with the session charset double-encoded them and orphaned the UTF-8 continuation bytes still sitting in the buffer, so any non-ASCII text immediately after a stray `&` turned to garbage. Players on games that use Cyrillic, umlauts, CJK and other non-ASCII text were the ones affected. The fix distinguishes the two cases: a custom entity value is decoded text, so it is re-encoded with the session encoding; a malformed/unknown entity value is raw bytes, so it is passed through unchanged with `toLatin1()`. #### Other info Follow-up to #9439. **Test case:** Two functional tests were added (using an ephemeral stub port so parallel test runs cannot collide): - `test_MalformedEntityKeepsNonAsciiBytes` - sends `Käse&Brötchen and &Ф too` and expects it back byte-for-byte. This fails on the pre-fix code (the tail garbles to mojibake) and passes with the fix. - `test_CustomEntityKeepsNonAsciiValue` - sends `<!ENTITY storm "Гроза">The &storm; rages` and expects `The Гроза rages`, guarding the #9439 behaviour so the new branching does not regress it. Manual check: connect to a UTF-8 game that emits a bare `&` followed by non-English text (e.g. `&Ф`). Before this fix the following characters render as garbage; after it they render correctly. Assisted-by: Claude:claude-fable-5 Assisted-by: Claude:claude-opus-4-8
2026-07-27 19:48:40 +02:00
// An unescaped '&' directly followed by (or running into) a non-ASCII character
// is not a valid entity; the original raw bytes must be passed through unchanged
// so the charset decoder can reassemble the multi-byte characters (follow-up to #9439)
void test_MalformedEntityKeepsNonAsciiBytes()
{
QString messageFromTheMud("\x1B[1zKäse&Brötchen and &Ф too");
QString messageToExpect("Käse&Brötchen and &Ф too");
mpServer->setWelcomeMessage(messageFromTheMud);
startProfile(mpHostname, mpLocalhost, mpPort);
QVERIFY2(waitForTextInBuffer(messageToExpect), qPrintable(qsl("Expected text '%1' not found in console buffer, which contains:\n%2").arg(messageToExpect, bufferContents())));
}
// A custom <!ENTITY> with a non-Latin1 value must resolve to that value intact
// in a UTF-8 session (the case #9439 fixed)
void test_CustomEntityKeepsNonAsciiValue()
{
QString messageFromTheMud("\x1B[1z<!ENTITY storm \"Гроза\">The &storm; rages");
QString messageToExpect("The Гроза rages");
mpServer->setWelcomeMessage(messageFromTheMud);
startProfile(mpHostname, mpLocalhost, mpPort);
QVERIFY2(waitForTextInBuffer(messageToExpect), qPrintable(qsl("Expected text '%1' not found in console buffer, which contains:\n%2").arg(messageToExpect, bufferContents())));
}
void cleanup()
{
delete mpServer;
mpServer = nullptr;
deleteProfileDirectory(mpHostname);
delete mudlet::self();
}
// Utility function to manually start a profile like a user would do via the
// GUI
void startProfile(const QString& hostname, const QString& address, const QString& port)
{
infrastructure: use std::chrono literals for time durations (#9493) #### Brief overview of PR changes/additions Convert raw millisecond integer literals at time-duration call sites to `std::chrono` literals, and add `#include <chrono>` to each touched translation unit. Examples: - `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)` - `mpTimerReplay->setInterval(1000)` → `setInterval(1s)` - `mPendingTimer.start(60000)` → `start(1min)` - `QObject::startTimer(50)` → `startTimer(50ms)` - `QTest::qWait(100)` → `QTest::qWait(100ms)` - `QThread::msleep(10)` → `QThread::sleep(10ms)` This is a semantics-preserving refactor - every duration is kept exactly equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes `1min`). No behavioural change. #### Motivation for adding to Mudlet Chrono literals make time durations self-documenting and type-safe. `1s` / `100ms` read unambiguously where a bare `1000` / `100` forces the reader to remember each API's unit, and the compiler now rejects unit mismatches. Only genuine duration arguments were converted - loop counts, scroll-line counts, sizes, ports and the like were deliberately left as plain integers. All targeted APIs provide `std::chrono` overloads in the minimum supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8), `QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)` (6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7). #### Other info (issues closed, discussion etc) Test case: the full application builds cleanly and the entire functional `ctest` suite passes. The only failing test is the known, pre-existing `PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is unrelated to this change. Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
QTimer::singleShot(0ms, qApp, [hostname, address, port]() {
mudlet::self()->startAutoLogin({});
infrastructure: use std::chrono literals for time durations (#9493) #### Brief overview of PR changes/additions Convert raw millisecond integer literals at time-duration call sites to `std::chrono` literals, and add `#include <chrono>` to each touched translation unit. Examples: - `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)` - `mpTimerReplay->setInterval(1000)` → `setInterval(1s)` - `mPendingTimer.start(60000)` → `start(1min)` - `QObject::startTimer(50)` → `startTimer(50ms)` - `QTest::qWait(100)` → `QTest::qWait(100ms)` - `QThread::msleep(10)` → `QThread::sleep(10ms)` This is a semantics-preserving refactor - every duration is kept exactly equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes `1min`). No behavioural change. #### Motivation for adding to Mudlet Chrono literals make time durations self-documenting and type-safe. `1s` / `100ms` read unambiguously where a bare `1000` / `100` forces the reader to remember each API's unit, and the compiler now rejects unit mismatches. Only genuine duration arguments were converted - loop counts, scroll-line counts, sizes, ports and the like were deliberately left as plain integers. All targeted APIs provide `std::chrono` overloads in the minimum supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8), `QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)` (6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7). #### Other info (issues closed, discussion etc) Test case: the full application builds cleanly and the entire functional `ctest` suite passes. The only failing test is the known, pre-existing `PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is unrelated to this change. Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
QTest::qWait(100ms);
QTest::mouseClick(mudlet::self()->mpConnectionDialog->new_profile_button, Qt::LeftButton);
infrastructure: use std::chrono literals for time durations (#9493) #### Brief overview of PR changes/additions Convert raw millisecond integer literals at time-duration call sites to `std::chrono` literals, and add `#include <chrono>` to each touched translation unit. Examples: - `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)` - `mpTimerReplay->setInterval(1000)` → `setInterval(1s)` - `mPendingTimer.start(60000)` → `start(1min)` - `QObject::startTimer(50)` → `startTimer(50ms)` - `QTest::qWait(100)` → `QTest::qWait(100ms)` - `QThread::msleep(10)` → `QThread::sleep(10ms)` This is a semantics-preserving refactor - every duration is kept exactly equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes `1min`). No behavioural change. #### Motivation for adding to Mudlet Chrono literals make time durations self-documenting and type-safe. `1s` / `100ms` read unambiguously where a bare `1000` / `100` forces the reader to remember each API's unit, and the compiler now rejects unit mismatches. Only genuine duration arguments were converted - loop counts, scroll-line counts, sizes, ports and the like were deliberately left as plain integers. All targeted APIs provide `std::chrono` overloads in the minimum supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8), `QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)` (6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7). #### Other info (issues closed, discussion etc) Test case: the full application builds cleanly and the entire functional `ctest` suite passes. The only failing test is the known, pre-existing `PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is unrelated to this change. Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
QTest::qWait(100ms);
QTest::keyClicks(QApplication::focusWidget(), hostname);
infrastructure: use std::chrono literals for time durations (#9493) #### Brief overview of PR changes/additions Convert raw millisecond integer literals at time-duration call sites to `std::chrono` literals, and add `#include <chrono>` to each touched translation unit. Examples: - `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)` - `mpTimerReplay->setInterval(1000)` → `setInterval(1s)` - `mPendingTimer.start(60000)` → `start(1min)` - `QObject::startTimer(50)` → `startTimer(50ms)` - `QTest::qWait(100)` → `QTest::qWait(100ms)` - `QThread::msleep(10)` → `QThread::sleep(10ms)` This is a semantics-preserving refactor - every duration is kept exactly equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes `1min`). No behavioural change. #### Motivation for adding to Mudlet Chrono literals make time durations self-documenting and type-safe. `1s` / `100ms` read unambiguously where a bare `1000` / `100` forces the reader to remember each API's unit, and the compiler now rejects unit mismatches. Only genuine duration arguments were converted - loop counts, scroll-line counts, sizes, ports and the like were deliberately left as plain integers. All targeted APIs provide `std::chrono` overloads in the minimum supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8), `QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)` (6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7). #### Other info (issues closed, discussion etc) Test case: the full application builds cleanly and the entire functional `ctest` suite passes. The only failing test is the known, pre-existing `PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is unrelated to this change. Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
QTest::qWait(100ms);
QTest::keyClick(QApplication::focusWidget(), Qt::Key_Tab);
infrastructure: use std::chrono literals for time durations (#9493) #### Brief overview of PR changes/additions Convert raw millisecond integer literals at time-duration call sites to `std::chrono` literals, and add `#include <chrono>` to each touched translation unit. Examples: - `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)` - `mpTimerReplay->setInterval(1000)` → `setInterval(1s)` - `mPendingTimer.start(60000)` → `start(1min)` - `QObject::startTimer(50)` → `startTimer(50ms)` - `QTest::qWait(100)` → `QTest::qWait(100ms)` - `QThread::msleep(10)` → `QThread::sleep(10ms)` This is a semantics-preserving refactor - every duration is kept exactly equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes `1min`). No behavioural change. #### Motivation for adding to Mudlet Chrono literals make time durations self-documenting and type-safe. `1s` / `100ms` read unambiguously where a bare `1000` / `100` forces the reader to remember each API's unit, and the compiler now rejects unit mismatches. Only genuine duration arguments were converted - loop counts, scroll-line counts, sizes, ports and the like were deliberately left as plain integers. All targeted APIs provide `std::chrono` overloads in the minimum supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8), `QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)` (6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7). #### Other info (issues closed, discussion etc) Test case: the full application builds cleanly and the entire functional `ctest` suite passes. The only failing test is the known, pre-existing `PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is unrelated to this change. Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
QTest::qWait(100ms);
QTest::keyClicks(QApplication::focusWidget(), address);
infrastructure: use std::chrono literals for time durations (#9493) #### Brief overview of PR changes/additions Convert raw millisecond integer literals at time-duration call sites to `std::chrono` literals, and add `#include <chrono>` to each touched translation unit. Examples: - `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)` - `mpTimerReplay->setInterval(1000)` → `setInterval(1s)` - `mPendingTimer.start(60000)` → `start(1min)` - `QObject::startTimer(50)` → `startTimer(50ms)` - `QTest::qWait(100)` → `QTest::qWait(100ms)` - `QThread::msleep(10)` → `QThread::sleep(10ms)` This is a semantics-preserving refactor - every duration is kept exactly equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes `1min`). No behavioural change. #### Motivation for adding to Mudlet Chrono literals make time durations self-documenting and type-safe. `1s` / `100ms` read unambiguously where a bare `1000` / `100` forces the reader to remember each API's unit, and the compiler now rejects unit mismatches. Only genuine duration arguments were converted - loop counts, scroll-line counts, sizes, ports and the like were deliberately left as plain integers. All targeted APIs provide `std::chrono` overloads in the minimum supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8), `QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)` (6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7). #### Other info (issues closed, discussion etc) Test case: the full application builds cleanly and the entire functional `ctest` suite passes. The only failing test is the known, pre-existing `PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is unrelated to this change. Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
QTest::qWait(100ms);
QTest::keyClick(QApplication::focusWidget(), Qt::Key_Tab);
infrastructure: use std::chrono literals for time durations (#9493) #### Brief overview of PR changes/additions Convert raw millisecond integer literals at time-duration call sites to `std::chrono` literals, and add `#include <chrono>` to each touched translation unit. Examples: - `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)` - `mpTimerReplay->setInterval(1000)` → `setInterval(1s)` - `mPendingTimer.start(60000)` → `start(1min)` - `QObject::startTimer(50)` → `startTimer(50ms)` - `QTest::qWait(100)` → `QTest::qWait(100ms)` - `QThread::msleep(10)` → `QThread::sleep(10ms)` This is a semantics-preserving refactor - every duration is kept exactly equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes `1min`). No behavioural change. #### Motivation for adding to Mudlet Chrono literals make time durations self-documenting and type-safe. `1s` / `100ms` read unambiguously where a bare `1000` / `100` forces the reader to remember each API's unit, and the compiler now rejects unit mismatches. Only genuine duration arguments were converted - loop counts, scroll-line counts, sizes, ports and the like were deliberately left as plain integers. All targeted APIs provide `std::chrono` overloads in the minimum supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8), `QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)` (6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7). #### Other info (issues closed, discussion etc) Test case: the full application builds cleanly and the entire functional `ctest` suite passes. The only failing test is the known, pre-existing `PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is unrelated to this change. Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
QTest::qWait(100ms);
QTest::keyClicks(QApplication::focusWidget(), port);
infrastructure: use std::chrono literals for time durations (#9493) #### Brief overview of PR changes/additions Convert raw millisecond integer literals at time-duration call sites to `std::chrono` literals, and add `#include <chrono>` to each touched translation unit. Examples: - `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)` - `mpTimerReplay->setInterval(1000)` → `setInterval(1s)` - `mPendingTimer.start(60000)` → `start(1min)` - `QObject::startTimer(50)` → `startTimer(50ms)` - `QTest::qWait(100)` → `QTest::qWait(100ms)` - `QThread::msleep(10)` → `QThread::sleep(10ms)` This is a semantics-preserving refactor - every duration is kept exactly equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes `1min`). No behavioural change. #### Motivation for adding to Mudlet Chrono literals make time durations self-documenting and type-safe. `1s` / `100ms` read unambiguously where a bare `1000` / `100` forces the reader to remember each API's unit, and the compiler now rejects unit mismatches. Only genuine duration arguments were converted - loop counts, scroll-line counts, sizes, ports and the like were deliberately left as plain integers. All targeted APIs provide `std::chrono` overloads in the minimum supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8), `QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)` (6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7). #### Other info (issues closed, discussion etc) Test case: the full application builds cleanly and the entire functional `ctest` suite passes. The only failing test is the known, pre-existing `PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is unrelated to this change. Assisted-by: Claude:claude-opus-4-8
2026-07-25 20:24:31 +02:00
QTest::qWait(100ms);
QTest::keyClick(QApplication::focusWidget(), Qt::Key_Return);
});
QSignalSpy spy(mudlet::self(), &mudlet::signal_profileLoaded);
if (!spy.wait(5000)) {
QFAIL("Profile took too long to load.");
}
auto host = mudlet::self()->getActiveHost();
if (!host) {
QFAIL("No active host available for the test.");
}
QSignalSpy spy2(&(host->mTelnet), &cTelnet::signal_connected);
if (!spy2.wait(2000)) {
QFAIL("Could not connect with the host.");
}
}
// Polls the console buffer until the expected text appears on any line, with
// a timeout
bool waitForTextInBuffer(const QString& text, int timeoutMs = 5000)
{
auto console = mudlet::self()->getActiveHost()->mpConsole;
return QTest::qWaitFor(
[&]() {
for (int i = 0; i <= console->buffer.getLastLineNumber(); ++i) {
if (console->buffer.line(i) == text) {
return true;
}
}
return false;
},
timeoutMs);
}
fix: non-English game text no longer garbles after a stray "&" (#9489) #### Brief overview of the PR When a game sends a bare `&` that runs straight into non-English (multi-byte) text - which is not a valid MXP entity - Mudlet was re-encoding the recovered bytes, mangling the following characters into mojibake. For example `&Ф` rendered as `&Ð�`, and `Käse&Brötchen` lost its umlaut. This restores the raw-byte passthrough for these malformed/unknown entities while keeping the correct session-encoding handling for custom `<!ENTITY>` values. Non-English text now displays intact. #### Motivation for adding to Mudlet #9439 fixed custom MXP entities that carry non-Latin1 values, but the same code path also handles malformed and unknown entities, whose value is raw, still-undecoded bytes (one Latin1 code unit per byte). Encoding those bytes with the session charset double-encoded them and orphaned the UTF-8 continuation bytes still sitting in the buffer, so any non-ASCII text immediately after a stray `&` turned to garbage. Players on games that use Cyrillic, umlauts, CJK and other non-ASCII text were the ones affected. The fix distinguishes the two cases: a custom entity value is decoded text, so it is re-encoded with the session encoding; a malformed/unknown entity value is raw bytes, so it is passed through unchanged with `toLatin1()`. #### Other info Follow-up to #9439. **Test case:** Two functional tests were added (using an ephemeral stub port so parallel test runs cannot collide): - `test_MalformedEntityKeepsNonAsciiBytes` - sends `Käse&Brötchen and &Ф too` and expects it back byte-for-byte. This fails on the pre-fix code (the tail garbles to mojibake) and passes with the fix. - `test_CustomEntityKeepsNonAsciiValue` - sends `<!ENTITY storm "Гроза">The &storm; rages` and expects `The Гроза rages`, guarding the #9439 behaviour so the new branching does not regress it. Manual check: connect to a UTF-8 game that emits a bare `&` followed by non-English text (e.g. `&Ф`). Before this fix the following characters render as garbage; after it they render correctly. Assisted-by: Claude:claude-fable-5 Assisted-by: Claude:claude-opus-4-8
2026-07-27 19:48:40 +02:00
// All buffer lines joined together, for failure diagnostics
QString bufferContents()
{
auto console = mudlet::self()->getActiveHost()->mpConsole;
QStringList lines;
for (int i = 0; i <= console->buffer.getLastLineNumber(); ++i) {
lines << console->buffer.line(i);
}
return lines.join(QChar::LineFeed);
}
// Utility function
void deleteProfileDirectory(const QString& profileName)
{
const QString path = mudlet::getMudletPath(enums::profileHomePath, profileName);
QDir dir(path);
if (!dir.exists()) {
qInfo() << "Profile directory does not exist:" << path;
return;
}
dir.removeRecursively();
}
};
void initializeQRCResources()
{
#ifdef INCLUDE_VARIABLE_SPLASH_SCREEN
qInitResources_additional_splash_screens();
#endif
#ifdef INCLUDE_FONTS
qInitResources_mudlet_fonts_common();
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
qInitResources_mudlet_fonts_posix();
#endif
#endif
qInitResources_mudlet();
qInitResources_qm();
}
#include "TelnetTextDisplayedTest.moc"
QTEST_MAIN(TelnetTextDisplayedTest)