Skip to content

Commit

Permalink
- Add a visual test suite for navigating and comparing formatting of …
Browse files Browse the repository at this point in the history
…documents for correctness, in particular for testing the layout engine. Includes some examples from the CSS specifications.

- A python script for a "best-effort" conversion of the full CSS 2.1 test suite into RML, which you can load into the visual tests suite (see CMake option VISUAL_TESTS_DIRECTORIES).
- Split the tests into separate executables: UnitTests, VisualTests, and Benchmarks.
  • Loading branch information
mikke89 committed Jul 27, 2020
1 parent 7bcbf3b commit 61a14fd
Show file tree
Hide file tree
Showing 43 changed files with 2,438 additions and 266 deletions.
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)

if(BUILD_TESTING)
set(RMLUI_TESTS_ENABLED ON)
option(ENABLE_BENCHMARKS "Will enable benchmarks while running Tests." ON)
option(BUILD_UNIT_TESTS "Enable to build UnitTests." ON)
option(BUILD_VISUAL_TESTS "Enable to build VisualTests." ON)
set(VISUAL_TESTS_DIRECTORIES "" CACHE STRING "Specify additional directories containing *.rml documents for the visual tests. Backslashes must be escaped. Separate multiple directories by semicolon.")
option(BUILD_BENCHMARKS "Enable to build Benchmarks." ON)
endif()
endif()

Expand Down
4 changes: 4 additions & 0 deletions Include/RmlUi/Core/Element.h
Original file line number Diff line number Diff line change
Expand Up @@ -466,8 +466,12 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr<E
/// @param[in] event Event to attach to.
/// @param[in] listener The listener object to be attached.
/// @param[in] in_capture_phase True to attach in the capture phase, false in bubble phase.
/// @lifetime The added listener must stay alive until after the dispatched call from EventListener::OnDetach(). This occurs
/// eg. when the element is destroyed or when RemoveEventListener() is called with the same parameters passed here.
void AddEventListener(const String& event, EventListener* listener, bool in_capture_phase = false);
/// Adds an event listener to this element by id.
/// @lifetime The added listener must stay alive until after the dispatched call from EventListener::OnDetach(). This occurs
/// eg. when the element is destroyed or when RemoveEventListener() is called with the same parameters passed here.
void AddEventListener(EventId id, EventListener* listener, bool in_capture_phase = false);
/// Removes an event listener from this element.
/// @param[in] event Event to detach from.
Expand Down
4 changes: 4 additions & 0 deletions Include/RmlUi/Core/XMLParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ class RMLUICORE_API XMLParser : public BaseXMLParser
/// @param[in] handler The custom handler.
/// @return The registered XML node handler.
static XMLNodeHandler* RegisterNodeHandler(const String& tag, SharedPtr<XMLNodeHandler> handler);
/// Retrieve a registered node handler.
/// @param[in] tag The tag the custom parser handles.
/// @return The registered XML node handler or nullptr if it does not exist for the given tag.
static XMLNodeHandler* GetNodeHandler(const String& tag);
/// Releases all registered node handlers. This is called internally.
static void ReleaseHandlers();

Expand Down
75 changes: 19 additions & 56 deletions Samples/basic/treeview/src/FileSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,14 @@
#include <cstdlib>
#include <cstdio>
#include <string.h>

#ifdef RMLUI_PLATFORM_WIN32
#include <io.h>
#else
#include <dirent.h>
#endif
#include <Shell.h>

struct FileSystemNode;

typedef Rml::UnorderedMap< Rml::String, FileSystemNode* > NodeMap;
using NodeMap = Rml::UnorderedMap< Rml::String, FileSystemNode* >;

FileSystemNode* file_system_root = nullptr;
NodeMap node_map;
static Rml::UniquePtr<FileSystemNode> file_system_root;
static NodeMap node_map;


/**
Expand All @@ -64,60 +59,29 @@ struct FileSystemNode
}

~FileSystemNode()
{
for (size_t i = 0; i < child_nodes.size(); ++i)
delete child_nodes[i];
}
{}

// Build the list of files and directories within this directory.
void BuildTree(const Rml::String& root = "")
{
#ifdef RMLUI_PLATFORM_WIN32
_finddata_t find_data;
intptr_t find_handle = _findfirst((root + name + "/*.*").c_str(), &find_data);
if (find_handle != -1)
{
do
{
if (strcmp(find_data.name, ".") == 0 ||
strcmp(find_data.name, "..") == 0)
continue;
const Rml::String current_directory = root + name + '/';

child_nodes.push_back(new FileSystemNode(find_data.name, (find_data.attrib & _A_SUBDIR) == _A_SUBDIR, depth + 1));
const Rml::StringList directories = Shell::ListDirectories(current_directory);

} while (_findnext(find_handle, &find_data) == 0);

_findclose(find_handle);
for (const Rml::String& directory : directories)
{
child_nodes.push_back(Rml::MakeUnique<FileSystemNode>(directory, true, depth + 1));
child_nodes.back()->BuildTree(current_directory);
}
#else
struct dirent** file_list = nullptr;
int file_count = -1;
file_count = scandir((root + name).c_str(), &file_list, 0, alphasort);
if (file_count == -1)
return;

while (file_count--)
{
if (strcmp(file_list[file_count]->d_name, ".") == 0 ||
strcmp(file_list[file_count]->d_name, "..") == 0)
continue;

child_nodes.push_back(new FileSystemNode(file_list[file_count]->d_name, (file_list[file_count]->d_type & DT_DIR) == DT_DIR, depth + 1));

free(file_list[file_count]);
}
free(file_list);
#endif

// Generate the trees of all of our subdirectories.
for (size_t i = 0; i < child_nodes.size(); ++i)

const Rml::StringList files = Shell::ListFiles(current_directory);
for (const Rml::String& file : files)
{
if (child_nodes[i]->directory)
child_nodes[i]->BuildTree(root + name + "/");
child_nodes.push_back(Rml::MakeUnique<FileSystemNode>(file, false, depth + 1));
}
}

typedef Rml::Vector< FileSystemNode* > NodeList;
using NodeList = Rml::Vector< Rml::UniquePtr<FileSystemNode> >;

Rml::String id;
Rml::String name;
Expand All @@ -131,14 +95,13 @@ struct FileSystemNode
FileSystem::FileSystem(const Rml::String& root) : Rml::DataSource("file")
{
// Generate the file system nodes starting at the RmlUi's root directory.
file_system_root = new FileSystemNode(".", true);
file_system_root = Rml::MakeUnique<FileSystemNode>(".", true);
file_system_root->BuildTree(root);
}

FileSystem::~FileSystem()
{
delete file_system_root;
file_system_root = nullptr;
file_system_root.reset();
}

void FileSystem::GetRow(Rml::StringList& row, const Rml::String& table, int row_index, const Rml::StringList& columns)
Expand Down Expand Up @@ -183,7 +146,7 @@ FileSystemNode* FileSystem::GetNode(const Rml::String& table)
{
// Determine which node the row is being requested from.
if (table == "root")
return file_system_root;
return file_system_root.get();
else
{
NodeMap::iterator i = node_map.find(table);
Expand Down
6 changes: 2 additions & 4 deletions Samples/basic/treeview/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,8 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))

Shell::LoadFonts("assets/");

// Create the file data source and formatter.
Rml::String root = Shell::FindSamplesRoot();
FileSystem file_system(root + "basic/");
// Create the file data source and formatter. The samples directory '/' acts as our root.
FileSystem file_system("/");
FileFormatter file_formatter;

// Load and show the demo document.
Expand All @@ -121,7 +120,6 @@ int main(int RMLUI_UNUSED_PARAMETER(argc), char** RMLUI_UNUSED_PARAMETER(argv))

Shell::EventLoop(GameLoop);

// Shutdown RmlUi.
Rml::Shutdown();

Shell::CloseWindow();
Expand Down
5 changes: 5 additions & 0 deletions Samples/shell/include/Shell.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ class Shell
/// Loads the default fonts from the given path.
static void LoadFonts(const char* directory);

/// List files in the given directory. An initial forward slash '/' makes it relative to the samples root.
static Rml::StringList ListFiles(const Rml::String& in_directory, const Rml::String& extension = Rml::String());
/// List subdirectories in the given directory. An initial forward slash '/' makes it relative to the samples root.
static Rml::StringList ListDirectories(const Rml::String& in_directory);

/// Open a platform specific window, optionally initialising an OpenGL context on it.
/// @param[in] title Title of the window.
/// @param[in] srie Provides the interface for attaching a renderer to the window and performing related bits of interface.
Expand Down
88 changes: 88 additions & 0 deletions Samples/shell/src/Shell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
#include "Shell.h"
#include <RmlUi/Core/Core.h>

#ifdef RMLUI_PLATFORM_WIN32
#include <io.h>
#else
#include <dirent.h>
#endif

/// Loads the default fonts from the given path.
void Shell::LoadFonts(const char* directory)
{
Expand All @@ -47,3 +53,85 @@ void Shell::LoadFonts(const char* directory)
}
}


enum class ListType { Files, Directories };

static Rml::StringList ListFilesOrDirectories(ListType type, const Rml::String& in_directory, const Rml::String& extension)
{
if (in_directory.empty())
return Rml::StringList();

Rml::String directory;

if (in_directory[0] == '/')
directory = Shell::FindSamplesRoot() + in_directory.substr(1);
else
directory = in_directory;

const Rml::String find_path = directory + "/*." + (extension.empty() ? Rml::String("*") : extension);

Rml::StringList result;

#ifdef RMLUI_PLATFORM_WIN32
_finddata_t find_data;
intptr_t find_handle = _findfirst(find_path.c_str(), &find_data);
if (find_handle != -1)
{
do
{
if (strcmp(find_data.name, ".") == 0 ||
strcmp(find_data.name, "..") == 0)
continue;

bool is_directory = ((find_data.attrib & _A_SUBDIR) == _A_SUBDIR);
bool is_file = (!is_directory && ((find_data.attrib & _A_NORMAL) == _A_NORMAL));

if (((type == ListType::Files) && is_file) ||
((type == ListType::Directories) && is_directory))
{
result.push_back(find_data.name);
}

} while (_findnext(find_handle, &find_data) == 0);

_findclose(find_handle);
}
#else
struct dirent** file_list = nullptr;
int file_count = -1;
file_count = scandir(find_path.c_str(), &file_list, 0, alphasort);
if (file_count == -1)
return;

// TODO: Untested
while (file_count--)
{
if (strcmp(file_list[file_count]->d_name, ".") == 0 ||
strcmp(file_list[file_count]->d_name, "..") == 0)
continue;

bool is_directory = ((file_list[file_count]->d_type & DT_DIR) == DT_DIR);
bool is_file = ((file_list[file_count]->d_type & DT_REG) == DT_REG);

if ((type == ListType::Files && is_file) ||
(type == ListType::Directories && is_directory))
{
result.push_back(file_list[file_count]->d_name);
}
}
free(file_list);
#endif

return result;
}

Rml::StringList Shell::ListDirectories(const Rml::String& in_directory)
{
return ListFilesOrDirectories(ListType::Directories, in_directory, Rml::String());
}

Rml::StringList Shell::ListFiles(const Rml::String& in_directory, const Rml::String& extension)
{
return ListFilesOrDirectories(ListType::Files, in_directory, extension);
}

9 changes: 9 additions & 0 deletions Source/Core/XMLParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ XMLNodeHandler* XMLParser::RegisterNodeHandler(const String& _tag, SharedPtr<XML
return result;
}

XMLNodeHandler* XMLParser::GetNodeHandler(const String& tag)
{
auto it = node_handlers.find(tag);
if (it != node_handlers.end())
return it->second.get();

return nullptr;
}

// Releases all registered node handlers. This is called internally.
void XMLParser::ReleaseHandlers()
{
Expand Down
Loading

0 comments on commit 61a14fd

Please sign in to comment.