mudlet/test/functional_tests/TOscTest.cpp

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

1445 lines
57 KiB
C++
Raw Permalink Normal View History

/***************************************************************************
* Copyright (C) 2026 by Mudlet Developers *
* *
* 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. *
***************************************************************************/
/*
* Tests for OSC (Operating System Command) escape sequence handling.
*
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
* Uses loopbackTest() to inject data directly into the telnet processing
* pipeline, avoiding per-test TCP connections and profile creation.
* Mudlet is started once in initTestCase and reused across all tests.
*
* Run with: ctest -R TOscTest -V
*/
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
#include <QKeyEvent>
#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 "MudletInstanceCoordinator.h"
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
#include "TAccessibleTextEdit.h"
Add: OSC 8 context menu title support (#8910) ## Summary Adds a `"title"` property to OSC 8 hyperlink configuration that renders a non-clickable section header at the top of right-click context menus. This gives players immediate context about what a menu relates to (e.g., an item name, NPC name, or action category). Context menus now also use the main console display font set in Settings, matching the rest of the game output. ## Configuration **Simple string** (renders with default teal color): ```json {"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` **With Tier 2 style properties:** ```json { "title": { "text": "Lamb and Barley Stew", "style": {"color": "#5fbdaf", "bold": true} }, "menu": [ {"View Details": "send:look stew"}, {"Buy": "send:buy stew"} ] } ``` **Compact syntax** (shorthand `ti` for `title`): ```json {"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` ## Title style The title's `style` object uses the same [Tier 2 style properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties) as OSC 8 hyperlink styling, including all supported color formats (named colors, hex, rgb). Currently supported properties for the title: | Property | Type | Description | |----------|------|-------------| | `color` | String | Title text color (default: `#5fbdaf`) | | `bg` | String | Title background color | | `bold` | Boolean | Bold text | | `italic` | Boolean | Italic text | ## Behaviour notes - Title renders as a non-clickable label with a separator line below, appearing above all menu items - If `title` is set but no `menu` is configured, the title has no effect (no popup is shown for single-action links) - Left-click behaviour is unaffected - When no style is specified, the title defaults to teal (`#5fbdaf`) to visually distinguish it from clickable menu items - Title without a `style` object is backward-compatible - older clients that don't recognize the `title` key will silently ignore it ## Files changed - **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to `HyperlinkStyling` - **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and object formats), query param extraction and URL stripping - **`src/TTextEdit.cpp`** - Renders the title label in the popup menu with style properties; sets console font on context menus - **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset property ## Screenshots <img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56" src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0" /> ## Test plan - [x] Send an OSC 8 link with `"title"` as a simple string - verify teal header appears above menu items - [x] Send an OSC 8 link with `"title"` as an object with custom style - verify color/bold/italic apply - [x] Send an OSC 8 link with menu but no title - verify no header appears - [x] Send an OSC 8 link with title but no menu - verify no popup on right-click - [x] Verify left-click still executes the primary action - [x] Change the console font in Settings - verify context menu font updates to match - [x] Verify compact syntax `"ti"` works as shorthand for `"title"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-03-26 08:33:44 +01:00
#include "THyperlinkStyling.h"
#include "TLinkStore.h"
#include "TMainConsole.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 initializeQRCResourcesForOscTest();
class TOscTest : public QObject {
Q_OBJECT
private:
TelnetServerStub *mpServer = nullptr;
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
Host *mpHost = nullptr;
const QString mHostname = "OSC-Test-Host";
QString mPort; // assigned the stub's actual ephemeral port in initTestCase()
const QString mLocalhost = "localhost";
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
// Injects raw telnet data into the processing pipeline via loopback and
// waits for the buffer to process it.
void injectData(const QString &message) {
QByteArray data = (message + qsl("\r\n")).toUtf8();
mpHost->mTelnet.loopbackTest(data);
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(50ms);
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
}
// Scans backward through the buffer to find the first hyperlink and returns
// its command list from the link store.
QStringList findFirstLinkCommands() {
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
if (!mpHost || !mpHost->mpConsole) {
return {};
}
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
TMainConsole *console = mpHost->mpConsole;
int linkId = 0;
for (int line = console->buffer.getLastLineNumber();
line >= 0 && linkId == 0; --line) {
linkId = console->buffer.getLinkIndexAt(line, 0);
}
if (linkId <= 0) {
return {};
}
return console->getLinkStore().getLinksConst(linkId);
}
Add: OSC 8 context menu title support (#8910) ## Summary Adds a `"title"` property to OSC 8 hyperlink configuration that renders a non-clickable section header at the top of right-click context menus. This gives players immediate context about what a menu relates to (e.g., an item name, NPC name, or action category). Context menus now also use the main console display font set in Settings, matching the rest of the game output. ## Configuration **Simple string** (renders with default teal color): ```json {"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` **With Tier 2 style properties:** ```json { "title": { "text": "Lamb and Barley Stew", "style": {"color": "#5fbdaf", "bold": true} }, "menu": [ {"View Details": "send:look stew"}, {"Buy": "send:buy stew"} ] } ``` **Compact syntax** (shorthand `ti` for `title`): ```json {"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` ## Title style The title's `style` object uses the same [Tier 2 style properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties) as OSC 8 hyperlink styling, including all supported color formats (named colors, hex, rgb). Currently supported properties for the title: | Property | Type | Description | |----------|------|-------------| | `color` | String | Title text color (default: `#5fbdaf`) | | `bg` | String | Title background color | | `bold` | Boolean | Bold text | | `italic` | Boolean | Italic text | ## Behaviour notes - Title renders as a non-clickable label with a separator line below, appearing above all menu items - If `title` is set but no `menu` is configured, the title has no effect (no popup is shown for single-action links) - Left-click behaviour is unaffected - When no style is specified, the title defaults to teal (`#5fbdaf`) to visually distinguish it from clickable menu items - Title without a `style` object is backward-compatible - older clients that don't recognize the `title` key will silently ignore it ## Files changed - **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to `HyperlinkStyling` - **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and object formats), query param extraction and URL stripping - **`src/TTextEdit.cpp`** - Renders the title label in the popup menu with style properties; sets console font on context menus - **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset property ## Screenshots <img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56" src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0" /> ## Test plan - [x] Send an OSC 8 link with `"title"` as a simple string - verify teal header appears above menu items - [x] Send an OSC 8 link with `"title"` as an object with custom style - verify color/bold/italic apply - [x] Send an OSC 8 link with menu but no title - verify no header appears - [x] Send an OSC 8 link with title but no menu - verify no popup on right-click - [x] Verify left-click still executes the primary action - [x] Change the console font in Settings - verify context menu font updates to match - [x] Verify compact syntax `"ti"` works as shorthand for `"title"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-03-26 08:33:44 +01:00
// Scans backward through the buffer to find the first hyperlink and returns
// its HyperlinkStyling from the link store.
Mudlet::HyperlinkStyling findFirstLinkStyling() {
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
if (!mpHost || !mpHost->mpConsole) {
Add: OSC 8 context menu title support (#8910) ## Summary Adds a `"title"` property to OSC 8 hyperlink configuration that renders a non-clickable section header at the top of right-click context menus. This gives players immediate context about what a menu relates to (e.g., an item name, NPC name, or action category). Context menus now also use the main console display font set in Settings, matching the rest of the game output. ## Configuration **Simple string** (renders with default teal color): ```json {"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` **With Tier 2 style properties:** ```json { "title": { "text": "Lamb and Barley Stew", "style": {"color": "#5fbdaf", "bold": true} }, "menu": [ {"View Details": "send:look stew"}, {"Buy": "send:buy stew"} ] } ``` **Compact syntax** (shorthand `ti` for `title`): ```json {"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` ## Title style The title's `style` object uses the same [Tier 2 style properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties) as OSC 8 hyperlink styling, including all supported color formats (named colors, hex, rgb). Currently supported properties for the title: | Property | Type | Description | |----------|------|-------------| | `color` | String | Title text color (default: `#5fbdaf`) | | `bg` | String | Title background color | | `bold` | Boolean | Bold text | | `italic` | Boolean | Italic text | ## Behaviour notes - Title renders as a non-clickable label with a separator line below, appearing above all menu items - If `title` is set but no `menu` is configured, the title has no effect (no popup is shown for single-action links) - Left-click behaviour is unaffected - When no style is specified, the title defaults to teal (`#5fbdaf`) to visually distinguish it from clickable menu items - Title without a `style` object is backward-compatible - older clients that don't recognize the `title` key will silently ignore it ## Files changed - **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to `HyperlinkStyling` - **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and object formats), query param extraction and URL stripping - **`src/TTextEdit.cpp`** - Renders the title label in the popup menu with style properties; sets console font on context menus - **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset property ## Screenshots <img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56" src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0" /> ## Test plan - [x] Send an OSC 8 link with `"title"` as a simple string - verify teal header appears above menu items - [x] Send an OSC 8 link with `"title"` as an object with custom style - verify color/bold/italic apply - [x] Send an OSC 8 link with menu but no title - verify no header appears - [x] Send an OSC 8 link with title but no menu - verify no popup on right-click - [x] Verify left-click still executes the primary action - [x] Change the console font in Settings - verify context menu font updates to match - [x] Verify compact syntax `"ti"` works as shorthand for `"title"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-03-26 08:33:44 +01:00
return {};
}
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
TMainConsole *console = mpHost->mpConsole;
Add: OSC 8 context menu title support (#8910) ## Summary Adds a `"title"` property to OSC 8 hyperlink configuration that renders a non-clickable section header at the top of right-click context menus. This gives players immediate context about what a menu relates to (e.g., an item name, NPC name, or action category). Context menus now also use the main console display font set in Settings, matching the rest of the game output. ## Configuration **Simple string** (renders with default teal color): ```json {"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` **With Tier 2 style properties:** ```json { "title": { "text": "Lamb and Barley Stew", "style": {"color": "#5fbdaf", "bold": true} }, "menu": [ {"View Details": "send:look stew"}, {"Buy": "send:buy stew"} ] } ``` **Compact syntax** (shorthand `ti` for `title`): ```json {"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` ## Title style The title's `style` object uses the same [Tier 2 style properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties) as OSC 8 hyperlink styling, including all supported color formats (named colors, hex, rgb). Currently supported properties for the title: | Property | Type | Description | |----------|------|-------------| | `color` | String | Title text color (default: `#5fbdaf`) | | `bg` | String | Title background color | | `bold` | Boolean | Bold text | | `italic` | Boolean | Italic text | ## Behaviour notes - Title renders as a non-clickable label with a separator line below, appearing above all menu items - If `title` is set but no `menu` is configured, the title has no effect (no popup is shown for single-action links) - Left-click behaviour is unaffected - When no style is specified, the title defaults to teal (`#5fbdaf`) to visually distinguish it from clickable menu items - Title without a `style` object is backward-compatible - older clients that don't recognize the `title` key will silently ignore it ## Files changed - **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to `HyperlinkStyling` - **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and object formats), query param extraction and URL stripping - **`src/TTextEdit.cpp`** - Renders the title label in the popup menu with style properties; sets console font on context menus - **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset property ## Screenshots <img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56" src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0" /> ## Test plan - [x] Send an OSC 8 link with `"title"` as a simple string - verify teal header appears above menu items - [x] Send an OSC 8 link with `"title"` as an object with custom style - verify color/bold/italic apply - [x] Send an OSC 8 link with menu but no title - verify no header appears - [x] Send an OSC 8 link with title but no menu - verify no popup on right-click - [x] Verify left-click still executes the primary action - [x] Change the console font in Settings - verify context menu font updates to match - [x] Verify compact syntax `"ti"` works as shorthand for `"title"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-03-26 08:33:44 +01:00
int linkId = 0;
for (int line = console->buffer.getLastLineNumber();
line >= 0 && linkId == 0; --line) {
linkId = console->buffer.getLinkIndexAt(line, 0);
}
if (linkId <= 0) {
return {};
}
return console->getLinkStore().getStyling(linkId);
}
// Builds an OSC 8 hyperlink message with optional JSON config in the URL.
// The config is appended as a ?config= query parameter.
QString buildOsc8WithConfig(const QString &baseUrl, const QString &linkText,
const QString &jsonConfig) {
QString url = baseUrl;
if (!jsonConfig.isEmpty()) {
url += qsl("?config=") + jsonConfig;
}
return qsl("\x1b]8;;") + url + qsl("\x1b\\") + linkText +
qsl("\x1b]8;;\x1b\\");
}
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
// Scans forward through the buffer to find the first hyperlink and returns
// its (line, column) position, or std::nullopt if none found.
std::optional<std::pair<int, int>> findFirstLinkPosition() {
if (!mpHost || !mpHost->mpConsole) {
return std::nullopt;
}
TMainConsole *console = mpHost->mpConsole;
for (int line = 0; line <= console->buffer.getLastLineNumber(); ++line) {
for (int col = 0; col < console->buffer.line(line).length(); ++col) {
if (console->buffer.getLinkIndexAt(line, col) > 0) {
return std::pair{line, col};
}
}
}
return std::nullopt;
}
// Scans forward through the buffer to find the first hyperlink and returns
// its (line, column, linkId), or std::nullopt if none found.
std::optional<std::tuple<int, int, int>> findFirstLinkPositionWithId() {
if (!mpHost || !mpHost->mpConsole) {
return std::nullopt;
}
TMainConsole *console = mpHost->mpConsole;
for (int line = 0; line <= console->buffer.getLastLineNumber(); ++line) {
for (int col = 0; col < console->buffer.line(line).length(); ++col) {
int lid = console->buffer.getLinkIndexAt(line, col);
if (lid > 0) {
return std::tuple{line, col, lid};
}
}
}
return std::nullopt;
}
// Scans forward through the buffer to find the nth distinct hyperlink
// (1-based) and returns its (line, column, linkId), or std::nullopt.
std::optional<std::tuple<int, int, int>> findNthDistinctLinkPosition(int n) {
if (!mpHost || !mpHost->mpConsole) {
return std::nullopt;
}
TMainConsole *console = mpHost->mpConsole;
QSet<int> seenIds;
for (int line = 0; line <= console->buffer.getLastLineNumber(); ++line) {
for (int col = 0; col < console->buffer.line(line).length(); ++col) {
int lid = console->buffer.getLinkIndexAt(line, col);
if (lid > 0 && !seenIds.contains(lid)) {
seenIds.insert(lid);
if (seenIds.size() == n) {
return std::tuple{line, col, lid};
}
}
}
}
return std::nullopt;
}
// Joins all buffer lines into one string.
QString allBufferText() {
TMainConsole *console = mpHost->mpConsole;
QString allText;
for (int i = 0; i <= console->buffer.getLastLineNumber(); ++i) {
allText += console->buffer.line(i);
}
return allText;
}
private slots:
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
// Start mudlet and create a profile once for all tests.
void initTestCase() {
initializeQRCResourcesForOscTest();
mpServer = new TelnetServerStub(qApp);
mpServer->start(mLocalhost, 0); // ephemeral OS-assigned port avoids collisions across concurrent test runs
QVERIFY2(mpServer->isListening(),
"TelnetServerStub failed to bind a loopback port");
mPort = QString::number(mpServer->serverPort());
mudlet::start();
mudlet::self()->setupConfig();
mudlet::self()->takeOwnershipOfInstanceCoordinator(
std::make_unique<MudletInstanceCoordinator>(
"MudletInstanceCoordinator"));
mudlet::self()->init();
mudlet::self()->setStorePasswordsSecurely(false);
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
const QString path =
mudlet::getMudletPath(enums::profileHomePath, mHostname);
QDir(path).removeRecursively();
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, [this]() {
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
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);
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
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);
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::keyClicks(QApplication::focusWidget(), mHostname);
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);
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
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);
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::keyClicks(QApplication::focusWidget(), mLocalhost);
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);
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
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);
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::keyClicks(QApplication::focusWidget(), mPort);
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);
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::keyClick(QApplication::focusWidget(), Qt::Key_Return);
});
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QSignalSpy spy(mudlet::self(), &mudlet::signal_profileLoaded);
if (!spy.wait(1000)) {
QFAIL("Profile took too long to load.");
}
mpHost = mudlet::self()->getActiveHost();
if (!mpHost) {
QFAIL("No active host available for the test.");
}
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QSignalSpy spy2(&(mpHost->mTelnet), &cTelnet::signal_connected);
if (!spy2.wait(500)) {
QFAIL("Could not connect with the host.");
}
}
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
// Clear buffer and link state before each test for isolation.
void init() {
QVERIFY(mpHost);
QVERIFY(mpHost->mpConsole);
mpHost->mpConsole->buffer.clear();
}
// Data-driven test: verifies text after various OSC sequences is displayed
// correctly (not swallowed by the parser).
void test_OscTextDisplay_data() {
QTest::addColumn<QString>("message");
QTest::addColumn<QString>("expectedText");
// BEL-terminated OSC 8 splits link text across buffer lines in loopback
// mode, so exact match on the joined text isn't possible - use contains.
QTest::addColumn<bool>("exactMatch");
QTest::newRow("BEL-terminated OSC 2 (window title)")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< QString("\x1b]2;Window Title\x07Hello World") << qsl("Hello World")
<< true;
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("ST-terminated OSC P (color redefine)")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< QString("\x1b]P0FF0000\x1b\\Hello") << qsl("Hello") << true;
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("BEL-terminated OSC 8 (hyperlink)")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< QString(
"\x1b]8;;http://example.com\x07Link Text\x1b]8;;\x07 After Link")
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
<< qsl("Link Text After Link") << false;
QTest::newRow("BEL-terminated OSC P")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< QString("\x1b]P0FF0000\x07Hello") << qsl("Hello") << true;
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("empty OSC sequence")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< QString("\x1b]\x07Normal text") << qsl("Normal text") << true;
QTest::newRow("stray ESC c then literal bracket text")
<< QString("\x1b"
"c[literal] text")
<< qsl("[literal] text") << true;
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("OSC exceeds length limit")
<< QString("\x1b]2;") + QString(5000, 'A') + QString("\x07Normal text")
<< qsl("Normal text") << true;
}
void test_OscTextDisplay() {
QFETCH(QString, message);
QFETCH(QString, expectedText);
QFETCH(bool, exactMatch);
injectData(message);
// Join all buffer lines since cursor position varies with loopback.
TMainConsole *console = mpHost->mpConsole;
QString allText;
for (int i = 0; i <= console->buffer.getLastLineNumber(); ++i) {
allText += console->buffer.line(i);
}
if (exactMatch) {
QCOMPARE(allText.trimmed(), expectedText);
} else {
QVERIFY2(allText.contains(expectedText),
qPrintable(qsl("Expected buffer to contain '%1' but got '%2'")
.arg(expectedText, allText)));
}
}
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
// =====================================================================
// Split sequence / interleaved local feed tests
// =====================================================================
// A CSI/OSC sequence split across two server packets must still parse when
// locally generated text (a feedTriggers() call, an MMCP chat message)
// arrives in between: the local feed runs through the same parser and must
// not consume or clear the pending ESC latch of the server stream.
void test_SplitSequenceSurvivesInterleavedLocalFeed_data() {
QTest::addColumn<QString>("secondPacket");
QTest::addColumn<QString>("mustNotContain");
QTest::newRow("split CSI") << qsl("[32mAfter\n") << qsl("[32mAfter");
QTest::newRow("split OSC")
<< qsl("]2;Window Title\x07"
"After\n")
<< qsl("]2;Window Title");
}
void test_SplitSequenceSurvivesInterleavedLocalFeed() {
QFETCH(QString, secondPacket);
QFETCH(QString, mustNotContain);
// First server packet ends in a bare ESC...
std::string part1{"Before\n\x1b"};
mpHost->mpConsole->printOnDisplay(part1, true);
// ...then locally generated text arrives in between...
std::string localText{"local tick\n"};
mpHost->mpConsole->printOnDisplay(localText, false);
// ...then the server packet with the rest of the sequence:
std::string part2{secondPacket.toStdString()};
mpHost->mpConsole->printOnDisplay(part2, true);
const QString allText = allBufferText();
QVERIFY2(!allText.contains(mustNotContain),
qPrintable(qsl("Sequence fragment '%1' leaked into display: '%2'")
.arg(mustNotContain, allText)));
QVERIFY2(allText.contains(qsl("After")),
qPrintable(
qsl("Text after the split sequence went missing from: '%1'")
.arg(allText)));
QVERIFY2(allText.contains(qsl("local tick")),
qPrintable(qsl("Locally fed text went missing from: '%1'")
.arg(allText)));
}
// The don't-decode-this-payload flag set by a DCS/SOS/PM/APC introducer
// (mGotString) is carry-over parser state just like mGotOSC and must swap
// with the rest of it around a local feed. If it leaks: a complete OSC in
// the local feed is consumed but silently not decoded, and the server's DCS
// payload is wrongly decoded as an OSC when its terminator arrives.
void test_SplitDcsStringStateDoesNotLeakAcrossChannels() {
const bool savedMayRedefine = mpHost->getMayRedefineColors();
mpHost->setMayRedefineColors(true);
const QColor savedRed = mpHost->mRed;
const QColor savedGreen = mpHost->mGreen;
// First server packet ends inside a DCS (ESC P) with no terminator; its
// payload is shaped like an OSC colour redefinition for ANSI colour 2 so
// that wrongly feeding it to the OSC decoder is observable:
std::string part1{"Before\n\x1bPP2665544"};
mpHost->mpConsole->printOnDisplay(part1, true);
// Interleaved local feed carrying a complete OSC colour redefinition for
// ANSI colour 1 that must be decoded:
std::string localText{"\x1b]P1223344\x07local tick\n"};
mpHost->mpConsole->printOnDisplay(localText, false);
// The server DCS terminator arrives; the payload must be consumed
// without being decoded:
std::string part2{"\x07"
"After\n"};
mpHost->mpConsole->printOnDisplay(part2, true);
const QColor redAfter = mpHost->mRed;
const QColor greenAfter = mpHost->mGreen;
const QString allText = allBufferText();
// Restore profile colour state before asserting so a failure does not
// poison later tests:
mpHost->mRed = savedRed;
mpHost->mGreen = savedGreen;
mpHost->setMayRedefineColors(savedMayRedefine);
QVERIFY2(allText.contains(qsl("After")),
qPrintable(
qsl("Text after the split sequence went missing from: '%1'")
.arg(allText)));
QVERIFY2(allText.contains(qsl("local tick")),
qPrintable(qsl("Locally fed text went missing from: '%1'")
.arg(allText)));
QVERIFY2(!allText.contains(qsl("P2665544")),
qPrintable(qsl("DCS payload leaked into display: '%1'")
.arg(allText)));
QVERIFY2(redAfter == QColor(0x22, 0x33, 0x44),
qPrintable(qsl("Complete OSC inside the local feed was not "
"decoded: expected colour 1 to become #223344 "
"but it is %1")
.arg(redAfter.name())));
QVERIFY2(greenAfter == savedGreen,
qPrintable(qsl("Server DCS payload was wrongly fed to the OSC "
"decoder: colour 2 changed to %1")
.arg(greenAfter.name())));
}
// =====================================================================
// OSC 8 Hyperlink URL Parameter Tests
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
// =====================================================================
// Data-driven test: verifies OSC 8 URL query parameter handling - reserved
// params (config, preset) are stripped while user params are preserved.
void test_Osc8UrlParams_data() {
QTest::addColumn<QString>("message");
QTest::addColumn<QStringList>("mustContain");
QTest::addColumn<QStringList>("mustNotContain");
QTest::newRow("preserves web URL query params")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("\x1b]8;;https://example.com/"
"?id=42&lang=en\x1b\\Link\x1b]8;;\x1b\\")
<< QStringList{"id=42&lang=en"} << QStringList{};
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("strips config param")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("\x1b]8;;https://example.com/"
"?config=%7B%22style%22%3A%7B%22color%22%3A%22red%22%7D%"
"7D\x1b\\Styled\x1b]8;;\x1b\\")
<< QStringList{} << QStringList{"config="};
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("strips preset, preserves other params")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("\x1b]8;;https://example.com/"
"?page=1&preset=danger\x1b\\Link\x1b]8;;\x1b\\")
<< QStringList{"page=1"} << QStringList{"preset="};
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("strips preset with encoded equals")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("\x1b]8;;https://example.com/"
"?preset%3Ddefault&page=1\x1b\\Link\x1b]8;;\x1b\\")
<< QStringList{"page=1"} << QStringList{"preset%3D"};
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("strips config with encoded equals (lowercase)")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("\x1b]8;;https://example.com/"
"?config%3dvalue&foo=bar\x1b\\Link\x1b]8;;\x1b\\")
<< QStringList{"foo=bar"} << QStringList{"config%3d"};
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("preserves percent-encoded reserved names")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("\x1b]8;;https://example.com/"
"?%63%6F%6E%66%69%67=value\x1b\\Link\x1b]8;;\x1b\\")
<< QStringList{"%63%6F%6E%66%69%67=value"} << QStringList{};
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("send URL strips all query params")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("\x1b]8;;send:attack?config=%7B%22style%22%3A%7B%22color%22%3A%"
"22red%22%7D%7D\x1b\\Attack\x1b]8;;\x1b\\")
<< QStringList{"attack"} << QStringList{"config="};
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
}
void test_Osc8UrlParams() {
QFETCH(QString, message);
QFETCH(QStringList, mustContain);
QFETCH(QStringList, mustNotContain);
injectData(message);
QStringList commands = findFirstLinkCommands();
QVERIFY2(!commands.isEmpty(), "No hyperlink found in buffer");
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
for (const auto &expected : mustContain) {
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
QVERIFY2(
commands.first().contains(expected),
qPrintable(
qsl("Expected '%1' in: %2").arg(expected, commands.first())));
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
}
for (const auto &forbidden : mustNotContain) {
QVERIFY2(!commands.first().contains(forbidden),
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
qPrintable(qsl("Did not expect '%1' in: %2")
.arg(forbidden, commands.first())));
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
}
}
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
// =====================================================================
// OSC 8 Hyperlink Context Menu Title Tests
// =====================================================================
// Data-driven test: verifies menuTitle text is parsed correctly from various
// config JSON shapes (simple string, object, compact syntax, invalid types).
void test_Osc8Title_TextParsing_data() {
QTest::addColumn<QString>("config");
QTest::addColumn<QString>("expectedTitle");
QTest::addColumn<bool>("hasCustomStyling");
QTest::newRow("simple string")
<< qsl(R"({"title":"Lamb and Barley Stew","menu":[{"View Details":"send:look stew"}]})")
<< qsl("Lamb and Barley Stew") << false;
QTest::newRow("compact ti shorthand")
<< qsl(R"({"ti":"Rusty Sword","m":[{"Equip":"send:wield sword"}]})")
<< qsl("Rusty Sword") << false;
QTest::newRow("title without menu")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl(R"({"title":"Lonely Title"})") << qsl("Lonely Title") << false;
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("menu without title")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl(R"({"menu":[{"North":"send:north"}]})") << QString() << false;
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("empty string")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl(R"({"title":"","menu":[{"Action":"send:action"}]})") << QString()
<< false;
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("object with empty text")
<< qsl(R"({"title":{"text":"","style":{"color":"#ff0000"}},"menu":[{"Action":"send:action"}]})")
<< QString() << true;
QTest::newRow("object without text key")
<< qsl(R"({"title":{"style":{"color":"#ff0000"}},"menu":[{"Action":"send:action"}]})")
<< QString() << true;
QTest::newRow("object without style key")
<< qsl(R"({"title":{"text":"Style-less Title"},"menu":[{"Action":"send:action"}]})")
<< qsl("Style-less Title") << false;
QTest::newRow("unicode characters")
<< qsl(R"({"title":"Potion du Guerrier","menu":[{"Drink":"send:drink potion"}]})")
<< qsl("Potion du Guerrier") << false;
QTest::newRow("special characters")
<< qsl(R"({"title":"Item <Rare> [+5] & More!","menu":[{"Use":"send:use item"}]})")
<< qsl("Item <Rare> [+5] & More!") << false;
QTest::newRow("alongside other config")
<< qsl(R"({"title":"Full Config","tooltip":"A helpful tooltip","style":{"color":"#00ffff"},"menu":[{"Action 1":"send:action1"}]})")
<< qsl("Full Config") << true;
QTest::newRow("numeric value ignored")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl(R"({"title":42,"menu":[{"Action":"send:action"}]})") << QString()
<< false;
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("boolean value ignored")
<< qsl(R"({"title":true,"menu":[{"Action":"send:action"}]})")
<< QString() << false;
QTest::newRow("array value ignored")
<< qsl(R"({"title":["a","b"],"menu":[{"Action":"send:action"}]})")
<< QString() << false;
}
void test_Osc8Title_TextParsing() {
QFETCH(QString, config);
QFETCH(QString, expectedTitle);
QFETCH(bool, hasCustomStyling);
injectData(buildOsc8WithConfig(qsl("send:action"), qsl("[Link]"), config));
// Verify a link was actually created in the buffer - prevents silent
// passes when the injection pipeline is broken entirely.
QStringList commands = findFirstLinkCommands();
QVERIFY2(!commands.isEmpty(), "No hyperlink found in buffer");
Add: OSC 8 context menu title support (#8910) ## Summary Adds a `"title"` property to OSC 8 hyperlink configuration that renders a non-clickable section header at the top of right-click context menus. This gives players immediate context about what a menu relates to (e.g., an item name, NPC name, or action category). Context menus now also use the main console display font set in Settings, matching the rest of the game output. ## Configuration **Simple string** (renders with default teal color): ```json {"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` **With Tier 2 style properties:** ```json { "title": { "text": "Lamb and Barley Stew", "style": {"color": "#5fbdaf", "bold": true} }, "menu": [ {"View Details": "send:look stew"}, {"Buy": "send:buy stew"} ] } ``` **Compact syntax** (shorthand `ti` for `title`): ```json {"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` ## Title style The title's `style` object uses the same [Tier 2 style properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties) as OSC 8 hyperlink styling, including all supported color formats (named colors, hex, rgb). Currently supported properties for the title: | Property | Type | Description | |----------|------|-------------| | `color` | String | Title text color (default: `#5fbdaf`) | | `bg` | String | Title background color | | `bold` | Boolean | Bold text | | `italic` | Boolean | Italic text | ## Behaviour notes - Title renders as a non-clickable label with a separator line below, appearing above all menu items - If `title` is set but no `menu` is configured, the title has no effect (no popup is shown for single-action links) - Left-click behaviour is unaffected - When no style is specified, the title defaults to teal (`#5fbdaf`) to visually distinguish it from clickable menu items - Title without a `style` object is backward-compatible - older clients that don't recognize the `title` key will silently ignore it ## Files changed - **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to `HyperlinkStyling` - **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and object formats), query param extraction and URL stripping - **`src/TTextEdit.cpp`** - Renders the title label in the popup menu with style properties; sets console font on context menus - **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset property ## Screenshots <img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56" src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0" /> ## Test plan - [x] Send an OSC 8 link with `"title"` as a simple string - verify teal header appears above menu items - [x] Send an OSC 8 link with `"title"` as an object with custom style - verify color/bold/italic apply - [x] Send an OSC 8 link with menu but no title - verify no header appears - [x] Send an OSC 8 link with title but no menu - verify no popup on right-click - [x] Verify left-click still executes the primary action - [x] Change the console font in Settings - verify context menu font updates to match - [x] Verify compact syntax `"ti"` works as shorthand for `"title"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-03-26 08:33:44 +01:00
auto styling = findFirstLinkStyling();
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QCOMPARE(styling.menuTitle, expectedTitle);
if (!hasCustomStyling) {
QVERIFY2(!styling.menuTitleStyle.hasCustomStyling,
"Expected no custom styling on menu title");
}
Add: OSC 8 context menu title support (#8910) ## Summary Adds a `"title"` property to OSC 8 hyperlink configuration that renders a non-clickable section header at the top of right-click context menus. This gives players immediate context about what a menu relates to (e.g., an item name, NPC name, or action category). Context menus now also use the main console display font set in Settings, matching the rest of the game output. ## Configuration **Simple string** (renders with default teal color): ```json {"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` **With Tier 2 style properties:** ```json { "title": { "text": "Lamb and Barley Stew", "style": {"color": "#5fbdaf", "bold": true} }, "menu": [ {"View Details": "send:look stew"}, {"Buy": "send:buy stew"} ] } ``` **Compact syntax** (shorthand `ti` for `title`): ```json {"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` ## Title style The title's `style` object uses the same [Tier 2 style properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties) as OSC 8 hyperlink styling, including all supported color formats (named colors, hex, rgb). Currently supported properties for the title: | Property | Type | Description | |----------|------|-------------| | `color` | String | Title text color (default: `#5fbdaf`) | | `bg` | String | Title background color | | `bold` | Boolean | Bold text | | `italic` | Boolean | Italic text | ## Behaviour notes - Title renders as a non-clickable label with a separator line below, appearing above all menu items - If `title` is set but no `menu` is configured, the title has no effect (no popup is shown for single-action links) - Left-click behaviour is unaffected - When no style is specified, the title defaults to teal (`#5fbdaf`) to visually distinguish it from clickable menu items - Title without a `style` object is backward-compatible - older clients that don't recognize the `title` key will silently ignore it ## Files changed - **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to `HyperlinkStyling` - **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and object formats), query param extraction and URL stripping - **`src/TTextEdit.cpp`** - Renders the title label in the popup menu with style properties; sets console font on context menus - **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset property ## Screenshots <img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56" src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0" /> ## Test plan - [x] Send an OSC 8 link with `"title"` as a simple string - verify teal header appears above menu items - [x] Send an OSC 8 link with `"title"` as an object with custom style - verify color/bold/italic apply - [x] Send an OSC 8 link with menu but no title - verify no header appears - [x] Send an OSC 8 link with title but no menu - verify no popup on right-click - [x] Verify left-click still executes the primary action - [x] Change the console font in Settings - verify context menu font updates to match - [x] Verify compact syntax `"ti"` works as shorthand for `"title"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-03-26 08:33:44 +01:00
}
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
// Data-driven test: verifies menuTitleStyle properties are parsed correctly.
void test_Osc8Title_StyleParsing_data() {
QTest::addColumn<QString>("config");
QTest::addColumn<QString>("expectedTitle");
QTest::addColumn<bool>("bold");
QTest::addColumn<bool>("italic");
QTest::addColumn<bool>("underlined");
QTest::addColumn<bool>("strikeOut");
QTest::addColumn<QString>("fgColor");
QTest::addColumn<QString>("bgColor");
QTest::addColumn<int>("underlineStyle");
QTest::addColumn<QString>("underlineColor");
QTest::newRow("bold and color")
<< qsl(R"({"title":{"text":"Magic Shop - Potions","style":{"color":"#ffd700","bold":true}},"menu":[{"Buy":"send:buy potion"}]})")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("Magic Shop - Potions") << true << false << false << false
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
<< qsl("#ffd700") << QString() << -1 << QString();
QTest::newRow("italic and background")
<< qsl(R"({"title":{"text":"Sir Galahad the Brave","style":{"color":"#ffffff","bg":"#333333","italic":true}},"menu":[{"Talk":"send:talk galahad"}]})")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("Sir Galahad the Brave") << false << true << false << false
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
<< qsl("#ffffff") << qsl("#333333") << -1 << QString();
QTest::newRow("all text decorations")
<< qsl(R"({"title":{"text":"Decorated Title","style":{"color":"#ff0000","bold":true,"italic":true,"underline":true,"strikethrough":true}},"menu":[{"Action":"send:action"}]})")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("Decorated Title") << true << true << true << true
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
<< qsl("#ff0000") << QString() << -1 << QString();
QTest::newRow("wavy underline")
<< qsl(R"({"title":{"text":"Wavy Title","style":{"color":"#00ff00","underline":"wavy"}},"menu":[{"Test":"send:test"}]})")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("Wavy Title") << false << false << true << false
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
<< qsl("#00ff00") << QString()
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< static_cast<int>(Mudlet::HyperlinkStyling::UnderlineWavy)
<< QString();
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("dotted underline")
<< qsl(R"({"title":{"text":"Dotted Title","style":{"color":"#00ff00","underline":"dotted"}},"menu":[{"Test":"send:test"}]})")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("Dotted Title") << false << false << true << false
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
<< qsl("#00ff00") << QString()
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< static_cast<int>(Mudlet::HyperlinkStyling::UnderlineDotted)
<< QString();
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("dashed underline")
<< qsl(R"({"title":{"text":"Dashed Title","style":{"color":"#00ff00","underline":"dashed"}},"menu":[{"Test":"send:test"}]})")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("Dashed Title") << false << false << true << false
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
<< qsl("#00ff00") << QString()
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< static_cast<int>(Mudlet::HyperlinkStyling::UnderlineDashed)
<< QString();
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QTest::newRow("background-color CSS property")
<< qsl(R"({"title":{"text":"CSS BG Title","style":{"color":"#ffffff","background-color":"#660000"}},"menu":[{"Action":"send:action"}]})")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("CSS BG Title") << false << false << false << false
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
<< qsl("#ffffff") << qsl("#660000") << -1 << QString();
QTest::newRow("text-decoration-color")
<< qsl(R"({"title":{"text":"Color Decoration","style":{"color":"#ffffff","underline":true,"text-decoration-color":"#ff00ff"}},"menu":[{"Action":"send:action"}]})")
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
<< qsl("Color Decoration") << false << false << true << false
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
<< qsl("#ffffff") << QString() << -1 << qsl("#ff00ff");
}
void test_Osc8Title_StyleParsing() {
QFETCH(QString, config);
QFETCH(QString, expectedTitle);
QFETCH(bool, bold);
QFETCH(bool, italic);
QFETCH(bool, underlined);
QFETCH(bool, strikeOut);
QFETCH(QString, fgColor);
QFETCH(QString, bgColor);
QFETCH(int, underlineStyle);
QFETCH(QString, underlineColor);
injectData(buildOsc8WithConfig(qsl("send:action"), qsl("[Link]"), config));
Add: OSC 8 context menu title support (#8910) ## Summary Adds a `"title"` property to OSC 8 hyperlink configuration that renders a non-clickable section header at the top of right-click context menus. This gives players immediate context about what a menu relates to (e.g., an item name, NPC name, or action category). Context menus now also use the main console display font set in Settings, matching the rest of the game output. ## Configuration **Simple string** (renders with default teal color): ```json {"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` **With Tier 2 style properties:** ```json { "title": { "text": "Lamb and Barley Stew", "style": {"color": "#5fbdaf", "bold": true} }, "menu": [ {"View Details": "send:look stew"}, {"Buy": "send:buy stew"} ] } ``` **Compact syntax** (shorthand `ti` for `title`): ```json {"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` ## Title style The title's `style` object uses the same [Tier 2 style properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties) as OSC 8 hyperlink styling, including all supported color formats (named colors, hex, rgb). Currently supported properties for the title: | Property | Type | Description | |----------|------|-------------| | `color` | String | Title text color (default: `#5fbdaf`) | | `bg` | String | Title background color | | `bold` | Boolean | Bold text | | `italic` | Boolean | Italic text | ## Behaviour notes - Title renders as a non-clickable label with a separator line below, appearing above all menu items - If `title` is set but no `menu` is configured, the title has no effect (no popup is shown for single-action links) - Left-click behaviour is unaffected - When no style is specified, the title defaults to teal (`#5fbdaf`) to visually distinguish it from clickable menu items - Title without a `style` object is backward-compatible - older clients that don't recognize the `title` key will silently ignore it ## Files changed - **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to `HyperlinkStyling` - **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and object formats), query param extraction and URL stripping - **`src/TTextEdit.cpp`** - Renders the title label in the popup menu with style properties; sets console font on context menus - **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset property ## Screenshots <img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56" src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0" /> ## Test plan - [x] Send an OSC 8 link with `"title"` as a simple string - verify teal header appears above menu items - [x] Send an OSC 8 link with `"title"` as an object with custom style - verify color/bold/italic apply - [x] Send an OSC 8 link with menu but no title - verify no header appears - [x] Send an OSC 8 link with title but no menu - verify no popup on right-click - [x] Verify left-click still executes the primary action - [x] Change the console font in Settings - verify context menu font updates to match - [x] Verify compact syntax `"ti"` works as shorthand for `"title"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-03-26 08:33:44 +01:00
auto styling = findFirstLinkStyling();
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
QCOMPARE(styling.menuTitle, expectedTitle);
QCOMPARE(styling.menuTitleStyle.isBold, bold);
QCOMPARE(styling.menuTitleStyle.isItalic, italic);
QCOMPARE(styling.menuTitleStyle.isUnderlined, underlined);
QCOMPARE(styling.menuTitleStyle.isStrikeOut, strikeOut);
if (!fgColor.isEmpty()) {
QVERIFY(styling.menuTitleStyle.hasForegroundColor);
QCOMPARE(styling.menuTitleStyle.foregroundColor, QColor(fgColor));
}
if (!bgColor.isEmpty()) {
QVERIFY(styling.menuTitleStyle.hasBackgroundColor);
QCOMPARE(styling.menuTitleStyle.backgroundColor, QColor(bgColor));
}
if (underlineStyle >= 0) {
QCOMPARE(styling.menuTitleStyle.underlineStyle,
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
static_cast<Mudlet::HyperlinkStyling::UnderlineStyle>(
underlineStyle));
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
}
if (!underlineColor.isEmpty()) {
QVERIFY(styling.menuTitleStyle.hasUnderlineColor);
QCOMPARE(styling.menuTitleStyle.underlineColor, QColor(underlineColor));
}
Add: OSC 8 context menu title support (#8910) ## Summary Adds a `"title"` property to OSC 8 hyperlink configuration that renders a non-clickable section header at the top of right-click context menus. This gives players immediate context about what a menu relates to (e.g., an item name, NPC name, or action category). Context menus now also use the main console display font set in Settings, matching the rest of the game output. ## Configuration **Simple string** (renders with default teal color): ```json {"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` **With Tier 2 style properties:** ```json { "title": { "text": "Lamb and Barley Stew", "style": {"color": "#5fbdaf", "bold": true} }, "menu": [ {"View Details": "send:look stew"}, {"Buy": "send:buy stew"} ] } ``` **Compact syntax** (shorthand `ti` for `title`): ```json {"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` ## Title style The title's `style` object uses the same [Tier 2 style properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties) as OSC 8 hyperlink styling, including all supported color formats (named colors, hex, rgb). Currently supported properties for the title: | Property | Type | Description | |----------|------|-------------| | `color` | String | Title text color (default: `#5fbdaf`) | | `bg` | String | Title background color | | `bold` | Boolean | Bold text | | `italic` | Boolean | Italic text | ## Behaviour notes - Title renders as a non-clickable label with a separator line below, appearing above all menu items - If `title` is set but no `menu` is configured, the title has no effect (no popup is shown for single-action links) - Left-click behaviour is unaffected - When no style is specified, the title defaults to teal (`#5fbdaf`) to visually distinguish it from clickable menu items - Title without a `style` object is backward-compatible - older clients that don't recognize the `title` key will silently ignore it ## Files changed - **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to `HyperlinkStyling` - **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and object formats), query param extraction and URL stripping - **`src/TTextEdit.cpp`** - Renders the title label in the popup menu with style properties; sets console font on context menus - **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset property ## Screenshots <img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56" src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0" /> ## Test plan - [x] Send an OSC 8 link with `"title"` as a simple string - verify teal header appears above menu items - [x] Send an OSC 8 link with `"title"` as an object with custom style - verify color/bold/italic apply - [x] Send an OSC 8 link with menu but no title - verify no header appears - [x] Send an OSC 8 link with title but no menu - verify no popup on right-click - [x] Verify left-click still executes the primary action - [x] Change the console font in Settings - verify context menu font updates to match - [x] Verify compact syntax `"ti"` works as shorthand for `"title"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-03-26 08:33:44 +01:00
}
void test_Osc8Title_LinkTextDisplayedInBuffer() {
QString config = qsl(
R"({"title":"Lamb and Barley Stew","menu":[{"View Details":"send:look stew"}]})");
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
injectData(buildOsc8WithConfig(qsl("send:look stew"),
qsl("[Lamb and Barley Stew]"), config));
Add: OSC 8 context menu title support (#8910) ## Summary Adds a `"title"` property to OSC 8 hyperlink configuration that renders a non-clickable section header at the top of right-click context menus. This gives players immediate context about what a menu relates to (e.g., an item name, NPC name, or action category). Context menus now also use the main console display font set in Settings, matching the rest of the game output. ## Configuration **Simple string** (renders with default teal color): ```json {"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` **With Tier 2 style properties:** ```json { "title": { "text": "Lamb and Barley Stew", "style": {"color": "#5fbdaf", "bold": true} }, "menu": [ {"View Details": "send:look stew"}, {"Buy": "send:buy stew"} ] } ``` **Compact syntax** (shorthand `ti` for `title`): ```json {"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` ## Title style The title's `style` object uses the same [Tier 2 style properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties) as OSC 8 hyperlink styling, including all supported color formats (named colors, hex, rgb). Currently supported properties for the title: | Property | Type | Description | |----------|------|-------------| | `color` | String | Title text color (default: `#5fbdaf`) | | `bg` | String | Title background color | | `bold` | Boolean | Bold text | | `italic` | Boolean | Italic text | ## Behaviour notes - Title renders as a non-clickable label with a separator line below, appearing above all menu items - If `title` is set but no `menu` is configured, the title has no effect (no popup is shown for single-action links) - Left-click behaviour is unaffected - When no style is specified, the title defaults to teal (`#5fbdaf`) to visually distinguish it from clickable menu items - Title without a `style` object is backward-compatible - older clients that don't recognize the `title` key will silently ignore it ## Files changed - **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to `HyperlinkStyling` - **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and object formats), query param extraction and URL stripping - **`src/TTextEdit.cpp`** - Renders the title label in the popup menu with style properties; sets console font on context menus - **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset property ## Screenshots <img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56" src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0" /> ## Test plan - [x] Send an OSC 8 link with `"title"` as a simple string - verify teal header appears above menu items - [x] Send an OSC 8 link with `"title"` as an object with custom style - verify color/bold/italic apply - [x] Send an OSC 8 link with menu but no title - verify no header appears - [x] Send an OSC 8 link with title but no menu - verify no popup on right-click - [x] Verify left-click still executes the primary action - [x] Change the console font in Settings - verify context menu font updates to match - [x] Verify compact syntax `"ti"` works as shorthand for `"title"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-03-26 08:33:44 +01:00
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
TMainConsole *console = mpHost->mpConsole;
QString allText;
for (int i = 0; i <= console->buffer.getLastLineNumber(); ++i) {
allText += console->buffer.line(i);
}
Add: OSC 8 context menu title support (#8910) ## Summary Adds a `"title"` property to OSC 8 hyperlink configuration that renders a non-clickable section header at the top of right-click context menus. This gives players immediate context about what a menu relates to (e.g., an item name, NPC name, or action category). Context menus now also use the main console display font set in Settings, matching the rest of the game output. ## Configuration **Simple string** (renders with default teal color): ```json {"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` **With Tier 2 style properties:** ```json { "title": { "text": "Lamb and Barley Stew", "style": {"color": "#5fbdaf", "bold": true} }, "menu": [ {"View Details": "send:look stew"}, {"Buy": "send:buy stew"} ] } ``` **Compact syntax** (shorthand `ti` for `title`): ```json {"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` ## Title style The title's `style` object uses the same [Tier 2 style properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties) as OSC 8 hyperlink styling, including all supported color formats (named colors, hex, rgb). Currently supported properties for the title: | Property | Type | Description | |----------|------|-------------| | `color` | String | Title text color (default: `#5fbdaf`) | | `bg` | String | Title background color | | `bold` | Boolean | Bold text | | `italic` | Boolean | Italic text | ## Behaviour notes - Title renders as a non-clickable label with a separator line below, appearing above all menu items - If `title` is set but no `menu` is configured, the title has no effect (no popup is shown for single-action links) - Left-click behaviour is unaffected - When no style is specified, the title defaults to teal (`#5fbdaf`) to visually distinguish it from clickable menu items - Title without a `style` object is backward-compatible - older clients that don't recognize the `title` key will silently ignore it ## Files changed - **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to `HyperlinkStyling` - **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and object formats), query param extraction and URL stripping - **`src/TTextEdit.cpp`** - Renders the title label in the popup menu with style properties; sets console font on context menus - **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset property ## Screenshots <img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56" src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0" /> ## Test plan - [x] Send an OSC 8 link with `"title"` as a simple string - verify teal header appears above menu items - [x] Send an OSC 8 link with `"title"` as an object with custom style - verify color/bold/italic apply - [x] Send an OSC 8 link with menu but no title - verify no header appears - [x] Send an OSC 8 link with title but no menu - verify no popup on right-click - [x] Verify left-click still executes the primary action - [x] Change the console font in Settings - verify context menu font updates to match - [x] Verify compact syntax `"ti"` works as shorthand for `"title"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-03-26 08:33:44 +01:00
QVERIFY2(
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
allText.contains(qsl("[Lamb and Barley Stew]")),
Improve: ARIA accessibility for OSC 8 hyperlinks in caret mode (#9206) #### Brief overview of PR changes/additions Adds screen reader accessibility support for OSC 8 hyperlinks when using caret mode. Users can now navigate between links with Tab/Shift+Tab, activate links with Enter/Space, and open link context menus with the Menu key or Shift+F10. Links announce their tooltip, visited/disabled/selected state, and menu availability to assistive technology. IAccessible2 text attributes now expose link URLs, visited state, disabled state, selection state, and group membership. Hidden links are announced when the visibility manager conceals them. The Accessibility settings combobox that picks the caret-mode pane-switching key now lists these link shortcuts in its tooltip and accessible description, announces them when the key is changed, and warns if Tab is selected (since Tab is then shared with link navigation). Ctrl+End / Ctrl+Home jump to the latest content / start of the buffer with audible announcements so screen-reader users can opt in to following new output without losing their current link focus. **Scope note:** While this PR was motivated by OSC 8 hyperlinks, the keyboard navigation and screen reader features work with **all link types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space activation, `announceLinkFocus`, and IAccessible2 text attributes apply to every link in the buffer. The only OSC 8-specific behaviors are additive: spoiler reveal, selection group toggling, disabled state, and custom styling—these gracefully no-op for links that lack styling data. #### Motivation for adding to Mudlet MUD games rely heavily on clickable hyperlinks for interaction, and blind or low-vision players using screen readers had no way to discover, navigate, or activate these links from the keyboard. This brings Mudlet's hyperlink system in line with web accessibility standards. #### Other info (issues closed, discussion etc) [Wiki Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility) - Tests are included. - `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD and `look` to try out the test room. - For accessibility, you can try out caret mode (go to Accessibility tab on Mudlet settings and pick a shortcut like F6). - Navigate links with Tab/Shift+Tab, activate with Enter or Space, open the link context menu with Menu or Shift+F10, and use Ctrl+End / Ctrl+Home to jump to the latest content or the start of the buffer. - Use arrow keys on a menu, space or enter to select a menu item or follow the hyperlinks. - Output-window navigation in caret mode has been tuned for snappier response.
2026-04-25 10:19:05 -04:00
qPrintable(
qsl("Expected link text in buffer but got '%1'").arg(allText)));
}
// =====================================================================
// OSC 8 Hyperlink Navigation Tests (Tab/Shift+Tab support)
// =====================================================================
void test_FindNextLink_MultipleLinks() {
// Inject two distinct links on the same line
injectData(qsl("\x1b]8;;send:north\x1b\\North\x1b]8;;\x1b\\ - "
"\x1b]8;;send:south\x1b\\South\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
// Starting from position 0 (on the first link), findNextLink should find
// the second
int outLine = -1, outCol = -1;
bool found = console->buffer.findNextLink(0, 0, outLine, outCol);
QVERIFY2(found, "Expected to find a second link after the first");
// The second link should be at a column past "North - "
int secondLinkIndex = console->buffer.getLinkIndexAt(outLine, outCol);
QVERIFY2(secondLinkIndex > 0,
"Expected a valid link index at the found position");
// Verify it's actually a different link from the first
int firstLinkIndex = console->buffer.getLinkIndexAt(0, 0);
QVERIFY2(
firstLinkIndex != secondLinkIndex,
"findNextLink should return a different link than the starting one");
}
void test_FindNextLink_NoLinks() {
// Inject plain text with no links
injectData(qsl("Just some plain text with no links"));
TMainConsole *console = mpHost->mpConsole;
int outLine = -1, outCol = -1;
bool found = console->buffer.findNextLink(0, 0, outLine, outCol);
QVERIFY2(!found, "findNextLink should return false when no links exist");
}
void test_FindNextLink_SkipsCurrentLink() {
// Inject a single link
injectData(qsl("\x1b]8;;send:look\x1b\\Look around\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
// Find where the link starts
auto pos = findFirstLinkPosition();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol] = *pos;
// Starting from the link, there should be no "next" link
int outLine = -1, outCol = -1;
bool found2 =
console->buffer.findNextLink(linkLine, linkCol, outLine, outCol);
QVERIFY2(!found2,
"findNextLink should skip the current link and find nothing else");
}
void test_FindPreviousLink_MultipleLinks() {
// Inject two distinct links
injectData(qsl("\x1b]8;;send:north\x1b\\North\x1b]8;;\x1b\\ - "
"\x1b]8;;send:south\x1b\\South\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
// Find the first and second distinct links
auto first = findNthDistinctLinkPosition(1);
QVERIFY2(first.has_value(), "Expected to find a first link in the buffer");
auto [firstLine, firstCol, firstLinkId] = *first;
auto second = findNthDistinctLinkPosition(2);
QVERIFY2(second.has_value(),
"Expected to find a second link in the buffer");
auto [secondLine, secondCol, secondLinkId] = *second;
// From the second link, findPreviousLink should find the first
int outLine = -1, outCol = -1;
bool found = console->buffer.findPreviousLink(secondLine, secondCol,
outLine, outCol);
QVERIFY2(found, "Expected to find the first link when scanning backward "
"from the second");
int foundLinkId = console->buffer.getLinkIndexAt(outLine, outCol);
QCOMPARE(foundLinkId, firstLinkId);
}
void test_FindPreviousLink_AtStartOfBuffer() {
// Inject a single link
injectData(qsl("\x1b]8;;send:look\x1b\\Look\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
// Starting from 0,0 there should be no previous link
int outLine = -1, outCol = -1;
bool found = console->buffer.findPreviousLink(0, 0, outLine, outCol);
QVERIFY2(!found,
"findPreviousLink should return false at the start of the buffer");
}
// =====================================================================
// OSC 8 Hyperlink Tooltip Tests
// =====================================================================
void test_GetLinkTooltip_data() {
QTest::addColumn<QString>("config");
QTest::addColumn<QString>("expectedTooltip");
QTest::newRow("tooltip from config")
<< qsl(R"({"tooltip":"A magical sword","style":{"color":"#ff0000"}})")
<< qsl("A magical sword");
QTest::newRow("no tooltip in config - uses default send hint")
<< qsl(R"({"style":{"color":"#00ff00"}})") << qsl("Send: look sword");
QTest::newRow("empty config - uses default send hint")
<< QString() << qsl("Send: look sword");
}
void test_GetLinkTooltip() {
QFETCH(QString, config);
QFETCH(QString, expectedTooltip);
injectData(
buildOsc8WithConfig(qsl("send:look sword"), qsl("[Sword]"), config));
TMainConsole *console = mpHost->mpConsole;
int linkId = 0;
for (int line = console->buffer.getLastLineNumber();
line >= 0 && linkId == 0; --line) {
linkId = console->buffer.getLinkIndexAt(line, 0);
}
QVERIFY2(linkId > 0, "Expected to find a link in the buffer");
QString tooltip = console->buffer.getLinkTooltip(linkId);
QCOMPARE(tooltip, expectedTooltip);
}
void test_GetLinkTooltip_InvalidIndex() {
TMainConsole *console = mpHost->mpConsole;
// Index 0 and negative should return empty
QCOMPARE(console->buffer.getLinkTooltip(0), QString());
QCOMPARE(console->buffer.getLinkTooltip(-1), QString());
// Non-existent positive index should return empty
QCOMPARE(console->buffer.getLinkTooltip(99999), QString());
}
// =====================================================================
// OSC 8 Hyperlink Visited State Tests
// =====================================================================
void test_LinkVisitedState() {
injectData(qsl("\x1b]8;;send:look\x1b\\Look\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
int linkId = 0;
for (int line = console->buffer.getLastLineNumber();
line >= 0 && linkId == 0; --line) {
linkId = console->buffer.getLinkIndexAt(line, 0);
}
QVERIFY2(linkId > 0, "Expected to find a link in the buffer");
// Should not be visited initially
QVERIFY2(!console->buffer.isLinkVisited(linkId),
"Link should not be visited initially");
// Mark as visited
console->buffer.markLinkAsVisited(linkId);
QVERIFY2(console->buffer.isLinkVisited(linkId),
"Link should be visited after markLinkAsVisited");
}
// =====================================================================
// OSC 8 Hyperlink Accessible Attributes Tests
// =====================================================================
void test_LinkAttributes_ExposedInAccessible() {
injectData(qsl("\x1b]8;;https://example.com\x1b\\Click me\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
// Find the link position in the buffer
auto pos = findFirstLinkPosition();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol] = *pos;
// Create the accessible interface for the upper pane
TTextEdit *pane = console->mUpperPane;
QVERIFY2(pane, "Expected upper pane to exist");
TAccessibleTextEdit accessible(pane);
// Compute the offset for the link position
int offset = 0;
for (int i = 0; i < linkLine; ++i) {
offset += console->buffer.line(i).length() + 1; // +1 for \n
}
offset += linkCol;
// Query attributes at the link offset
int startOffset = 0, endOffset = 0;
QString attrs = accessible.attributes(offset, &startOffset, &endOffset);
// Verify link-specific attributes are present
QVERIFY2(
attrs.contains(qsl("text-link:")),
qPrintable(
qsl("Expected 'text-link:' in attributes but got: %1").arg(attrs)));
QVERIFY2(
attrs.contains(qsl("example.com")),
qPrintable(qsl("Expected URL in attributes but got: %1").arg(attrs)));
}
void test_LinkAttributes_NotPresentOnPlainText() {
injectData(qsl("Just plain text here"));
TMainConsole *console = mpHost->mpConsole;
TTextEdit *pane = console->mUpperPane;
QVERIFY2(pane, "Expected upper pane to exist");
TAccessibleTextEdit accessible(pane);
// Query attributes at position 0 (plain text)
int startOffset = 0, endOffset = 0;
QString attrs = accessible.attributes(0, &startOffset, &endOffset);
// Verify no link attributes are present
QVERIFY2(!attrs.contains(qsl("text-link:")),
qPrintable(
qsl("Did not expect 'text-link:' in plain text attributes: %1")
.arg(attrs)));
}
void test_LinkAttributes_VisitedState() {
injectData(qsl("\x1b]8;;send:look\x1b\\Look\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
// Find the link
auto pos = findFirstLinkPositionWithId();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol, linkId] = *pos;
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
TAccessibleTextEdit accessible(pane);
int offset = 0;
for (int i = 0; i < linkLine; ++i) {
offset += console->buffer.line(i).length() + 1;
}
offset += linkCol;
// Before visiting: should not have visited attribute
int s = 0, e = 0;
QString attrsBefore = accessible.attributes(offset, &s, &e);
QVERIFY2(!attrsBefore.contains(qsl("text-link-visited:true")),
"Link should not show visited before being visited");
// Mark as visited
console->buffer.markLinkAsVisited(linkId);
// After visiting: should have visited attribute
QString attrsAfter = accessible.attributes(offset, &s, &e);
QVERIFY2(
attrsAfter.contains(qsl("text-link-visited:true")),
qPrintable(
qsl("Expected 'text-link-visited:true' after visiting but got: %1")
.arg(attrsAfter)));
}
void test_LinkAttributes_DisabledState() {
injectData(qsl("\x1b]8;;send:look\x1b\\Disabled Link\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
auto pos = findFirstLinkPositionWithId();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol, linkId] = *pos;
// Set link state to disabled
console->buffer.setLinkState(linkId,
Mudlet::HyperlinkStyling::StateDisabled);
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
TAccessibleTextEdit accessible(pane);
int offset = 0;
for (int i = 0; i < linkLine; ++i) {
offset += console->buffer.line(i).length() + 1;
}
offset += linkCol;
int s = 0, e = 0;
QString attrs = accessible.attributes(offset, &s, &e);
QVERIFY2(
attrs.contains(qsl("text-link-disabled:true;")),
qPrintable(
qsl("Expected 'text-link-disabled:true;' in attributes but got: %1")
.arg(attrs)));
}
void test_LinkAttributes_SelectedState() {
injectData(qsl("\x1b]8;;send:look\x1b\\Select Me\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
auto pos = findFirstLinkPositionWithId();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol, linkId] = *pos;
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
TAccessibleTextEdit accessible(pane);
int offset = 0;
for (int i = 0; i < linkLine; ++i) {
offset += console->buffer.line(i).length() + 1;
}
offset += linkCol;
// Before selecting: should not have selected attribute
int s = 0, e = 0;
QString attrsBefore = accessible.attributes(offset, &s, &e);
QVERIFY2(!attrsBefore.contains(qsl("text-link-selected:true")),
"Link should not show selected before being selected");
// Mark as selected
console->buffer.setLinkSelected(linkId, true);
// After selecting: should have selected attribute
QString attrsAfter = accessible.attributes(offset, &s, &e);
QVERIFY2(attrsAfter.contains(qsl("text-link-selected:true;")),
qPrintable(qsl("Expected 'text-link-selected:true;' after "
"selecting but got: %1")
.arg(attrsAfter)));
}
void test_LinkAttributes_UrlSanitization() {
injectData(
qsl("\x1b]8;;https://example.com:8080/path\x1b\\Look\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
auto pos = findFirstLinkPosition();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol] = *pos;
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
TAccessibleTextEdit accessible(pane);
int offset = 0;
for (int i = 0; i < linkLine; ++i) {
offset += console->buffer.line(i).length() + 1;
}
offset += linkCol;
int s = 0, e = 0;
QString attrs = accessible.attributes(offset, &s, &e);
// Colons in the URL should be escaped as \:
QVERIFY2(attrs.contains(qsl("\\:")),
qPrintable(qsl("Expected escaped colons (\\:) in text-link "
"attribute but got: %1")
.arg(attrs)));
// Verify the attribute does not contain an unescaped colon after
// "text-link:" The "text-link:" prefix itself has an unescaped colon, but
// colons within the URL value must be escaped
int linkAttrStart = attrs.indexOf(qsl("text-link:"));
QVERIFY(linkAttrStart >= 0);
QString linkValue = attrs.mid(linkAttrStart + 10); // after "text-link:"
int semiPos = linkValue.indexOf(QLatin1Char(';'));
QVERIFY(semiPos > 0);
linkValue = linkValue.left(semiPos);
// Every colon in the value should be preceded by a backslash
for (int i = 0; i < linkValue.size(); ++i) {
if (linkValue.at(i) == QLatin1Char(':')) {
QVERIFY2(i > 0 && linkValue.at(i - 1) == QLatin1Char('\\'),
qPrintable(qsl("Found unescaped colon in text-link value: %1")
.arg(linkValue)));
}
}
}
void test_LinkAttributes_SemicolonAndBackslashEscaping() {
// URL contains semicolons and backslashes that must be escaped in
// IAccessible2 text attributes
injectData(qsl("\x1b]8;;https://example.com/"
"path;param\\extra\x1b\\Look\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
auto pos = findFirstLinkPosition();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol] = *pos;
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
TAccessibleTextEdit accessible(pane);
int offset = 0;
for (int i = 0; i < linkLine; ++i) {
offset += console->buffer.line(i).length() + 1;
}
offset += linkCol;
int s = 0, e = 0;
QString attrs = accessible.attributes(offset, &s, &e);
// Extract the text-link value, skipping escaped semicolons (\;)
int linkAttrStart = attrs.indexOf(qsl("text-link:"));
QVERIFY(linkAttrStart >= 0);
QString linkValue = attrs.mid(linkAttrStart + 10);
int semiEnd = -1;
for (int i = 0; i < linkValue.length(); ++i) {
if (linkValue.at(i) == QLatin1Char(';') &&
(i == 0 || linkValue.at(i - 1) != QLatin1Char('\\'))) {
semiEnd = i;
break;
}
}
QVERIFY(semiEnd > 0);
linkValue = linkValue.left(semiEnd);
// Semicolons within the URL value should be escaped as \;
QVERIFY2(linkValue.contains(qsl("\\;")),
qPrintable(qsl("Expected escaped semicolons in text-link "
"value but got: %1")
.arg(linkValue)));
// Backslashes within the URL value should be escaped as double backslash
const QString escapedBackslash = qsl("\\\\");
QVERIFY2(linkValue.contains(escapedBackslash),
qPrintable(qsl("Expected escaped backslashes in text-link "
"value but got: %1")
.arg(linkValue)));
}
void test_LinkAttributes_GroupAttribute() {
// Link with a selection group should expose text-link-group attribute
QString config = qsl(
R"({"selection":{"group":"combat","value":"sword","toggle":true}})");
injectData(
buildOsc8WithConfig(qsl("send:equip sword"), qsl("[Sword]"), config));
TMainConsole *console = mpHost->mpConsole;
auto pos = findFirstLinkPosition();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol] = *pos;
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
TAccessibleTextEdit accessible(pane);
int offset = 0;
for (int i = 0; i < linkLine; ++i) {
offset += console->buffer.line(i).length() + 1;
}
offset += linkCol;
int s = 0, e = 0;
QString attrs = accessible.attributes(offset, &s, &e);
QVERIFY2(attrs.contains(qsl("text-link-group:")),
qPrintable(qsl("Expected 'text-link-group:' in attributes "
"but got: %1")
.arg(attrs)));
// The group value should contain the group name (may be escaped)
QVERIFY2(attrs.contains(qsl("combat")),
qPrintable(qsl("Expected group name 'combat' in attributes "
"but got: %1")
.arg(attrs)));
}
// =====================================================================
// OSC 8 Hyperlink QAccessible::Description Tests
// =====================================================================
void test_Description_CaretOnLink() {
QString config = qsl(
R"({"tooltip":"A magical sword","menu":[{"View Details":"send:look sword"}]})");
injectData(
buildOsc8WithConfig(qsl("send:look sword"), qsl("[Sword]"), config));
TMainConsole *console = mpHost->mpConsole;
// Find the link position
auto pos = findFirstLinkPosition();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol] = *pos;
// Enable caret mode and move caret to the link
mpHost->setCaretEnabled(true);
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
pane->setCaretPosition(linkLine, linkCol);
TAccessibleTextEdit accessible(pane);
QString desc = accessible.text(QAccessible::Description);
QVERIFY2(
desc.contains(qsl("A magical sword")),
qPrintable(
qsl("Expected tooltip in Description but got: '%1'").arg(desc)));
// Cleanup: disable caret mode
mpHost->setCaretEnabled(false);
}
void test_Description_CaretNotOnLink() {
injectData(qsl("Plain text with no links"));
TMainConsole *console = mpHost->mpConsole;
// Enable caret mode, caret at line 0 col 0 (plain text)
mpHost->setCaretEnabled(true);
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
pane->setCaretPosition(0, 0);
TAccessibleTextEdit accessible(pane);
QString desc = accessible.text(QAccessible::Description);
QVERIFY(desc.isEmpty());
// Cleanup
mpHost->setCaretEnabled(false);
}
// =====================================================================
// OSC 8 attributes() run-range / caret-cell isolation Tests
// =====================================================================
// Helper: compute the offset of a (line, column) position the same way the
// accessible interface does it (one '\n' per line break).
int offsetForPosition(TMainConsole *console, int line, int column) {
int offset = 0;
for (int i = 0; i < line; ++i) {
offset += console->buffer.line(i).length() + 1;
}
return offset + column;
}
void test_Attributes_RunRangeCoversFullLink() {
// A whole link run should be returned in a single attributes() call
// (start..end describe the contiguous range with these attributes).
injectData(qsl("\x1b]8;;https://example.com\x1b\\Click me\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
auto pos = findFirstLinkPositionWithId();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol, linkId] = *pos;
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
TAccessibleTextEdit accessible(pane);
const int offset = offsetForPosition(console, linkLine, linkCol);
int startOffset = -1, endOffset = -1;
QString attrs = accessible.attributes(offset, &startOffset, &endOffset);
QVERIFY2(attrs.contains(qsl("text-link:")),
qPrintable(qsl("Expected link attributes; got: %1").arg(attrs)));
// The reported range must include the queried offset and stay within the
// link span on this line.
QVERIFY2(startOffset <= offset && offset < endOffset,
qPrintable(qsl("Range [%1,%2) must include offset %3")
.arg(startOffset)
.arg(endOffset)
.arg(offset)));
// Every column inside [startCol, endCol) on this line must belong to the
// same linkId, otherwise the run-expansion crossed a boundary.
const int lineStart = offsetForPosition(console, linkLine, 0);
for (int o = startOffset; o < endOffset; ++o) {
const int col = o - lineStart;
QVERIFY2(col >= 0 && col < console->buffer.line(linkLine).length(),
"run range should not cross a line boundary");
QCOMPARE(console->buffer.getLinkIndexAt(linkLine, col), linkId);
}
}
void test_Attributes_RunRangeStopsAtLinkBoundary() {
// " " between "X" and the link must not be merged with the link's run.
injectData(qsl("X \x1b]8;;https://example.com\x1b\\L\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
auto pos = findFirstLinkPositionWithId();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol, linkId] = *pos;
QVERIFY2(linkCol >= 1, "Link should be preceded by plain text");
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
TAccessibleTextEdit accessible(pane);
// Query attributes at the plain-text character immediately before the link.
const int plainOffset = offsetForPosition(console, linkLine, linkCol - 1);
int s = -1, e = -1;
QString plainAttrs = accessible.attributes(plainOffset, &s, &e);
QVERIFY2(!plainAttrs.contains(qsl("text-link:")),
"Plain text before link should not have link attributes");
// The run from the plain-text query must end at or before the link starts.
const int linkOffset = offsetForPosition(console, linkLine, linkCol);
QVERIFY2(e <= linkOffset,
qPrintable(qsl("Plain run [%1,%2) leaked into link starting at %3")
.arg(s)
.arg(e)
.arg(linkOffset)));
}
void test_Attributes_CaretCellIsolatedFromRun() {
// The caret cell renders inverted; it must not be merged into a
// neighbouring run even when they otherwise share styling.
injectData(qsl("\x1b]8;;https://example.com\x1b\\Hello\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
auto pos = findFirstLinkPositionWithId();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol, linkId] = *pos;
mpHost->setCaretEnabled(true);
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
// Park the caret on the second character of the link.
pane->setCaretPosition(linkLine, linkCol + 1);
TAccessibleTextEdit accessible(pane);
const int caretOffset = offsetForPosition(console, linkLine, linkCol + 1);
int sCaret = -1, eCaret = -1;
QString attrsCaret = accessible.attributes(caretOffset, &sCaret, &eCaret);
QVERIFY2(attrsCaret.contains(qsl("text-link:")),
"Caret-on-link should still report link attributes");
// The caret cell's range must be just the caret cell, not the whole link.
QCOMPARE(eCaret - sCaret, 1);
// The character before the caret must form its own run that ends at the
// caret cell (does not engulf it).
const int beforeOffset = offsetForPosition(console, linkLine, linkCol);
int sBefore = -1, eBefore = -1;
accessible.attributes(beforeOffset, &sBefore, &eBefore);
QVERIFY2(eBefore == caretOffset,
qPrintable(qsl("Run before caret should end at caret offset %1, "
"got [%2,%3)")
.arg(caretOffset)
.arg(sBefore)
.arg(eBefore)));
mpHost->setCaretEnabled(false);
}
// =====================================================================
// OSC 8 link navigation key Tests
// =====================================================================
void test_FindLink_WrapForward() {
// With only one link in the buffer, starting from after it we should wrap
// to the start and rediscover the same link (and report wrapped == true).
injectData(qsl("\x1b]8;;send:look\x1b\\Look\x1b]8;;\x1b\\ trailing text"));
TMainConsole *console = mpHost->mpConsole;
auto pos = findFirstLinkPositionWithId();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol, linkId] = *pos;
Q_UNUSED(linkLine);
Q_UNUSED(linkCol);
// Scan forward starting at a position past the only link: the search must
// fall through to the wrap-around branch and rediscover the same link.
const int lastLine = console->buffer.getLastLineNumber();
const int lastCol = console->buffer.line(lastLine).length() - 1;
int outLine2 = -1, outCol2 = -1;
bool wrapped2 = false;
bool found2 = console->buffer.findNextLink(lastLine, lastCol, outLine2,
outCol2, &wrapped2);
QVERIFY2(found2, "findNextLink with wrap should rediscover the only link");
QVERIFY2(wrapped2, "wrapped should be set to true when the search wrapped");
QCOMPARE(console->buffer.getLinkIndexAt(outLine2, outCol2), linkId);
}
void test_FindLink_WrapBackward() {
// Place the link on a later line so that a backward search from line 0
// exhausts the pre-start range and falls through to the wrap path.
injectData(qsl("plain leading line"));
injectData(qsl("\x1b]8;;send:look\x1b\\Look\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
auto pos = findFirstLinkPositionWithId();
QVERIFY2(pos.has_value(), "Expected to find a link in the buffer");
auto [linkLine, linkCol, linkId] = *pos;
QVERIFY2(linkLine > 0,
"Test setup expects the link on a line after the leading text");
Q_UNUSED(linkCol);
// Searching backward from the very start of the buffer must wrap to find
// the only link further down the buffer.
int outLine = -1, outCol = -1;
bool wrapped = false;
bool found =
console->buffer.findPreviousLink(0, 0, outLine, outCol, &wrapped);
QVERIFY2(found,
"findPreviousLink with wrap should rediscover the only link");
QVERIFY2(wrapped,
"wrapped should be set to true when backward search wrapped");
QCOMPARE(outLine, linkLine);
QCOMPARE(console->buffer.getLinkIndexAt(outLine, outCol), linkId);
}
void test_KeyNav_CtrlBracketRight_MovesToNextLink() {
injectData(qsl("\x1b]8;;send:north\x1b\\North\x1b]8;;\x1b\\ - "
"\x1b]8;;send:south\x1b\\South\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
auto first = findNthDistinctLinkPosition(1);
auto second = findNthDistinctLinkPosition(2);
QVERIFY(first.has_value() && second.has_value());
auto [firstLine, firstCol, firstLinkId] = *first;
auto [secondLine, secondCol, secondLinkId] = *second;
Q_UNUSED(firstLinkId);
mpHost->setCaretEnabled(true);
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
pane->setCaretPosition(firstLine, firstCol);
QKeyEvent press(QEvent::KeyPress, Qt::Key_BracketRight,
Qt::ControlModifier);
QApplication::sendEvent(pane, &press);
QCOMPARE(pane->mCaretLine, secondLine);
QCOMPARE(pane->mCaretColumn, secondCol);
QCOMPARE(console->buffer.getFocusedLink(), secondLinkId);
mpHost->setCaretEnabled(false);
}
void test_KeyNav_CtrlBracketLeft_MovesToPreviousLink() {
injectData(qsl("\x1b]8;;send:north\x1b\\North\x1b]8;;\x1b\\ - "
"\x1b]8;;send:south\x1b\\South\x1b]8;;\x1b\\"));
TMainConsole *console = mpHost->mpConsole;
auto first = findNthDistinctLinkPosition(1);
auto second = findNthDistinctLinkPosition(2);
QVERIFY(first.has_value() && second.has_value());
auto [firstLine, firstCol, firstLinkId] = *first;
auto [secondLine, secondCol, secondLinkId] = *second;
Q_UNUSED(secondLinkId);
mpHost->setCaretEnabled(true);
TTextEdit *pane = console->mUpperPane;
QVERIFY(pane);
pane->setCaretPosition(secondLine, secondCol);
QKeyEvent press(QEvent::KeyPress, Qt::Key_BracketLeft, Qt::ControlModifier);
QApplication::sendEvent(pane, &press);
QCOMPARE(pane->mCaretLine, firstLine);
QCOMPARE(pane->mCaretColumn, firstCol);
QCOMPARE(console->buffer.getFocusedLink(), firstLinkId);
mpHost->setCaretEnabled(false);
Add: OSC 8 context menu title support (#8910) ## Summary Adds a `"title"` property to OSC 8 hyperlink configuration that renders a non-clickable section header at the top of right-click context menus. This gives players immediate context about what a menu relates to (e.g., an item name, NPC name, or action category). Context menus now also use the main console display font set in Settings, matching the rest of the game output. ## Configuration **Simple string** (renders with default teal color): ```json {"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` **With Tier 2 style properties:** ```json { "title": { "text": "Lamb and Barley Stew", "style": {"color": "#5fbdaf", "bold": true} }, "menu": [ {"View Details": "send:look stew"}, {"Buy": "send:buy stew"} ] } ``` **Compact syntax** (shorthand `ti` for `title`): ```json {"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]} ``` ## Title style The title's `style` object uses the same [Tier 2 style properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties) as OSC 8 hyperlink styling, including all supported color formats (named colors, hex, rgb). Currently supported properties for the title: | Property | Type | Description | |----------|------|-------------| | `color` | String | Title text color (default: `#5fbdaf`) | | `bg` | String | Title background color | | `bold` | Boolean | Bold text | | `italic` | Boolean | Italic text | ## Behaviour notes - Title renders as a non-clickable label with a separator line below, appearing above all menu items - If `title` is set but no `menu` is configured, the title has no effect (no popup is shown for single-action links) - Left-click behaviour is unaffected - When no style is specified, the title defaults to teal (`#5fbdaf`) to visually distinguish it from clickable menu items - Title without a `style` object is backward-compatible - older clients that don't recognize the `title` key will silently ignore it ## Files changed - **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to `HyperlinkStyling` - **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and object formats), query param extraction and URL stripping - **`src/TTextEdit.cpp`** - Renders the title label in the popup menu with style properties; sets console font on context menus - **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset property ## Screenshots <img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56" src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0" /> ## Test plan - [x] Send an OSC 8 link with `"title"` as a simple string - verify teal header appears above menu items - [x] Send an OSC 8 link with `"title"` as an object with custom style - verify color/bold/italic apply - [x] Send an OSC 8 link with menu but no title - verify no header appears - [x] Send an OSC 8 link with title but no menu - verify no popup on right-click - [x] Verify left-click still executes the primary action - [x] Change the console font in Settings - verify context menu font updates to match - [x] Verify compact syntax `"ti"` works as shorthand for `"title"` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
2026-03-26 08:33:44 +01:00
}
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
void cleanupTestCase() {
delete mpServer;
mpServer = nullptr;
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
mpHost = nullptr;
const QString path =
infrastructure: speed up OSC tests with data-driven approach (#9120) #### Brief overview of PR changes/additions - Convert 30 individual TOscTest functions into 4 data-driven tests + 1 standalone, using Qt's `_data()` pattern - Start mudlet once in `initTestCase` instead of per-test, and use `cTelnet::loopbackTest()` to inject data directly into the telnet processing pipeline - eliminating 29 redundant startup/teardown cycles and TCP connections - `buffer.clear()` in `init()` provides test isolation between runs - Net reduction of 336 lines of duplicated test boilerplate #### Motivation for adding to Mudlet TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms (TOscTest only, not including other test executables): | Platform | Before | After | Speedup | |---|---|---|---| | macOS arm64 | 54s | 5.5s | 10x | | macOS x86_64 | 56s | 5.7s | 10x | | Ubuntu gcc | 58s | 3.6s | 16x | | Ubuntu x86_64 | 44s | 3.1s | 14x | | Ubuntu clang | 44s | 3.0s | 14x | #### Other info (issues closed, discussion etc) No issues closed. Pure test refactor, no behavioral changes. The `loopbackTest()` approach was already used by TelnetBenchmark - this extends it to functional tests. Loopback injection means `getCurrentLine()` can't be used (cursor position varies by OSC terminator type), so text display tests join all buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the BEL-terminated OSC 8 row uses `contains` because its link text splits across buffer lines in loopback mode. **Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass with the same coverage as before. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:01:24 +01:00
mudlet::getMudletPath(enums::profileHomePath, mHostname);
QDir(path).removeRecursively();
delete mudlet::self();
}
};
void initializeQRCResourcesForOscTest() {
#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 "TOscTest.moc"
QTEST_MAIN(TOscTest)