Add XCUI expansion state and coverage tests

This commit is contained in:
2026-04-05 07:29:27 +08:00
parent 646e5855ce
commit 511e94fd30
18 changed files with 1213 additions and 53 deletions

View File

@@ -0,0 +1,283 @@
#include <gtest/gtest.h>
#include "XCUIBackend/IEditorHostCompositor.h"
#include "XCUIBackend/ImGuiWindowUICompositor.h"
#include "XCUIBackend/UITextureRegistration.h"
#include <array>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace {
using XCEngine::Editor::Platform::D3D12WindowRenderer;
using XCEngine::Editor::XCUIBackend::IEditorHostCompositor;
using XCEngine::Editor::XCUIBackend::ImGuiWindowUICompositor;
using XCEngine::Editor::XCUIBackend::UITextureRegistration;
class RecordingHostCompositor final : public IEditorHostCompositor {
public:
bool initializeResult = true;
bool handleWindowMessageResult = false;
bool createTextureDescriptorResult = false;
bool invokeConfigureFonts = false;
int initializeCount = 0;
int shutdownCount = 0;
int beginFrameCount = 0;
int endFrameCount = 0;
int handleWindowMessageCount = 0;
int createTextureDescriptorCount = 0;
int freeTextureDescriptorCount = 0;
HWND lastHwnd = nullptr;
UINT lastMessage = 0u;
WPARAM lastWParam = 0u;
LPARAM lastLParam = 0u;
D3D12WindowRenderer* initializedRenderer = nullptr;
D3D12WindowRenderer* presentedRenderer = nullptr;
::XCEngine::RHI::RHIDevice* lastDevice = nullptr;
::XCEngine::RHI::RHITexture* lastTexture = nullptr;
bool beforeUiRenderProvided = false;
bool afterUiRenderProvided = false;
std::array<float, 4> lastClearColor = { 0.0f, 0.0f, 0.0f, 0.0f };
UITextureRegistration nextRegistration = {};
UITextureRegistration freedRegistration = {};
std::vector<std::string> callOrder = {};
bool Initialize(
HWND hwnd,
D3D12WindowRenderer& windowRenderer,
const ConfigureFontsCallback& configureFonts) override {
++initializeCount;
lastHwnd = hwnd;
initializedRenderer = &windowRenderer;
callOrder.push_back("initialize");
if (invokeConfigureFonts && configureFonts) {
configureFonts();
}
return initializeResult;
}
void Shutdown() override {
++shutdownCount;
callOrder.push_back("shutdown");
}
bool HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) override {
++handleWindowMessageCount;
lastHwnd = hwnd;
lastMessage = message;
lastWParam = wParam;
lastLParam = lParam;
callOrder.push_back("message");
return handleWindowMessageResult;
}
void BeginFrame() override {
++beginFrameCount;
callOrder.push_back("begin");
}
void EndFrameAndPresent(
D3D12WindowRenderer& windowRenderer,
const float clearColor[4],
const RenderCallback& beforeUiRender,
const RenderCallback& afterUiRender) override {
++endFrameCount;
presentedRenderer = &windowRenderer;
beforeUiRenderProvided = static_cast<bool>(beforeUiRender);
afterUiRenderProvided = static_cast<bool>(afterUiRender);
for (std::size_t index = 0; index < lastClearColor.size(); ++index) {
lastClearColor[index] = clearColor[index];
}
callOrder.push_back("present");
}
bool CreateTextureDescriptor(
::XCEngine::RHI::RHIDevice* device,
::XCEngine::RHI::RHITexture* texture,
UITextureRegistration& outRegistration) override {
++createTextureDescriptorCount;
lastDevice = device;
lastTexture = texture;
if (createTextureDescriptorResult) {
outRegistration = nextRegistration;
}
return createTextureDescriptorResult;
}
void FreeTextureDescriptor(const UITextureRegistration& registration) override {
++freeTextureDescriptorCount;
freedRegistration = registration;
}
};
HWND MakeFakeHwnd() {
return reinterpret_cast<HWND>(static_cast<std::uintptr_t>(0x1234u));
}
UITextureRegistration MakeRegistration() {
UITextureRegistration registration = {};
registration.cpuHandle.ptr = 11u;
registration.gpuHandle.ptr = 29u;
registration.texture.nativeHandle = 43u;
registration.texture.width = 256u;
registration.texture.height = 128u;
registration.texture.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView;
return registration;
}
TEST(ImGuiWindowUICompositorTest, InitializeForwardsToHostAndConfigureFontsCallback) {
auto host = std::make_unique<RecordingHostCompositor>();
RecordingHostCompositor* hostPtr = host.get();
hostPtr->invokeConfigureFonts = true;
ImGuiWindowUICompositor compositor(std::move(host));
D3D12WindowRenderer renderer = {};
bool configureFontsCalled = false;
EXPECT_TRUE(compositor.Initialize(
MakeFakeHwnd(),
renderer,
[&configureFontsCalled]() { configureFontsCalled = true; }));
EXPECT_TRUE(configureFontsCalled);
ASSERT_NE(hostPtr, nullptr);
EXPECT_EQ(hostPtr->initializeCount, 1);
EXPECT_EQ(hostPtr->lastHwnd, MakeFakeHwnd());
EXPECT_EQ(hostPtr->initializedRenderer, &renderer);
}
TEST(ImGuiWindowUICompositorTest, RenderFrameCallsHostBeginUiAndPresentInOrder) {
auto host = std::make_unique<RecordingHostCompositor>();
RecordingHostCompositor* hostPtr = host.get();
ImGuiWindowUICompositor compositor(std::move(host));
D3D12WindowRenderer renderer = {};
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), renderer, {}));
hostPtr->callOrder.clear();
bool uiRendered = false;
constexpr float clearColor[4] = { 0.1f, 0.2f, 0.3f, 0.4f };
compositor.RenderFrame(
clearColor,
[&]() {
uiRendered = true;
hostPtr->callOrder.push_back("ui");
},
[](const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&) {},
[](const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&) {});
EXPECT_TRUE(uiRendered);
EXPECT_EQ(hostPtr->beginFrameCount, 1);
EXPECT_EQ(hostPtr->endFrameCount, 1);
EXPECT_EQ(hostPtr->presentedRenderer, &renderer);
EXPECT_TRUE(hostPtr->beforeUiRenderProvided);
EXPECT_TRUE(hostPtr->afterUiRenderProvided);
EXPECT_EQ(hostPtr->lastClearColor[0], clearColor[0]);
EXPECT_EQ(hostPtr->lastClearColor[1], clearColor[1]);
EXPECT_EQ(hostPtr->lastClearColor[2], clearColor[2]);
EXPECT_EQ(hostPtr->lastClearColor[3], clearColor[3]);
EXPECT_EQ(
hostPtr->callOrder,
(std::vector<std::string>{ "begin", "ui", "present" }));
}
TEST(ImGuiWindowUICompositorTest, HandleWindowMessageAndTextureRegistrationForwardToHost) {
auto host = std::make_unique<RecordingHostCompositor>();
RecordingHostCompositor* hostPtr = host.get();
hostPtr->handleWindowMessageResult = true;
hostPtr->createTextureDescriptorResult = true;
hostPtr->nextRegistration = MakeRegistration();
ImGuiWindowUICompositor compositor(std::move(host));
EXPECT_TRUE(compositor.HandleWindowMessage(MakeFakeHwnd(), WM_SIZE, 7u, 19u));
EXPECT_EQ(hostPtr->handleWindowMessageCount, 1);
EXPECT_EQ(hostPtr->lastMessage, static_cast<UINT>(WM_SIZE));
EXPECT_EQ(hostPtr->lastWParam, static_cast<WPARAM>(7u));
EXPECT_EQ(hostPtr->lastLParam, static_cast<LPARAM>(19u));
UITextureRegistration registration = {};
auto* fakeDevice = reinterpret_cast<::XCEngine::RHI::RHIDevice*>(static_cast<std::uintptr_t>(0x41u));
auto* fakeTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(static_cast<std::uintptr_t>(0x59u));
EXPECT_TRUE(compositor.CreateTextureDescriptor(fakeDevice, fakeTexture, registration));
EXPECT_EQ(hostPtr->createTextureDescriptorCount, 1);
EXPECT_EQ(hostPtr->lastDevice, fakeDevice);
EXPECT_EQ(hostPtr->lastTexture, fakeTexture);
EXPECT_EQ(registration.cpuHandle.ptr, hostPtr->nextRegistration.cpuHandle.ptr);
EXPECT_EQ(registration.gpuHandle.ptr, hostPtr->nextRegistration.gpuHandle.ptr);
EXPECT_EQ(registration.texture.nativeHandle, hostPtr->nextRegistration.texture.nativeHandle);
compositor.FreeTextureDescriptor(registration);
EXPECT_EQ(hostPtr->freeTextureDescriptorCount, 1);
EXPECT_EQ(hostPtr->freedRegistration.cpuHandle.ptr, registration.cpuHandle.ptr);
EXPECT_EQ(hostPtr->freedRegistration.gpuHandle.ptr, registration.gpuHandle.ptr);
EXPECT_EQ(hostPtr->freedRegistration.texture.nativeHandle, registration.texture.nativeHandle);
}
TEST(ImGuiWindowUICompositorTest, ShutdownClearsRendererBindingAndPreventsFurtherRender) {
auto host = std::make_unique<RecordingHostCompositor>();
RecordingHostCompositor* hostPtr = host.get();
ImGuiWindowUICompositor compositor(std::move(host));
D3D12WindowRenderer renderer = {};
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), renderer, {}));
bool firstUiRendered = false;
compositor.RenderFrame(
std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f }.data(),
[&]() { firstUiRendered = true; },
{},
{});
EXPECT_TRUE(firstUiRendered);
EXPECT_EQ(hostPtr->beginFrameCount, 1);
EXPECT_EQ(hostPtr->endFrameCount, 1);
compositor.Shutdown();
EXPECT_EQ(hostPtr->shutdownCount, 1);
bool secondUiRendered = false;
compositor.RenderFrame(
std::array<float, 4>{ 1.0f, 0.0f, 0.0f, 1.0f }.data(),
[&]() { secondUiRendered = true; },
{},
{});
EXPECT_FALSE(secondUiRendered);
EXPECT_EQ(hostPtr->beginFrameCount, 1);
EXPECT_EQ(hostPtr->endFrameCount, 1);
}
TEST(ImGuiWindowUICompositorTest, NullHostCompositorReturnsSafeDefaults) {
ImGuiWindowUICompositor compositor(std::unique_ptr<IEditorHostCompositor>{});
D3D12WindowRenderer renderer = {};
bool configureFontsCalled = false;
EXPECT_FALSE(compositor.Initialize(
MakeFakeHwnd(),
renderer,
[&configureFontsCalled]() { configureFontsCalled = true; }));
EXPECT_FALSE(configureFontsCalled);
EXPECT_FALSE(compositor.HandleWindowMessage(MakeFakeHwnd(), WM_CLOSE, 0u, 0u));
UITextureRegistration registration = {};
EXPECT_FALSE(compositor.CreateTextureDescriptor(nullptr, nullptr, registration));
bool uiRendered = false;
compositor.RenderFrame(
std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f }.data(),
[&]() { uiRendered = true; },
{},
{});
EXPECT_FALSE(uiRendered);
compositor.Shutdown();
}
} // namespace