From 7150633c1adcc126b71ee153079e6fb50f8b970b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 3 May 2026 02:22:37 -0300 Subject: [PATCH] Fixes in URI implementation to handle some special cases, added some tests too. Fix flaky test. Renamed tests to have consistent naming. --- bin/unit_tests/assets/html/ensoft/ensoft.css | 2 - src/eepp/network/uri.cpp | 12 +- src/eepp/ui/uirichtext.cpp | 13 +- src/eepp/ui/uiscenenode.cpp | 11 +- .../{eterm_test.cpp => eterm_tests.cpp} | 0 ...trendering.cpp => fontrendering_tests.cpp} | 0 ...ionstring.cpp => functionstring_tests.cpp} | 0 .../{htmlsvg.cpp => htmlsvg_tests.cpp} | 0 .../unit_tests/{regex.cpp => regex_tests.cpp} | 0 .../{richtext.cpp => richtext_tests.cpp} | 0 ...gmapmodel.cpp => stringmapmodel_tests.cpp} | 0 ...ations.cpp => stringsoperations_tests.cpp} | 0 ...extdocument.cpp => textdocument_tests.cpp} | 0 .../{textformat.cpp => textformat_tests.cpp} | 0 ...editor_test.cpp => uicodeeditor_tests.cpp} | 0 ...e_test.cpp => uicss_inheritance_tests.cpp} | 0 ...diffview_test.cpp => uidiffview_tests.cpp} | 0 ...list.cpp => uidropdownmodellist_tests.cpp} | 0 ...textnode_test.cpp => uitextnode_tests.cpp} | 0 src/tests/unit_tests/uri_tests.cpp | 211 ++++++++++++++++++ 20 files changed, 235 insertions(+), 14 deletions(-) rename src/tests/unit_tests/{eterm_test.cpp => eterm_tests.cpp} (100%) rename src/tests/unit_tests/{fontrendering.cpp => fontrendering_tests.cpp} (100%) rename src/tests/unit_tests/{functionstring.cpp => functionstring_tests.cpp} (100%) rename src/tests/unit_tests/{htmlsvg.cpp => htmlsvg_tests.cpp} (100%) rename src/tests/unit_tests/{regex.cpp => regex_tests.cpp} (100%) rename src/tests/unit_tests/{richtext.cpp => richtext_tests.cpp} (100%) rename src/tests/unit_tests/{stringmapmodel.cpp => stringmapmodel_tests.cpp} (100%) rename src/tests/unit_tests/{stringsoperations.cpp => stringsoperations_tests.cpp} (100%) rename src/tests/unit_tests/{textdocument.cpp => textdocument_tests.cpp} (100%) rename src/tests/unit_tests/{textformat.cpp => textformat_tests.cpp} (100%) rename src/tests/unit_tests/{uicodeeditor_test.cpp => uicodeeditor_tests.cpp} (100%) rename src/tests/unit_tests/{uicss_inheritance_test.cpp => uicss_inheritance_tests.cpp} (100%) rename src/tests/unit_tests/{uidiffview_test.cpp => uidiffview_tests.cpp} (100%) rename src/tests/unit_tests/{uidropdownmodellist.cpp => uidropdownmodellist_tests.cpp} (100%) rename src/tests/unit_tests/{uitextnode_test.cpp => uitextnode_tests.cpp} (100%) create mode 100644 src/tests/unit_tests/uri_tests.cpp diff --git a/bin/unit_tests/assets/html/ensoft/ensoft.css b/bin/unit_tests/assets/html/ensoft/ensoft.css index b9d032425..45d9cb9bb 100644 --- a/bin/unit_tests/assets/html/ensoft/ensoft.css +++ b/bin/unit_tests/assets/html/ensoft/ensoft.css @@ -1,5 +1,3 @@ -@import url(https://fonts.googleapis.com/css?family=Lato); -@import "../anim.css"; a:active, a:focus { outline: none; diff --git a/src/eepp/network/uri.cpp b/src/eepp/network/uri.cpp index 754631b76..cf8fe1ba1 100644 --- a/src/eepp/network/uri.cpp +++ b/src/eepp/network/uri.cpp @@ -148,11 +148,13 @@ void URI::clear() { std::string URI::toString() const { std::string uri; - if ( isRelative() ) { + if ( isRelative() && mHost.empty() ) { encode( mPath, RESERVED_PATH, uri ); } else { - uri = mScheme; - uri += ':'; + if ( !mScheme.empty() ) { + uri = mScheme; + uri += ':'; + } std::string auth = getAuthority(); if ( !auth.empty() || mScheme == "file" ) { uri.append( "//" ); @@ -604,6 +606,10 @@ void URI::parse( const std::string& uri ) { it = uri.begin(); parsePathEtc( it, end ); } + } else if ( *it == '/' && ( it + 1 ) != end && *( it + 1 ) == '/' ) { + it += 2; + parseAuthority( it, end ); + parsePathEtc( it, end ); } else { parsePathEtc( it, end ); } diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index 3bfdc9c3b..977936009 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -368,13 +368,12 @@ std::string UIRichText::getPropertyString( const PropertyDefinition* propertyDef std::vector UIRichText::getPropertiesImplemented() const { auto props = UIHTMLWidget::getPropertiesImplemented(); - auto local = { PropertyId::FontFamily, PropertyId::FontSize, - PropertyId::FontStyle, PropertyId::Color, - PropertyId::BackgroundColor, PropertyId::TextShadowColor, - PropertyId::TextShadowOffset, PropertyId::TextStrokeWidth, - PropertyId::TextStrokeColor, PropertyId::TextAlign, - PropertyId::SelectionColor, PropertyId::SelectionBackColor, - PropertyId::TextSelection, PropertyId::TextDecoration }; + auto local = { + PropertyId::FontFamily, PropertyId::FontSize, PropertyId::FontStyle, + PropertyId::Color, PropertyId::TextShadowColor, PropertyId::TextShadowOffset, + PropertyId::TextStrokeWidth, PropertyId::TextStrokeColor, PropertyId::TextAlign, + PropertyId::SelectionColor, PropertyId::SelectionBackColor, PropertyId::TextSelection, + PropertyId::TextDecoration }; props.insert( props.end(), local.begin(), local.end() ); return props; } diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index 394c7227f..a902cabb3 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -1235,8 +1235,15 @@ URI UISceneNode::solveRelativePath( URI uri, URI baseURI ) { // directory merging, and ".." segment collapsing! base.resolve( uri ); - if ( base.getAuthority().empty() && base.getScheme().empty() ) - base.setScheme( "file" ); + // If after resolution the scheme is still empty: + // - For plain relative paths (no authority), default to "file" + // - For protocol-relative URLs (has authority via //), default to "https" + if ( base.getScheme().empty() ) { + if ( base.getAuthority().empty() ) + base.setScheme( "file" ); + else + base.setScheme( "https" ); + } return base; } diff --git a/src/tests/unit_tests/eterm_test.cpp b/src/tests/unit_tests/eterm_tests.cpp similarity index 100% rename from src/tests/unit_tests/eterm_test.cpp rename to src/tests/unit_tests/eterm_tests.cpp diff --git a/src/tests/unit_tests/fontrendering.cpp b/src/tests/unit_tests/fontrendering_tests.cpp similarity index 100% rename from src/tests/unit_tests/fontrendering.cpp rename to src/tests/unit_tests/fontrendering_tests.cpp diff --git a/src/tests/unit_tests/functionstring.cpp b/src/tests/unit_tests/functionstring_tests.cpp similarity index 100% rename from src/tests/unit_tests/functionstring.cpp rename to src/tests/unit_tests/functionstring_tests.cpp diff --git a/src/tests/unit_tests/htmlsvg.cpp b/src/tests/unit_tests/htmlsvg_tests.cpp similarity index 100% rename from src/tests/unit_tests/htmlsvg.cpp rename to src/tests/unit_tests/htmlsvg_tests.cpp diff --git a/src/tests/unit_tests/regex.cpp b/src/tests/unit_tests/regex_tests.cpp similarity index 100% rename from src/tests/unit_tests/regex.cpp rename to src/tests/unit_tests/regex_tests.cpp diff --git a/src/tests/unit_tests/richtext.cpp b/src/tests/unit_tests/richtext_tests.cpp similarity index 100% rename from src/tests/unit_tests/richtext.cpp rename to src/tests/unit_tests/richtext_tests.cpp diff --git a/src/tests/unit_tests/stringmapmodel.cpp b/src/tests/unit_tests/stringmapmodel_tests.cpp similarity index 100% rename from src/tests/unit_tests/stringmapmodel.cpp rename to src/tests/unit_tests/stringmapmodel_tests.cpp diff --git a/src/tests/unit_tests/stringsoperations.cpp b/src/tests/unit_tests/stringsoperations_tests.cpp similarity index 100% rename from src/tests/unit_tests/stringsoperations.cpp rename to src/tests/unit_tests/stringsoperations_tests.cpp diff --git a/src/tests/unit_tests/textdocument.cpp b/src/tests/unit_tests/textdocument_tests.cpp similarity index 100% rename from src/tests/unit_tests/textdocument.cpp rename to src/tests/unit_tests/textdocument_tests.cpp diff --git a/src/tests/unit_tests/textformat.cpp b/src/tests/unit_tests/textformat_tests.cpp similarity index 100% rename from src/tests/unit_tests/textformat.cpp rename to src/tests/unit_tests/textformat_tests.cpp diff --git a/src/tests/unit_tests/uicodeeditor_test.cpp b/src/tests/unit_tests/uicodeeditor_tests.cpp similarity index 100% rename from src/tests/unit_tests/uicodeeditor_test.cpp rename to src/tests/unit_tests/uicodeeditor_tests.cpp diff --git a/src/tests/unit_tests/uicss_inheritance_test.cpp b/src/tests/unit_tests/uicss_inheritance_tests.cpp similarity index 100% rename from src/tests/unit_tests/uicss_inheritance_test.cpp rename to src/tests/unit_tests/uicss_inheritance_tests.cpp diff --git a/src/tests/unit_tests/uidiffview_test.cpp b/src/tests/unit_tests/uidiffview_tests.cpp similarity index 100% rename from src/tests/unit_tests/uidiffview_test.cpp rename to src/tests/unit_tests/uidiffview_tests.cpp diff --git a/src/tests/unit_tests/uidropdownmodellist.cpp b/src/tests/unit_tests/uidropdownmodellist_tests.cpp similarity index 100% rename from src/tests/unit_tests/uidropdownmodellist.cpp rename to src/tests/unit_tests/uidropdownmodellist_tests.cpp diff --git a/src/tests/unit_tests/uitextnode_test.cpp b/src/tests/unit_tests/uitextnode_tests.cpp similarity index 100% rename from src/tests/unit_tests/uitextnode_test.cpp rename to src/tests/unit_tests/uitextnode_tests.cpp diff --git a/src/tests/unit_tests/uri_tests.cpp b/src/tests/unit_tests/uri_tests.cpp new file mode 100644 index 000000000..db4a84cc4 --- /dev/null +++ b/src/tests/unit_tests/uri_tests.cpp @@ -0,0 +1,211 @@ +#include "utest.hpp" +#include + +using namespace EE; +using namespace EE::Network; + +UTEST( URI, parseAbsolute ) { + URI uri( "https://example.com/path/to/resource?q=hello#section" ); + EXPECT_STDSTREQ( "https", uri.getScheme() ); + EXPECT_STDSTREQ( "example.com", uri.getHost() ); + EXPECT_STDSTREQ( "/path/to/resource", uri.getPath() ); + EXPECT_STDSTREQ( "q=hello", uri.getQuery() ); + EXPECT_STDSTREQ( "section", uri.getFragment() ); + EXPECT_EQ( 443u, uri.getPort() ); + + URI uri2( "http://user:pass@host.com:8080/api/data" ); + EXPECT_STDSTREQ( "http", uri2.getScheme() ); + EXPECT_STDSTREQ( "user:pass", uri2.getUserInfo() ); + EXPECT_STDSTREQ( "host.com", uri2.getHost() ); + EXPECT_EQ( 8080u, uri2.getPort() ); + EXPECT_STDSTREQ( "/api/data", uri2.getPath() ); +} + +UTEST( URI, parseRelative ) { + URI uri( "relative/path/file.html" ); + EXPECT_TRUE( uri.getScheme().empty() ); + EXPECT_TRUE( uri.getHost().empty() ); + EXPECT_STDSTREQ( "relative/path/file.html", uri.getPath() ); + EXPECT_TRUE( uri.isRelative() ); + + URI uri2( "../parent/file.html" ); + EXPECT_STDSTREQ( "../parent/file.html", uri2.getPath() ); + + URI uri3( "/root/relative.html" ); + EXPECT_STDSTREQ( "/root/relative.html", uri3.getPath() ); +} + +UTEST( URI, parseProtocolRelative ) { + URI uri( "//cdn.example.com/images/logo.png" ); + EXPECT_TRUE( uri.getScheme().empty() ); + EXPECT_STDSTREQ( "cdn.example.com", uri.getHost() ); + EXPECT_STDSTREQ( "/images/logo.png", uri.getPath() ); + EXPECT_STDSTREQ( "cdn.example.com", uri.getAuthority() ); + + URI uri2( "//example.com:8080/api/data" ); + EXPECT_TRUE( uri2.getScheme().empty() ); + EXPECT_STDSTREQ( "example.com", uri2.getHost() ); + EXPECT_EQ( 8080u, uri2.getPort() ); + EXPECT_STDSTREQ( "/api/data", uri2.getPath() ); + + URI uri3( "//example.com/search?q=hello" ); + EXPECT_STDSTREQ( "example.com", uri3.getHost() ); + EXPECT_STDSTREQ( "/search", uri3.getPath() ); + EXPECT_STDSTREQ( "q=hello", uri3.getQuery() ); + + URI uri4( "//example.com/page#section" ); + EXPECT_STDSTREQ( "example.com", uri4.getHost() ); + EXPECT_STDSTREQ( "/page", uri4.getPath() ); + EXPECT_STDSTREQ( "section", uri4.getFragment() ); + + URI uri5( "//user:pass@example.com/path" ); + EXPECT_STDSTREQ( "user:pass", uri5.getUserInfo() ); + EXPECT_STDSTREQ( "example.com", uri5.getHost() ); + EXPECT_STDSTREQ( "/path", uri5.getPath() ); +} + +UTEST( URI, toString ) { + URI uri1( "https://example.com/path?q=1#frag" ); + EXPECT_STDSTREQ( "https://example.com/path?q=1#frag", uri1.toString() ); + + URI uri2( "relative/path.html" ); + EXPECT_STDSTREQ( "relative/path.html", uri2.toString() ); + + URI uri3( "//example.com/images/logo.png" ); + EXPECT_STDSTREQ( "//example.com/images/logo.png", uri3.toString() ); + + URI uri4( "//example.com:8080/api" ); + EXPECT_STDSTREQ( "//example.com:8080/api", uri4.toString() ); + + URI uri5( "file:///home/user/doc.html" ); + EXPECT_STDSTREQ( "file:///home/user/doc.html", uri5.toString() ); +} + +UTEST( URI, resolveProtocolRelativeAgainstHttp ) { + URI base( "https://mysite.com/page.html" ); + URI rel( "//cdn.example.com/img/hero.jpg" ); + base.resolve( rel ); + + EXPECT_STDSTREQ( "https", base.getScheme() ); + EXPECT_STDSTREQ( "cdn.example.com", base.getHost() ); + EXPECT_STDSTREQ( "/img/hero.jpg", base.getPath() ); + EXPECT_STDSTREQ( "https://cdn.example.com/img/hero.jpg", base.toString() ); +} + +UTEST( URI, resolveProtocolRelativeAgainstFile ) { + URI base( "file:///home/user/page.html" ); + URI rel( "//example.com/image.png" ); + base.resolve( rel ); + + EXPECT_STDSTREQ( "file", base.getScheme() ); + EXPECT_STDSTREQ( "example.com", base.getHost() ); + EXPECT_STDSTREQ( "/image.png", base.getPath() ); + EXPECT_STDSTREQ( "file://example.com/image.png", base.toString() ); +} + +UTEST( URI, resolveProtocolRelativeAgainstEmpty ) { + URI base; + URI rel( "//example.com/path" ); + base.resolve( rel ); + + EXPECT_TRUE( base.getScheme().empty() ); + EXPECT_STDSTREQ( "example.com", base.getHost() ); + EXPECT_STDSTREQ( "/path", base.getPath() ); + EXPECT_STDSTREQ( "//example.com/path", base.toString() ); +} + +UTEST( URI, resolveAbsoluteKeepsTarget ) { + URI base( "https://oldsite.com/oldpath" ); + URI abs( "https://newsite.com/newpath" ); + base.resolve( abs ); + + EXPECT_STDSTREQ( "https", base.getScheme() ); + EXPECT_STDSTREQ( "newsite.com", base.getHost() ); + EXPECT_STDSTREQ( "/newpath", base.getPath() ); + EXPECT_STDSTREQ( "https://newsite.com/newpath", base.toString() ); +} + +UTEST( URI, resolveRootRelative ) { + URI base( "https://example.com/dir/page.html" ); + URI rel( "/other/file.html" ); + base.resolve( rel ); + + EXPECT_STDSTREQ( "https", base.getScheme() ); + EXPECT_STDSTREQ( "example.com", base.getHost() ); + EXPECT_STDSTREQ( "/other/file.html", base.getPath() ); +} + +UTEST( URI, resolveRelative ) { + URI base( "https://example.com/dir/page.html" ); + URI rel( "other/file.html" ); + base.resolve( rel ); + + EXPECT_STDSTREQ( "https", base.getScheme() ); + EXPECT_STDSTREQ( "example.com", base.getHost() ); + EXPECT_STDSTREQ( "/dir/other/file.html", base.getPath() ); +} + +UTEST( URI, resolveRelativeWithDots ) { + URI base( "https://example.com/dir/sub/page.html" ); + URI rel( "../other/file.html" ); + base.resolve( rel ); + + EXPECT_STDSTREQ( "/dir/other/file.html", base.getPath() ); +} + +UTEST( URI, resolveRelativeTrailingSlash ) { + URI base( "https://example.com/dir/" ); + URI rel( "file.html" ); + base.resolve( rel ); + + EXPECT_STDSTREQ( "/dir/file.html", base.getPath() ); +} + +UTEST( URI, isRelative ) { + EXPECT_TRUE( URI( "relative/path" ).isRelative() ); + EXPECT_TRUE( URI( "//example.com/path" ).isRelative() ); + EXPECT_FALSE( URI( "https://example.com/path" ).isRelative() ); + EXPECT_FALSE( URI( "file:///path" ).isRelative() ); +} + +UTEST( URI, empty ) { + URI empty; + EXPECT_TRUE( empty.empty() ); + + URI notEmpty( "https://example.com" ); + EXPECT_FALSE( notEmpty.empty() ); + + URI protocolRelative( "//example.com" ); + EXPECT_FALSE( protocolRelative.empty() ); +} + +UTEST( URI, pathSegments ) { + URI uri( "/path/to/resource" ); + std::vector segments; + uri.getPathSegments( segments ); + EXPECT_EQ( 3ul, segments.size() ); + EXPECT_STDSTREQ( "path", segments[0] ); + EXPECT_STDSTREQ( "to", segments[1] ); + EXPECT_STDSTREQ( "resource", segments[2] ); +} + +UTEST( URI, normalize ) { + URI uri( "https://example.com/path/../other/./file.html" ); + uri.normalize(); + EXPECT_STDSTREQ( "/other/file.html", uri.getPath() ); +} + +UTEST( URI, getLastPathSegment ) { + URI uri( "https://example.com/path/to/file.html?query=1" ); + EXPECT_STDSTREQ( "file.html", uri.getLastPathSegment() ); + + URI uri2( "/path/to/dir/" ); + EXPECT_STDSTREQ( "dir", uri2.getLastPathSegment() ); +} + +UTEST( URI, schemeCaseInsensitive ) { + URI uri( "HTTPS://Example.COM/Path" ); + EXPECT_STDSTREQ( "https", uri.getScheme() ); + EXPECT_STDSTREQ( "example.com", uri.getHost() ); + EXPECT_STDSTREQ( "/Path", uri.getPath() ); +}