|
|
@@ -26,7 +26,7 @@
|
|
|
*
|
|
|
*/
|
|
|
|
|
|
-#include "../Common/TestsInterface.h"
|
|
|
+#include "../Common/TestsShell.h"
|
|
|
#include <RmlUi/Core/Context.h>
|
|
|
#include <RmlUi/Core/Core.h>
|
|
|
#include <RmlUi/Core/Element.h>
|
|
|
@@ -46,6 +46,7 @@ static const String doc_begin = R"(
|
|
|
width: 400px;
|
|
|
height: 300px;
|
|
|
margin: auto;
|
|
|
+ font-family: LatoLatin;
|
|
|
}
|
|
|
)";
|
|
|
static const String doc_end = R"(
|
|
|
@@ -57,9 +58,10 @@ static const String doc_end = R"(
|
|
|
<div id="Z" class="hello world"/>
|
|
|
<div id="P" class="parent">
|
|
|
<h1 id="A"/>
|
|
|
+ Some text
|
|
|
<p id="B"/>
|
|
|
<p id="C"/>
|
|
|
- <p id="D"> <span id="D0"/><span id="D1"/> </p>
|
|
|
+ <p id="D"> <span id="D0"> </span><span id="D1">Text</span> </p>
|
|
|
<h3 id="E"/>
|
|
|
<p id="F"> <span id="F0"/> </p>
|
|
|
<p id="G"/>
|
|
|
@@ -70,10 +72,24 @@ static const String doc_end = R"(
|
|
|
</rml>
|
|
|
)";
|
|
|
|
|
|
+enum class SelectorOp { None, RemoveElementsByIds, InsertElementBefore, RemoveClasses, RemoveChecked };
|
|
|
+
|
|
|
struct QuerySelector {
|
|
|
+ QuerySelector(String selector, String expected_ids) : selector(std::move(selector)), expected_ids(std::move(expected_ids)) {}
|
|
|
+ QuerySelector(String selector, String expected_ids, SelectorOp operation, String operation_argument, String expected_ids_after_operation) :
|
|
|
+ selector(std::move(selector)), expected_ids(std::move(expected_ids)), operation(operation), operation_argument(std::move(operation_argument)),
|
|
|
+ expected_ids_after_operation(std::move(expected_ids_after_operation))
|
|
|
+ {}
|
|
|
String selector;
|
|
|
String expected_ids;
|
|
|
+
|
|
|
+ // Optionally also test the selector after dynamically making a structural operation on the document.
|
|
|
+ SelectorOp operation = SelectorOp::None;
|
|
|
+ String operation_argument;
|
|
|
+ String expected_ids_after_operation;
|
|
|
};
|
|
|
+
|
|
|
+// clang-format off
|
|
|
static const Vector<QuerySelector> query_selectors =
|
|
|
{
|
|
|
{ "span", "Y D0 D1 F0" },
|
|
|
@@ -85,26 +101,54 @@ static const Vector<QuerySelector> query_selectors =
|
|
|
{ "body > .hello", "X Z" },
|
|
|
{ ".parent *", "A B C D D0 D1 E F F0 G H" },
|
|
|
{ ".parent > *", "A B C D E F G H" },
|
|
|
- { ":checked", "I" },
|
|
|
+ { ":checked", "I", SelectorOp::RemoveChecked, "I", "" },
|
|
|
{ ".parent :nth-child(odd)", "A C D0 E F0 G" },
|
|
|
- { ".parent > :nth-child(even)", "B D F H" },
|
|
|
- { ":first-child", "X A D0 F0" },
|
|
|
- { ":last-child", "D1 F0 H I" },
|
|
|
- { "p:nth-child(2)", "B" },
|
|
|
- { "h1:nth-child(2)", "" },
|
|
|
- { "p:nth-child(3n+1)", "D G" },
|
|
|
+ { ".parent > :nth-child(even)", "B D F H", SelectorOp::RemoveClasses, "parent", "" },
|
|
|
+ { ":first-child", "X A D0 F0", SelectorOp::RemoveElementsByIds, "A F0", "X B D0" },
|
|
|
+ { ":last-child", "D1 F0 H I", SelectorOp::RemoveElementsByIds, "D0 H", "D1 F0 G I" },
|
|
|
+ { "h1:nth-child(2)", "", SelectorOp::InsertElementBefore, "A", "A" },
|
|
|
+ { "p:nth-child(2)", "B", SelectorOp::InsertElementBefore, "A", "" },
|
|
|
+ { "p:nth-child(2)", "B", SelectorOp::RemoveElementsByIds, "A", "C" },
|
|
|
+ { "p:nth-child(3n+1)", "D G", SelectorOp::RemoveElementsByIds, "B", "H" },
|
|
|
{ "p:nth-child(3n + 1)", "D G" },
|
|
|
{ "#P > :nth-last-child(2n+1)", "B D F H" },
|
|
|
{ "#P p:nth-of-type(odd)", "B D G" },
|
|
|
+ { "span:first-child", "D0 F0" },
|
|
|
+ { "body span:first-child", "D0 F0" },
|
|
|
+ { "body > p span:first-child", "" },
|
|
|
+ { "body > div span:first-child", "D0 F0" },
|
|
|
+ { ":nth-child(4) * span:first-child", "D0 F0", SelectorOp::RemoveElementsByIds, "X", "" },
|
|
|
{ "p:nth-last-of-type(3n+1)", "D H" },
|
|
|
{ ":first-of-type", "X Y A B D0 E F0 I" },
|
|
|
{ ":last-of-type", "Y P A D1 E F0 H I" },
|
|
|
- { ":only-child", "F0" },
|
|
|
+ { ":only-child", "F0", SelectorOp::RemoveElementsByIds, "D0", "D1 F0" },
|
|
|
{ ":only-of-type", "Y A E F0 I" },
|
|
|
- { "span:empty", "Y D0 D1 F0" },
|
|
|
- { ".hello.world, #P span, #I", "Z D0 D1 F0 I" },
|
|
|
+ { "span:empty", "Y D0 F0" },
|
|
|
+ { ".hello.world, #P span, #I", "Z D0 D1 F0 I", SelectorOp::RemoveClasses, "world", "D0 D1 F0 I" },
|
|
|
{ "body * span", "D0 D1 F0" },
|
|
|
+ { "D1 *", "" },
|
|
|
+ { "#E + #F", "F", SelectorOp::InsertElementBefore, "F", "" },
|
|
|
+ { "#E+#F", "F" },
|
|
|
+ { "#E +#F", "F" },
|
|
|
+ { "#E+ #F", "F" },
|
|
|
+ { "#F + #E", "" },
|
|
|
+ { "div.parent > #B + p", "C" },
|
|
|
+ { "div.parent > #B + div", "" },
|
|
|
+ { "#B ~ #F", "F" },
|
|
|
+ { "#B~#F", "F" },
|
|
|
+ { "#B ~#F", "F" },
|
|
|
+ { "#B~ #F", "F" },
|
|
|
+ { "#F ~ #B", "" },
|
|
|
+ { "div.parent > #B ~ p:empty", "C G H", SelectorOp::InsertElementBefore, "H", "C G Inserted H" },
|
|
|
+ { "div.parent > #B ~ * span", "D0 D1 F0" },
|
|
|
+ { ":not(*)", "" },
|
|
|
+ { ":not(span)", "X Z P A B C D E F G H I" },
|
|
|
+ { "#D :not(#D0)", "D1" },
|
|
|
+ { "body > :not(:checked)", "X Y Z P", SelectorOp::RemoveChecked, "I", "X Y Z P I" },
|
|
|
+ { "div.hello:not(.world)", "X" },
|
|
|
+ { ":not(div,:nth-child(2),p *)", "A C D E F G H I" },
|
|
|
};
|
|
|
+
|
|
|
struct ClosestSelector {
|
|
|
String start_id;
|
|
|
String selector;
|
|
|
@@ -123,7 +167,7 @@ static const Vector<ClosestSelector> closest_selectors =
|
|
|
{ "D1", ":nth-child(4)", "D" },
|
|
|
{ "D1", "div:nth-child(4)", "P" },
|
|
|
};
|
|
|
-
|
|
|
+// clang-format on
|
|
|
|
|
|
// Recursively iterate through 'element' and all of its descendants to find all
|
|
|
// elements matching a particular property used to tag matching selectors.
|
|
|
@@ -132,7 +176,9 @@ static void GetMatchingIds(String& matching_ids, Element* element)
|
|
|
String id = element->GetId();
|
|
|
if (!id.empty() && element->GetProperty<int>("drag") == (int)Style::Drag::Drag)
|
|
|
{
|
|
|
- matching_ids += id + ' ';
|
|
|
+ if (!matching_ids.empty())
|
|
|
+ matching_ids += ' ';
|
|
|
+ matching_ids += id;
|
|
|
}
|
|
|
|
|
|
for (int i = 0; i < element->GetNumChildren(); i++)
|
|
|
@@ -157,20 +203,39 @@ static String ElementListToIds(const ElementList& elements)
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
-TEST_CASE("Selectors")
|
|
|
+static void RemoveElementsWithIds(ElementDocument* document, const String& remove_ids)
|
|
|
{
|
|
|
- const Vector2i window_size(1024, 768);
|
|
|
-
|
|
|
- TestsSystemInterface system_interface;
|
|
|
- TestsRenderInterface render_interface;
|
|
|
-
|
|
|
- SetRenderInterface(&render_interface);
|
|
|
- SetSystemInterface(&system_interface);
|
|
|
-
|
|
|
- Initialise();
|
|
|
+ StringList remove_id_list;
|
|
|
+ StringUtilities::ExpandString(remove_id_list, remove_ids, ' ');
|
|
|
+ for (const String& id : remove_id_list)
|
|
|
+ {
|
|
|
+ if (Element* element = document->GetElementById(id))
|
|
|
+ element->GetParentNode()->RemoveChild(element);
|
|
|
+ }
|
|
|
+}
|
|
|
+static void RemoveClassesFromAllElements(ElementDocument* document, const String& remove_classes)
|
|
|
+{
|
|
|
+ StringList class_list;
|
|
|
+ StringUtilities::ExpandString(class_list, remove_classes, ' ');
|
|
|
+ for (const String& name : class_list)
|
|
|
+ {
|
|
|
+ ElementList element_list;
|
|
|
+ document->GetElementsByClassName(element_list, name);
|
|
|
+ for (Element* element : element_list)
|
|
|
+ element->SetClass(name, false);
|
|
|
+ }
|
|
|
+}
|
|
|
+static void InsertElementBefore(ElementDocument* document, const String& before_id)
|
|
|
+{
|
|
|
+ Element* element = document->GetElementById(before_id);
|
|
|
+ ElementPtr new_element = document->CreateElement("p");
|
|
|
+ new_element->SetId("Inserted");
|
|
|
+ element->GetParentNode()->InsertBefore(std::move(new_element), element);
|
|
|
+}
|
|
|
|
|
|
- Context* context = Rml::CreateContext("main", window_size);
|
|
|
- REQUIRE(context);
|
|
|
+TEST_CASE("Selectors")
|
|
|
+{
|
|
|
+ Context* context = TestsShell::GetContext();
|
|
|
|
|
|
SUBCASE("RCSS document selectors")
|
|
|
{
|
|
|
@@ -183,11 +248,41 @@ TEST_CASE("Selectors")
|
|
|
|
|
|
String matching_ids;
|
|
|
GetMatchingIds(matching_ids, document);
|
|
|
+ CHECK_MESSAGE(matching_ids == selector.expected_ids, "Selector: " << selector.selector);
|
|
|
+
|
|
|
+ // Also check validity of selectors after structural document changes.
|
|
|
+ if (selector.operation != SelectorOp::None)
|
|
|
+ {
|
|
|
+ String operation_str;
|
|
|
+ switch (selector.operation)
|
|
|
+ {
|
|
|
+ case SelectorOp::RemoveElementsByIds:
|
|
|
+ RemoveElementsWithIds(document, selector.operation_argument);
|
|
|
+ operation_str = "RemoveElementsByIds";
|
|
|
+ break;
|
|
|
+ case SelectorOp::InsertElementBefore:
|
|
|
+ InsertElementBefore(document, selector.operation_argument);
|
|
|
+ operation_str = "InsertElementBefore";
|
|
|
+ break;
|
|
|
+ case SelectorOp::RemoveClasses:
|
|
|
+ RemoveClassesFromAllElements(document, selector.operation_argument);
|
|
|
+ operation_str = "RemoveClasses";
|
|
|
+ break;
|
|
|
+ case SelectorOp::RemoveChecked:
|
|
|
+ document->GetElementById(selector.operation_argument)->RemoveAttribute("checked");
|
|
|
+ operation_str = "RemoveChecked";
|
|
|
+ break;
|
|
|
+ case SelectorOp::None:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ context->Update();
|
|
|
|
|
|
- if (!matching_ids.empty())
|
|
|
- matching_ids.pop_back();
|
|
|
+ String matching_ids_after_operation;
|
|
|
+ GetMatchingIds(matching_ids_after_operation, document);
|
|
|
+ CHECK_MESSAGE(matching_ids_after_operation == selector.expected_ids_after_operation, "Selector: ", selector.selector,
|
|
|
+ " Operation: ", operation_str, " Argument: ", selector.operation_argument);
|
|
|
+ }
|
|
|
|
|
|
- CHECK_MESSAGE(matching_ids == selector.expected_ids, "Selector: " << selector.selector);
|
|
|
context->UnloadDocument(document);
|
|
|
}
|
|
|
}
|
|
|
@@ -237,5 +332,5 @@ TEST_CASE("Selectors")
|
|
|
context->UnloadDocument(document);
|
|
|
}
|
|
|
|
|
|
- Rml::Shutdown();
|
|
|
+ TestsShell::ShutdownShell();
|
|
|
}
|