mudlet/test/TMxpSendTagHandlerTest.cpp

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

362 lines
12 KiB
C++
Raw Permalink Normal View History

Refactoring/reimplementation of MXP support (#3625) Refactoring/reimplementation of MXP protocol Extracts MXP support from TBuffer Separates input parsing from protocol processing: input is parsed into MxpTag objects by the MxpNodeBuilder and are passed to be handled by the MxpTagProcessor Class hierarchy of MxpTagHandler for implementing support for each tag: allows for a clear separation of the implementation of each tag and makes it easier for adding support for new tags Defines a clear interface (MxpClient) separating the MXP implementation from other parts of the code Solves some limitations of previous implementation such as supporting element definitions not only for<SEND> tags, interpolating named and positional attributes and interpolating &text; placeholder by tag content Includes support for COLOR tag Handles FONT, U, I, B, VAR and !ENTITY tags, but so far no defined behavior (can be implemented in MxpMudlet) Attributes that hold MXP state are now in the Host class to avoid needless copies when TBuffer is copied around Introduces QtTests for some of the classes Other refactorings intended to simplify TBuffer class, extracting responbilities that aren't directly related to the buffer mgmt Extracted TEncodingTable from TBuffer, this class is responsible for mapping encoding names to tables. It also removes some bloat from TBuffer. TLinkStore to manage links and associated hints to be displayed. TEntityResolver to map character entities, such as &gt; &#32; &#x20; to their associated string values; supports interpolating strings replacing the entity placeholders by their values
2020-05-03 14:09:42 -03:00
#include "TMxpSendTagHandler.h"
#include "TMxpStubClient.h"
#include <QTest>
#include "TMxpProcessor.h"
#include "TMxpTagParser.h"
#include "TMxpTagProcessor.h"
Refactoring/reimplementation of MXP support (#3625) Refactoring/reimplementation of MXP protocol Extracts MXP support from TBuffer Separates input parsing from protocol processing: input is parsed into MxpTag objects by the MxpNodeBuilder and are passed to be handled by the MxpTagProcessor Class hierarchy of MxpTagHandler for implementing support for each tag: allows for a clear separation of the implementation of each tag and makes it easier for adding support for new tags Defines a clear interface (MxpClient) separating the MXP implementation from other parts of the code Solves some limitations of previous implementation such as supporting element definitions not only for<SEND> tags, interpolating named and positional attributes and interpolating &text; placeholder by tag content Includes support for COLOR tag Handles FONT, U, I, B, VAR and !ENTITY tags, but so far no defined behavior (can be implemented in MxpMudlet) Attributes that hold MXP state are now in the Host class to avoid needless copies when TBuffer is copied around Introduces QtTests for some of the classes Other refactorings intended to simplify TBuffer class, extracting responbilities that aren't directly related to the buffer mgmt Extracted TEncodingTable from TBuffer, this class is responsible for mapping encoding names to tables. It also removes some bloat from TBuffer. TLinkStore to manage links and associated hints to be displayed. TEntityResolver to map character entities, such as &gt; &#32; &#x20; to their associated string values; supports interpolating strings replacing the entity placeholders by their values
2020-05-03 14:09:42 -03:00
class TMxpSendTagHandlerTest : public QObject {
Q_OBJECT
Refactoring/reimplementation of MXP support (#3625) Refactoring/reimplementation of MXP protocol Extracts MXP support from TBuffer Separates input parsing from protocol processing: input is parsed into MxpTag objects by the MxpNodeBuilder and are passed to be handled by the MxpTagProcessor Class hierarchy of MxpTagHandler for implementing support for each tag: allows for a clear separation of the implementation of each tag and makes it easier for adding support for new tags Defines a clear interface (MxpClient) separating the MXP implementation from other parts of the code Solves some limitations of previous implementation such as supporting element definitions not only for<SEND> tags, interpolating named and positional attributes and interpolating &text; placeholder by tag content Includes support for COLOR tag Handles FONT, U, I, B, VAR and !ENTITY tags, but so far no defined behavior (can be implemented in MxpMudlet) Attributes that hold MXP state are now in the Host class to avoid needless copies when TBuffer is copied around Introduces QtTests for some of the classes Other refactorings intended to simplify TBuffer class, extracting responbilities that aren't directly related to the buffer mgmt Extracted TEncodingTable from TBuffer, this class is responsible for mapping encoding names to tables. It also removes some bloat from TBuffer. TLinkStore to manage links and associated hints to be displayed. TEntityResolver to map character entities, such as &gt; &#32; &#x20; to their associated string values; supports interpolating strings replacing the entity placeholders by their values
2020-05-03 14:09:42 -03:00
private:
QSharedPointer<MxpNode> parseNode(const QString &tagText) const {
auto nodes = TMxpTagParser::parseToMxpNodeList(tagText);
return !nodes.isEmpty() ? nodes.first() : nullptr;
}
Refactoring/reimplementation of MXP support (#3625) Refactoring/reimplementation of MXP protocol Extracts MXP support from TBuffer Separates input parsing from protocol processing: input is parsed into MxpTag objects by the MxpNodeBuilder and are passed to be handled by the MxpTagProcessor Class hierarchy of MxpTagHandler for implementing support for each tag: allows for a clear separation of the implementation of each tag and makes it easier for adding support for new tags Defines a clear interface (MxpClient) separating the MXP implementation from other parts of the code Solves some limitations of previous implementation such as supporting element definitions not only for<SEND> tags, interpolating named and positional attributes and interpolating &text; placeholder by tag content Includes support for COLOR tag Handles FONT, U, I, B, VAR and !ENTITY tags, but so far no defined behavior (can be implemented in MxpMudlet) Attributes that hold MXP state are now in the Host class to avoid needless copies when TBuffer is copied around Introduces QtTests for some of the classes Other refactorings intended to simplify TBuffer class, extracting responbilities that aren't directly related to the buffer mgmt Extracted TEncodingTable from TBuffer, this class is responsible for mapping encoding names to tables. It also removes some bloat from TBuffer. TLinkStore to manage links and associated hints to be displayed. TEntityResolver to map character entities, such as &gt; &#32; &#x20; to their associated string values; supports interpolating strings replacing the entity placeholders by their values
2020-05-03 14:09:42 -03:00
private slots:
void testSendHrefUTF8FromMxpProcessor() {
// issue #4368
TMxpStubClient stub;
TMxpProcessor processor(&stub);
processor.setMode(MXP_MODE_CODE_LOCK_SECURE);
std::string input = "<SEND href=\"áéíóúñ\" >test link: áéíóúñ</SEND>";
for (char &ch : input) {
processor.processMxpInput(ch, true);
}
QCOMPARE(stub.mHrefs.size(), 1);
QCOMPARE(stub.mHrefs[0], "send([[áéíóúñ]])");
Refactoring/reimplementation of MXP support (#3625) Refactoring/reimplementation of MXP protocol Extracts MXP support from TBuffer Separates input parsing from protocol processing: input is parsed into MxpTag objects by the MxpNodeBuilder and are passed to be handled by the MxpTagProcessor Class hierarchy of MxpTagHandler for implementing support for each tag: allows for a clear separation of the implementation of each tag and makes it easier for adding support for new tags Defines a clear interface (MxpClient) separating the MXP implementation from other parts of the code Solves some limitations of previous implementation such as supporting element definitions not only for<SEND> tags, interpolating named and positional attributes and interpolating &text; placeholder by tag content Includes support for COLOR tag Handles FONT, U, I, B, VAR and !ENTITY tags, but so far no defined behavior (can be implemented in MxpMudlet) Attributes that hold MXP state are now in the Host class to avoid needless copies when TBuffer is copied around Introduces QtTests for some of the classes Other refactorings intended to simplify TBuffer class, extracting responbilities that aren't directly related to the buffer mgmt Extracted TEncodingTable from TBuffer, this class is responsible for mapping encoding names to tables. It also removes some bloat from TBuffer. TLinkStore to manage links and associated hints to be displayed. TEntityResolver to map character entities, such as &gt; &#32; &#x20; to their associated string values; supports interpolating strings replacing the entity placeholders by their values
2020-05-03 14:09:42 -03:00
QCOMPARE(stub.mHints.size(), 1);
QCOMPARE(stub.mHints[0], "áéíóúñ");
}
Refactoring/reimplementation of MXP support (#3625) Refactoring/reimplementation of MXP protocol Extracts MXP support from TBuffer Separates input parsing from protocol processing: input is parsed into MxpTag objects by the MxpNodeBuilder and are passed to be handled by the MxpTagProcessor Class hierarchy of MxpTagHandler for implementing support for each tag: allows for a clear separation of the implementation of each tag and makes it easier for adding support for new tags Defines a clear interface (MxpClient) separating the MXP implementation from other parts of the code Solves some limitations of previous implementation such as supporting element definitions not only for<SEND> tags, interpolating named and positional attributes and interpolating &text; placeholder by tag content Includes support for COLOR tag Handles FONT, U, I, B, VAR and !ENTITY tags, but so far no defined behavior (can be implemented in MxpMudlet) Attributes that hold MXP state are now in the Host class to avoid needless copies when TBuffer is copied around Introduces QtTests for some of the classes Other refactorings intended to simplify TBuffer class, extracting responbilities that aren't directly related to the buffer mgmt Extracted TEncodingTable from TBuffer, this class is responsible for mapping encoding names to tables. It also removes some bloat from TBuffer. TLinkStore to manage links and associated hints to be displayed. TEntityResolver to map character entities, such as &gt; &#32; &#x20; to their associated string values; supports interpolating strings replacing the entity placeholders by their values
2020-05-03 14:09:42 -03:00
fix: MXP text now shows correctly on non-English games (#9491) #### Brief overview of PR changes/additions MXP element attribute values - such as `SEND` link targets/hints and `<!ENTITY>` values - were always decoded as UTF-8, ignoring the session's negotiated encoding. On non-UTF-8 games (Latin1, GBK, Big5, WINDOWS-1251, ...) any non-ASCII bytes in an attribute were mis-decoded, so MXP links and entities carried garbled text. This threads the active session encoding into `TMxpNodeBuilder` (set by `TMxpProcessor` before parsing each character) and decodes attribute names/values through a new shared `TStringUtils::decodeBytes` helper. That helper also replaces the duplicated decode logic previously inline in `TMxpProcessor::decodeRawBytes`, so the attribute path and the raw tag/content path now interpret bytes identically. An empty (not-yet-negotiated) encoding continues to be treated as UTF-8, preserving prior behaviour for default sessions. #### Motivation for adding to Mudlet Players on non-English MUDs saw corrupted text in MXP links and custom entities whenever those carried accented or non-Latin characters - the attribute parser was the only MXP path still hardcoding UTF-8 while element content already honoured the session encoding. #### Other info (issues closed, discussion etc) Added regression tests: - `TMxpEntityTagHandlerTest`: quoted and unquoted `<!ENTITY>` values in a WINDOWS-1251 session decode to "Гроза"; a UTF-8 session still resolves the same value unchanged. - `TMxpSendTagHandlerTest`: a `SEND href` with Latin1 bytes decodes to "señor"; an empty (default) session encoding still decodes attributes as UTF-8. The WINDOWS-1251 entity test was verified to fail on the baseline (producing U+FFFD replacement characters) before the fix. All 14 MXP/encoding/entity unit tests pass. Assisted-by: Claude:claude-opus-4-8
2026-07-27 19:55:26 +02:00
// Attribute values must be decoded with the session encoding, not hardcoded
// UTF-8. On a Latin1 (ISO 8859-1) session, the href bytes "se\xF1or" are
// "señor" - decoding them as UTF-8 would corrupt the 0xF1 byte.
void testSendHrefNonUtf8FromMxpProcessor() {
TMxpStubClient stub;
stub.mEncoding = QByteArrayLiteral("ISO 8859-1");
TMxpProcessor processor(&stub);
processor.setMode(MXP_MODE_CODE_LOCK_SECURE);
std::string input = "<SEND href=\"se\xF1or\">link</SEND>";
for (char &ch : input) {
processor.processMxpInput(ch, true);
}
QCOMPARE(stub.mHrefs.size(), 1);
QCOMPARE(stub.mHrefs[0], "send([[señor]])");
}
// With no encoding negotiated yet (empty session encoding) attribute values
// must still decode as UTF-8, matching the historical default.
void testSendHrefEmptyEncodingDefaultsToUtf8() {
TMxpStubClient stub;
stub.mEncoding = QByteArray();
TMxpProcessor processor(&stub);
processor.setMode(MXP_MODE_CODE_LOCK_SECURE);
std::string input = "<SEND href=\"áéíóúñ\">link</SEND>";
for (char &ch : input) {
processor.processMxpInput(ch, true);
}
QCOMPARE(stub.mHrefs.size(), 1);
QCOMPARE(stub.mHrefs[0], "send([[áéíóúñ]])");
}
void testSendHrefUTF8() {
// issue #4368
QString input = "<SEND href=\"áéíóúñ\" >test link: áéíóúñ</SEND>";
Refactoring/reimplementation of MXP support (#3625) Refactoring/reimplementation of MXP protocol Extracts MXP support from TBuffer Separates input parsing from protocol processing: input is parsed into MxpTag objects by the MxpNodeBuilder and are passed to be handled by the MxpTagProcessor Class hierarchy of MxpTagHandler for implementing support for each tag: allows for a clear separation of the implementation of each tag and makes it easier for adding support for new tags Defines a clear interface (MxpClient) separating the MXP implementation from other parts of the code Solves some limitations of previous implementation such as supporting element definitions not only for<SEND> tags, interpolating named and positional attributes and interpolating &text; placeholder by tag content Includes support for COLOR tag Handles FONT, U, I, B, VAR and !ENTITY tags, but so far no defined behavior (can be implemented in MxpMudlet) Attributes that hold MXP state are now in the Host class to avoid needless copies when TBuffer is copied around Introduces QtTests for some of the classes Other refactorings intended to simplify TBuffer class, extracting responbilities that aren't directly related to the buffer mgmt Extracted TEncodingTable from TBuffer, this class is responsible for mapping encoding names to tables. It also removes some bloat from TBuffer. TLinkStore to manage links and associated hints to be displayed. TEntityResolver to map character entities, such as &gt; &#32; &#x20; to their associated string values; supports interpolating strings replacing the entity placeholders by their values
2020-05-03 14:09:42 -03:00
TMxpTagProcessor processor;
TMxpStubClient stub;
Refactoring/reimplementation of MXP support (#3625) Refactoring/reimplementation of MXP protocol Extracts MXP support from TBuffer Separates input parsing from protocol processing: input is parsed into MxpTag objects by the MxpNodeBuilder and are passed to be handled by the MxpTagProcessor Class hierarchy of MxpTagHandler for implementing support for each tag: allows for a clear separation of the implementation of each tag and makes it easier for adding support for new tags Defines a clear interface (MxpClient) separating the MXP implementation from other parts of the code Solves some limitations of previous implementation such as supporting element definitions not only for<SEND> tags, interpolating named and positional attributes and interpolating &text; placeholder by tag content Includes support for COLOR tag Handles FONT, U, I, B, VAR and !ENTITY tags, but so far no defined behavior (can be implemented in MxpMudlet) Attributes that hold MXP state are now in the Host class to avoid needless copies when TBuffer is copied around Introduces QtTests for some of the classes Other refactorings intended to simplify TBuffer class, extracting responbilities that aren't directly related to the buffer mgmt Extracted TEncodingTable from TBuffer, this class is responsible for mapping encoding names to tables. It also removes some bloat from TBuffer. TLinkStore to manage links and associated hints to be displayed. TEntityResolver to map character entities, such as &gt; &#32; &#x20; to their associated string values; supports interpolating strings replacing the entity placeholders by their values
2020-05-03 14:09:42 -03:00
auto nodes = TMxpTagParser::parseToMxpNodeList(input, false);
QCOMPARE(nodes.size(), 3);
for (const auto &node : std::as_const(nodes)) {
processor.handleNode(processor, stub, node.get());
Refactoring/reimplementation of MXP support (#3625) Refactoring/reimplementation of MXP protocol Extracts MXP support from TBuffer Separates input parsing from protocol processing: input is parsed into MxpTag objects by the MxpNodeBuilder and are passed to be handled by the MxpTagProcessor Class hierarchy of MxpTagHandler for implementing support for each tag: allows for a clear separation of the implementation of each tag and makes it easier for adding support for new tags Defines a clear interface (MxpClient) separating the MXP implementation from other parts of the code Solves some limitations of previous implementation such as supporting element definitions not only for<SEND> tags, interpolating named and positional attributes and interpolating &text; placeholder by tag content Includes support for COLOR tag Handles FONT, U, I, B, VAR and !ENTITY tags, but so far no defined behavior (can be implemented in MxpMudlet) Attributes that hold MXP state are now in the Host class to avoid needless copies when TBuffer is copied around Introduces QtTests for some of the classes Other refactorings intended to simplify TBuffer class, extracting responbilities that aren't directly related to the buffer mgmt Extracted TEncodingTable from TBuffer, this class is responsible for mapping encoding names to tables. It also removes some bloat from TBuffer. TLinkStore to manage links and associated hints to be displayed. TEntityResolver to map character entities, such as &gt; &#32; &#x20; to their associated string values; supports interpolating strings replacing the entity placeholders by their values
2020-05-03 14:09:42 -03:00
}
QCOMPARE(stub.mHrefs.size(), 1);
QCOMPARE(stub.mHrefs[0], "send([[áéíóúñ]])");
QCOMPARE(stub.mHints.size(), 1);
QCOMPARE(stub.mHints[0], "áéíóúñ");
}
void testStaticText() {
// <SEND "tell Zugg " PROMPT>Zugg</SEND>
TMxpStubContext ctx;
TMxpStubClient stub;
auto startTag = parseNode("<SEND \"tell Zugg \" PROMPT>");
auto endTag = parseNode("</SEND>");
QVERIFY(startTag);
QVERIFY(endTag);
TMxpSendTagHandler sendTagHandler;
TMxpTagHandler &tagHandler = sendTagHandler;
tagHandler.handleTag(ctx, stub, startTag->asStartTag());
tagHandler.handleContent("Zugg");
tagHandler.handleTag(ctx, stub, endTag->asEndTag());
QCOMPARE(stub.mHrefs.size(), 1);
QCOMPARE(stub.mHrefs[0], "printCmdLine([[tell Zugg ]])");
QCOMPARE(stub.mHints.size(), 1);
QCOMPARE(stub.mHints[0], "tell Zugg ");
}
void testSimpleSend() {
// <SEND>north</SEND>
TMxpStubContext ctx;
TMxpStubClient stub;
MxpStartTag startTag("SEND");
MxpEndTag endTag("SEND");
TMxpSendTagHandler sendTagHandler;
TMxpTagHandler &tagHandler = sendTagHandler;
tagHandler.handleTag(ctx, stub, &startTag);
tagHandler.handleContent("north");
tagHandler.handleTag(ctx, stub, &endTag);
QCOMPARE(stub.mHrefs.size(), 1);
QCOMPARE(stub.mHrefs[0], "send([[north]])");
QCOMPARE(stub.mHints.size(), 1);
QCOMPARE(stub.mHints[0], "north");
}
void testSendPrompt() {
// <SEND href="&text;" PROMPT>north</SEND>
TMxpStubContext ctx;
TMxpStubClient stub;
auto startTag = parseNode("<SEND href=\"&text;\" PROMPT>");
auto endTag = parseNode("</SEND>");
QVERIFY(startTag);
QVERIFY(endTag);
TMxpSendTagHandler sendTagHandler;
TMxpTagHandler &tagHandler = sendTagHandler;
tagHandler.handleTag(ctx, stub, startTag->asStartTag());
tagHandler.handleContent("north");
tagHandler.handleTag(ctx, stub, endTag->asEndTag());
QCOMPARE(stub.mHrefs.size(), 1);
QCOMPARE(stub.mHrefs[0], "printCmdLine([[north]])");
QCOMPARE(stub.mHints.size(), 1);
QCOMPARE(stub.mHints[0], "north");
}
Fix: MXP EXPIRE tag support and custom element attribute handling (#8431) #### Brief overview of PR changes/additions * **Add MXP EXPIRE tag support**: Implements the EXPIRE attribute for MXP SEND tags, allowing servers to expire groups of links by name * **Tooltip fix**: Fixed MXP link tooltips displaying "HREF" instead of the actual link command when the EXPIRE attribute appears before the HREF attribute in SEND tags * **Custom element fix**: Fixed custom MXP elements to properly pass through ALL attributes (EXPIRE, HREF, HINT, etc.) to their expanded tag definitions #### Motivation for adding to Mudlet **EXPIRE tag support**: The MXP specification defines an EXPIRE attribute that allows servers to tag groups of links and expire them all at once (e.g., `<EXPIRE Exits>` expires all links tagged with `EXPIRE="Exits"`). This is essential for dynamic content like room exits that change as players move around. **Tooltip fix**: Addresses reviewer feedback in PR #8383. When games send MXP links like `<SEND EXPIRE="Exits" HREF="east">East</SEND>`, the tooltip was incorrectly showing the literal text "HREF" instead of "east" (the command that would be executed), making links confusing for users. **Custom element fix**: When servers define custom elements like `<!ELEMENT Ex '<SEND>'>` and use them with attributes like `<Ex EXPIRE="Exits" HREF="north">North</Ex>`, those attributes weren't being passed through to the expanded SEND tag. This made custom elements less functional than regular tags. Now all attributes are properly inherited. #### Other info (issues closed, discussion etc) - Related to PR #8383 (initial EXPIRE tag implementation discussion) - Addresses @mfontani's comment about custom elements not passing through EXPIRE attributes - Added comprehensive unit tests: - Tooltip attribute ordering tests in `TMxpSendTagHandlerTest` - Custom element attribute pass-through test in `TMxpCustomElementTagHandlerTest` - All existing MXP tests continue to pass (100% success rate) - Changes summary: 5 files changed, 101 insertions(+), 2 deletions(-)
2025-10-26 15:48:42 -04:00
void testSendHrefTextEntity() {
// Example from Age of Elements
QString input = "<send 'push &text;' HINT='push button'>button</send>";
Fix: MXP EXPIRE tag support and custom element attribute handling (#8431) #### Brief overview of PR changes/additions * **Add MXP EXPIRE tag support**: Implements the EXPIRE attribute for MXP SEND tags, allowing servers to expire groups of links by name * **Tooltip fix**: Fixed MXP link tooltips displaying "HREF" instead of the actual link command when the EXPIRE attribute appears before the HREF attribute in SEND tags * **Custom element fix**: Fixed custom MXP elements to properly pass through ALL attributes (EXPIRE, HREF, HINT, etc.) to their expanded tag definitions #### Motivation for adding to Mudlet **EXPIRE tag support**: The MXP specification defines an EXPIRE attribute that allows servers to tag groups of links and expire them all at once (e.g., `<EXPIRE Exits>` expires all links tagged with `EXPIRE="Exits"`). This is essential for dynamic content like room exits that change as players move around. **Tooltip fix**: Addresses reviewer feedback in PR #8383. When games send MXP links like `<SEND EXPIRE="Exits" HREF="east">East</SEND>`, the tooltip was incorrectly showing the literal text "HREF" instead of "east" (the command that would be executed), making links confusing for users. **Custom element fix**: When servers define custom elements like `<!ELEMENT Ex '<SEND>'>` and use them with attributes like `<Ex EXPIRE="Exits" HREF="north">North</Ex>`, those attributes weren't being passed through to the expanded SEND tag. This made custom elements less functional than regular tags. Now all attributes are properly inherited. #### Other info (issues closed, discussion etc) - Related to PR #8383 (initial EXPIRE tag implementation discussion) - Addresses @mfontani's comment about custom elements not passing through EXPIRE attributes - Added comprehensive unit tests: - Tooltip attribute ordering tests in `TMxpSendTagHandlerTest` - Custom element attribute pass-through test in `TMxpCustomElementTagHandlerTest` - All existing MXP tests continue to pass (100% success rate) - Changes summary: 5 files changed, 101 insertions(+), 2 deletions(-)
2025-10-26 15:48:42 -04:00
TMxpTagProcessor processor;
TMxpStubClient stub;
Fix: MXP EXPIRE tag support and custom element attribute handling (#8431) #### Brief overview of PR changes/additions * **Add MXP EXPIRE tag support**: Implements the EXPIRE attribute for MXP SEND tags, allowing servers to expire groups of links by name * **Tooltip fix**: Fixed MXP link tooltips displaying "HREF" instead of the actual link command when the EXPIRE attribute appears before the HREF attribute in SEND tags * **Custom element fix**: Fixed custom MXP elements to properly pass through ALL attributes (EXPIRE, HREF, HINT, etc.) to their expanded tag definitions #### Motivation for adding to Mudlet **EXPIRE tag support**: The MXP specification defines an EXPIRE attribute that allows servers to tag groups of links and expire them all at once (e.g., `<EXPIRE Exits>` expires all links tagged with `EXPIRE="Exits"`). This is essential for dynamic content like room exits that change as players move around. **Tooltip fix**: Addresses reviewer feedback in PR #8383. When games send MXP links like `<SEND EXPIRE="Exits" HREF="east">East</SEND>`, the tooltip was incorrectly showing the literal text "HREF" instead of "east" (the command that would be executed), making links confusing for users. **Custom element fix**: When servers define custom elements like `<!ELEMENT Ex '<SEND>'>` and use them with attributes like `<Ex EXPIRE="Exits" HREF="north">North</Ex>`, those attributes weren't being passed through to the expanded SEND tag. This made custom elements less functional than regular tags. Now all attributes are properly inherited. #### Other info (issues closed, discussion etc) - Related to PR #8383 (initial EXPIRE tag implementation discussion) - Addresses @mfontani's comment about custom elements not passing through EXPIRE attributes - Added comprehensive unit tests: - Tooltip attribute ordering tests in `TMxpSendTagHandlerTest` - Custom element attribute pass-through test in `TMxpCustomElementTagHandlerTest` - All existing MXP tests continue to pass (100% success rate) - Changes summary: 5 files changed, 101 insertions(+), 2 deletions(-)
2025-10-26 15:48:42 -04:00
auto nodes = TMxpTagParser::parseToMxpNodeList(input, false);
for (const auto &node : std::as_const(nodes)) {
processor.handleNode(processor, stub, node.get());
Fix: MXP EXPIRE tag support and custom element attribute handling (#8431) #### Brief overview of PR changes/additions * **Add MXP EXPIRE tag support**: Implements the EXPIRE attribute for MXP SEND tags, allowing servers to expire groups of links by name * **Tooltip fix**: Fixed MXP link tooltips displaying "HREF" instead of the actual link command when the EXPIRE attribute appears before the HREF attribute in SEND tags * **Custom element fix**: Fixed custom MXP elements to properly pass through ALL attributes (EXPIRE, HREF, HINT, etc.) to their expanded tag definitions #### Motivation for adding to Mudlet **EXPIRE tag support**: The MXP specification defines an EXPIRE attribute that allows servers to tag groups of links and expire them all at once (e.g., `<EXPIRE Exits>` expires all links tagged with `EXPIRE="Exits"`). This is essential for dynamic content like room exits that change as players move around. **Tooltip fix**: Addresses reviewer feedback in PR #8383. When games send MXP links like `<SEND EXPIRE="Exits" HREF="east">East</SEND>`, the tooltip was incorrectly showing the literal text "HREF" instead of "east" (the command that would be executed), making links confusing for users. **Custom element fix**: When servers define custom elements like `<!ELEMENT Ex '<SEND>'>` and use them with attributes like `<Ex EXPIRE="Exits" HREF="north">North</Ex>`, those attributes weren't being passed through to the expanded SEND tag. This made custom elements less functional than regular tags. Now all attributes are properly inherited. #### Other info (issues closed, discussion etc) - Related to PR #8383 (initial EXPIRE tag implementation discussion) - Addresses @mfontani's comment about custom elements not passing through EXPIRE attributes - Added comprehensive unit tests: - Tooltip attribute ordering tests in `TMxpSendTagHandlerTest` - Custom element attribute pass-through test in `TMxpCustomElementTagHandlerTest` - All existing MXP tests continue to pass (100% success rate) - Changes summary: 5 files changed, 101 insertions(+), 2 deletions(-)
2025-10-26 15:48:42 -04:00
}
QCOMPARE(stub.mHrefs.size(), 1);
QCOMPARE(stub.mHrefs[0], "send([[push button]])");
Fix: MXP EXPIRE tag support and custom element attribute handling (#8431) #### Brief overview of PR changes/additions * **Add MXP EXPIRE tag support**: Implements the EXPIRE attribute for MXP SEND tags, allowing servers to expire groups of links by name * **Tooltip fix**: Fixed MXP link tooltips displaying "HREF" instead of the actual link command when the EXPIRE attribute appears before the HREF attribute in SEND tags * **Custom element fix**: Fixed custom MXP elements to properly pass through ALL attributes (EXPIRE, HREF, HINT, etc.) to their expanded tag definitions #### Motivation for adding to Mudlet **EXPIRE tag support**: The MXP specification defines an EXPIRE attribute that allows servers to tag groups of links and expire them all at once (e.g., `<EXPIRE Exits>` expires all links tagged with `EXPIRE="Exits"`). This is essential for dynamic content like room exits that change as players move around. **Tooltip fix**: Addresses reviewer feedback in PR #8383. When games send MXP links like `<SEND EXPIRE="Exits" HREF="east">East</SEND>`, the tooltip was incorrectly showing the literal text "HREF" instead of "east" (the command that would be executed), making links confusing for users. **Custom element fix**: When servers define custom elements like `<!ELEMENT Ex '<SEND>'>` and use them with attributes like `<Ex EXPIRE="Exits" HREF="north">North</Ex>`, those attributes weren't being passed through to the expanded SEND tag. This made custom elements less functional than regular tags. Now all attributes are properly inherited. #### Other info (issues closed, discussion etc) - Related to PR #8383 (initial EXPIRE tag implementation discussion) - Addresses @mfontani's comment about custom elements not passing through EXPIRE attributes - Added comprehensive unit tests: - Tooltip attribute ordering tests in `TMxpSendTagHandlerTest` - Custom element attribute pass-through test in `TMxpCustomElementTagHandlerTest` - All existing MXP tests continue to pass (100% success rate) - Changes summary: 5 files changed, 101 insertions(+), 2 deletions(-)
2025-10-26 15:48:42 -04:00
QCOMPARE(stub.mHints.size(), 1);
QCOMPARE(stub.mHints[0], "push button");
}
void testResolvingEntity() {
TMxpStubContext ctx;
TMxpStubClient stub;
ctx.getEntityResolver().registerEntity("&charName;", "Gandalf");
auto startTag = parseNode("<SEND href=\"say I am &charName;\">");
auto endTag = parseNode("</SEND>");
QVERIFY(startTag);
QVERIFY(endTag);
TMxpSendTagHandler sendTagHandler;
TMxpTagHandler &tagHandler = sendTagHandler;
tagHandler.handleTag(ctx, stub, startTag->asStartTag());
tagHandler.handleContent("TAG CONTENT");
tagHandler.handleTag(ctx, stub, endTag->asEndTag());
QCOMPARE(stub.mHrefs.size(), 1);
QCOMPARE(stub.mHrefs[0], "send([[say I am Gandalf]])");
QCOMPARE(stub.mHints.size(), 1);
QCOMPARE(stub.mHints[0], "say I am Gandalf");
}
void testResolvingEntityWithPipe() {
TMxpStubContext ctx;
TMxpStubClient stub;
ctx.getEntityResolver().registerEntity("&frontHint;", "");
ctx.getEntityResolver().registerEntity("&frontHref;", "");
ctx.getEntityResolver().registerEntity("&backHints;", "");
ctx.getEntityResolver().registerEntity("&backHrefs;", "");
TMxpSendTagHandler sendTagHandler;
TMxpTagHandler &tagHandler = sendTagHandler;
// First check the SEND TAG with empty entities
auto startTag =
parseNode("<SEND href=\"&frontHref;look|say hello&backHrefs;\" "
"hint=\"&frontHint;LOOK AROUND|SAY HELLO&backHints;\">");
auto endTag = parseNode("</SEND>");
QVERIFY(startTag);
QVERIFY(endTag);
tagHandler.handleTag(ctx, stub, startTag->asStartTag());
tagHandler.handleContent("TAG CONTENT");
tagHandler.handleTag(ctx, stub, endTag->asEndTag());
QCOMPARE(stub.mHrefs.size(), 2);
QCOMPARE(stub.mHrefs[0], "send([[look]])");
QCOMPARE(stub.mHrefs[1], "send([[say hello]])");
QCOMPARE(stub.mHints.size(), 2);
QCOMPARE(stub.mHints[0], "LOOK AROUND");
QCOMPARE(stub.mHints[1], "SAY HELLO");
// Now add top menu entries
ctx.getEntityResolver().registerEntity("&frontHint;", "WHO IS ONLINE?|");
ctx.getEntityResolver().registerEntity("&frontHref;", "who|");
tagHandler.handleTag(ctx, stub, startTag->asStartTag());
tagHandler.handleContent("TAG CONTENT");
tagHandler.handleTag(ctx, stub, endTag->asEndTag());
QCOMPARE(stub.mHrefs.size(), 3);
QCOMPARE(stub.mHrefs[0], "send([[who]])");
QCOMPARE(stub.mHrefs[1], "send([[look]])");
QCOMPARE(stub.mHrefs[2], "send([[say hello]])");
QCOMPARE(stub.mHints.size(), 3);
QCOMPARE(stub.mHints[0], "WHO IS ONLINE?");
QCOMPARE(stub.mHints[1], "LOOK AROUND");
QCOMPARE(stub.mHints[2], "SAY HELLO");
// Finally add something to the end of the menu
ctx.getEntityResolver().registerEntity("&backHints;",
"|KNOCK AT THE DOOR|BREAK THE DOOR");
ctx.getEntityResolver().registerEntity("&backHrefs;",
"|knock at door|break door");
tagHandler.handleTag(ctx, stub, startTag->asStartTag());
tagHandler.handleContent("TAG CONTENT");
tagHandler.handleTag(ctx, stub, endTag->asEndTag());
QCOMPARE(stub.mHrefs.size(), 5);
QCOMPARE(stub.mHrefs[0], "send([[who]])");
QCOMPARE(stub.mHrefs[1], "send([[look]])");
QCOMPARE(stub.mHrefs[2], "send([[say hello]])");
QCOMPARE(stub.mHrefs[3], "send([[knock at door]])");
QCOMPARE(stub.mHrefs[4], "send([[break door]])");
QCOMPARE(stub.mHints.size(), 5);
QCOMPARE(stub.mHints[0], "WHO IS ONLINE?");
QCOMPARE(stub.mHints[1], "LOOK AROUND");
QCOMPARE(stub.mHints[2], "SAY HELLO");
QCOMPARE(stub.mHints[3], "KNOCK AT THE DOOR");
QCOMPARE(stub.mHints[4], "BREAK THE DOOR");
}
void testSendHrefHintMismatch() {
// Example from starmourn on WARES command from NPCs
// <SEND HREF="PROBE SUSPENDERS30901|BUY SUSPENDERS30901" hint="Click to see
// command menu">30901</SEND>
TMxpStubContext ctx;
TMxpStubClient stub;
auto startTag = parseNode(
R"(<SEND HREF="PROBE SUSPENDERS30901|BUY SUSPENDERS30901" hint="Click to see command menu">)");
auto endTag = parseNode("</SEND>");
QVERIFY(startTag);
QVERIFY(endTag);
TMxpSendTagHandler sendTagHandler;
TMxpTagHandler &tagHandler = sendTagHandler;
tagHandler.handleTag(ctx, stub, startTag->asStartTag());
tagHandler.handleContent("3091");
tagHandler.handleTag(ctx, stub, endTag->asEndTag());
QCOMPARE(stub.mHrefs.size(), 2);
QCOMPARE(stub.mHrefs[0], "send([[PROBE SUSPENDERS30901]])");
QCOMPARE(stub.mHrefs[1], "send([[BUY SUSPENDERS30901]])");
QCOMPARE(stub.mHints.size(), 2);
QCOMPARE(stub.mHints[0], "PROBE SUSPENDERS30901");
QCOMPARE(stub.mHints[1], "BUY SUSPENDERS30901");
}
void testSendExpireHref() {
// Test case for issue #8383
// When EXPIRE comes before HREF, the tooltip should show the HREF value,
// not the literal string "HREF" <SEND EXPIRE="Exits"
// HREF="east">East</SEND>
TMxpStubContext ctx;
TMxpStubClient stub;
auto startTag = parseNode(R"(<SEND EXPIRE="Exits" HREF="east">)");
auto endTag = parseNode("</SEND>");
QVERIFY(startTag);
QVERIFY(endTag);
TMxpSendTagHandler sendTagHandler;
TMxpTagHandler &tagHandler = sendTagHandler;
tagHandler.handleTag(ctx, stub, startTag->asStartTag());
tagHandler.handleContent("East");
tagHandler.handleTag(ctx, stub, endTag->asEndTag());
QCOMPARE(stub.mHrefs.size(), 1);
QCOMPARE(stub.mHrefs[0], "send([[east]])");
QCOMPARE(stub.mHints.size(), 1);
QCOMPARE(stub.mHints[0], "east"); // Should be "east", not "HREF"
}
void testSendExpireHrefWithoutHint() {
// Test case for standard order: HREF then EXPIRE
// <SEND HREF="west" EXPIRE="Exits">West</SEND>
TMxpStubContext ctx;
TMxpStubClient stub;
auto startTag = parseNode(R"(<SEND HREF="west" EXPIRE="Exits">)");
auto endTag = parseNode("</SEND>");
QVERIFY(startTag);
QVERIFY(endTag);
TMxpSendTagHandler sendTagHandler;
TMxpTagHandler &tagHandler = sendTagHandler;
tagHandler.handleTag(ctx, stub, startTag->asStartTag());
tagHandler.handleContent("West");
tagHandler.handleTag(ctx, stub, endTag->asEndTag());
QCOMPARE(stub.mHrefs.size(), 1);
QCOMPARE(stub.mHrefs[0], "send([[west]])");
QCOMPARE(stub.mHints.size(), 1);
QCOMPARE(stub.mHints[0], "west"); // Should be "west"
}
Refactoring/reimplementation of MXP support (#3625) Refactoring/reimplementation of MXP protocol Extracts MXP support from TBuffer Separates input parsing from protocol processing: input is parsed into MxpTag objects by the MxpNodeBuilder and are passed to be handled by the MxpTagProcessor Class hierarchy of MxpTagHandler for implementing support for each tag: allows for a clear separation of the implementation of each tag and makes it easier for adding support for new tags Defines a clear interface (MxpClient) separating the MXP implementation from other parts of the code Solves some limitations of previous implementation such as supporting element definitions not only for<SEND> tags, interpolating named and positional attributes and interpolating &text; placeholder by tag content Includes support for COLOR tag Handles FONT, U, I, B, VAR and !ENTITY tags, but so far no defined behavior (can be implemented in MxpMudlet) Attributes that hold MXP state are now in the Host class to avoid needless copies when TBuffer is copied around Introduces QtTests for some of the classes Other refactorings intended to simplify TBuffer class, extracting responbilities that aren't directly related to the buffer mgmt Extracted TEncodingTable from TBuffer, this class is responsible for mapping encoding names to tables. It also removes some bloat from TBuffer. TLinkStore to manage links and associated hints to be displayed. TEntityResolver to map character entities, such as &gt; &#32; &#x20; to their associated string values; supports interpolating strings replacing the entity placeholders by their values
2020-05-03 14:09:42 -03:00
};
#include "TMxpSendTagHandlerTest.moc"
QTEST_MAIN(TMxpSendTagHandlerTest)