#include #include #include #include "utils.h" class TEntityResolverTest : public QObject { Q_OBJECT private: private slots: void initTestCase() { } void testStandardEntities() { TEntityResolver resolver; QCOMPARE(resolver.getResolution(" "), " "); QCOMPARE(resolver.getResolution(">"), ">"); QCOMPARE(resolver.getResolution("<"), "<"); QCOMPARE(resolver.getResolution("&"), "&"); QCOMPARE(resolver.getResolution("""), "\""); QCOMPARE(resolver.getResolution("Ú"), "Ú"); QCOMPARE(resolver.getResolution("ú"), "ú"); } void testStandardEntitiesCaseInsensitive() { TEntityResolver resolver; TEntityType type; QCOMPARE(resolver.getResolution(">"), ">"); QCOMPARE(resolver.getResolution("≪"), "<"); QCOMPARE(resolver.getResolution("&AmP;"), "&"); QCOMPARE(resolver.getResolution("""), "\""); QCOMPARE(resolver.getResolution("&Copy;", true, &type), "©"); QCOMPARE(type, ENTITY_TYPE_SYSTEM); } void testDecimalCode() { TEntityResolver resolver; QCOMPARE(resolver.getResolution(" "), "\n"); QCOMPARE(resolver.getResolution("%"), "%"); QCOMPARE(resolver.getResolution(" "), " "); } void testHexCode() { TEntityResolver resolver; QCOMPARE(resolver.getResolution(" "), " "); QCOMPARE(resolver.getResolution("&"), "&"); QCOMPARE(resolver.getResolution("+"), "+"); } void testMalformedNumericEntity() { TEntityResolver resolver; // A numeric entity with no digits ("&#;") leaves an empty value that was // previously passed to QString::front() (undefined behaviour). It must // now resolve safely rather than crash/assert. QCOMPARE(resolver.getResolution("&#;"), ""); QCOMPARE(resolver.getResolution("&#x;"), ""); } void testRegisteredEntities() { TEntityResolver resolver; TEntityType type; // Examples from MXP specification https://www.zuggsoft.com/zmud/mxp.htm#ENTITY resolver.registerEntity("&Version;", "6.15"); resolver.registerEntity("&Start;", ""); resolver.registerEntity("&End;", ""); QCOMPARE(resolver.getResolution("&VERSION;"), "6.15"); QCOMPARE(resolver.getResolution("&Start;"), ""); QCOMPARE(resolver.getResolution("&end;", true, &type), ""); QCOMPARE(type, ENTITY_TYPE_CUSTOM); // Check if disabling resolution of custom entities works and is properly signalled QCOMPARE(resolver.getResolution("&end;", false, &type), "&end;"); QCOMPARE(type, ENTITY_TYPE_UNKNOWN); } void testRegisterEntityAsChar() { TEntityResolver resolver; resolver.registerEntity("&symbol;", '@'); QCOMPARE(resolver.getResolution("&symbol;"), "@"); } void testInvalidRegister() { TEntityResolver resolver; QVERIFY(!resolver.registerEntity("&symbol", '@')); QVERIFY(!resolver.registerEntity("symbol", '@')); QVERIFY(resolver.registerEntity("&symbol;", '@')); } void testResolveNonExistentEntity() { TEntityResolver resolver; QCOMPARE(resolver.getResolution("&symbol;"), "&symbol;"); } void testInterpolation() { TEntityResolver resolver; QCOMPARE(resolver.interpolate("2 < 4"), "2 < 4"); QCOMPARE(resolver.interpolate("2 ≪ 4"), "2 < 4"); QCOMPARE(resolver.interpolate("You say "Hello World""), "You say \"Hello World\""); } void testCustomInterpolation() { const QMap attributes = { {qsl("&name;"), qsl("drunk sailor")}, {qsl("&desc;"), qsl("A drunk sailor is lying here")} }; auto mapping = [attributes](auto& attr) { auto ptr = attributes.find(attr); return ptr != attributes.end() ? *ptr : attr; }; QCOMPARE(TEntityResolver::interpolate("attack '&name;'|look '&name;'", mapping), "attack 'drunk sailor'|look 'drunk sailor'"); QCOMPARE(TEntityResolver::interpolate("desc: '&desc;'", mapping), "desc: 'A drunk sailor is lying here'"); } void cleanupTestCase() { } }; #include "TEntityResolverTest.moc" QTEST_MAIN(TEntityResolverTest)