Files
GacUI/Import/GacUI.UnitTest.h
2025-07-18 18:40:47 -07:00

1689 lines
60 KiB
C++

/***********************************************************************
THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY
DEVELOPER: Zihan Chen(vczh)
***********************************************************************/
#include "GacUI.h"
#include "VlppGlrParser.h"
#include "VlppWorkflowLibrary.h"
#include "VlppReflection.h"
#include "VlppOS.h"
#include "Vlpp.h"
#include "VlppRegex.h"
#include "GacUIReflection.h"
#include "VlppWorkflowCompiler.h"
#include "VlppWorkflowRuntime.h"
/***********************************************************************
.\GUIUNITTESTPROTOCOL_SHARED.H
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Unit Test Snapsnot and other Utilities
***********************************************************************/
#ifndef VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_SHARED
#define VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_SHARED
namespace vl::presentation::unittest
{
struct WindowStyleConfig
{
WString title;
bool enabled = true;
bool topMost = false;
bool showInTaskBar = true;
bool customFrameMode = false;
bool maximizedBox = true;
bool minimizedBox = true;
bool border = true;
bool sizeBox = true;
bool iconVisible = true;
bool titleBar = true;
bool activated = false;
auto operator<=>(const WindowStyleConfig&) const = default;
};
enum class UnitTestRemoteChannel
{
None,
Sync,
Async,
};
struct UnitTestScreenConfig
{
using FontConfig = vl::presentation::remoteprotocol::FontConfig;
using ScreenConfig = vl::presentation::remoteprotocol::ScreenConfig;
WString executablePath;
NativeMargin customFramePadding;
FontConfig fontConfig;
ScreenConfig screenConfig;
bool useDomDiff = false;
UnitTestRemoteChannel useChannel = UnitTestRemoteChannel::None;
void FastInitialize(vint width, vint height, vint taskBarHeight = 0);
};
class UnitTestRemoteProtocolBase : public Object, protected virtual IGuiRemoteProtocol
{
protected:
IGuiRemoteProtocolEvents* events = nullptr;
UnitTestScreenConfig globalConfig;
public:
UnitTestRemoteProtocolBase(UnitTestScreenConfig _globalConfig)
: globalConfig(_globalConfig)
{
}
IGuiRemoteProtocol* GetProtocol()
{
return this;
}
IGuiRemoteProtocolEvents* GetEvents() const
{
return events;
}
const UnitTestScreenConfig& GetGlobalConfig() const
{
return globalConfig;
}
protected:
/***********************************************************************
IGuiRemoteProtocol
***********************************************************************/
void Initialize(IGuiRemoteProtocolEvents* _events) override
{
events = _events;
}
WString GetExecutablePath() override
{
return globalConfig.executablePath;
}
};
}
#endif
/***********************************************************************
.\GUIUNITTESTPROTOCOL_IO.H
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Unit Test Snapsnot and other Utilities
***********************************************************************/
#ifndef VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_IO
#define VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_IO
namespace vl::presentation::unittest
{
template<typename TProtocol>
class UnitTestRemoteProtocol_IO : public TProtocol
{
using GlobalShortcutKey = remoteprotocol::GlobalShortcutKey;
using GlobalShortcutKeyList = collections::List<GlobalShortcutKey>;
public:
bool capturing = false;
GlobalShortcutKeyList globalShortcutKeys;
template<typename ...TArgs>
UnitTestRemoteProtocol_IO(TArgs&& ...args)
: TProtocol(std::forward<TArgs&&>(args)...)
{
}
protected:
/***********************************************************************
IGuiRemoteProtocolMessages (IO)
***********************************************************************/
void RequestIOUpdateGlobalShortcutKey(const Ptr<GlobalShortcutKeyList>& arguments) override
{
if (arguments)
{
CopyFrom(globalShortcutKeys, *arguments.Obj());
}
else
{
globalShortcutKeys.Clear();
}
}
void RequestIORequireCapture() override
{
capturing = true;
}
void RequestIOReleaseCapture() override
{
capturing = false;
}
void RequestIOIsKeyPressing(vint id, const VKEY& arguments) override
{
CHECK_FAIL(L"Not Implemented!");
}
void RequestIOIsKeyToggled(vint id, const VKEY& arguments) override
{
CHECK_FAIL(L"Not Implemented!");
}
};
}
#endif
/***********************************************************************
.\GUIUNITTESTPROTOCOL_IOCOMMANDS.H
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Unit Test Snapsnot and other Utilities
***********************************************************************/
#ifndef VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_IOCOMMANDS
#define VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_IOCOMMANDS
namespace vl::presentation::unittest
{
/***********************************************************************
UnitTestRemoteProtocol
***********************************************************************/
template<typename TProtocol>
class UnitTestRemoteProtocol_IOCommands : public TProtocol
{
protected:
Nullable<NativePoint> mousePosition;
collections::SortedList<VKEY> pressingKeys;
bool leftPressing = false;
bool middlePressing = false;
bool rightPressing = false;
bool capslockToggled = false;
IGuiRemoteProtocolEvents& UseEvents()
{
return *this->GetEvents();
}
bool IsPressing(VKEY key)
{
return pressingKeys.Contains(key);
}
NativeWindowMouseInfo MakeMouseInfo()
{
NativeWindowMouseInfo info;
info.ctrl = IsPressing(VKEY::KEY_CONTROL) || IsPressing(VKEY::KEY_LCONTROL) || IsPressing(VKEY::KEY_RCONTROL);
info.shift = IsPressing(VKEY::KEY_SHIFT) || IsPressing(VKEY::KEY_LSHIFT) || IsPressing(VKEY::KEY_RSHIFT);
info.left = leftPressing;
info.middle = middlePressing;
info.right = rightPressing;
info.x = mousePosition.Value().x.value;
info.y = mousePosition.Value().y.value;
info.wheel = 0;
info.nonClient = false;
return info;
}
NativeWindowKeyInfo MakeKeyInfo(VKEY key, bool autoRepeatKeyDown = false)
{
NativeWindowKeyInfo info;
info.code = key;
info.ctrl = IsPressing(VKEY::KEY_CONTROL) || IsPressing(VKEY::KEY_LCONTROL) || IsPressing(VKEY::KEY_RCONTROL);
info.shift = IsPressing(VKEY::KEY_SHIFT) || IsPressing(VKEY::KEY_LSHIFT) || IsPressing(VKEY::KEY_RSHIFT);
info.alt = IsPressing(VKEY::KEY_MENU) || IsPressing(VKEY::KEY_LMENU) || IsPressing(VKEY::KEY_RMENU);
info.capslock = capslockToggled;
info.autoRepeatKeyDown = autoRepeatKeyDown;
return info;
}
public:
template<typename ...TArgs>
UnitTestRemoteProtocol_IOCommands(TArgs&& ...args)
: TProtocol(std::forward<TArgs&&>(args)...)
{
}
/***********************************************************************
Helper Functions
***********************************************************************/
NativePoint LocationOf(compositions::GuiGraphicsComposition* composition, double ratioX = 0.5, double ratioY = 0.5, vint offsetX = 0, vint offsetY = 0)
{
INativeWindow* nativeWindow = composition->GetRelatedControlHost()->GetNativeWindow();
Rect bounds = composition->GetGlobalBounds();
NativeRect nativeBounds = { nativeWindow->Convert(bounds.LeftTop()),nativeWindow->Convert(bounds.GetSize()) };
vint x = nativeBounds.x1.value + (vint)(nativeBounds.Width().value * ratioX) + offsetX;
vint y = nativeBounds.y1.value + (vint)(nativeBounds.Height().value * ratioY) + offsetY;
NativePoint windowLocation = nativeWindow->GetBounds().LeftTop();
return { windowLocation.x.value + x,windowLocation.y.value + y };
}
NativePoint LocationOf(controls::GuiControl* control, double ratioX = 0.5, double ratioY = 0.5, vint offsetX = 0, vint offsetY = 0)
{
return LocationOf(control->GetBoundsComposition(), ratioX, ratioY, offsetX, offsetY);
}
#define CLASS_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_IOCommands<TProtocol>::"
/***********************************************************************
Keys
***********************************************************************/
void _KeyDown(VKEY key)
{
#define ERROR_MESSAGE_PREFIX CLASS_PREFIX L"_KeyDown(...)#"
CHECK_ERROR(!pressingKeys.Contains(key), ERROR_MESSAGE_PREFIX L"The key is already being pressed.");
pressingKeys.Add(key);
if (key == VKEY::KEY_CAPITAL)
{
capslockToggled = !capslockToggled;
}
UseEvents().OnIOKeyDown(MakeKeyInfo(key, false));
#undef ERROR_MESSAGE_PREFIX
}
void _KeyDownRepeat(VKEY key)
{
#define ERROR_MESSAGE_PREFIX CLASS_PREFIX L"_KeyDownRepeat(...)#"
CHECK_ERROR(pressingKeys.Contains(key), ERROR_MESSAGE_PREFIX L"The key is not being pressed.");
UseEvents().OnIOKeyDown(MakeKeyInfo(key, true));
#undef ERROR_MESSAGE_PREFIX
}
void _KeyUp(VKEY key)
{
#define ERROR_MESSAGE_PREFIX CLASS_PREFIX L"_KeyUp(...)#"
CHECK_ERROR(pressingKeys.Contains(key), ERROR_MESSAGE_PREFIX L"The key is not being pressed.");
pressingKeys.Remove(key);
UseEvents().OnIOKeyUp(MakeKeyInfo(key, false));
#undef ERROR_MESSAGE_PREFIX
}
void KeyPress(VKEY key)
{
_KeyDown(key);
_KeyUp(key);
}
void KeyPress(VKEY key, bool ctrl, bool shift, bool alt)
{
if (ctrl) _KeyDown(VKEY::KEY_CONTROL);
if (shift) _KeyDown(VKEY::KEY_SHIFT);
if (alt) _KeyDown(VKEY::KEY_MENU);
KeyPress(key);
if (alt) _KeyUp(VKEY::KEY_MENU);
if (shift) _KeyUp(VKEY::KEY_SHIFT);
if (ctrl) _KeyUp(VKEY::KEY_CONTROL);
}
/***********************************************************************
Mouse Move Events
***********************************************************************/
void MouseMove(NativePoint position)
{
if (!mousePosition)
{
UseEvents().OnIOMouseEntered();
goto DO_MOUSE_MOVE;
}
if (mousePosition.Value() == position) return;
DO_MOUSE_MOVE:
mousePosition = position;
UseEvents().OnIOMouseMoving(MakeMouseInfo());
}
NativePoint GetMousePosition()
{
#define ERROR_MESSAGE_PREFIX CLASS_PREFIX L"GetMousePosition()#"
CHECK_ERROR(mousePosition, CLASS_PREFIX L"The mouse position is not set.");
return mousePosition.Value();
#undef ERROR_MESSAGE_PREFIX
}
/***********************************************************************
Mouse Wheel Events
***********************************************************************/
void _Wheel(vint up, Nullable<NativePoint> position = {})
{
if (position) MouseMove(position.Value());
auto info = MakeMouseInfo();
info.wheel = up;
UseEvents().OnIOVWheel(info);
}
void _Wheel(vint up, Nullable<NativePoint> position, bool ctrl, bool shift, bool alt)
{
if (ctrl) _KeyDown(VKEY::KEY_CONTROL);
if (shift) _KeyDown(VKEY::KEY_SHIFT);
if (alt) _KeyDown(VKEY::KEY_MENU);
_Wheel(up, position);
if (alt) _KeyUp(VKEY::KEY_MENU);
if (shift) _KeyUp(VKEY::KEY_SHIFT);
if (ctrl) _KeyUp(VKEY::KEY_CONTROL);
}
void _HWheel(vint right, Nullable<NativePoint> position = {})
{
if (position) MouseMove(position.Value());
auto info = MakeMouseInfo();
info.wheel = right;
UseEvents().OnIOHWheel(info);
}
void _HWheel(vint right, Nullable<NativePoint> position, bool ctrl, bool shift, bool alt)
{
if (ctrl) _KeyDown(VKEY::KEY_CONTROL);
if (shift) _KeyDown(VKEY::KEY_SHIFT);
if (alt) _KeyDown(VKEY::KEY_MENU);
_HWheel(right, position);
if (alt) _KeyUp(VKEY::KEY_MENU);
if (shift) _KeyUp(VKEY::KEY_SHIFT);
if (ctrl) _KeyUp(VKEY::KEY_CONTROL);
}
void WheelDown(vint jumps = 1, Nullable<NativePoint> position = {})
{
_Wheel(-jumps * 120, position);
}
void WheelDown(vint jumps, Nullable<NativePoint> position, bool ctrl, bool shift, bool alt)
{
_Wheel(-jumps * 120, position, ctrl, shift, alt);
}
void WheelUp(vint jumps = 1, Nullable<NativePoint> position = {})
{
_Wheel(jumps * 120, position);
}
void WheelUp(vint jumps, Nullable<NativePoint> position, bool ctrl, bool shift, bool alt)
{
_Wheel(jumps * 120, position, ctrl, shift, alt);
}
void HWheelLeft(vint jumps = 1, Nullable<NativePoint> position = {})
{
_HWheel(-jumps * 120, position);
}
void HWheelLeft(vint jumps, Nullable<NativePoint> position, bool ctrl, bool shift, bool alt)
{
_HWheel(-jumps * 120, position, ctrl, shift, alt);
}
void HWheelRight(vint jumps = 1, Nullable<NativePoint> position = {})
{
_HWheel(jumps * 120, position);
}
void HWheelRight(vint jumps, Nullable<NativePoint> position, bool ctrl, bool shift, bool alt)
{
_HWheel(jumps * 120, position, ctrl, shift, alt);
}
/***********************************************************************
Mouse Click Events
***********************************************************************/
#define DEFINE_MOUSE_ACTIONS(PREFIX, LOWER, UPPER)\
void _ ## PREFIX ## Down(Nullable<NativePoint> position = {})\
{\
if (position) MouseMove(position.Value());\
CHECK_ERROR(!LOWER ## Pressing, CLASS_PREFIX L"_" L ## #PREFIX L"Down(...)#" L"The button should not be being pressed.");\
LOWER ## Pressing = true;\
UseEvents().OnIOButtonDown({ remoteprotocol::IOMouseButton::UPPER,MakeMouseInfo() });\
}\
void _ ## PREFIX ## Up(Nullable<NativePoint> position = {})\
{\
if (position) MouseMove(position.Value());\
CHECK_ERROR(LOWER ## Pressing, CLASS_PREFIX L"_" L ## #PREFIX L"Up(...)#" L"The button should be being pressed.");\
LOWER ## Pressing = false;\
UseEvents().OnIOButtonUp({ remoteprotocol::IOMouseButton::UPPER,MakeMouseInfo() });\
}\
void _ ## PREFIX ## DBClick(Nullable<NativePoint> position = {})\
{\
if (position) MouseMove(position.Value());\
CHECK_ERROR(!LOWER ## Pressing, CLASS_PREFIX L"_" L ## #PREFIX L"DBClick(...)#" L"The button should not be being pressed.");\
LOWER ## Pressing = true;\
UseEvents().OnIOButtonDoubleClick({ remoteprotocol::IOMouseButton::UPPER,MakeMouseInfo() });\
}\
void PREFIX ## Click(Nullable<NativePoint> position = {})\
{\
_ ## PREFIX ## Down(position);\
_ ## PREFIX ## Up(position);\
}\
void PREFIX ## Click(Nullable<NativePoint> position, bool ctrl, bool shift, bool alt)\
{\
if (ctrl) _KeyDown(VKEY::KEY_CONTROL);\
if (shift) _KeyDown(VKEY::KEY_SHIFT);\
if (alt) _KeyDown(VKEY::KEY_MENU);\
PREFIX ## Click(position);\
if (alt) _KeyUp(VKEY::KEY_MENU);\
if (shift) _KeyUp(VKEY::KEY_SHIFT);\
if (ctrl) _KeyUp(VKEY::KEY_CONTROL);\
}\
void PREFIX ## DBClick(Nullable<NativePoint> position = {})\
{\
_ ## PREFIX ## Down(position);\
_ ## PREFIX ## Up(position);\
_ ## PREFIX ## DBClick(position);\
_ ## PREFIX ## Up(position);\
}\
void PREFIX ## DBClick(Nullable<NativePoint> position, bool ctrl, bool shift, bool alt)\
{\
if (ctrl) _KeyDown(VKEY::KEY_CONTROL);\
if (shift) _KeyDown(VKEY::KEY_SHIFT);\
if (alt) _KeyDown(VKEY::KEY_MENU);\
PREFIX ## DBClick(position);\
if (alt) _KeyUp(VKEY::KEY_MENU);\
if (shift) _KeyUp(VKEY::KEY_SHIFT);\
if (ctrl) _KeyUp(VKEY::KEY_CONTROL);\
}\
DEFINE_MOUSE_ACTIONS(L, left, Left);
DEFINE_MOUSE_ACTIONS(M, middle, Middle);
DEFINE_MOUSE_ACTIONS(R, right, Right);
#undef DEFINE_MOUSE_ACTIONS
#undef CLASS_PREFIX
};
}
#endif
/***********************************************************************
.\GUIUNITTESTPROTOCOL_MAINWINDOW.H
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Unit Test Snapsnot and other Utilities
***********************************************************************/
#ifndef VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_MAINWINDOW
#define VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_MAINWINDOW
namespace vl::presentation::unittest
{
template<typename TProtocol>
class UnitTestRemoteProtocol_MainWindow : public TProtocol
{
using WindowSizingConfig = remoteprotocol::WindowSizingConfig;
using WindowShowing = remoteprotocol::WindowShowing;
public:
WindowSizingConfig sizingConfig;
WindowStyleConfig styleConfig;
NativeRect lastRestoredSize;
template<typename ...TArgs>
UnitTestRemoteProtocol_MainWindow(TArgs&& ...args)
: TProtocol(std::forward<TArgs&&>(args)...)
{
sizingConfig.bounds = { 0,0,0,0 };
sizingConfig.clientBounds = { 0,0,0,0 };
sizingConfig.customFramePadding = this->GetGlobalConfig().customFramePadding;
sizingConfig.sizeState = INativeWindow::Restored;
}
protected:
/***********************************************************************
IGuiRemoteProtocolMessages (Controller)
***********************************************************************/
void RequestControllerGetFontConfig(vint id) override
{
this->GetEvents()->RespondControllerGetFontConfig(id, this->GetGlobalConfig().fontConfig);
}
void RequestControllerGetScreenConfig(vint id) override
{
this->GetEvents()->RespondControllerGetScreenConfig(id, this->GetGlobalConfig().screenConfig);
}
/***********************************************************************
IGuiRemoteProtocolMessages (Window)
***********************************************************************/
void RequestWindowGetBounds(vint id) override
{
this->GetEvents()->RespondWindowGetBounds(id, sizingConfig);
}
void RequestWindowNotifySetTitle(const ::vl::WString& arguments) override
{
styleConfig.title = arguments;
}
void RequestWindowNotifySetEnabled(const bool& arguments) override
{
styleConfig.enabled = arguments;
}
void RequestWindowNotifySetTopMost(const bool& arguments) override
{
styleConfig.topMost = arguments;
}
void RequestWindowNotifySetShowInTaskBar(const bool& arguments) override
{
styleConfig.showInTaskBar = arguments;
}
void OnBoundsUpdated()
{
sizingConfig.clientBounds = sizingConfig.bounds;
if (sizingConfig.sizeState == INativeWindow::Restored)
{
lastRestoredSize = sizingConfig.bounds;
}
this->GetEvents()->OnWindowBoundsUpdated(sizingConfig);
}
void RequestWindowNotifySetBounds(const NativeRect& arguments) override
{
sizingConfig.bounds = arguments;
OnBoundsUpdated();
}
void RequestWindowNotifySetClientSize(const NativeSize& arguments) override
{
sizingConfig.bounds = { sizingConfig.bounds.LeftTop(), arguments };
OnBoundsUpdated();
}
void RequestWindowNotifySetCustomFrameMode(const bool& arguments) override { styleConfig.customFrameMode = arguments; this->GetEvents()->OnWindowBoundsUpdated(sizingConfig); }
void RequestWindowNotifySetMaximizedBox(const bool& arguments) override { styleConfig.maximizedBox = arguments; this->GetEvents()->OnWindowBoundsUpdated(sizingConfig); }
void RequestWindowNotifySetMinimizedBox(const bool& arguments) override { styleConfig.minimizedBox = arguments; this->GetEvents()->OnWindowBoundsUpdated(sizingConfig); }
void RequestWindowNotifySetBorder(const bool& arguments) override { styleConfig.border = arguments; this->GetEvents()->OnWindowBoundsUpdated(sizingConfig); }
void RequestWindowNotifySetSizeBox(const bool& arguments) override { styleConfig.sizeBox = arguments; this->GetEvents()->OnWindowBoundsUpdated(sizingConfig); }
void RequestWindowNotifySetIconVisible(const bool& arguments) override { styleConfig.iconVisible = arguments; this->GetEvents()->OnWindowBoundsUpdated(sizingConfig); }
void RequestWindowNotifySetTitleBar(const bool& arguments) override { styleConfig.titleBar = arguments; this->GetEvents()->OnWindowBoundsUpdated(sizingConfig); }
void RequestWindowNotifyActivate() override { styleConfig.activated = true; }
void RequestWindowNotifyShow(const WindowShowing& arguments) override
{
styleConfig.activated = arguments.activate;
if (sizingConfig.sizeState != arguments.sizeState)
{
sizingConfig.sizeState = arguments.sizeState;
switch (arguments.sizeState)
{
case INativeWindow::Maximized:
sizingConfig.bounds = this->GetGlobalConfig().screenConfig.clientBounds;
OnBoundsUpdated();
break;
case INativeWindow::Minimized:
sizingConfig.bounds = NativeRect(
{
this->GetGlobalConfig().screenConfig.bounds.x2,
this->GetGlobalConfig().screenConfig.bounds.y2
},
{ 1,1 }
);
OnBoundsUpdated();
break;
case INativeWindow::Restored:
if (sizingConfig.bounds != lastRestoredSize)
{
sizingConfig.bounds = lastRestoredSize;
OnBoundsUpdated();
}
else
{
this->GetEvents()->OnWindowBoundsUpdated(sizingConfig);
}
break;
}
}
}
};
}
#endif
/***********************************************************************
.\GUIUNITTESTPROTOCOL_RENDERING.H
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Unit Test Snapsnot and other Utilities
***********************************************************************/
#ifndef VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_RENDERING
#define VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_RENDERING
namespace vl::presentation::unittest
{
/***********************************************************************
UnitTestRemoteProtocol
***********************************************************************/
using ElementDescVariant = remoteprotocol::UnitTest_ElementDescVariant;
using UnitTestRenderingDom = remoteprotocol::RenderingDom;
struct UnitTestLoggedFrame
{
vint frameId;
collections::List<WString> renderingCommandsLog;
Nullable<remoteprotocol::RenderingDom_DiffsInOrder> renderingDiffs;
Ptr<UnitTestRenderingDom> renderingDom;
};
using UnitTestLoggedFrameList = collections::List<Ptr<UnitTestLoggedFrame>>;
template<typename TProtocol>
class UnitTestRemoteProtocol_Rendering : public TProtocol
{
using IdSet = collections::SortedList<vint>;
using Base64ToImageMetadataMap = collections::Dictionary<WString, remoteprotocol::ImageMetadata>;
using ElementDescMap = collections::Dictionary<vint, ElementDescVariant>;
using ImageMetadataMap = collections::Dictionary<vint, remoteprotocol::ImageMetadata>;
protected:
remoteprotocol::RenderingDomBuilder renderingDomBuilder;
remoteprotocol::UnitTest_RenderingTrace loggedTrace;
UnitTestLoggedFrameList loggedFrames;
bool lastRenderingCommandsOpening = false;
Ptr<remoteprotocol::RenderingDom> receivedDom;
remoteprotocol::DomIndex receivedDomIndex;
bool receivedDomDiffMessage = false;
bool receivedElementMessage = false;
ElementDescMap lastElementDescs;
IdSet removedElementIds;
IdSet removedImageIds;
Ptr<Base64ToImageMetadataMap> cachedImageMetadatas;
remoteprotocol::ElementMeasurings measuringForNextRendering;
regex::Regex regexCrLf{ L"/n|/r(/n)?" };
void ResetCreatedObjects()
{
loggedTrace.createdElements = Ptr(new collections::Dictionary<vint, remoteprotocol::RendererType>);
loggedTrace.imageCreations = Ptr(new remoteprotocol::ArrayMap<vint, remoteprotocol::ImageCreation, &remoteprotocol::ImageCreation::id>);
loggedTrace.imageMetadatas = Ptr(new remoteprotocol::ArrayMap<vint, remoteprotocol::ImageMetadata, &remoteprotocol::ImageMetadata::id>);
lastElementDescs.Clear();
}
public:
template<typename ...TArgs>
UnitTestRemoteProtocol_Rendering(TArgs&& ...args)
: TProtocol(std::forward<TArgs&&>(args)...)
{
ResetCreatedObjects();
loggedTrace.frames = Ptr(new collections::List<remoteprotocol::UnitTest_RenderingFrame>);
}
protected:
/***********************************************************************
IGuiRemoteProtocolMessages (Rendering)
***********************************************************************/
Ptr<UnitTestLoggedFrame> GetLastRenderingFrame()
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::GetLastRenderingCommands()#"
CHECK_ERROR(lastRenderingCommandsOpening, ERROR_MESSAGE_PREFIX L"The latest frame of commands is not accepting new commands.");
return loggedFrames[loggedFrames.Count() - 1];
#undef ERROR_MESSAGE_PREFIX
}
Ptr<UnitTestLoggedFrame> TryGetLastRenderingFrameAndReset()
{
if (loggedFrames.Count() == 0) return nullptr;
if (!lastRenderingCommandsOpening) return nullptr;
lastRenderingCommandsOpening = false;
return loggedFrames[loggedFrames.Count() - 1];
}
void RequestRendererBeginRendering(const remoteprotocol::ElementBeginRendering& arguments) override
{
receivedDomDiffMessage = false;
receivedElementMessage = false;
lastRenderingCommandsOpening = true;
auto frame = Ptr(new UnitTestLoggedFrame);
frame->frameId = arguments.frameId;
loggedFrames.Add(frame);
}
void RequestRendererEndRendering(vint id) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::RequestRendererEndRendering(vint)#"
CHECK_ERROR(receivedElementMessage || receivedDomDiffMessage, ERROR_MESSAGE_PREFIX L"Either dom-diff or element message should be sent before this message.");
auto lastFrame = GetLastRenderingFrame();
if (receivedElementMessage)
{
lastFrame->renderingDom = renderingDomBuilder.RequestRendererEndRendering();
}
if (receivedDomDiffMessage)
{
// receivedDom will be updated in RequestRendererRenderDomDiff
// store a copy to log
lastFrame->renderingDom = CopyDom(receivedDom);
}
this->GetEvents()->RespondRendererEndRendering(id, measuringForNextRendering);
measuringForNextRendering = {};
#undef ERROR_MESSAGE_PREFIX
}
/***********************************************************************
IGuiRemoteProtocolMessages (Rendering - Element)
***********************************************************************/
void RequestRendererBeginBoundary(const remoteprotocol::ElementBoundary& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::RequestRendererBeginBoundary(const ElementBoundary&)#"
CHECK_ERROR(!receivedDomDiffMessage, ERROR_MESSAGE_PREFIX L"This message could not be used with dom-diff messages in the same rendering cycle.");
if (!receivedElementMessage)
{
renderingDomBuilder.RequestRendererBeginRendering();
receivedElementMessage = true;
}
renderingDomBuilder.RequestRendererBeginBoundary(arguments);
glr::json::JsonFormatting formatting;
formatting.spaceAfterColon = true;
formatting.spaceAfterComma = true;
formatting.crlf = false;
formatting.compact = true;
GetLastRenderingFrame()->renderingCommandsLog.Add(L"RequestRendererBeginBoundary: " + stream::GenerateToStream([&](stream::TextWriter& writer)
{
auto jsonLog = remoteprotocol::ConvertCustomTypeToJson(arguments);
writer.WriteString(glr::json::JsonToString(jsonLog, formatting));
}));
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererEndBoundary() override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::RequestRendererEndBoundary()#"
CHECK_ERROR(!receivedDomDiffMessage, ERROR_MESSAGE_PREFIX L"This message could not be used with dom-diff messages in the same rendering cycle.");
if (!receivedElementMessage)
{
renderingDomBuilder.RequestRendererBeginRendering();
receivedElementMessage = true;
}
renderingDomBuilder.RequestRendererEndBoundary();
GetLastRenderingFrame()->renderingCommandsLog.Add(L"RequestRendererEndBoundary");
#undef ERROR_MESSAGE_PREFIX
}
void EnsureRenderedElement(vint id, Rect bounds)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::EnsureRenderedElement(id&)#"
vint index = loggedTrace.createdElements->Keys().IndexOf(id);
CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been created.");
auto rendererType = loggedTrace.createdElements->Values()[index];
if (rendererType == remoteprotocol::RendererType::FocusRectangle)
{
// FocusRectangle does not has a ElementDesc
return;
}
index = lastElementDescs.Keys().IndexOf(id);
CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been updated after created.");
lastElementDescs.Values()[index].Apply(Overloading(
[](remoteprotocol::RendererType)
{
CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been updated after created.");
},
[&](const remoteprotocol::ElementDesc_SolidLabel& solidLabel)
{
CalculateSolidLabelSizeIfNecessary(bounds.Width(), bounds.Height(), solidLabel);
},
[&](const auto& element)
{
}));
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererRenderElement(const remoteprotocol::ElementRendering& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::RequestRendererRenderElement(const ElementRendering&)#"
CHECK_ERROR(!receivedDomDiffMessage, ERROR_MESSAGE_PREFIX L"This message could not be used with dom-diff messages in the same rendering cycle.");
if (!receivedElementMessage)
{
renderingDomBuilder.RequestRendererBeginRendering();
receivedElementMessage = true;
}
{
renderingDomBuilder.RequestRendererRenderElement(arguments);
glr::json::JsonFormatting formatting;
formatting.spaceAfterColon = true;
formatting.spaceAfterComma = true;
formatting.crlf = false;
formatting.compact = true;
GetLastRenderingFrame()->renderingCommandsLog.Add(L"RequestRendererRenderElement: " + stream::GenerateToStream([&](stream::TextWriter& writer)
{
auto jsonLog = remoteprotocol::ConvertCustomTypeToJson(arguments);
writer.WriteString(glr::json::JsonToString(jsonLog, formatting));
}));
}
EnsureRenderedElement(arguments.id, arguments.bounds);
#undef ERROR_MESSAGE_PREFIX
}
/***********************************************************************
IGuiRemoteProtocolMessages (Rendering - Dom)
***********************************************************************/
void CalculateSolidLabelSizesIfNecessary(Ptr<remoteprotocol::RenderingDom> dom)
{
if (dom->content.element)
{
EnsureRenderedElement(dom->content.element.Value(), dom->content.bounds);
}
if (dom->children)
{
for (auto child : *dom->children.Obj())
{
CalculateSolidLabelSizesIfNecessary(child);
}
}
}
void RequestRendererRenderDom(const Ptr<remoteprotocol::RenderingDom>& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::RequestRendererRenderElement(const RenderingDom&)#"
CHECK_ERROR(!receivedElementMessage, ERROR_MESSAGE_PREFIX L"This message could not be used with element messages in the same rendering cycle.");
if (!receivedDomDiffMessage)
{
receivedDomDiffMessage = true;
}
receivedDom = arguments;
remoteprotocol::BuildDomIndex(receivedDom, receivedDomIndex);
CalculateSolidLabelSizesIfNecessary(receivedDom);
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererRenderDomDiff(const remoteprotocol::RenderingDom_DiffsInOrder& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::RequestRendererRenderElement(const RenderingDom_DiffsInOrder&)#"
CHECK_ERROR(!receivedElementMessage, ERROR_MESSAGE_PREFIX L"This message could not be used with element messages in the same rendering cycle.");
if (!receivedDomDiffMessage)
{
receivedDomDiffMessage = true;
}
remoteprotocol::UpdateDomInplace(receivedDom, receivedDomIndex, arguments);
GetLastRenderingFrame()->renderingDiffs = arguments;
CalculateSolidLabelSizesIfNecessary(receivedDom);
#undef ERROR_MESSAGE_PREFIX
}
/***********************************************************************
IGuiRemoteProtocolMessages (Elements)
***********************************************************************/
void RequestRendererCreated(const Ptr<collections::List<remoteprotocol::RendererCreation>>& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::RequestRendererCreated(const Ptr<List<RendererCreation>>&)#"
if (arguments)
{
for (auto creation : *arguments.Obj())
{
CHECK_ERROR(!loggedTrace.createdElements->Keys().Contains(creation.id), ERROR_MESSAGE_PREFIX L"Renderer with the specified id has been created or used.");
loggedTrace.createdElements->Add(creation.id, creation.type);
}
}
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererDestroyed(const Ptr<collections::List<vint>>& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::RequestRendererDestroyed(const Ptr<List<vint>>&)#"
if (arguments)
{
for (auto id : *arguments.Obj())
{
CHECK_ERROR(loggedTrace.createdElements->Keys().Contains(id), ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been created.");
CHECK_ERROR(!removedElementIds.Contains(id), ERROR_MESSAGE_PREFIX L"Renderer with the specified id has been destroyed.");
removedElementIds.Add(id);
lastElementDescs.Remove(id);
}
}
#undef ERROR_MESSAGE_PREFIX
}
template<remoteprotocol::RendererType RendererType, typename TElementDesc>
void RequestRendererUpdateElement(const TElementDesc& arguments, const wchar_t* emWrongId, const wchar_t* emWrongType)
{
vint index = loggedTrace.createdElements->Keys().IndexOf(arguments.id);
CHECK_ERROR(index != -1, emWrongId);
CHECK_ERROR(loggedTrace.createdElements->Values()[index] == RendererType, emWrongType);
lastElementDescs.Set(arguments.id, arguments);
}
#define REQUEST_RENDERER_UPDATE_ELEMENT2(ARGUMENTS, RENDERER_TYPE)\
RequestRendererUpdateElement<RENDERER_TYPE>(\
ARGUMENTS,\
ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been created.",\
ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type."\
)
#define REQUEST_RENDERER_UPDATE_ELEMENT(RENDERER_TYPE) REQUEST_RENDERER_UPDATE_ELEMENT2(arguments, RENDERER_TYPE)
void RequestRendererUpdateElement_SolidBorder(const remoteprotocol::ElementDesc_SolidBorder& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::RequestRendererUpdateElement_SolidBorder<TProtocol>::RequestRendererCreated(const ElementDesc_SolidBorder&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(remoteprotocol::RendererType::SolidBorder);
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererUpdateElement_SinkBorder(const remoteprotocol::ElementDesc_SinkBorder& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::RequestRendererUpdateElement_SinkBorder<TProtocol>::RequestRendererCreated(const ElementDesc_SinkBorder&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(remoteprotocol::RendererType::SinkBorder);
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererUpdateElement_SinkSplitter(const remoteprotocol::ElementDesc_SinkSplitter& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::RequestRendererUpdateElement_SinkSplitter<TProtocol>::RequestRendererCreated(const ElementDesc_SinkSplitter&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(remoteprotocol::RendererType::SinkSplitter);
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererUpdateElement_SolidBackground(const remoteprotocol::ElementDesc_SolidBackground& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::RequestRendererUpdateElement_SolidBackground<TProtocol>::RequestRendererCreated(const ElementDesc_SolidBackground&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(remoteprotocol::RendererType::SolidBackground);
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererUpdateElement_GradientBackground(const remoteprotocol::ElementDesc_GradientBackground& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::RequestRendererUpdateElement_GradientBackground<TProtocol>::RequestRendererCreated(const ElementDesc_GradientBackground&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(remoteprotocol::RendererType::GradientBackground);
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererUpdateElement_InnerShadow(const remoteprotocol::ElementDesc_InnerShadow& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::RequestRendererUpdateElement_InnerShadow<TProtocol>::RequestRendererCreated(const ElementDesc_InnerShadow&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(remoteprotocol::RendererType::InnerShadow);
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererUpdateElement_Polygon(const remoteprotocol::ElementDesc_Polygon& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::RequestRendererUpdateElement_Polygon<TProtocol>::RequestRendererCreated(const ElementDesc_Polygon&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(remoteprotocol::RendererType::Polygon);
#undef ERROR_MESSAGE_PREFIX
}
/***********************************************************************
IGuiRemoteProtocolMessages (Elements - SolidLabel)
***********************************************************************/
void CalculateSolidLabelSizeIfNecessary(vint width, vint height, const remoteprotocol::ElementDesc_SolidLabel& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::RequestRendererUpdateElement_SolidLabel<TProtocol>::CalculateSolidLabelSizeIfNecessary(vint, vint, const ElementDesc_SolidLabel&)#"
if (arguments.measuringRequest)
{
switch (arguments.measuringRequest.Value())
{
case remoteprotocol::ElementSolidLabelMeasuringRequest::FontHeight:
CHECK_ERROR(arguments.font, ERROR_MESSAGE_PREFIX L"Font is missing for calculating font height.");
if (!measuringForNextRendering.fontHeights)
{
measuringForNextRendering.fontHeights = Ptr(new collections::List<remoteprotocol::ElementMeasuring_FontHeight>);
}
{
remoteprotocol::ElementMeasuring_FontHeight measuring;
measuring.fontFamily = arguments.font.Value().fontFamily;
measuring.fontSize = arguments.font.Value().size;
measuring.height = measuring.fontSize + 4;
measuringForNextRendering.fontHeights->Add(measuring);
}
break;
case remoteprotocol::ElementSolidLabelMeasuringRequest::TotalSize:
{
// font and text has already been verified exist in RequestRendererUpdateElement_SolidLabel
vint size = arguments.font.Value().size;
auto text = arguments.text.Value();
vint textWidth = 0;
vint textHeight = 0;
collections::List<vint> lines;
{
collections::List<Ptr<regex::RegexMatch>> matches;
regexCrLf.Split(text, true, matches);
if (matches.Count() == 0)
{
// when there is no text, measure a space
lines.Add(1);
}
else
{
auto normalizedLines =
From(matches)
.Select([](auto&& match) { return match->Result().Length(); })
.Select([](vint length) { return length ? length : 1; })
;
if (arguments.multiline)
{
// calculate text as multiple lines
CopyFrom(
lines,
normalizedLines
);
}
else
{
// calculate text as single line, insert a space between each line
lines.Add(
normalizedLines
.template Aggregate<vint>(-1, [](auto a, auto b) { return a + b + 1; })
);
}
}
}
if (arguments.wrapLine)
{
// width of the text is 0
// insert a line break when there is no space horizontally
textHeight = 4 + size * From(lines)
.Select([columns = width / size](vint length)
{
if (columns == 0)
{
return length;
}
else
{
return (length + columns - 1) / columns;
}
})
.template Aggregate<vint>(0, [](auto a, auto b) { return a + b; });
}
else
{
// width of the text is width of the longest line
textWidth = size * From(lines).Max();
textHeight = 4 + size * lines.Count();
}
if (!measuringForNextRendering.minSizes)
{
measuringForNextRendering.minSizes = Ptr(new collections::List<remoteprotocol::ElementMeasuring_ElementMinSize>);
}
{
remoteprotocol::ElementMeasuring_ElementMinSize measuring;
measuring.id = arguments.id;
measuring.minSize = { textWidth,textHeight };
measuringForNextRendering.minSizes->Add(measuring);
}
}
break;
default:
CHECK_FAIL(L"Unknown value of ElementSolidLabelMeasuringRequest.");
}
}
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererUpdateElement_SolidLabel(const remoteprotocol::ElementDesc_SolidLabel& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::RequestRendererUpdateElement_SolidLabel<TProtocol>::RequestRendererCreated(const ElementDesc_SolidLabel&)#"
auto element = arguments;
if (!element.font || !element.text)
{
vint index = loggedTrace.createdElements->Keys().IndexOf(element.id);
CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been created.");
auto rendererType = loggedTrace.createdElements->Values()[index];
CHECK_ERROR(rendererType == remoteprotocol::RendererType::SolidLabel, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type.");
index = lastElementDescs.Keys().IndexOf(arguments.id);
if (index != -1)
{
auto solidLabel = lastElementDescs.Values()[index].TryGet<remoteprotocol::ElementDesc_SolidLabel>();
CHECK_ERROR(solidLabel, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type.");
if (!element.font) element.font = solidLabel->font;
if (!element.text) element.text = solidLabel->text;
}
else
{
if (!element.font) element.font = FontProperties();
if (!element.text) element.text = WString::Empty;
}
}
REQUEST_RENDERER_UPDATE_ELEMENT2(element, remoteprotocol::RendererType::SolidLabel);
#undef ERROR_MESSAGE_PREFIX
}
/***********************************************************************
IGuiRemoteProtocolMessages (Elements - Image)
***********************************************************************/
WString GetBinaryKeyFromBinary(stream::IStream& binary)
{
stream::MemoryStream base64WStringStream;
{
stream::UtfGeneralEncoder<wchar_t, char8_t> utf8ToWCharEncoder;
stream::EncoderStream utf8ToWCharStream(base64WStringStream, utf8ToWCharEncoder);
stream::Utf8Base64Encoder binaryToBase64Utf8Encoder;
stream::EncoderStream binaryToBase64Utf8Stream(utf8ToWCharStream, binaryToBase64Utf8Encoder);
binary.SeekFromBegin(0);
stream::CopyStream(binary, binaryToBase64Utf8Stream);
}
{
base64WStringStream.SeekFromBegin(0);
stream::StreamReader reader(base64WStringStream);
return reader.ReadToEnd();
}
}
WString GetBinaryKeyFromImage(Ptr<INativeImage> image)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::GetBinaryKeyFromImage(Ptr<INativeImage>)#"
auto remoteImage = image.Cast<GuiRemoteGraphicsImage>();
CHECK_ERROR(remoteImage, ERROR_MESSAGE_PREFIX L"The image object must be GuiRemoteGraphicsImage.");
return GetBinaryKeyFromBinary(remoteImage->GetBinaryData());
#undef ERROR_MESSAGE_PREFIX
}
remoteprotocol::ImageMetadata MakeImageMetadata(const remoteprotocol::ImageCreation& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::MakeImageMetadata(const remoteprotocol::ImageCreation)#"
if (!cachedImageMetadatas)
{
cachedImageMetadatas = Ptr(new Base64ToImageMetadataMap);
for (auto resource : GetResourceManager()->GetLoadedResources())
{
if (auto xmlImageData = resource->GetValueByPath(WString::Unmanaged(L"UnitTestConfig/ImageData")).Cast<glr::xml::XmlDocument>())
{
for (auto elementImage : glr::xml::XmlGetElements(xmlImageData->rootElement, WString::Unmanaged(L"Image")))
{
WString path, format, frames = WString::Unmanaged(L"1"), width, height;
auto attPath = glr::xml::XmlGetAttribute(elementImage.Obj(), WString::Unmanaged(L"Path"));
auto attFormat = glr::xml::XmlGetAttribute(elementImage.Obj(), WString::Unmanaged(L"Format"));
auto attFrames = glr::xml::XmlGetAttribute(elementImage.Obj(), WString::Unmanaged(L"Frames"));
auto attWidth = glr::xml::XmlGetAttribute(elementImage.Obj(), WString::Unmanaged(L"Width"));
auto attHeight = glr::xml::XmlGetAttribute(elementImage.Obj(), WString::Unmanaged(L"Height"));
CHECK_ERROR(attPath, ERROR_MESSAGE_PREFIX L"Missing Path attribute in Image element in an UnitTestConfig/ImageData.");
CHECK_ERROR(attFormat, ERROR_MESSAGE_PREFIX L"Missing Format attribute in Image element in an UnitTestConfig/ImageData.");
CHECK_ERROR(attWidth, ERROR_MESSAGE_PREFIX L"Missing Width attribute in Image element in an UnitTestConfig/ImageData.");
CHECK_ERROR(attHeight, ERROR_MESSAGE_PREFIX L"Missing Height attribute in Image element in an UnitTestConfig/ImageData.");
path = attPath->value.value;
format = attFormat->value.value;
width = attWidth->value.value;
height = attHeight->value.value;
if (attFrames) frames = attFrames->value.value;
vint valueFrames = wtoi(frames);
vint valueWidth = wtoi(width);
vint valueHeight = wtoi(height);
CHECK_ERROR(itow(valueFrames) == frames, ERROR_MESSAGE_PREFIX L"Frames attribute must be an integer in Image element in an UnitTestConfig/ImageData.");
CHECK_ERROR(itow(valueWidth) == width, ERROR_MESSAGE_PREFIX L"Width attribute must be an integer in Image element in an UnitTestConfig/ImageData.");
CHECK_ERROR(itow(valueHeight) == height, ERROR_MESSAGE_PREFIX L"Height attribute must be an integer in Image element in an UnitTestConfig/ImageData.");
auto imageData = resource->GetImageByPath(path);
WString binaryKey = GetBinaryKeyFromImage(imageData->GetImage());
if (!cachedImageMetadatas->Keys().Contains(binaryKey))
{
remoteprotocol::ImageMetadata imageMetadata;
imageMetadata.id = -1;
imageMetadata.frames = Ptr(new collections::List<remoteprotocol::ImageFrameMetadata>);
{
auto node = Ptr(new glr::json::JsonString);
node->content.value = format;
remoteprotocol::ConvertJsonToCustomType(node, imageMetadata.format);
}
for (vint frame = 0; frame < valueFrames; frame++)
{
imageMetadata.frames->Add({ {valueWidth,valueHeight} });
}
cachedImageMetadatas->Add(binaryKey, imageMetadata);
}
}
}
}
}
auto binaryKey = GetBinaryKeyFromBinary(*arguments.imageData.Obj());
vint binaryIndex = cachedImageMetadatas->Keys().IndexOf(binaryKey);
CHECK_ERROR(binaryIndex != -1, ERROR_MESSAGE_PREFIX L"The image is not registered in any UnitTestConfig/ImageData.");
auto metadata = cachedImageMetadatas->Values()[binaryIndex];
metadata.id = arguments.id;
loggedTrace.imageCreations->Add(arguments);
loggedTrace.imageMetadatas->Add(metadata);
return metadata;
#undef ERROR_MESSAGE_PREFIX
}
void RequestImageCreated(vint id, const remoteprotocol::ImageCreation& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::RequestImageCreated(vint, const vint&)#"
CHECK_ERROR(!loggedTrace.imageMetadatas->Keys().Contains(arguments.id), ERROR_MESSAGE_PREFIX L"Image with the specified id has been created or used.");
this->GetEvents()->RespondImageCreated(id, MakeImageMetadata(arguments));
#undef ERROR_MESSAGE_PREFIX
}
void RequestImageDestroyed(const vint& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering<TProtocol>::RequestImageDestroyed(const vint&)#"
CHECK_ERROR(loggedTrace.imageMetadatas->Keys().Contains(arguments), ERROR_MESSAGE_PREFIX L"Image with the specified id has not been created.");
CHECK_ERROR(!removedImageIds.Contains(arguments), ERROR_MESSAGE_PREFIX L"Image with the specified id has been destroyed.");
removedImageIds.Add(arguments);
#undef ERROR_MESSAGE_PREFIX
}
void RequestRendererUpdateElement_ImageFrame(const remoteprotocol::ElementDesc_ImageFrame& arguments) override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::RequestRendererUpdateElement_ImageFrame<TProtocol>::RequestRendererCreated(const ElementDesc_ImageFrame&)#"
if (arguments.imageCreation)
{
auto&& imageCreation = arguments.imageCreation.Value();
if (!imageCreation.imageDataOmitted)
{
CHECK_ERROR(arguments.imageId && arguments.imageId.Value() != !imageCreation.id, ERROR_MESSAGE_PREFIX L"It should satisfy that (arguments.imageId.Value()id == imageCreation.id).");
CHECK_ERROR(!loggedTrace.imageMetadatas->Keys().Contains(imageCreation.id), ERROR_MESSAGE_PREFIX L"Image with the specified id has been created.");
CHECK_ERROR(imageCreation.imageData, ERROR_MESSAGE_PREFIX L"When imageDataOmitted == false, imageData should not be null.");
if (!measuringForNextRendering.createdImages)
{
measuringForNextRendering.createdImages = Ptr(new collections::List<remoteprotocol::ImageMetadata>);
}
measuringForNextRendering.createdImages->Add(MakeImageMetadata(imageCreation));
}
else
{
CHECK_ERROR(!imageCreation.imageData, ERROR_MESSAGE_PREFIX L"When imageDataOmitted == true, imageData should be null.");
}
}
else if (arguments.imageId)
{
CHECK_ERROR(loggedTrace.imageMetadatas->Keys().Contains(arguments.imageId.Value()), ERROR_MESSAGE_PREFIX L"Image with the specified id has not been created.");
}
auto element = arguments;
element.imageCreation.Reset();
REQUEST_RENDERER_UPDATE_ELEMENT2(element, remoteprotocol::RendererType::ImageFrame);
#undef ERROR_MESSAGE_PREFIX
}
#undef REQUEST_RENDERER_UPDATE_ELEMENT
#undef REQUEST_RENDERER_UPDATE_ELEMENT2
};
}
#endif
/***********************************************************************
.\GUIUNITTESTPROTOCOL_LOGGING.H
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Unit Test Snapsnot and other Utilities
***********************************************************************/
#ifndef VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_LOGGING
#define VCZH_PRESENTATION_GUIUNITTESTPROTOCOL_LOGGING
namespace vl::presentation::unittest
{
/***********************************************************************
UnitTestRemoteProtocol
***********************************************************************/
template<typename TProtocol>
class UnitTestRemoteProtocol_Logging : public TProtocol
{
protected:
bool everRendered = false;
Ptr<UnitTestLoggedFrame> candidateFrame;
bool LogRenderingResult()
{
if (auto lastFrame = this->TryGetLastRenderingFrameAndReset())
{
candidateFrame = lastFrame;
everRendered = true;
}
else if (everRendered)
{
if (candidateFrame)
{
auto descs = Ptr(new collections::Dictionary<vint, ElementDescVariant>);
CopyFrom(*descs.Obj(), this->lastElementDescs);
this->loggedTrace.frames->Add({
candidateFrame->frameId,
{},
this->sizingConfig,
descs,
candidateFrame->renderingDom
});
candidateFrame = {};
return true;
}
}
return false;
}
public:
template<typename ...TArgs>
UnitTestRemoteProtocol_Logging(TArgs&& ...args)
: TProtocol(std::forward<TArgs&&>(args)...)
{
}
const auto& GetLoggedTrace()
{
return this->loggedTrace;
}
const auto& GetLoggedFrames()
{
return this->loggedFrames;
}
};
}
#endif
/***********************************************************************
.\GUIUNITTESTPROTOCOL.H
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Unit Test Snapsnot and other Utilities
***********************************************************************/
#ifndef VCZH_PRESENTATION_GUIUNITTESTPROTOCOL
#define VCZH_PRESENTATION_GUIUNITTESTPROTOCOL
namespace vl::presentation::unittest
{
/***********************************************************************
UnitTestFrameworkConfig
***********************************************************************/
struct UnitTestFrameworkConfig
{
filesystem::FilePath snapshotFolder;
filesystem::FilePath resourceFolder;
};
extern const UnitTestFrameworkConfig& GetUnitTestFrameworkConfig();
/***********************************************************************
UnitTestRemoteProtocol
***********************************************************************/
template<typename TBase, template<typename> class ...TMixins>
struct Mixin;
template<typename TBase>
struct Mixin<TBase>
{
using Type = TBase;
};
template<typename TBase, template<typename> class TMixin, template<typename> class ...TOtherMixins>
struct Mixin<TBase, TMixin, TOtherMixins...>
{
using Type = typename Mixin<TMixin<TBase>, TOtherMixins...>::Type;
};
using UnitTestRemoteProtocolFeatures = Mixin<
UnitTestRemoteProtocolBase,
UnitTestRemoteProtocol_MainWindow,
UnitTestRemoteProtocol_IO,
UnitTestRemoteProtocol_Rendering,
UnitTestRemoteProtocol_Logging,
UnitTestRemoteProtocol_IOCommands
>::Type;
class UnitTestRemoteProtocol : public UnitTestRemoteProtocolFeatures
{
using EventPair = collections::Pair<Nullable<WString>, Func<void()>>;
protected:
const UnitTestFrameworkConfig& frameworkConfig;
WString appName;
collections::List<EventPair> processRemoteEvents;
vint lastFrameIndex = -1;
vint nextEventIndex = 0;
bool stopped = false;
public:
UnitTestRemoteProtocol(const WString& _appName, UnitTestScreenConfig _globalConfig)
: UnitTestRemoteProtocolFeatures(_globalConfig)
, frameworkConfig(GetUnitTestFrameworkConfig())
, appName(_appName)
{
}
template<typename TCallback>
void OnNextIdleFrame(TCallback&& callback)
{
processRemoteEvents.Add({ Nullable<WString>{},std::forward<TCallback&&>(callback) });
}
template<typename TCallback>
void OnNextIdleFrame(const WString& name, TCallback&& callback)
{
processRemoteEvents.Add({ name,std::forward<TCallback&&>(callback) });
}
protected:
/***********************************************************************
IGuiRemoteProtocolMessages (Initialization)
***********************************************************************/
void RequestControllerConnectionEstablished() override
{
ResetCreatedObjects();
}
void RequestControllerConnectionStopped() override
{
stopped = true;
}
/***********************************************************************
IGuiRemoteProtocol
***********************************************************************/
void Submit(bool& disconnected) override
{
// TODO: Failure injection to disconnected
}
void ProcessRemoteEvents() override
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol::ProcessRemoteEvents()#"
if (!stopped)
{
if (LogRenderingResult())
{
auto [name, func] = processRemoteEvents[nextEventIndex];
vl::unittest::UnitTest::PrintMessage(L"Execute idle frame[" + (name ? name.Value() : itow(nextEventIndex)) + L"]", vl::unittest::UnitTest::MessageKind::Info);
CHECK_ERROR(lastFrameIndex != loggedTrace.frames->Count() - 1, ERROR_MESSAGE_PREFIX L"No rendering occured after the last idle frame.");
lastFrameIndex = loggedTrace.frames->Count() - 1;
if (name)
{
auto&& lastFrame = (*loggedTrace.frames.Obj())[loggedTrace.frames->Count() - 1];
lastFrame.frameName = name;
}
func();
nextEventIndex++;
}
}
#undef ERROR_MESSAGE_PREFIX
}
};
}
#endif
/***********************************************************************
.\GUIUNITTESTUTILITIES.H
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Unit Test Snapsnot and other Utilities
***********************************************************************/
#ifndef VCZH_PRESENTATION_GUIUNITTESTUTILITIES
#define VCZH_PRESENTATION_GUIUNITTESTUTILITIES
namespace vl::presentation::unittest
{
class IUnitTestContext : public virtual Interface
{
public:
};
using UnitTestMainFunc = vl::Func<void(UnitTestRemoteProtocol*, IUnitTestContext*)>;
using UnitTestLinkFunc = vl::Func<void(UnitTestRemoteProtocol*, IUnitTestContext*, const UnitTestMainFunc&)>;
}
extern void GacUIUnitTest_Initialize(const vl::presentation::unittest::UnitTestFrameworkConfig* config);
extern void GacUIUnitTest_Finalize();
extern void GacUIUnitTest_SetGuiMainProxy(const vl::presentation::unittest::UnitTestMainFunc& proxy);
extern void GacUIUnitTest_LinkGuiMainProxy(const vl::presentation::unittest::UnitTestLinkFunc& proxy);
extern void GacUIUnitTest_Start(const vl::WString& appName, vl::Nullable<vl::presentation::unittest::UnitTestScreenConfig> config = {});
extern void GacUIUnitTest_StartAsync(const vl::WString& appName, vl::Nullable<vl::presentation::unittest::UnitTestScreenConfig> config = {});
extern void GacUIUnitTest_Start_WithResourceAsText(const vl::WString& appName, vl::Nullable<vl::presentation::unittest::UnitTestScreenConfig> config, const vl::WString& resourceText);
extern vl::Ptr<vl::presentation::GuiResource> GacUIUnitTest_CompileAndLoad(const vl::WString& xmlResource);
#ifdef VCZH_DESCRIPTABLEOBJECT_WITH_METADATA
template<typename TTheme>
void GacUIUnitTest_StartFast_WithResourceAsText(
const vl::WString& appName,
const vl::WString& windowTypeFullName,
const vl::WString& resourceText, vl::Func<void(vl::presentation::controls::GuiWindow*)> installWindow,
vl::Nullable<vl::presentation::unittest::UnitTestScreenConfig> config
)
{
GacUIUnitTest_LinkGuiMainProxy([=](
vl::presentation::unittest::UnitTestRemoteProtocol* protocol,
vl::presentation::unittest::IUnitTestContext* context,
const vl::presentation::unittest::UnitTestMainFunc& previousMainProxy
)
{
protocol->GetEvents()->OnControllerConnect();
auto theme = vl::Ptr(new TTheme);
vl::presentation::theme::RegisterTheme(theme);
{
auto windowValue = vl::reflection::description::Value::Create(windowTypeFullName);
TEST_ASSERT(windowValue.GetRawPtr());
auto window = vl::Ptr(windowValue.GetRawPtr()->SafeAggregationCast<vl::presentation::controls::GuiWindow>());
TEST_ASSERT(window);
if (installWindow)
{
installWindow(window.Obj());
}
window->MoveToScreenCenter();
previousMainProxy(protocol, context);
vl::presentation::controls::GetApplication()->Run(window.Obj());
}
vl::presentation::theme::UnregisterTheme(theme->Name);
});
GacUIUnitTest_Start_WithResourceAsText(appName, config, resourceText);
}
template<typename TTheme>
void GacUIUnitTest_StartFast_WithResourceAsText(
const vl::WString& appName,
const vl::WString& windowTypeFullName,
const vl::WString& resourceText,
vl::Nullable<vl::presentation::unittest::UnitTestScreenConfig> config
)
{
GacUIUnitTest_StartFast_WithResourceAsText<TTheme>(appName, windowTypeFullName, resourceText, {}, config);
}
template<typename TTheme>
void GacUIUnitTest_StartFast_WithResourceAsText(
const vl::WString& appName,
const vl::WString& windowTypeFullName,
const vl::WString& resourceText,
vl::Func<void(vl::presentation::controls::GuiWindow*)> installWindow
)
{
GacUIUnitTest_StartFast_WithResourceAsText<TTheme>(appName, windowTypeFullName, resourceText, installWindow, {});
}
template<typename TTheme>
void GacUIUnitTest_StartFast_WithResourceAsText(
const vl::WString& appName,
const vl::WString& windowTypeFullName,
const vl::WString& resourceText
)
{
GacUIUnitTest_StartFast_WithResourceAsText<TTheme>(appName, windowTypeFullName, resourceText, {}, {});
}
#endif
#endif