Files
GacUI/Import/GacUI.UnitTest.cpp
2026-01-28 18:48:25 -08:00

1998 lines
69 KiB
C++

/***********************************************************************
THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY
DEVELOPER: Zihan Chen(vczh)
***********************************************************************/
#include "GacUI.UnitTest.h"
/***********************************************************************
.\GUIUNITTESTPROTOCOL_RENDERING _DOCUMENT.CPP
***********************************************************************/
namespace vl::presentation::unittest
{
using namespace collections;
using namespace remoteprotocol;
/***********************************************************************
Helper Functions for Document Paragraph
***********************************************************************/
vint GetFontSizeForPosition(
const DocumentParagraphState& state,
vint pos,
Nullable<Pair<vint, DocumentInlineObjectRunProperty>>& inlineProp)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::GetFontSizeForPosition(...)#"
inlineProp.Reset();
for (auto [range, prop] : state.mergedRuns)
{
if (pos >= range.caretBegin && pos < range.caretEnd)
{
if (auto textProp = prop.TryGet<DocumentTextRunProperty>())
{
return textProp->fontProperties.size;
}
if (auto objProp = prop.TryGet<DocumentInlineObjectRunProperty>())
{
inlineProp = { range.caretEnd - range.caretBegin,*objProp };
return 0;
}
}
}
CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Every character is expected to have a font.");
#undef ERROR_MESSAGE_PREFIX
}
double GetCharacterWidth(wchar_t c, vint fontSize)
{
return (c < 128 ? 0.6 : 1.0) * fontSize;
}
void CalculateParagraphLayout(DocumentParagraphState& state)
{
state.characterLayouts.Clear();
state.lines.Clear();
state.cachedSize = Size(0, 16);
state.cachedInlineObjectBounds.Clear();
const WString& text = state.text;
if (text.Length() == 0)
{
// Empty paragraph has default size
DocumentParagraphLineInfo line;
line.startPos = 0;
line.endPos = 0;
line.y = 0;
line.height = 16; // Default: 12 (font) + 4
line.baseline = 12;
line.width = 0;
state.lines.Add(line);
return;
}
// First pass: calculate per-character metrics
struct TempCharInfo
{
double x;
double width;
vint height;
Nullable<DocumentInlineObjectRunProperty> inlineObjectProp;
};
List<TempCharInfo> tempChars;
List<Pair<vint, vint>> lineRanges; // [start, end) for each line
double currentX = 0;
vint currentLineStart = 0;
for (vint i = 0; i < text.Length(); i++)
{
wchar_t c = text[i];
TempCharInfo info = { currentX, 0, 0, {} };
// Handle \r - zero width, no line break
if (c == L'\r')
{
tempChars.Add(info);
continue;
}
// Get character properties
Nullable<Pair<vint, DocumentInlineObjectRunProperty>> inlinePair;
vint fontSize = GetFontSizeForPosition(state, i, inlinePair);
if (inlinePair)
{
auto& prop = inlinePair.Value().value;
info.width = (double)prop.size.x;
info.height = prop.size.y;
info.inlineObjectProp = inlinePair.Value().value;
}
else
{
if (fontSize <= 0) fontSize = 12;
info.width = GetCharacterWidth(c, fontSize);
info.height = fontSize;
}
// Handle \n - always break line
if (c == L'\n')
{
info.width = 0;
tempChars.Add(info);
lineRanges.Add({ currentLineStart, i + 1 });
currentLineStart = i + 1;
currentX = 0;
continue;
}
// Check word wrap
if (state.wrapLine && state.maxWidth > 0 && currentX > 0)
{
if (currentX + info.width > state.maxWidth)
{
lineRanges.Add({ currentLineStart, i });
currentLineStart = i;
currentX = 0;
}
}
info.x = currentX;
tempChars.Add(info);
currentX += info.width;
if(inlinePair)
{
i += inlinePair.Value().key - 1;
}
}
// Add final line
if (currentLineStart <= text.Length())
{
lineRanges.Add({ currentLineStart, text.Length() });
}
// Handle empty case
if (lineRanges.Count() == 0)
{
lineRanges.Add({ 0, 0 });
}
// Second pass: calculate line heights using baseline alignment
vint currentY = 0;
for (auto [lineStart, lineEnd] : lineRanges)
{
vint maxAboveBaseline = 0;
vint maxBelowBaseline = 0;
for (vint i = lineStart; i < lineEnd && i < tempChars.Count(); i++)
{
auto& info = tempChars[i];
if (info.inlineObjectProp)
{
auto&& prop = info.inlineObjectProp.Value();
vint baseline = prop.baseline;
if (baseline == -1)
baseline = info.height;
vint above = baseline;
vint below = info.height - baseline;
if (above < 0) above = 0;
if (below < 0) below = 0;
if (maxAboveBaseline < above)
maxAboveBaseline = above;
if (maxBelowBaseline < below)
maxBelowBaseline = below;
}
else
{
if (maxAboveBaseline < info.height)
maxAboveBaseline = info.height;
}
}
DocumentParagraphLineInfo line;
line.startPos = lineStart;
line.endPos = lineEnd;
line.y = currentY;
line.height = maxAboveBaseline + maxBelowBaseline + 4;
line.baseline = maxAboveBaseline;
// Calculate line width
double lineWidth = 0;
for (vint i = lineStart; i < lineEnd && i < tempChars.Count(); i++)
{
double endX = tempChars[i].x + tempChars[i].width;
if (endX > lineWidth) lineWidth = endX;
}
line.width = (vint)lineWidth;
// Fill inline object bounds
for (vint i = lineStart; i < lineEnd && i < tempChars.Count(); i++)
{
auto& info = tempChars[i];
if (info.inlineObjectProp)
{
auto&& prop = info.inlineObjectProp.Value();
if (prop.callbackId != -1)
{
vint baseline = prop.baseline;
if (baseline == -1)
baseline = info.height;
vint y = line.y + 2 + line.baseline - baseline;
state.cachedInlineObjectBounds.Add(prop.callbackId, Rect(Point((vint)info.x, y), prop.size));
}
}
}
state.lines.Add(line);
currentY += line.height;
}
// Third pass: create final character layouts with line indices
vint lineIdx = 0;
for (vint i = 0; i < tempChars.Count(); i++)
{
while (lineIdx < state.lines.Count() - 1 && i >= state.lines[lineIdx].endPos)
{
lineIdx++;
}
DocumentParagraphCharLayout cl;
cl.x = tempChars[i].x;
cl.width = tempChars[i].width;
cl.lineIndex = lineIdx;
cl.height = tempChars[i].height;
state.characterLayouts.Add(cl);
}
// Calculate total size
vint maxWidth = 0;
for (auto&& line : state.lines)
{
if (line.width > maxWidth) maxWidth = line.width;
}
state.cachedSize = Size(maxWidth, currentY > 0 ? currentY : 16);
}
/***********************************************************************
IGuiRemoteProtocolMessages (Elements - Document)
***********************************************************************/
void UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_DocumentParagraph(vint id, const ElementDesc_DocumentParagraph& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_DocumentParagraph(vint, const ElementDesc_DocumentParagraph&)#"
vint index = paragraphStates.Keys().IndexOf(arguments.id);
CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"Paragraph not created.");
auto state = paragraphStates.Values()[index];
// Apply text if provided (distinguish null vs empty string)
if (arguments.text)
{
state->text = arguments.text.Value();
// Text changed - clear run maps since positions may be invalid
state->textRuns.Clear();
state->inlineObjectRuns.Clear();
}
// Always update these
state->wrapLine = arguments.wrapLine;
state->maxWidth = arguments.maxWidth;
state->alignment = arguments.alignment;
// Process removed inline objects first
if (arguments.removedInlineObjects)
{
for (auto callbackId : *arguments.removedInlineObjects.Obj())
{
// Find the range for this inline object and reset it
for (auto [range, prop] : state->inlineObjectRuns)
{
if (prop.callbackId == callbackId)
{
elements::ResetInlineObjectRun(state->inlineObjectRuns, range);
break;
}
}
}
}
// Apply runsDiff using helper functions
if (arguments.runsDiff)
{
for (auto run : *arguments.runsDiff.Obj())
{
elements::CaretRange range{ run.caretBegin, run.caretEnd };
if (auto textProp = run.props.TryGet<DocumentTextRunProperty>())
{
elements::DocumentTextRunPropertyOverrides overrides;
overrides.textColor = textProp->textColor;
overrides.backgroundColor = textProp->backgroundColor;
overrides.fontFamily = textProp->fontProperties.fontFamily;
overrides.size = textProp->fontProperties.size;
// Convert bool flags back to TextStyle
elements::IGuiGraphicsParagraph::TextStyle style = (elements::IGuiGraphicsParagraph::TextStyle)0;
if (textProp->fontProperties.bold) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Bold);
if (textProp->fontProperties.italic) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Italic);
if (textProp->fontProperties.underline) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Underline);
if (textProp->fontProperties.strikeline) style = (elements::IGuiGraphicsParagraph::TextStyle)((vint)style | (vint)elements::IGuiGraphicsParagraph::TextStyle::Strikeline);
overrides.textStyle = style;
elements::AddTextRun(state->textRuns, range, overrides);
}
else if (auto inlineProp = run.props.TryGet<DocumentInlineObjectRunProperty>())
{
elements::AddInlineObjectRun(state->inlineObjectRuns, range, *inlineProp);
}
}
}
// Merge runs to create final result
state->mergedRuns.Clear();
elements::MergeRuns(state->textRuns, state->inlineObjectRuns, state->mergedRuns);
// Recalculate layout
CalculateParagraphLayout(*state.Obj());
{
index = loggedTrace.createdElements->Keys().IndexOf(arguments.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 == RendererType::DocumentParagraph, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type.");
index = lastElementDescs.Keys().IndexOf(arguments.id);
ElementDesc_DocumentParagraphFull element;
if (index != -1)
{
auto paragraphRef = lastElementDescs.Values()[index].TryGet<ElementDesc_DocumentParagraphFull>();
CHECK_ERROR(paragraphRef, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type.");
element = *paragraphRef;
}
element.paragraph = arguments;
element.paragraph.text = state->text;
element.paragraph.createdInlineObjects = {};
element.paragraph.removedInlineObjects = {};
element.paragraph.runsDiff = Ptr(new List<remoteprotocol::DocumentRun>);
for (auto [range, props] : state->mergedRuns)
{
remoteprotocol::DocumentRun run;
run.caretBegin = range.caretBegin;
run.caretEnd = range.caretEnd;
run.props = props;
element.paragraph.runsDiff->Add(run);
}
lastElementDescs.Set(arguments.id, element);
}
// Send response with calculated size and inline object bounds
UpdateElement_DocumentParagraphResponse response;
response.documentSize = state->cachedSize;
if (state->cachedInlineObjectBounds.Count() > 0)
{
response.inlineObjectBounds = Ptr(new Dictionary<vint, Rect>);
CopyFrom(*response.inlineObjectBounds.Obj(), state->cachedInlineObjectBounds);
}
GetEvents()->RespondRendererUpdateElement_DocumentParagraph(id, response);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaretBounds(vint id, const GetCaretBoundsRequest& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaretBounds(vint, const GetCaretBoundsRequest&)#"
vint index = paragraphStates.Keys().IndexOf(arguments.id);
CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph.");
auto state = paragraphStates.Values()[index];
vint caret = arguments.caret;
// Handle empty text
if (state->text.Length() == 0 || state->lines.Count() == 0)
{
auto& line = state->lines[0];
GetEvents()->RespondDocumentParagraph_GetCaretBounds(id, Rect(Point(0, line.y), Size(0, line.height)));
return;
}
// Clamp caret to valid range
if (caret < 0) caret = 0;
if (caret > state->text.Length()) caret = state->text.Length();
// Find which line the caret is on
vint lineIdx = 0;
for (vint i = 0; i < state->lines.Count(); i++)
{
if (caret >= state->lines[i].startPos && caret <= state->lines[i].endPos)
{
lineIdx = i;
break;
}
if (i == state->lines.Count() - 1)
{
lineIdx = i;
}
}
auto& line = state->lines[lineIdx];
// Calculate x position
vint x = 0;
if (caret > 0 && caret <= state->characterLayouts.Count())
{
// Caret is at the end of the previous character
auto& prevChar = state->characterLayouts[caret - 1];
x = (vint)(prevChar.x + prevChar.width);
}
else if (caret < state->characterLayouts.Count())
{
// Caret is at the start of this character
x = (vint)state->characterLayouts[caret].x;
}
// Apply alignment offset
vint alignmentOffset = 0;
if (state->alignment == ElementHorizontalAlignment::Center)
{
alignmentOffset = (state->cachedSize.x - line.width) / 2;
}
else if (state->alignment == ElementHorizontalAlignment::Right)
{
alignmentOffset = state->cachedSize.x - line.width;
}
Rect bounds(Point(x + alignmentOffset, line.y), Size(0, line.height));
GetEvents()->RespondDocumentParagraph_GetCaretBounds(id, bounds);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaret(vint id, const GetCaretRequest& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetCaret(vint, const GetCaretRequest&)#"
vint index = paragraphStates.Keys().IndexOf(arguments.id);
CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph.");
auto state = paragraphStates.Values()[index];
vint caret = arguments.caret;
auto relPos = arguments.relativePosition;
GetCaretResponse response;
response.preferFrontSide = true;
vint textLen = state->text.Length();
// Clamp caret
if (caret < 0) caret = 0;
if (caret > textLen) caret = textLen;
// Find current line
vint lineIdx = 0;
for (vint i = 0; i < state->lines.Count(); i++)
{
if (caret >= state->lines[i].startPos && caret <= state->lines[i].endPos)
{
lineIdx = i;
break;
}
}
using CRP = elements::IGuiGraphicsParagraph::CaretRelativePosition;
switch (relPos)
{
case CRP::CaretFirst:
response.newCaret = 0;
break;
case CRP::CaretLast:
response.newCaret = textLen;
break;
case CRP::CaretLineFirst:
response.newCaret = state->lines[lineIdx].startPos;
break;
case CRP::CaretLineLast:
response.newCaret = state->lines[lineIdx].endPos;
if (response.newCaret > state->lines[lineIdx].startPos && response.newCaret > 0)
{
// Don't include CR/LF at end of line
while (response.newCaret > state->lines[lineIdx].startPos && response.newCaret > 0)
{
auto ch = state->text[response.newCaret - 1];
if (ch == L'\r' || ch == L'\n')
{
response.newCaret--;
}
else
{
break;
}
}
}
break;
case CRP::CaretMoveLeft:
response.newCaret = caret > 0 ? caret - 1 : 0;
break;
case CRP::CaretMoveRight:
response.newCaret = caret < textLen ? caret + 1 : textLen;
break;
case CRP::CaretMoveUp:
if (lineIdx > 0)
{
// Calculate x offset in current line
vint xOffset = 0;
if (caret > 0 && caret <= state->characterLayouts.Count())
{
auto& prevChar = state->characterLayouts[caret - 1];
xOffset = (vint)(prevChar.x + prevChar.width);
}
// Find corresponding position in previous line
auto& prevLine = state->lines[lineIdx - 1];
response.newCaret = prevLine.startPos;
for (vint i = prevLine.startPos; i < prevLine.endPos && i < state->characterLayouts.Count(); i++)
{
auto& ch = state->characterLayouts[i];
if (ch.x + ch.width / 2 > xOffset)
break;
response.newCaret = i + 1;
}
}
else
{
response.newCaret = caret;
}
break;
case CRP::CaretMoveDown:
if (lineIdx < state->lines.Count() - 1)
{
// Calculate x offset in current line
vint xOffset = 0;
if (caret > 0 && caret <= state->characterLayouts.Count())
{
auto& prevChar = state->characterLayouts[caret - 1];
xOffset = (vint)(prevChar.x + prevChar.width);
}
// Find corresponding position in next line
auto& nextLine = state->lines[lineIdx + 1];
response.newCaret = nextLine.startPos;
for (vint i = nextLine.startPos; i < nextLine.endPos && i < state->characterLayouts.Count(); i++)
{
auto& ch = state->characterLayouts[i];
if (ch.x + ch.width / 2 > xOffset)
break;
response.newCaret = i + 1;
}
}
else
{
response.newCaret = caret;
}
break;
default:
response.newCaret = caret;
break;
}
GetEvents()->RespondDocumentParagraph_GetCaret(id, response);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetNearestCaretFromTextPos(vint id, const GetCaretBoundsRequest& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetNearestCaretFromTextPos(vint, const GetCaretBoundsRequest&)#"
vint index = paragraphStates.Keys().IndexOf(arguments.id);
CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph.");
auto state = paragraphStates.Values()[index];
vint textPos = arguments.caret;
vint textLen = state->text.Length();
// Clamp to valid range
if (textPos < 0) textPos = 0;
if (textPos > textLen) textPos = textLen;
// For simple implementation, text position equals caret position
GetEvents()->RespondDocumentParagraph_GetNearestCaretFromTextPos(id, textPos);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetInlineObjectFromPoint(vint id, const GetInlineObjectFromPointRequest& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_GetInlineObjectFromPoint(vint, const GetInlineObjectFromPointRequest&)#"
vint index = paragraphStates.Keys().IndexOf(arguments.id);
CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"No active paragraph.");
auto state = paragraphStates.Values()[index];
Point pt = arguments.point;
Nullable<remoteprotocol::DocumentRun> result;
// Find the line containing the Y coordinate
vint lineIdx = -1;
for (vint i = 0; i < state->lines.Count(); i++)
{
if (pt.y >= state->lines[i].y && pt.y < state->lines[i].y + state->lines[i].height)
{
lineIdx = i;
break;
}
}
if (lineIdx >= 0)
{
auto& line = state->lines[lineIdx];
// Apply alignment offset
vint alignmentOffset = 0;
if (state->alignment == ElementHorizontalAlignment::Center)
{
alignmentOffset = (state->cachedSize.x - line.width) / 2;
}
else if (state->alignment == ElementHorizontalAlignment::Right)
{
alignmentOffset = state->cachedSize.x - line.width;
}
vint relativeX = pt.x - alignmentOffset;
// Check each character in the line
for (vint i = line.startPos; i < line.endPos && i < state->characterLayouts.Count(); i++)
{
auto& ch = state->characterLayouts[i];
if (ch.isInlineObject && relativeX >= ch.x && relativeX < ch.x + ch.width)
{
// Found an inline object - look up its properties
for (auto [range, prop] : state->mergedRuns)
{
if (i >= range.caretBegin && i < range.caretEnd)
{
if (auto inlineProp = prop.TryGet<DocumentInlineObjectRunProperty>())
{
remoteprotocol::DocumentRun run;
run.caretBegin = range.caretBegin;
run.caretEnd = range.caretEnd;
run.props = *inlineProp;
result = run;
break;
}
}
}
break;
}
}
}
GetEvents()->RespondDocumentParagraph_GetInlineObjectFromPoint(id, result);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_IsValidCaret(vint id, const IsValidCaretRequest& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_IsValidCaret(vint, const IsValidCaretRequest&)#"
vint index = paragraphStates.Keys().IndexOf(arguments.id);
if (index == -1)
{
GetEvents()->RespondDocumentParagraph_IsValidCaret(id, false);
return;
}
auto state = paragraphStates.Values()[index];
vint caret = arguments.caret;
// Check range: valid positions are 0 to text.Length() inclusive
if (caret < 0 || caret > state->text.Length())
{
GetEvents()->RespondDocumentParagraph_IsValidCaret(id, false);
return;
}
// Check if position is inside an inline object (not at the beginning)
// Inline objects occupy a range [caretBegin, caretEnd)
// Position at caretBegin is valid (cursor can be placed before the object)
// Positions inside (caretBegin < pos < caretEnd) are invalid
for (auto&& [range, _] : state->inlineObjectRuns)
{
if (caret > range.caretBegin && caret < range.caretEnd)
{
GetEvents()->RespondDocumentParagraph_IsValidCaret(id, false);
return;
}
}
GetEvents()->RespondDocumentParagraph_IsValidCaret(id, true);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_OpenCaret(const OpenCaretRequest& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_OpenCaret(const OpenCaretRequest&)#"
vint index = loggedTrace.createdElements->Keys().IndexOf(arguments.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 == RendererType::DocumentParagraph, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type.");
index = lastElementDescs.Keys().IndexOf(arguments.id);
ElementDesc_DocumentParagraphFull element;
if (index != -1)
{
auto paragraphRef = lastElementDescs.Values()[index].TryGet<ElementDesc_DocumentParagraphFull>();
CHECK_ERROR(paragraphRef, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type.");
element = *paragraphRef;
}
element.caret = arguments;
lastElementDescs.Set(arguments.id, element);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_CloseCaret(const vint& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_DocumentParagraph_CloseCaret(const vint&)#"
vint index = loggedTrace.createdElements->Keys().IndexOf(arguments);
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 == RendererType::DocumentParagraph, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type.");
index = lastElementDescs.Keys().IndexOf(arguments);
ElementDesc_DocumentParagraphFull element;
if (index != -1)
{
auto paragraphRef = lastElementDescs.Values()[index].TryGet<ElementDesc_DocumentParagraphFull>();
CHECK_ERROR(paragraphRef, ERROR_MESSAGE_PREFIX L"Renderer with the specified id is not of the expected type.");
element = *paragraphRef;
}
element.caret.Reset();
lastElementDescs.Set(arguments, element);
#undef ERROR_MESSAGE_PREFIX
}
}
/***********************************************************************
.\GUIUNITTESTPROTOCOL_RENDERING.CPP
***********************************************************************/
namespace vl::presentation::unittest
{
using namespace collections;
using namespace remoteprotocol;
/***********************************************************************
IGuiRemoteProtocolMessages (Rendering)
***********************************************************************/
Ptr<UnitTestLoggedFrame> UnitTestRemoteProtocol_Rendering::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<UnitTestLoggedFrame> UnitTestRemoteProtocol_Rendering::TryGetLastRenderingFrameAndReset()
{
if (loggedFrames.Count() == 0) return nullptr;
if (!lastRenderingCommandsOpening) return nullptr;
lastRenderingCommandsOpening = false;
return loggedFrames[loggedFrames.Count() - 1];
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererBeginRendering(const ElementBeginRendering& arguments)
{
receivedDomDiffMessage = false;
receivedElementMessage = false;
lastRenderingCommandsOpening = true;
auto frame = Ptr(new UnitTestLoggedFrame);
frame->frameId = arguments.frameId;
loggedFrames.Add(frame);
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererEndRendering(vint id)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererEndRendering(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 UnitTestRemoteProtocol_Rendering::Impl_RendererBeginBoundary(const ElementBoundary& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererBeginBoundary(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 = ConvertCustomTypeToJson(arguments);
writer.WriteString(glr::json::JsonToString(jsonLog, formatting));
}));
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererEndBoundary()
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererEndBoundary()#"
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 UnitTestRemoteProtocol_Rendering::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 == RendererType::FocusRectangle || rendererType == 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(
[](RendererType)
{
CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Renderer with the specified id has not been updated after created.");
},
[&](const ElementDesc_SolidLabel& solidLabel)
{
CalculateSolidLabelSizeIfNecessary(bounds.Width(), bounds.Height(), solidLabel);
},
[&](const auto& element)
{
}));
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererRenderElement(const ElementRendering& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererRenderElement(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 = ConvertCustomTypeToJson(arguments);
writer.WriteString(glr::json::JsonToString(jsonLog, formatting));
}));
}
EnsureRenderedElement(arguments.id, arguments.bounds);
#undef ERROR_MESSAGE_PREFIX
}
/***********************************************************************
IGuiRemoteProtocolMessages (Rendering - Dom)
***********************************************************************/
void UnitTestRemoteProtocol_Rendering::CalculateSolidLabelSizesIfNecessary(Ptr<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 UnitTestRemoteProtocol_Rendering::Impl_RendererRenderDom(const Ptr<RenderingDom>& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererRenderElement(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;
BuildDomIndex(receivedDom, receivedDomIndex);
CalculateSolidLabelSizesIfNecessary(receivedDom);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererRenderDomDiff(const RenderingDom_DiffsInOrder& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererRenderElement(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;
}
UpdateDomInplace(receivedDom, receivedDomIndex, arguments);
GetLastRenderingFrame()->renderingDiffs = arguments;
CalculateSolidLabelSizesIfNecessary(receivedDom);
#undef ERROR_MESSAGE_PREFIX
}
/***********************************************************************
IGuiRemoteProtocolMessages (Elements)
***********************************************************************/
void UnitTestRemoteProtocol_Rendering::Impl_RendererCreated(const Ptr<List<RendererCreation>>& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererCreated(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);
// Create paragraph state for DocumentParagraph elements
if (creation.type == RendererType::DocumentParagraph)
{
auto state = Ptr(new DocumentParagraphState());
paragraphStates.Add(creation.id, state);
}
}
}
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererDestroyed(const Ptr<List<vint>>& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererDestroyed(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);
// Remove paragraph state if this was a DocumentParagraph
paragraphStates.Remove(id);
}
}
#undef ERROR_MESSAGE_PREFIX
}
#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 UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_SolidBorder(const ElementDesc_SolidBorder& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_SolidBorder(const ElementDesc_SolidBorder&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(RendererType::SolidBorder);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_SinkBorder(const ElementDesc_SinkBorder& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_SinkBorder(const ElementDesc_SinkBorder&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(RendererType::SinkBorder);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_SinkSplitter(const ElementDesc_SinkSplitter& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_SinkSplitter(const ElementDesc_SinkSplitter&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(RendererType::SinkSplitter);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_SolidBackground(const ElementDesc_SolidBackground& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_SolidBackground(const ElementDesc_SolidBackground&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(RendererType::SolidBackground);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_GradientBackground(const ElementDesc_GradientBackground& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_GradientBackground(const ElementDesc_GradientBackground&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(RendererType::GradientBackground);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_InnerShadow(const ElementDesc_InnerShadow& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_InnerShadow(const ElementDesc_InnerShadow&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(RendererType::InnerShadow);
#undef ERROR_MESSAGE_PREFIX
}
void UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_Polygon(const ElementDesc_Polygon& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_Polygon(const ElementDesc_Polygon&)#"
REQUEST_RENDERER_UPDATE_ELEMENT(RendererType::Polygon);
#undef ERROR_MESSAGE_PREFIX
}
/***********************************************************************
IGuiRemoteProtocolMessages (Elements - SolidLabel)
***********************************************************************/
void UnitTestRemoteProtocol_Rendering::CalculateSolidLabelSizeIfNecessary(vint width, vint height, const ElementDesc_SolidLabel& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::CalculateSolidLabelSizeIfNecessary(vint, vint, const ElementDesc_SolidLabel&)#"
if (arguments.measuringRequest)
{
switch (arguments.measuringRequest.Value())
{
case ElementSolidLabelMeasuringRequest::FontHeight:
CHECK_ERROR(arguments.font, ERROR_MESSAGE_PREFIX L"Font is missing for calculating font height.");
if (!measuringForNextRendering.fontHeights)
{
measuringForNextRendering.fontHeights = Ptr(new List<ElementMeasuring_FontHeight>);
}
{
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 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;
List<WString> lines;
{
List<Ptr<regex::RegexMatch>> 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 List<ElementMeasuring_ElementMinSize>);
}
{
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 UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_SolidLabel(const ElementDesc_SolidLabel& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_SolidLabel(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 == 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<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, RendererType::SolidLabel);
#undef ERROR_MESSAGE_PREFIX
}
/***********************************************************************
IGuiRemoteProtocolMessages (Elements - Image)
***********************************************************************/
WString UnitTestRemoteProtocol_Rendering::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 UnitTestRemoteProtocol_Rendering::GetBinaryKeyFromImage(Ptr<INativeImage> image)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::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
}
ImageMetadata UnitTestRemoteProtocol_Rendering::MakeImageMetadata(const 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<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))
{
ImageMetadata imageMetadata;
imageMetadata.id = -1;
imageMetadata.frames = Ptr(new List<ImageFrameMetadata>);
{
auto node = Ptr(new glr::json::JsonString);
node->content.value = format;
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 UnitTestRemoteProtocol_Rendering::Impl_ImageCreated(vint id, const ImageCreation& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_ImageCreated(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 UnitTestRemoteProtocol_Rendering::Impl_ImageDestroyed(const vint& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_ImageDestroyed(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 UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_ImageFrame(const ElementDesc_ImageFrame& arguments)
{
#define ERROR_MESSAGE_PREFIX L"vl::presentation::unittest::UnitTestRemoteProtocol_Rendering::Impl_RendererUpdateElement_ImageFrame(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 List<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, RendererType::ImageFrame);
#undef ERROR_MESSAGE_PREFIX
}
#undef REQUEST_RENDERER_UPDATE_ELEMENT
#undef REQUEST_RENDERER_UPDATE_ELEMENT2
}
/***********************************************************************
.\GUIUNITTESTPROTOCOL_SHARED.CPP
***********************************************************************/
namespace vl::presentation::unittest
{
using namespace vl::collections;
/***********************************************************************
UnitTestScreenConfig
***********************************************************************/
void UnitTestScreenConfig::FastInitialize(vint width, vint height, vint taskBarHeight)
{
executablePath = WString::Unmanaged(L"/GacUI/Remote/Protocol/UnitTest.exe");
customFramePadding = { 8,8,8,8 };
fontConfig.defaultFont.fontFamily = WString::Unmanaged(L"GacUI Default Font");
fontConfig.defaultFont.size = 12;
fontConfig.supportedFonts = Ptr(new List<WString>());
fontConfig.supportedFonts->Add(fontConfig.defaultFont.fontFamily);
screenConfig.bounds = { 0,0,width,height };
screenConfig.clientBounds = { 0,0,width,(height - taskBarHeight) };
screenConfig.scalingX = 1;
screenConfig.scalingY = 1;
}
}
/***********************************************************************
.\GUIUNITTESTUTILITIES.CPP
***********************************************************************/
namespace vl::presentation::controls
{
extern bool GACUI_UNITTEST_ONLY_SKIP_THREAD_LOCAL_STORAGE_DISPOSE_STORAGES;
extern bool GACUI_UNITTEST_ONLY_SKIP_TYPE_AND_PLUGIN_LOAD_UNLOAD;
}
namespace vl::presentation::unittest
{
const UnitTestFrameworkConfig* globalUnitTestFrameworkConfig = nullptr;
const UnitTestFrameworkConfig& GetUnitTestFrameworkConfig()
{
CHECK_ERROR(globalUnitTestFrameworkConfig, L"vl::presentation::unittest::GetUnitTestFrameworkConfig()#GacUIUnitTest_Initialize has not been called.");
return *globalUnitTestFrameworkConfig;
}
}
namespace vl::presentation::GuiHostedController_UnitTestHelper
{
extern bool ExceptionOccuredUnderUnitTestReleaseMode();
}
using namespace vl;
using namespace vl::collections;
using namespace vl::filesystem;
using namespace vl::reflection::description;
using namespace vl::glr::json;
using namespace vl::presentation;
using namespace vl::presentation::remoteprotocol;
using namespace vl::presentation::controls;
using namespace vl::presentation::unittest;
using namespace vl::presentation::GuiHostedController_UnitTestHelper;
class UnitTestContextImpl : public Object, public virtual IUnitTestContext
{
UnitTestRemoteProtocol* protocol = nullptr;
public:
UnitTestContextImpl(UnitTestRemoteProtocol* _protocol)
: protocol(_protocol)
{
}
UnitTestRemoteProtocol* GetProtocol()
{
return protocol;
}
};
UnitTestMainFunc guiMainProxy;
UnitTestContextImpl* guiMainUnitTestContext = nullptr;
void GacUIUnitTest_Initialize(const UnitTestFrameworkConfig* config)
{
CHECK_ERROR(config, L"GacUIUnitTest_Initialize()#Argument config should not be null.");
globalUnitTestFrameworkConfig = config;
GACUI_UNITTEST_ONLY_SKIP_THREAD_LOCAL_STORAGE_DISPOSE_STORAGES = true;
GACUI_UNITTEST_ONLY_SKIP_TYPE_AND_PLUGIN_LOAD_UNLOAD = true;
GetGlobalTypeManager()->Load();
GetPluginManager()->Load(true, false);
}
void GacUIUnitTest_Finalize()
{
ResetGlobalTypeManager();
GetPluginManager()->Unload(true, false);
DestroyPluginManager();
ThreadLocalStorage::DisposeStorages();
GACUI_UNITTEST_ONLY_SKIP_THREAD_LOCAL_STORAGE_DISPOSE_STORAGES = false;
GACUI_UNITTEST_ONLY_SKIP_TYPE_AND_PLUGIN_LOAD_UNLOAD = false;
globalUnitTestFrameworkConfig = nullptr;
}
void GacUIUnitTest_SetGuiMainProxy(const UnitTestMainFunc& proxy)
{
guiMainProxy = proxy;
}
void GacUIUnitTest_LinkGuiMainProxy(const UnitTestLinkFunc& proxy)
{
auto previousMainProxy = guiMainProxy;
GacUIUnitTest_SetGuiMainProxy([=](UnitTestRemoteProtocol* protocol, IUnitTestContext* context)
{
proxy(protocol, context, previousMainProxy);
});
}
File GacUIUnitTest_PrepareSnapshotFile(const WString& appName, const WString& extension)
{
#define ERROR_MESSAGE_PREFIX L"GacUIUnitTest_PrepareSnapshotFile(const WString&, const WString&)#"
Folder snapshotFolder = GetUnitTestFrameworkConfig().snapshotFolder;
CHECK_ERROR(snapshotFolder.Exists(), ERROR_MESSAGE_PREFIX L"UnitTestFrameworkConfig::snapshotFolder does not point to an existing folder.");
File snapshotFile = snapshotFolder.GetFilePath() / (appName + extension);
{
auto pathPrefix = snapshotFolder.GetFilePath().GetFullPath() + WString::FromChar(FilePath::Delimiter);
auto snapshotPath = snapshotFile.GetFilePath().GetFullPath();
CHECK_ERROR(
snapshotPath.Length() > pathPrefix.Length() && snapshotPath.Left(pathPrefix.Length()) == pathPrefix,
ERROR_MESSAGE_PREFIX L"Argument appName should specify a file that is inside UnitTestFrameworkConfig::snapshotFolder"
);
Folder snapshotFileFolder = snapshotFile.GetFilePath().GetFolder();
if (!snapshotFileFolder.Exists())
{
CHECK_ERROR(snapshotFileFolder.Create(true), ERROR_MESSAGE_PREFIX L"Failed to create the folder to contain the snapshot file specified by argument appName.");
}
}
return snapshotFile;
#undef ERROR_MESSAGE_PREFIX
}
void GacUIUnitTest_WriteSnapshotFileIfChanged(File& snapshotFile, const WString& textLog)
{
#define ERROR_MESSAGE_PREFIX L"GacUIUnitTest_WriteSnapshotFileIfChanged(File&, const WString&)#"
bool skipWriting = false;
if (snapshotFile.Exists())
{
auto previousLog = snapshotFile.ReadAllTextByBom();
if (previousLog == textLog)
{
skipWriting = true;
}
}
if (!skipWriting)
{
bool succeeded = snapshotFile.WriteAllText(textLog, true, stream::BomEncoder::Utf8);
CHECK_ERROR(succeeded, ERROR_MESSAGE_PREFIX L"Failed to write the snapshot file.");
}
#undef ERROR_MESSAGE_PREFIX
}
void GacUIUnitTest_LogUI(const WString& appName, UnitTestRemoteProtocol& unitTestProtocol)
{
#define ERROR_MESSAGE_PREFIX L"GacUIUnitTest_LogUI(const WString&, UnitTestRemoteProtocol&)#"
File snapshotFile = GacUIUnitTest_PrepareSnapshotFile(appName, WString::Unmanaged(L".json"));
JsonFormatting formatting;
formatting.spaceAfterColon = true;
formatting.spaceAfterComma = true;
formatting.crlf = true;
formatting.compact = true;
auto jsonLog = remoteprotocol::ConvertCustomTypeToJson(unitTestProtocol.GetLoggedTrace());
auto textLog = JsonToString(jsonLog, formatting);
{
remoteprotocol::UnitTest_RenderingTrace deserialized;
remoteprotocol::ConvertJsonToCustomType(jsonLog, deserialized);
auto jsonLog2 = remoteprotocol::ConvertCustomTypeToJson(deserialized);
auto textLog2 = JsonToString(jsonLog2, formatting);
CHECK_ERROR(textLog == textLog2, ERROR_MESSAGE_PREFIX L"Serialization and deserialization doesn't match.");
}
GacUIUnitTest_WriteSnapshotFileIfChanged(snapshotFile, textLog);
#undef ERROR_MESSAGE_PREFIX
}
void GacUIUnitTest_LogCommands(const WString& appName, UnitTestRemoteProtocol& unitTestProtocol)
{
File snapshotFile = GacUIUnitTest_PrepareSnapshotFile(appName, WString::Unmanaged(L"[commands].txt"));
JsonFormatting formatting;
formatting.spaceAfterColon = true;
formatting.spaceAfterComma = true;
formatting.crlf = false;
formatting.compact = true;
auto textLog = stream::GenerateToStream([&unitTestProtocol, &formatting](stream::TextWriter& writer)
{
auto&& loggedFrames = unitTestProtocol.GetLoggedFrames();
for (auto loggedFrame : loggedFrames)
{
writer.WriteLine(L"========================================");
writer.WriteLine(itow(loggedFrame->frameId));
writer.WriteLine(L"========================================");
for (auto&& commandLog : loggedFrame->renderingCommandsLog)
{
writer.WriteLine(commandLog);
}
};
});
GacUIUnitTest_WriteSnapshotFileIfChanged(snapshotFile, textLog);
}
void GacUIUnitTest_LogDiffs(const WString& appName, UnitTestRemoteProtocol& unitTestProtocol)
{
File snapshotFile = GacUIUnitTest_PrepareSnapshotFile(appName, WString::Unmanaged(L"[diffs].txt"));
JsonFormatting formatting;
formatting.spaceAfterColon = true;
formatting.spaceAfterComma = true;
formatting.crlf = false;
formatting.compact = true;
auto textLog = stream::GenerateToStream([&unitTestProtocol, &formatting](stream::TextWriter& writer)
{
Ptr<RenderingDom> dom;
DomIndex domIndex;
auto&& loggedFrames = unitTestProtocol.GetLoggedFrames();
for (auto loggedFrame : loggedFrames)
{
writer.WriteLine(L"========================================");
writer.WriteLine(itow(loggedFrame->frameId));
writer.WriteLine(L"========================================");
if (!dom)
{
dom = loggedFrame->renderingDom;
BuildDomIndex(dom, domIndex);
List<Pair<vint, Ptr<RenderingDom>>> lines;
lines.Add({ 0,dom });
for (vint i = 0; i < lines.Count(); i++)
{
for (vint j = 0; j < lines[i].key; j++)
{
writer.WriteString(L" ");
}
auto line = lines[i].value;
writer.WriteString(itow(line->id));
writer.WriteString(L": ");
auto jsonLog = remoteprotocol::ConvertCustomTypeToJson(line->content);
writer.WriteLine(JsonToString(jsonLog, formatting));
if (line->children)
{
for (auto child : *line->children.Obj())
{
lines.Add({ lines[i].key + 1,child });
}
}
}
}
else
{
DomIndex nextDomIndex;
BuildDomIndex(loggedFrame->renderingDom, nextDomIndex);
Ptr<List<RenderingDom_Diff>> diffList;
if (loggedFrame->renderingDiffs)
{
diffList = loggedFrame->renderingDiffs.Value().diffsInOrder;
}
else
{
RenderingDom_DiffsInOrder diffs;
DiffDom(dom, domIndex, loggedFrame->renderingDom, nextDomIndex, diffs);
diffList = diffs.diffsInOrder;
auto copiedDom = CopyDom(dom);
DomIndex copiedDomIndex;
BuildDomIndex(copiedDom, copiedDomIndex);
UpdateDomInplace(copiedDom, copiedDomIndex, diffs);
auto expectedJson = JsonToString(remoteprotocol::ConvertCustomTypeToJson(loggedFrame->renderingDom));
auto actualJson = JsonToString(remoteprotocol::ConvertCustomTypeToJson(copiedDom));
TEST_ASSERT(actualJson == expectedJson);
}
if (diffList)
{
for (auto&& diff : *diffList.Obj())
{
auto jsonLog = remoteprotocol::ConvertCustomTypeToJson(diff);
writer.WriteLine(JsonToString(jsonLog, formatting));
}
}
dom = loggedFrame->renderingDom;
domIndex = std::move(nextDomIndex);
}
};
});
GacUIUnitTest_WriteSnapshotFileIfChanged(snapshotFile, textLog);
}
void GacUIUnitTest_Start(const WString& appName, Nullable<UnitTestScreenConfig> config)
{
UnitTestScreenConfig globalConfig;
if (config)
{
globalConfig = config.Value();
}
else
{
globalConfig.FastInitialize(1024, 768);
}
// Renderer
UnitTestRemoteProtocol unitTestProtocol(appName, globalConfig);
auto jsonParser = Ptr(new glr::json::Parser);
// Data Processing in Renderer
channeling::GuiRemoteJsonChannelFromProtocol channelReceiver(unitTestProtocol.GetProtocol());
channeling::GuiRemoteJsonChannelStringDeserializer channelJsonDeserializer(&channelReceiver, jsonParser);
channeling::GuiRemoteUtfStringChannelDeserializer<wchar_t, char8_t> channelUtf8Deserializer(&channelJsonDeserializer);
// Boundary between Binaries
// Data Processing in Core
channeling::GuiRemoteUtfStringChannelSerializer<wchar_t, char8_t> channelUtf8Serializer(&channelUtf8Deserializer);
channeling::GuiRemoteJsonChannelStringSerializer channelJsonSerializer(&channelUtf8Serializer, jsonParser);
// Boundary between threads
channeling::GuiRemoteProtocolFromJsonChannel channelSender(&channelJsonSerializer);
// Core
repeatfiltering::GuiRemoteProtocolFilterVerifier verifierProtocol(
globalConfig.useChannel == UnitTestRemoteChannel::None
? unitTestProtocol.GetProtocol()
: &channelSender
);
repeatfiltering::GuiRemoteProtocolFilter filteredProtocol(&verifierProtocol);
GuiRemoteProtocolDomDiffConverter diffConverterProtocol(&filteredProtocol);
UnitTestContextImpl unitTestContext(&unitTestProtocol);
guiMainUnitTestContext = &unitTestContext;
SetupRemoteNativeController(
globalConfig.useDomDiff
? static_cast<IGuiRemoteProtocol*>(&diffConverterProtocol)
: &filteredProtocol
);
GacUIUnitTest_SetGuiMainProxy({});
TEST_ASSERT(!ExceptionOccuredUnderUnitTestReleaseMode());
GacUIUnitTest_LogUI(appName, unitTestProtocol);
if (!globalConfig.useDomDiff)
{
GacUIUnitTest_LogCommands(appName, unitTestProtocol);
}
GacUIUnitTest_LogDiffs(appName, unitTestProtocol);
}
template<typename T>
void RunInNewThread(T&& threadProc)
{
Thread::CreateAndStart([threadProc]()
{
try
{
threadProc();
}
catch (const Exception& e)
{
(void)e;
throw;
}
catch (const Error& e)
{
(void)e;
throw;
}
});
}
void GacUIUnitTest_StartAsync(const WString& appName, Nullable<UnitTestScreenConfig> config)
{
TEST_ASSERT(config && config.Value().useChannel == UnitTestRemoteChannel::Async);
// Renderer
UnitTestRemoteProtocol unitTestProtocol(appName, config.Value());
auto jsonParser = Ptr(new glr::json::Parser);
// Data Processing in Renderer
channeling::GuiRemoteJsonChannelFromProtocol channelReceiver(unitTestProtocol.GetProtocol());
channeling::GuiRemoteJsonChannelStringDeserializer channelJsonDeserializer(&channelReceiver, jsonParser);
channeling::GuiRemoteUtfStringChannelDeserializer<wchar_t, char8_t> channelUtf8Deserializer(&channelJsonDeserializer);
// Boundary between Binaries
// Data Processing in Core
channeling::GuiRemoteUtfStringChannelSerializer<wchar_t, char8_t> channelUtf8Serializer(&channelUtf8Deserializer);
channeling::GuiRemoteJsonChannelStringSerializer channelJsonSerializer(&channelUtf8Serializer, jsonParser);
// Boundary between threads
channeling::GuiRemoteProtocolAsyncJsonChannelSerializer asyncChannelSender;
asyncChannelSender.Start(
&channelJsonSerializer,
[&unitTestProtocol, config](channeling::GuiRemoteProtocolAsyncJsonChannelSerializer* channel)
{
channeling::GuiRemoteProtocolFromJsonChannel channelSender(channel);
// Core
repeatfiltering::GuiRemoteProtocolFilterVerifier verifierProtocol(&channelSender);
repeatfiltering::GuiRemoteProtocolFilter filteredProtocol(&verifierProtocol);
GuiRemoteProtocolDomDiffConverter diffConverterProtocol(&filteredProtocol);
UnitTestContextImpl unitTestContext(&unitTestProtocol);
guiMainUnitTestContext = &unitTestContext;
SetupRemoteNativeController(
config.Value().useDomDiff
? static_cast<IGuiRemoteProtocol*>(&diffConverterProtocol)
: &filteredProtocol
);
GacUIUnitTest_SetGuiMainProxy({});
},
[](
channeling::GuiRemoteProtocolAsyncJsonChannelSerializer::TChannelThreadProc channelThreadProc,
channeling::GuiRemoteProtocolAsyncJsonChannelSerializer::TUIThreadProc uiThreadProc
)
{
RunInNewThread(channelThreadProc);
RunInNewThread(uiThreadProc);
});
asyncChannelSender.WaitForStopped();
TEST_ASSERT(!ExceptionOccuredUnderUnitTestReleaseMode());
GacUIUnitTest_LogUI(appName, unitTestProtocol);
if (!config.Value().useDomDiff)
{
GacUIUnitTest_LogCommands(appName, unitTestProtocol);
}
GacUIUnitTest_LogDiffs(appName, unitTestProtocol);
}
void GacUIUnitTest_Start_WithResourceAsText(const WString& appName, Nullable<UnitTestScreenConfig> config, const WString& resourceText)
{
#define ERROR_MESSAGE_PREFIX L"GacUIUnitTest_Start_WithResourceAsText(const WString&, Nullable<UnitTestScreenConfig>, const WString&)#"
auto previousMainProxy = guiMainProxy;
GacUIUnitTest_LinkGuiMainProxy([=](UnitTestRemoteProtocol* protocol, IUnitTestContext* context, const UnitTestMainFunc& previousMainProxy)
{
auto resource = GacUIUnitTest_CompileAndLoad(resourceText);
{
auto workflow = resource->GetStringByPath(L"UnitTest/Workflow");
File snapshotFile = GacUIUnitTest_PrepareSnapshotFile(
appName,
#ifdef VCZH_64
WString::Unmanaged(L"[x64].txt")
#else
WString::Unmanaged(L"[x86].txt")
#endif
);
bool skipWriting = false;
if (snapshotFile.Exists())
{
auto previousLog = snapshotFile.ReadAllTextByBom();
if (previousLog == workflow)
{
skipWriting = true;
}
}
if (!skipWriting)
{
bool succeeded = snapshotFile.WriteAllText(workflow, true, stream::BomEncoder::Utf8);
CHECK_ERROR(succeeded, ERROR_MESSAGE_PREFIX L"Failed to write the snapshot file.");
}
}
previousMainProxy(protocol, context);
});
if (config && config.Value().useChannel == UnitTestRemoteChannel::Async)
{
GacUIUnitTest_StartAsync(appName, config);
}
else
{
GacUIUnitTest_Start(appName, config);
}
#undef ERROR_MESSAGE_PREFIX
}
void GacUIUnitTest_PrintErrors(GuiResourceError::List& errors)
{
for (auto&& error : errors)
{
TEST_PRINT(L"Error in resource: " + error.location.resourcePath);
TEST_PRINT(L" ROW: " + itow(error.position.row) + L", COLUMN: " + itow(error.position.column));
TEST_PRINT(L" REASON: " + error.message);
}
}
Ptr<GuiResource> GacUIUnitTest_CompileAndLoad(const WString& xmlResource)
{
#define ERROR_MESSAGE_PREFIX L"GacUIUnitTest_CompileAndLoad(const WString&)#"
Ptr<GuiResource> resource;
GuiResourceError::List errors;
{
auto resourcePath = (GetUnitTestFrameworkConfig().resourceFolder / L"Resource.xml").GetFullPath();
auto parser = GetParserManager()->GetParser<glr::xml::XmlDocument>(L"XML");
auto xml = parser->Parse({ WString::Empty,resourcePath }, xmlResource, errors);
if(!xml || errors.Count()> 0)
{
GacUIUnitTest_PrintErrors(errors);
CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Failed to parse XML resource.");
}
resource = GuiResource::LoadFromXml(xml, resourcePath, GetFolderPath(resourcePath), errors);
if (!resource || errors.Count() > 0)
{
GacUIUnitTest_PrintErrors(errors);
CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Failed to load XML resource.");
}
}
auto precompiledFolder = resource->Precompile(
#ifdef VCZH_64
GuiResourceCpuArchitecture::x64,
#else
GuiResourceCpuArchitecture::x86,
#endif
nullptr,
errors
);
if (!precompiledFolder || errors.Count() > 0)
{
GacUIUnitTest_PrintErrors(errors);
CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Failed to precompile XML resource.");
}
auto compiledWorkflow = precompiledFolder->GetValueByPath(WString::Unmanaged(L"Workflow/InstanceClass")).Cast<GuiInstanceCompiledWorkflow>();
CHECK_ERROR(compiledWorkflow, ERROR_MESSAGE_PREFIX L"Failed to compile generated Workflow script.");
CHECK_ERROR(compiledWorkflow->assembly, ERROR_MESSAGE_PREFIX L"Failed to load Workflow assembly.");
{
WString text;
auto& codes = compiledWorkflow->assembly->insAfterCodegen->moduleCodes;
for (auto [code, codeIndex] : indexed(codes))
{
text += L"================================(" + itow(codeIndex + 1) + L"/" + itow(codes.Count()) + L")================================\r\n";
text += code + L"\r\n";
}
resource->CreateValueByPath(
WString::Unmanaged(L"UnitTest/Workflow"),
WString::Unmanaged(L"Text"),
Ptr(new GuiTextData(text))
);
}
GetResourceManager()->SetResource(resource, errors, GuiResourceUsage::InstanceClass);
CHECK_ERROR(errors.Count() == 0, ERROR_MESSAGE_PREFIX L"Failed to load compiled XML resource.");
return resource;
#undef ERROR_MESSAGE_PREFIX
}
void GuiMain()
{
if (guiMainUnitTestContext)
{
guiMainProxy(guiMainUnitTestContext->GetProtocol(), guiMainUnitTestContext);
}
else
{
guiMainProxy(nullptr, nullptr);
}
guiMainUnitTestContext = nullptr;
}