/*
* This source file is part of RmlUi, the HTML/CSS Interface Middleware
*
* For the latest information, see http://github.com/mikke89/RmlUi
*
* Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
* Copyright (c) 2019-2023 The RmlUi Team, and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#include "../Common/Mocks.h"
#include "../Common/TestsInterface.h"
#include "../Common/TestsShell.h"
#include "../Common/TypesToString.h"
#include
#include
#include
#include
#include
using namespace Rml;
static const String document_clone_rml = R"(
Test
This is a sample .
)";
static const String document_scroll_rml = R"(
Test
)";
void Run(Context* context)
{
context->Update();
context->Render();
TestsShell::RenderLoop();
}
TEST_CASE("Element")
{
Context* context = TestsShell::GetContext();
REQUIRE(context);
ElementDocument* document = context->LoadDocumentFromMemory(document_clone_rml);
REQUIRE(document);
document->Show();
context->Update();
context->Render();
Element* div = document->GetFirstChild();
Element* span = div->GetChild(1);
REQUIRE(div);
REQUIRE(div->GetTagName() == "div");
REQUIRE(span);
REQUIRE(span->GetTagName() == "span");
SUBCASE("Attribute")
{
auto* button = document->AppendChild(document->CreateElement("button"));
SUBCASE("Event listener")
{
namespace tl = trompeloeil;
static constexpr auto ON_CLICK_ATTRIBUTE = "onclick";
static constexpr auto ON_CLICK_VALUE = "moo";
std::vector> expectations;
UniquePtr mockEventListener;
const auto configureMockEventListener = [&]() {
mockEventListener.reset(new MockEventListener());
expectations.emplace_back(NAMED_ALLOW_CALL(*mockEventListener, OnAttach(button)));
expectations.emplace_back(NAMED_ALLOW_CALL(*mockEventListener, OnDetach(button)).LR_SIDE_EFFECT(mockEventListener.reset()));
};
MockEventListenerInstancer mockEventListenerInstancer;
const auto configureMockEventListenerInstancer = [&](const auto value) {
expectations.emplace_back(NAMED_REQUIRE_CALL(mockEventListenerInstancer, InstanceEventListener(value, button))
.LR_SIDE_EFFECT(configureMockEventListener())
.LR_RETURN(mockEventListener.get()));
};
Factory::RegisterEventListenerInstancer(&mockEventListenerInstancer);
configureMockEventListenerInstancer(ON_CLICK_VALUE);
button->SetAttribute(ON_CLICK_ATTRIBUTE, ON_CLICK_VALUE);
SUBCASE("Replacement")
{
static constexpr auto REPLACEMENT_ON_CLICK_VALUE = "boo";
configureMockEventListenerInstancer(REPLACEMENT_ON_CLICK_VALUE);
button->SetAttribute(ON_CLICK_ATTRIBUTE, REPLACEMENT_ON_CLICK_VALUE);
}
button->RemoveAttribute(ON_CLICK_ATTRIBUTE);
}
SUBCASE("Simple")
{
static constexpr auto DISABLED_ATTRIBUTE = "disabled";
REQUIRE_FALSE(button->HasAttribute(DISABLED_ATTRIBUTE));
button->SetAttribute(DISABLED_ATTRIBUTE, "");
REQUIRE(button->HasAttribute(DISABLED_ATTRIBUTE));
SUBCASE("Replacement")
{
static constexpr auto VALUE = "something";
button->SetAttribute(DISABLED_ATTRIBUTE, VALUE);
const auto* attributeValue = button->GetAttribute(DISABLED_ATTRIBUTE);
REQUIRE(attributeValue);
REQUIRE(attributeValue->GetType() == Variant::Type::STRING);
CHECK(attributeValue->Get() == VALUE);
}
button->RemoveAttribute(DISABLED_ATTRIBUTE);
CHECK_FALSE(button->HasAttribute(DISABLED_ATTRIBUTE));
}
}
SUBCASE("CloneDrag")
{
// Simulate input for mouse click and drag
context->ProcessMouseMove(10, 10, 0);
context->Update();
context->Render();
context->ProcessMouseButtonDown(0, 0);
context->Update();
context->Render();
// This should initiate a drag clone.
context->ProcessMouseMove(10, 11, 0);
context->Update();
context->Render();
context->ProcessMouseButtonUp(0, 0);
}
SUBCASE("CloneManual")
{
Element* element = document->GetFirstChild();
REQUIRE(element->GetProperty("background-color") == "#ff0000");
CHECK(element->Clone()->GetProperty("background-color") == "#ff0000");
element->SetProperty("background-color", "#0f0");
CHECK(element->Clone()->GetProperty("background-color") == "#00ff00");
element->RemoveProperty("background-color");
Element* clone = document->AppendChild(element->Clone());
context->Update();
CHECK(clone->GetProperty("background-color") == "#ffffff");
element->SetClass("blue", true);
clone = document->AppendChild(element->Clone());
context->Update();
CHECK(clone->GetProperty("background-color") == "#0000ff");
}
SUBCASE("SetInnerRML")
{
Element* element = document->GetFirstChild();
CHECK(element->GetInnerRML() == "This is a sample .");
element->SetInnerRML("text");
CHECK(element->GetInnerRML() == "text");
const char* inner_rml = R"(beforechild
after)";
element->SetInnerRML(inner_rml);
CHECK(element->GetInnerRML() == inner_rml);
ElementPtr element_ptr = document->CreateElement("div");
CHECK(element_ptr->GetInnerRML() == "");
element_ptr->SetInnerRML("text");
CHECK(element_ptr->GetInnerRML() == "text");
}
SUBCASE("GetInnerRML")
{
String inner_rml;
inner_rml = document->GetInnerRML();
CHECK(inner_rml == R"(This is a sample .
)");
div->SetProperty("background-color", "white");
inner_rml = document->GetInnerRML();
CHECK(inner_rml == R"(This is a sample .
)");
div->RemoveProperty("background-color");
inner_rml = document->GetInnerRML();
CHECK(inner_rml == R"(This is a sample .
)");
div->SetProperty("cursor", "xGetInnerRML();
CHECK(inner_rml == R"(This is a sample .
)");
span->SetProperty("font-weight", "bold");
inner_rml = document->GetInnerRML();
CHECK(inner_rml == R"(This is a sample .
)");
}
SUBCASE("InsertBefore")
{
String inner_rml;
inner_rml = document->GetInnerRML();
CHECK(inner_rml == R"(This is a sample .
)");
div->InsertBefore(document->CreateElement("img"), span);
inner_rml = document->GetInnerRML();
CHECK(inner_rml == R"(This is a
sample .
)");
div->InsertBefore(document->CreateElement("button"), nullptr)->SetInnerRML("Click me");
inner_rml = document->GetInnerRML();
CHECK(inner_rml == R"(This is a
sample .
Click me )");
}
document->Close();
TestsShell::ShutdownShell();
}
TEST_CASE("Element.ScrollIntoView")
{
Context* context = TestsShell::GetContext();
REQUIRE(context);
ElementDocument* document = context->LoadDocumentFromMemory(document_scroll_rml);
REQUIRE(document);
document->Show();
Run(context);
Element* scrollable = document->GetElementById("scrollable");
REQUIRE(scrollable);
Element* cells[4][4]{};
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
cells[i][j] = document->GetElementById(CreateString("cell%d%d", i, j));
REQUIRE(cells[i][j]);
}
}
REQUIRE(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(0, 0));
REQUIRE(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(100, 100));
REQUIRE(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(150, 150));
REQUIRE(scrollable->GetScrollLeft() == 0);
REQUIRE(scrollable->GetScrollTop() == 0);
SUBCASE("LegacyScroll")
{
cells[2][2]->ScrollIntoView(true);
Run(context);
CHECK(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-50, -100));
CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(50, 0));
CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(100, 50));
CHECK(scrollable->GetScrollLeft() == 50);
CHECK(scrollable->GetScrollTop() == 100);
cells[2][2]->ScrollIntoView(false);
Run(context);
CHECK(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-50, -50));
CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(50, 50));
CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(100, 100));
CHECK(scrollable->GetScrollLeft() == 50);
CHECK(scrollable->GetScrollTop() == 50);
}
SUBCASE("AdvancedScroll")
{
cells[2][2]->ScrollIntoView({ScrollAlignment::Center, ScrollAlignment::Center});
Run(context);
CHECK(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-75, -75));
CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(25, 25));
CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(75, 75));
SUBCASE("NearestAlready")
{
cells[2][2]->ScrollIntoView(ScrollAlignment::Nearest);
Run(context);
CHECK(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-75, -75));
CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(25, 25));
CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(75, 75));
}
SUBCASE("NearestBefore")
{
cells[1][1]->ScrollIntoView(ScrollAlignment::Nearest);
Run(context);
CHECK(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-50, -50));
CHECK(cells[1][1]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(0, 0));
CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(50, 50));
}
SUBCASE("NearestAfter")
{
cells[3][3]->ScrollIntoView(ScrollAlignment::Nearest);
Run(context);
CHECK(cells[1][1]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-50, -50));
CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(0, 0));
CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(50, 50));
}
SUBCASE("Smoothscroll")
{
TestsSystemInterface* system_interface = TestsShell::GetTestsSystemInterface();
system_interface->SetTime(0);
cells[3][3]->ScrollIntoView({ScrollAlignment::Nearest, ScrollAlignment::Nearest, ScrollBehavior::Smooth});
constexpr double dt = 1.0 / 15.0;
system_interface->SetTime(dt);
Run(context);
// We don't define the exact offset at this time step, but it should be somewhere between the start and end offsets.
Vector2f offset = cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border);
CHECK(offset.x > 50.f);
CHECK(offset.y > 50.f);
CHECK(offset.x < 75.f);
CHECK(offset.y < 75.f);
// After one second it should be at the destination offset.
for (double t = 2.0 * dt; t < 1.0; t += dt)
{
system_interface->SetTime(t);
Run(context);
}
CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(50, 50));
}
}
document->Close();
TestsShell::ShutdownShell();
}