/*********************************************************************** 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 class UnitTestRemoteProtocol_IO : public TProtocol { using GlobalShortcutKey = remoteprotocol::GlobalShortcutKey; using GlobalShortcutKeyList = collections::List; public: bool capturing = false; GlobalShortcutKeyList globalShortcutKeys; template UnitTestRemoteProtocol_IO(TArgs&& ...args) : TProtocol(std::forward(args)...) { } protected: /*********************************************************************** IGuiRemoteProtocolMessages (IO) ***********************************************************************/ void RequestIOUpdateGlobalShortcutKey(const Ptr& 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 class UnitTestRemoteProtocol_IOCommands : public TProtocol { protected: Nullable mousePosition; collections::SortedList 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 UnitTestRemoteProtocol_IOCommands(TArgs&& ...args) : TProtocol(std::forward(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::" /*********************************************************************** 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 position = {}) { if (position) MouseMove(position.Value()); auto info = MakeMouseInfo(); info.wheel = up; UseEvents().OnIOVWheel(info); } void _Wheel(vint up, Nullable 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 position = {}) { if (position) MouseMove(position.Value()); auto info = MakeMouseInfo(); info.wheel = right; UseEvents().OnIOHWheel(info); } void _HWheel(vint right, Nullable 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 position = {}) { _Wheel(-jumps * 120, position); } void WheelDown(vint jumps, Nullable position, bool ctrl, bool shift, bool alt) { _Wheel(-jumps * 120, position, ctrl, shift, alt); } void WheelUp(vint jumps = 1, Nullable position = {}) { _Wheel(jumps * 120, position); } void WheelUp(vint jumps, Nullable position, bool ctrl, bool shift, bool alt) { _Wheel(jumps * 120, position, ctrl, shift, alt); } void HWheelLeft(vint jumps = 1, Nullable position = {}) { _HWheel(-jumps * 120, position); } void HWheelLeft(vint jumps, Nullable position, bool ctrl, bool shift, bool alt) { _HWheel(-jumps * 120, position, ctrl, shift, alt); } void HWheelRight(vint jumps = 1, Nullable position = {}) { _HWheel(jumps * 120, position); } void HWheelRight(vint jumps, Nullable 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 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 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 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 position = {})\ {\ _ ## PREFIX ## Down(position);\ _ ## PREFIX ## Up(position);\ }\ void PREFIX ## Click(Nullable 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 position = {})\ {\ _ ## PREFIX ## Down(position);\ _ ## PREFIX ## Up(position);\ _ ## PREFIX ## DBClick(position);\ _ ## PREFIX ## Up(position);\ }\ void PREFIX ## DBClick(Nullable 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 class UnitTestRemoteProtocol_MainWindow : public TProtocol { using WindowSizingConfig = remoteprotocol::WindowSizingConfig; using WindowShowing = remoteprotocol::WindowShowing; public: WindowSizingConfig sizingConfig; WindowStyleConfig styleConfig; NativeRect lastRestoredSize; template UnitTestRemoteProtocol_MainWindow(TArgs&& ...args) : TProtocol(std::forward(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 RequestWindowNotifyMinSize(const NativeSize& arguments) override {} 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 renderingCommandsLog; Nullable renderingDiffs; Ptr renderingDom; }; using UnitTestLoggedFrameList = collections::List>; template class UnitTestRemoteProtocol_Rendering : public TProtocol { using IdSet = collections::SortedList; using Base64ToImageMetadataMap = collections::Dictionary; using ElementDescMap = collections::Dictionary; using ImageMetadataMap = collections::Dictionary; protected: remoteprotocol::RenderingDomBuilder renderingDomBuilder; remoteprotocol::UnitTest_RenderingTrace loggedTrace; UnitTestLoggedFrameList loggedFrames; bool lastRenderingCommandsOpening = false; Ptr receivedDom; remoteprotocol::DomIndex receivedDomIndex; bool receivedDomDiffMessage = false; bool receivedElementMessage = false; ElementDescMap lastElementDescs; IdSet removedElementIds; IdSet removedImageIds; Ptr cachedImageMetadatas; remoteprotocol::ElementMeasurings measuringForNextRendering; regex::Regex regexCrLf{ L"/n|/r(/n)?" }; void ResetCreatedObjects() { loggedTrace.createdElements = Ptr(new collections::Dictionary); loggedTrace.imageCreations = Ptr(new remoteprotocol::ArrayMap); loggedTrace.imageMetadatas = Ptr(new remoteprotocol::ArrayMap); lastElementDescs.Clear(); } public: template UnitTestRemoteProtocol_Rendering(TArgs&& ...args) : TProtocol(std::forward(args)...) { ResetCreatedObjects(); loggedTrace.frames = Ptr(new collections::List); } protected: /*********************************************************************** IGuiRemoteProtocolMessages (Rendering) ***********************************************************************/ Ptr GetLastRenderingFrame() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::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 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::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::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::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::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 || rendererType == remoteprotocol::RendererType::Raw) { // FocusRectangle or Raw does not have 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::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 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& arguments) override { #define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::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::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>& arguments) override { #define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::RequestRendererCreated(const Ptr>&)#" 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>& arguments) override { #define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::RequestRendererDestroyed(const Ptr>&)#" 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 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(\ 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::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::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::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::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::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::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::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::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 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 lines; { collections::List> matches; regexCrLf.Split(text, true, matches); if (matches.Count() == 0) { // when there is no text, measure a space lines.Add(WString::Unmanaged(L" ")); } else if (arguments.multiline) { // add all lines, and if any line is empty, measure a space for (auto match : matches) { auto line = match->Result().Value(); lines.Add(line.Length() ? line : WString::Unmanaged(L" ")); } } else { lines.Add(stream::GenerateToStream([&](stream::TextWriter& writer) { for (auto [match, index] : indexed(matches)) { if (index > 0) writer.WriteChar(L' '); auto line = match->Result().Value(); writer.WriteString(line); } })); if(lines[0].Length() == 0) { // when there is no text, measure a space lines[0] = WString::Unmanaged(L" "); } } } if (arguments.wrapLine) { // width of the text is 0 // insert a line break when there is no space horizontally vint totalLines = 0; for (auto&& line : lines) { if (line.Length() == 0) { totalLines++; continue; } double accumulatedWidth = 0; for (vint i = 0; i < line.Length(); i++) { auto c = line[i]; auto w = (c < 128 ? 0.6 : 1) * size; if (accumulatedWidth + w > width) { if (accumulatedWidth == 0) { totalLines++; } else { totalLines++; accumulatedWidth = w; } } else { accumulatedWidth += w; } } if (accumulatedWidth > 0) { totalLines++; } } textHeight = 4 + size * totalLines; } else { // width of the text is width of the longest line textWidth = (vint)(size * From(lines) .Select([](const WString& line) { double sum = 0; for (vint i = 0; i < line.Length(); i++) { auto c = line[i]; sum += (c < 128 ? 0.6 : 1); } return sum; }) .Max()); textHeight = 4 + size * lines.Count(); } if (!measuringForNextRendering.minSizes) { measuringForNextRendering.minSizes = Ptr(new collections::List); } { 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::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(); 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 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 image) { #define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::GetBinaryKeyFromImage(Ptr)#" auto remoteImage = image.Cast(); 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::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()) { 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); { 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::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::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::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); } 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 class UnitTestRemoteProtocol_Logging : public TProtocol { protected: bool everRendered = false; Ptr candidateFrame; vint idlingCounter = 0; bool LogRenderingResult() { #define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Logging::ProcessRemoteEvents()#" if (auto lastFrame = this->TryGetLastRenderingFrameAndReset()) { candidateFrame = lastFrame; everRendered = true; } else if (everRendered) { if (candidateFrame) { idlingCounter = 0; auto descs = Ptr(new collections::Dictionary); CopyFrom(*descs.Obj(), this->lastElementDescs); this->loggedTrace.frames->Add({ candidateFrame->frameId, {}, this->sizingConfig, descs, candidateFrame->renderingDom }); candidateFrame = {}; return true; } else { idlingCounter++; if (idlingCounter == 100) { CHECK_ERROR( idlingCounter < 100, ERROR_MESSAGE_PREFIX L"The last frame didn't trigger UI updating. The action registered by OnNextIdleFrame should always make any element or layout to change." ); } } } return false; #undef ERROR_MESSAGE_PREFIX } public: template UnitTestRemoteProtocol_Logging(TArgs&& ...args) : TProtocol(std::forward(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 class ...TMixins> struct Mixin; template struct Mixin { using Type = TBase; }; template class TMixin, template class ...TOtherMixins> struct Mixin { using Type = typename Mixin, TOtherMixins...>::Type; }; using UnitTestRemoteProtocolFeatures = Mixin< UnitTestRemoteProtocolBase, UnitTestRemoteProtocol_MainWindow, UnitTestRemoteProtocol_IO, UnitTestRemoteProtocol_Rendering, UnitTestRemoteProtocol_Logging, UnitTestRemoteProtocol_IOCommands >::Type; class UnitTestRemoteProtocol : public UnitTestRemoteProtocolFeatures , protected virtual IGuiRemoteEventProcessor { using EventPair = collections::Pair, Func>; protected: const UnitTestFrameworkConfig& frameworkConfig; WString appName; collections::List processRemoteEvents; vint lastFrameIndex = -1; vint nextEventIndex = 0; bool stopped = false; public: UnitTestRemoteProtocol(const WString& _appName, UnitTestScreenConfig _globalConfig) : UnitTestRemoteProtocolFeatures(_globalConfig) , frameworkConfig(GetUnitTestFrameworkConfig()) , appName(_appName) { } template void OnNextIdleFrame(TCallback&& callback) { processRemoteEvents.Add({ Nullable{},std::forward(callback) }); } template void OnNextIdleFrame(const WString& name, TCallback&& callback) { processRemoteEvents.Add({ name,std::forward(callback) }); } protected: /*********************************************************************** IGuiRemoteProtocolMessages (Initialization) ***********************************************************************/ void RequestControllerConnectionEstablished() override { ResetCreatedObjects(); } void RequestControllerConnectionStopped() override { stopped = true; } /*********************************************************************** IGuiRemoteProtocol ***********************************************************************/ void Submit(bool& disconnected) override { // TODO: Failure injection to disconnected } protected: bool frameExecuting = false; void ProcessRemoteEvents() override { #define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol::ProcessRemoteEvents()#" if (!stopped) { if (LogRenderingResult()) { auto [name, func] = processRemoteEvents[nextEventIndex]; CHECK_ERROR(!frameExecuting, ERROR_MESSAGE_PREFIX L"The action registered by OnNextIdleFrame should not call any blocking function, consider using InvokeInMainThread."); 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; } frameExecuting = true; func(); frameExecuting = false; nextEventIndex++; } } #undef ERROR_MESSAGE_PREFIX } public: IGuiRemoteEventProcessor* GetRemoteEventProcessor() override { return this; } }; } #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; using UnitTestLinkFunc = vl::Func; } 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 config = {}); extern void GacUIUnitTest_StartAsync(const vl::WString& appName, vl::Nullable config = {}); extern void GacUIUnitTest_Start_WithResourceAsText(const vl::WString& appName, vl::Nullable config, const vl::WString& resourceText); extern vl::Ptr GacUIUnitTest_CompileAndLoad(const vl::WString& xmlResource); #ifdef VCZH_DESCRIPTABLEOBJECT_WITH_METADATA template void GacUIUnitTest_StartFast_WithResourceAsText( const vl::WString& appName, const vl::WString& windowTypeFullName, const vl::WString& resourceText, vl::Func installWindow, vl::Nullable 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()); 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 void GacUIUnitTest_StartFast_WithResourceAsText( const vl::WString& appName, const vl::WString& windowTypeFullName, const vl::WString& resourceText, vl::Nullable config ) { GacUIUnitTest_StartFast_WithResourceAsText(appName, windowTypeFullName, resourceText, {}, config); } template void GacUIUnitTest_StartFast_WithResourceAsText( const vl::WString& appName, const vl::WString& windowTypeFullName, const vl::WString& resourceText, vl::Func installWindow ) { GacUIUnitTest_StartFast_WithResourceAsText(appName, windowTypeFullName, resourceText, installWindow, {}); } template void GacUIUnitTest_StartFast_WithResourceAsText( const vl::WString& appName, const vl::WString& windowTypeFullName, const vl::WString& resourceText ) { GacUIUnitTest_StartFast_WithResourceAsText(appName, windowTypeFullName, resourceText, {}, {}); } #endif #endif