/*
* 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/TestsShell.h"
#include
#include
#include
#include
#include
#include
using namespace Rml;
static void SimulateClick(Context* context, Vector2i position)
{
context->Update();
context->ProcessMouseMove(position.x, position.y, 0);
context->Update();
context->ProcessMouseButtonDown(0, 0);
context->Update();
context->ProcessMouseButtonUp(0, 0);
context->Update();
}
static const String document_focus_rml = R"(
P1
P2
P3
P4
P5
P6
P7
P8
P9
P10
)";
static const String focus_forward = "p1 p2 p3 p5 p7 p9 p10 p11 p12 p13";
TEST_SUITE_BEGIN("ElementDocument");
TEST_CASE("Focus")
{
Context* context = TestsShell::GetContext();
REQUIRE(context);
ElementDocument* document = context->LoadDocumentFromMemory(document_focus_rml);
REQUIRE(document);
document->Show();
context->Update();
context->Render();
TestsShell::RenderLoop();
SUBCASE("Tab order")
{
StringList ids;
StringUtilities::ExpandString(ids, focus_forward, ' ');
REQUIRE(!ids.empty());
document->Focus();
SUBCASE("Forward")
{
for (const String& id : ids)
{
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == id);
}
// Wrap around
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == ids[0]);
}
SUBCASE("Reverse")
{
std::reverse(ids.begin(), ids.end());
for (const String& id : ids)
{
context->ProcessKeyDown(Input::KI_TAB, Input::KM_SHIFT);
CHECK(context->GetFocusElement()->GetId() == id);
}
// Wrap around (reverse)
context->ProcessKeyDown(Input::KI_TAB, Input::KM_SHIFT);
CHECK(context->GetFocusElement()->GetId() == ids[0]);
}
}
SUBCASE("Tab to document")
{
Element* element = document->GetElementById("p13");
REQUIRE(element);
element->Focus();
document->SetProperty("tab-index", "auto");
document->UpdateDocument();
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == "body");
}
SUBCASE("Tab from container element")
{
Element* container = document->GetElementById("container");
REQUIRE(container);
container->Focus();
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == "p11");
container->Focus();
context->ProcessKeyDown(Input::KI_TAB, Input::KM_SHIFT);
CHECK(context->GetFocusElement()->GetId() == "p10");
}
SUBCASE("Single element")
{
document->SetProperty("tab-index", "none");
document->SetInnerRML(R"( P1)");
document->UpdateDocument();
document->Focus();
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == "p1");
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == "p1");
context->ProcessKeyDown(Input::KI_TAB, Input::KM_SHIFT);
CHECK(context->GetFocusElement()->GetId() == "p1");
document->SetProperty("tab-index", "auto");
document->UpdateDocument();
document->Focus();
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == "p1");
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == "body");
context->ProcessKeyDown(Input::KI_TAB, Input::KM_SHIFT);
CHECK(context->GetFocusElement()->GetId() == "p1");
context->ProcessKeyDown(Input::KI_TAB, Input::KM_SHIFT);
CHECK(context->GetFocusElement()->GetId() == "body");
}
SUBCASE("Single, non-tabable element")
{
document->SetProperty("tab-index", "none");
document->SetInnerRML(R"(
)");
document->UpdateDocument();
Element* child = document->GetChild(0);
document->Focus();
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == "body");
context->ProcessKeyDown(Input::KI_TAB, Input::KM_SHIFT);
CHECK(context->GetFocusElement()->GetId() == "body");
child->Focus();
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == "child");
context->ProcessKeyDown(Input::KI_TAB, Input::KM_SHIFT);
CHECK(context->GetFocusElement()->GetId() == "child");
document->SetProperty("tab-index", "auto");
document->UpdateDocument();
document->Focus();
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == "body");
context->ProcessKeyDown(Input::KI_TAB, Input::KM_SHIFT);
CHECK(context->GetFocusElement()->GetId() == "body");
child->Focus();
context->ProcessKeyDown(Input::KI_TAB, 0);
CHECK(context->GetFocusElement()->GetId() == "body");
child->Focus();
context->ProcessKeyDown(Input::KI_TAB, Input::KM_SHIFT);
CHECK(context->GetFocusElement()->GetId() == "body");
}
document->Close();
TestsShell::ShutdownShell();
}
TEST_CASE("Load")
{
namespace tl = trompeloeil;
constexpr auto BODY_TAG = "body";
Context* context = TestsShell::GetContext();
REQUIRE(context);
MockEventListener mockEventListener;
MockEventListenerInstancer mockEventListenerInstancer;
tl::sequence sequence;
REQUIRE_CALL(mockEventListenerInstancer, InstanceEventListener("something", tl::_))
.WITH(_2->GetTagName() == BODY_TAG)
.IN_SEQUENCE(sequence)
.LR_RETURN(&mockEventListener);
ALLOW_CALL(mockEventListener, OnAttach(tl::_));
ALLOW_CALL(mockEventListener, OnDetach(tl::_));
REQUIRE_CALL(mockEventListener, ProcessEvent(tl::_))
.WITH(_1.GetId() == EventId::Load && _1.GetTargetElement()->GetTagName() == BODY_TAG)
.IN_SEQUENCE(sequence);
Factory::RegisterEventListenerInstancer(&mockEventListenerInstancer);
ElementDocument* document = context->LoadDocumentFromMemory(document_focus_rml);
REQUIRE(document);
document->Close();
TestsShell::ShutdownShell();
}
TEST_CASE("ReloadStyleSheet")
{
Context* context = TestsShell::GetContext();
ElementDocument* document = context->LoadDocument("basic/demo/data/demo.rml");
// There should be no warnings when reloading style sheets.
document->ReloadStyleSheet();
document->Close();
TestsShell::ShutdownShell();
}
TEST_CASE("Modal.MultipleDocuments")
{
Context* context = TestsShell::GetContext();
constexpr float halfwidth = 150;
ElementDocument* document1 = context->LoadDocument("assets/demo.rml");
document1->Show(Rml::ModalFlag::Modal);
document1->GetElementById("title")->SetInnerRML("Modal 1");
constexpr float margin1 = 50;
document1->SetProperty(PropertyId::MarginLeft, Property{margin1, Unit::PX});
Rml::ElementDocument* document2 = context->LoadDocument("assets/demo.rml");
document2->Show(Rml::ModalFlag::Modal);
document2->GetElementById("title")->SetInnerRML("Modal 2");
constexpr float margin2 = 350;
document2->SetProperty(PropertyId::MarginLeft, Property{margin2, Unit::PX});
Rml::ElementDocument* document3 = context->LoadDocument("assets/demo.rml");
document3->Show(Rml::ModalFlag::None);
document3->GetElementById("title")->SetInnerRML("Non-modal");
constexpr float margin3 = 650;
document3->SetProperty(PropertyId::MarginLeft, Property{margin3, Unit::PX});
context->Update();
TestsShell::RenderLoop();
REQUIRE(context->GetFocusElement() == document2);
SUBCASE("FocusFromModal")
{
document1->Focus();
REQUIRE(context->GetFocusElement() == document1);
document3->Focus();
REQUIRE(context->GetFocusElement() == document1);
}
SUBCASE("ClickFromModal")
{
SimulateClick(context, Vector2i(int(margin1 + halfwidth), context->GetDimensions().y / 2));
REQUIRE(context->GetFocusElement() == document2);
SimulateClick(context, Vector2i(int(margin3 + halfwidth), context->GetDimensions().y / 2));
REQUIRE(context->GetFocusElement() == document2);
}
SUBCASE("ModalFlag")
{
document1->Show(ModalFlag::None, FocusFlag::Document);
REQUIRE(context->GetFocusElement() == document2);
document3->Show(ModalFlag::None, FocusFlag::Document);
REQUIRE(context->GetFocusElement() == document2);
document3->Show(ModalFlag::Modal, FocusFlag::Document);
REQUIRE(context->GetFocusElement() == document3);
}
document1->Close();
document2->Close();
document3->Close();
TestsShell::ShutdownShell();
}
TEST_CASE("Modal.FocusWithin")
{
Context* context = TestsShell::GetContext();
Rml::ElementDocument* document = context->LoadDocument("assets/demo.rml");
document->GetElementById("content")->SetInnerRML(" ");
document->Show(Rml::ModalFlag::Modal);
REQUIRE(context->GetFocusElement() == document);
Element* input = document->GetElementById("input");
input->Focus();
REQUIRE(context->GetFocusElement() == input);
document->Close();
TestsShell::ShutdownShell();
}
TEST_SUITE_END();