Add XCUI schema document regression coverage
This commit is contained in:
@@ -9,8 +9,11 @@ Old `editor` replacement is explicitly out of scope for this phase.
|
|||||||
|
|
||||||
- Phase 1 sandbox batch committed and pushed as `67a28bd` (`Add XCUI new editor sandbox phase 1`).
|
- Phase 1 sandbox batch committed and pushed as `67a28bd` (`Add XCUI new editor sandbox phase 1`).
|
||||||
- Phase 2 common/runtime batch committed and pushed as `ade5be3` (`Add XCUI runtime screen layer and demo textarea`).
|
- Phase 2 common/runtime batch committed and pushed as `ade5be3` (`Add XCUI runtime screen layer and demo textarea`).
|
||||||
- Current work has moved into Phase 3: stabilize schema/validation and continue filling the remaining common/runtime/editor gaps instead of replacing the old editor.
|
- Phase 3 has now produced a stable mixed batch across common/runtime/editor:
|
||||||
- The current stable editor-layer batch is centered on `LayoutLab` as the widget proving ground for tree/list/property-section style controls.
|
- schema document definition data is now retained on `UIDocumentModel` and round-trips through the UI artifact path
|
||||||
|
- engine runtime coverage was tightened again around `UISystem` and concrete document-host rendering
|
||||||
|
- `LayoutLab` continues as the editor widget proving ground for tree/list/property-section style controls
|
||||||
|
- Old `editor` replacement remains deferred; all active execution still stays inside XCUI shared code and `new_editor`.
|
||||||
|
|
||||||
## Three-Layer Status
|
## Three-Layer Status
|
||||||
|
|
||||||
@@ -18,6 +21,8 @@ Old `editor` replacement is explicitly out of scope for this phase.
|
|||||||
|
|
||||||
- `UI::DrawData`, input event types, focus routing, style/theme resolution are in active use.
|
- `UI::DrawData`, input event types, focus routing, style/theme resolution are in active use.
|
||||||
- `UIDocumentCompiler` is buildable again after repairing the duplicated schema helper regression introduced by overlapping schema work.
|
- `UIDocumentCompiler` is buildable again after repairing the duplicated schema helper regression introduced by overlapping schema work.
|
||||||
|
- `UIDocumentModel` / `UIDocumentResource` now retain schema definition metadata explicitly, including memory accounting and `UISchema` accessors.
|
||||||
|
- `.xcschema` round-trip coverage is now present through compile, loader, artifact write, and artifact read paths.
|
||||||
- Build-system hardening for MSVC/PDB output paths has started in root CMake, `engine/CMakeLists.txt`, `new_editor/CMakeLists.txt`, and `tests/NewEditor/CMakeLists.txt`.
|
- Build-system hardening for MSVC/PDB output paths has started in root CMake, `engine/CMakeLists.txt`, `new_editor/CMakeLists.txt`, and `tests/NewEditor/CMakeLists.txt`.
|
||||||
- Shared engine-side XCUI runtime scaffolding is now present under `engine/include/XCEngine/UI/Runtime` and `engine/src/UI/Runtime`.
|
- Shared engine-side XCUI runtime scaffolding is now present under `engine/include/XCEngine/UI/Runtime` and `engine/src/UI/Runtime`.
|
||||||
- Shared engine-side `UIDocumentScreenHost` now compiles `.xcui` / `.xctheme` screen documents into a runtime-facing document host path instead of leaving all document ownership in `new_editor`.
|
- Shared engine-side `UIDocumentScreenHost` now compiles `.xcui` / `.xctheme` screen documents into a runtime-facing document host path instead of leaving all document ownership in `new_editor`.
|
||||||
@@ -25,7 +30,7 @@ Old `editor` replacement is explicitly out of scope for this phase.
|
|||||||
|
|
||||||
Current gap:
|
Current gap:
|
||||||
|
|
||||||
- Schema/validation is not yet landed in a stable form.
|
- Minimal schema self-definition support is landed, but schema-driven validation for `.xcui` / `.xctheme` instances is still not implemented.
|
||||||
- Shared widget/runtime instantiation is still thin and mostly editor-side.
|
- Shared widget/runtime instantiation is still thin and mostly editor-side.
|
||||||
- Common widget primitives are still incomplete: multiline text editing, tree/list virtualization, property-grid composition, and native image/source-rect level APIs.
|
- Common widget primitives are still incomplete: multiline text editing, tree/list virtualization, property-grid composition, and native image/source-rect level APIs.
|
||||||
|
|
||||||
@@ -35,6 +40,7 @@ Current gap:
|
|||||||
- The demo runtime has moved past single-line input: multiline `TextArea` behavior is now covered in the sandbox testbed.
|
- The demo runtime has moved past single-line input: multiline `TextArea` behavior is now covered in the sandbox testbed.
|
||||||
- Engine-side runtime ownership is no longer zero: `UIScreenPlayer`, `UIDocumentScreenHost`, and `UISystem` now define a shared runtime contract for loading a screen document, ticking it with input, and collecting `UI::UIDrawData`.
|
- Engine-side runtime ownership is no longer zero: `UIScreenPlayer`, `UIDocumentScreenHost`, and `UISystem` now define a shared runtime contract for loading a screen document, ticking it with input, and collecting `UI::UIDrawData`.
|
||||||
- `UISystem` now supports layered screen composition semantics: stacked screen players, top-interactive input routing, and modal layers that block lower screens.
|
- `UISystem` now supports layered screen composition semantics: stacked screen players, top-interactive input routing, and modal layers that block lower screens.
|
||||||
|
- Runtime screen emission now also carries concrete button text in the shared document host path instead of silently dropping button labels.
|
||||||
|
|
||||||
Current gap:
|
Current gap:
|
||||||
|
|
||||||
@@ -66,6 +72,7 @@ Current gap:
|
|||||||
- `XCNewEditor` Debug target builds successfully
|
- `XCNewEditor` Debug target builds successfully
|
||||||
- `core_ui_tests`: `14/14`
|
- `core_ui_tests`: `14/14`
|
||||||
- `core_ui_style_tests`: `5/5`
|
- `core_ui_style_tests`: `5/5`
|
||||||
|
- `ui_resource_tests`: `7/7`
|
||||||
|
|
||||||
## Landed This Phase
|
## Landed This Phase
|
||||||
|
|
||||||
@@ -74,11 +81,17 @@ Current gap:
|
|||||||
- Demo authored resources updated to exercise the input field.
|
- Demo authored resources updated to exercise the input field.
|
||||||
- LayoutLab `ScrollView` prototype with clipping and hover rejection outside clipped content.
|
- LayoutLab `ScrollView` prototype with clipping and hover rejection outside clipped content.
|
||||||
- LayoutLab editor-widget prototypes for tree/list/property-style sections with dedicated runtime coverage.
|
- LayoutLab editor-widget prototypes for tree/list/property-style sections with dedicated runtime coverage.
|
||||||
|
- Schema document support extended with:
|
||||||
|
- retained `UISchemaDefinition` data on `UIDocumentModel`
|
||||||
|
- artifact schema version bump for UI documents
|
||||||
|
- loader/resource accessors and memory accounting
|
||||||
|
- schema compile/load/artifact regression coverage
|
||||||
- Engine runtime layer added:
|
- Engine runtime layer added:
|
||||||
- `UIScreenPlayer`
|
- `UIScreenPlayer`
|
||||||
- `UIDocumentScreenHost`
|
- `UIDocumentScreenHost`
|
||||||
- `UISystem`
|
- `UISystem`
|
||||||
- layered screen composition and modal blocking semantics
|
- layered screen composition and modal blocking semantics
|
||||||
|
- Runtime document-host draw emission now preserves button labels for shared screen rendering.
|
||||||
- RHI image path improvements:
|
- RHI image path improvements:
|
||||||
- clipped image UV adjustment
|
- clipped image UV adjustment
|
||||||
- mirrored image UV preservation
|
- mirrored image UV preservation
|
||||||
@@ -91,7 +104,7 @@ Current gap:
|
|||||||
|
|
||||||
## Phase Risks Still Open
|
## Phase Risks Still Open
|
||||||
|
|
||||||
- Schema/validation still needs a clean minimal landing instead of the current partially merged state in `UIDocumentCompiler.cpp`.
|
- Schema instance validation is still open beyond `.xcschema` self-definition and artifact round-trip coverage.
|
||||||
- `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet.
|
- `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet.
|
||||||
- `Image` widgets still do not have source-rect/atlas-subregion level API in the high-level draw command model.
|
- `Image` widgets still do not have source-rect/atlas-subregion level API in the high-level draw command model.
|
||||||
- Editor shell still depends on ImGui as host chrome.
|
- Editor shell still depends on ImGui as host chrome.
|
||||||
@@ -99,8 +112,11 @@ Current gap:
|
|||||||
|
|
||||||
## Next Phase
|
## Next Phase
|
||||||
|
|
||||||
1. Cleanly stabilize schema/validation in `UIDocumentCompiler.cpp` and add targeted schema regression tests.
|
1. Expand schema rules just one level further inside `UIDocumentCompiler.cpp`:
|
||||||
|
- `allowedValues` only for `Enum`
|
||||||
|
- `documentKind` / `restrictDocumentKind` only for `Document`
|
||||||
|
- `restrictDocumentKind=true` requires explicit `documentKind`
|
||||||
2. Expand runtime/game-layer ownership from the current document host + layered `UISystem` into reusable menu/HUD stack patterns and engine runtime integration.
|
2. Expand runtime/game-layer ownership from the current document host + layered `UISystem` into reusable menu/HUD stack patterns and engine runtime integration.
|
||||||
3. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu and more native shell-owned chrome.
|
3. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu and more native shell-owned chrome.
|
||||||
4. Move more diagnostics and shell affordances into XCUI-owned editor-layer surfaces instead of only ImGui HUDs.
|
4. Start the window-level compositor split in `new_editor` so the editor shell can run through `ImGuiHostCompositor` first and then grow a native XCUI compositor path on the same seam.
|
||||||
5. Continue phased validation, commit, push, and plan refresh after each stable batch.
|
5. Continue phased validation, commit, push, and plan refresh after each stable batch.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ constexpr Core::uint32 kTextureArtifactSchemaVersion = 1;
|
|||||||
constexpr Core::uint32 kMaterialArtifactSchemaVersion = 2;
|
constexpr Core::uint32 kMaterialArtifactSchemaVersion = 2;
|
||||||
constexpr Core::uint32 kMeshArtifactSchemaVersion = 2;
|
constexpr Core::uint32 kMeshArtifactSchemaVersion = 2;
|
||||||
constexpr Core::uint32 kShaderArtifactSchemaVersion = 1;
|
constexpr Core::uint32 kShaderArtifactSchemaVersion = 1;
|
||||||
constexpr Core::uint32 kUIDocumentArtifactSchemaVersion = 1;
|
constexpr Core::uint32 kUIDocumentArtifactSchemaVersion = 2;
|
||||||
|
|
||||||
struct TextureArtifactHeader {
|
struct TextureArtifactHeader {
|
||||||
char magic[8] = { 'X', 'C', 'T', 'E', 'X', '0', '1', '\0' };
|
char magic[8] = { 'X', 'C', 'T', 'E', 'X', '0', '1', '\0' };
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ enum class UIDocumentDiagnosticSeverity : Core::uint8 {
|
|||||||
Error
|
Error
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class UISchemaValueType : Core::uint8 {
|
||||||
|
String = 0,
|
||||||
|
Boolean,
|
||||||
|
Integer,
|
||||||
|
Number,
|
||||||
|
Document,
|
||||||
|
Enum
|
||||||
|
};
|
||||||
|
|
||||||
struct UIDocumentSourceLocation {
|
struct UIDocumentSourceLocation {
|
||||||
Core::uint32 line = 1;
|
Core::uint32 line = 1;
|
||||||
Core::uint32 column = 1;
|
Core::uint32 column = 1;
|
||||||
@@ -35,6 +44,67 @@ struct UIDocumentDiagnostic {
|
|||||||
Containers::String message;
|
Containers::String message;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct UISchemaAttributeDefinition {
|
||||||
|
Containers::String name;
|
||||||
|
UISchemaValueType valueType = UISchemaValueType::String;
|
||||||
|
UIDocumentKind documentKind = UIDocumentKind::View;
|
||||||
|
Containers::Array<Containers::String> allowedValues;
|
||||||
|
UIDocumentSourceLocation location = {};
|
||||||
|
bool required = false;
|
||||||
|
bool restrictDocumentKind = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UISchemaElementDefinition {
|
||||||
|
Containers::String tagName;
|
||||||
|
Containers::Array<UISchemaAttributeDefinition> attributes;
|
||||||
|
Containers::Array<UISchemaElementDefinition> children;
|
||||||
|
UIDocumentSourceLocation location = {};
|
||||||
|
bool allowUnknownAttributes = false;
|
||||||
|
bool allowUnknownChildren = false;
|
||||||
|
|
||||||
|
const UISchemaAttributeDefinition* FindAttribute(const Containers::String& name) const {
|
||||||
|
for (const UISchemaAttributeDefinition& attribute : attributes) {
|
||||||
|
if (attribute.name == name) {
|
||||||
|
return &attribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UISchemaElementDefinition* FindChild(const Containers::String& tagNameValue) const {
|
||||||
|
for (const UISchemaElementDefinition& child : children) {
|
||||||
|
if (child.tagName == tagNameValue) {
|
||||||
|
return &child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UISchemaDefinition {
|
||||||
|
Containers::String name;
|
||||||
|
Containers::Array<UISchemaElementDefinition> elements;
|
||||||
|
bool valid = false;
|
||||||
|
|
||||||
|
const UISchemaElementDefinition* FindElement(const Containers::String& tagNameValue) const {
|
||||||
|
for (const UISchemaElementDefinition& element : elements) {
|
||||||
|
if (element.tagName == tagNameValue) {
|
||||||
|
return &element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clear() {
|
||||||
|
name.Clear();
|
||||||
|
elements.Clear();
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct UIDocumentNode {
|
struct UIDocumentNode {
|
||||||
Containers::String tagName;
|
Containers::String tagName;
|
||||||
Containers::Array<UIDocumentAttribute> attributes;
|
Containers::Array<UIDocumentAttribute> attributes;
|
||||||
@@ -58,6 +128,7 @@ struct UIDocumentModel {
|
|||||||
Containers::String sourcePath;
|
Containers::String sourcePath;
|
||||||
Containers::String displayName;
|
Containers::String displayName;
|
||||||
UIDocumentNode rootNode;
|
UIDocumentNode rootNode;
|
||||||
|
UISchemaDefinition schemaDefinition;
|
||||||
Containers::Array<Containers::String> dependencies;
|
Containers::Array<Containers::String> dependencies;
|
||||||
Containers::Array<UIDocumentDiagnostic> diagnostics;
|
Containers::Array<UIDocumentDiagnostic> diagnostics;
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
@@ -66,6 +137,7 @@ struct UIDocumentModel {
|
|||||||
sourcePath.Clear();
|
sourcePath.Clear();
|
||||||
displayName.Clear();
|
displayName.Clear();
|
||||||
rootNode = UIDocumentNode();
|
rootNode = UIDocumentNode();
|
||||||
|
schemaDefinition.Clear();
|
||||||
dependencies.Clear();
|
dependencies.Clear();
|
||||||
diagnostics.Clear();
|
diagnostics.Clear();
|
||||||
valid = false;
|
valid = false;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public:
|
|||||||
|
|
||||||
const UIDocumentModel& GetDocument() const { return m_document; }
|
const UIDocumentModel& GetDocument() const { return m_document; }
|
||||||
const UIDocumentNode& GetRootNode() const { return m_document.rootNode; }
|
const UIDocumentNode& GetRootNode() const { return m_document.rootNode; }
|
||||||
|
const UISchemaDefinition& GetSchemaDefinition() const { return m_document.schemaDefinition; }
|
||||||
const Containers::Array<Containers::String>& GetDependencies() const { return m_document.dependencies; }
|
const Containers::Array<Containers::String>& GetDependencies() const { return m_document.dependencies; }
|
||||||
const Containers::Array<UIDocumentDiagnostic>& GetDiagnostics() const { return m_document.diagnostics; }
|
const Containers::Array<UIDocumentDiagnostic>& GetDiagnostics() const { return m_document.diagnostics; }
|
||||||
const Containers::String& GetSourcePath() const { return m_document.sourcePath; }
|
const Containers::String& GetSourcePath() const { return m_document.sourcePath; }
|
||||||
|
|||||||
@@ -24,6 +24,33 @@ size_t MeasureDiagnosticMemorySize(const UIDocumentDiagnostic& diagnostic) {
|
|||||||
return sizeof(UIDocumentDiagnostic) + diagnostic.message.Length();
|
return sizeof(UIDocumentDiagnostic) + diagnostic.message.Length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t MeasureSchemaAttributeMemorySize(const UISchemaAttributeDefinition& attribute) {
|
||||||
|
size_t size = sizeof(UISchemaAttributeDefinition) + attribute.name.Length();
|
||||||
|
for (const Containers::String& value : attribute.allowedValues) {
|
||||||
|
size += sizeof(Containers::String) + value.Length();
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t MeasureSchemaElementMemorySize(const UISchemaElementDefinition& element) {
|
||||||
|
size_t size = sizeof(UISchemaElementDefinition) + element.tagName.Length();
|
||||||
|
for (const UISchemaAttributeDefinition& attribute : element.attributes) {
|
||||||
|
size += MeasureSchemaAttributeMemorySize(attribute);
|
||||||
|
}
|
||||||
|
for (const UISchemaElementDefinition& child : element.children) {
|
||||||
|
size += MeasureSchemaElementMemorySize(child);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t MeasureSchemaDefinitionMemorySize(const UISchemaDefinition& schemaDefinition) {
|
||||||
|
size_t size = sizeof(UISchemaDefinition) + schemaDefinition.name.Length();
|
||||||
|
for (const UISchemaElementDefinition& element : schemaDefinition.elements) {
|
||||||
|
size += MeasureSchemaElementMemorySize(element);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void UIDocumentResource::Release() {
|
void UIDocumentResource::Release() {
|
||||||
@@ -47,6 +74,7 @@ void UIDocumentResource::RecalculateMemorySize() {
|
|||||||
size += m_document.sourcePath.Length();
|
size += m_document.sourcePath.Length();
|
||||||
size += m_document.displayName.Length();
|
size += m_document.displayName.Length();
|
||||||
size += MeasureNodeMemorySize(m_document.rootNode);
|
size += MeasureNodeMemorySize(m_document.rootNode);
|
||||||
|
size += MeasureSchemaDefinitionMemorySize(m_document.schemaDefinition);
|
||||||
for (const Containers::String& dependency : m_document.dependencies) {
|
for (const Containers::String& dependency : m_document.dependencies) {
|
||||||
size += sizeof(Containers::String) + dependency.Length();
|
size += sizeof(Containers::String) + dependency.Length();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
set(UI_RESOURCE_TEST_SOURCES
|
set(UI_RESOURCE_TEST_SOURCES
|
||||||
test_ui_document_loader.cpp
|
test_ui_document_loader.cpp
|
||||||
|
test_ui_schema_document.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(ui_resource_tests ${UI_RESOURCE_TEST_SOURCES})
|
add_executable(ui_resource_tests ${UI_RESOURCE_TEST_SOURCES})
|
||||||
|
|||||||
153
tests/Resources/UI/test_ui_schema_document.cpp
Normal file
153
tests/Resources/UI/test_ui_schema_document.cpp
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
|
||||||
|
#include <XCEngine/Resources/UI/UIDocumentLoaders.h>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
using namespace XCEngine::Resources;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void WriteTextFile(const std::filesystem::path& path, const std::string& contents) {
|
||||||
|
std::filesystem::create_directories(path.parent_path());
|
||||||
|
std::ofstream output(path, std::ios::binary | std::ios::trunc);
|
||||||
|
ASSERT_TRUE(output.is_open());
|
||||||
|
output << contents;
|
||||||
|
ASSERT_TRUE(static_cast<bool>(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UISchemaDocument, CompileAndArtifactLoadPopulateSchemaDefinition) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
const fs::path root = fs::temp_directory_path() / "xc_ui_schema_compile_test";
|
||||||
|
const fs::path schemaPath = root / "markup.xcschema";
|
||||||
|
const fs::path artifactPath = root / "markup.xcschemaasset";
|
||||||
|
fs::remove_all(root);
|
||||||
|
|
||||||
|
WriteTextFile(
|
||||||
|
schemaPath,
|
||||||
|
"<Schema name=\"EditorMarkup\">\n"
|
||||||
|
" <Element tag=\"View\" allowUnknownChildren=\"true\">\n"
|
||||||
|
" <Attribute name=\"theme\" type=\"document\" kind=\"theme\" />\n"
|
||||||
|
" <Attribute name=\"mode\" type=\"enum\" values=\"compact, cozy\" />\n"
|
||||||
|
" <Element tag=\"Column\">\n"
|
||||||
|
" <Attribute name=\"gap\" type=\"number\" />\n"
|
||||||
|
" </Element>\n"
|
||||||
|
" </Element>\n"
|
||||||
|
"</Schema>\n");
|
||||||
|
|
||||||
|
UISchemaLoader loader;
|
||||||
|
UIDocumentCompileResult compileResult = {};
|
||||||
|
ASSERT_TRUE(loader.CompileDocument(schemaPath.string().c_str(), compileResult));
|
||||||
|
ASSERT_TRUE(compileResult.succeeded);
|
||||||
|
ASSERT_TRUE(compileResult.document.valid);
|
||||||
|
ASSERT_TRUE(compileResult.document.schemaDefinition.valid);
|
||||||
|
EXPECT_EQ(compileResult.document.schemaDefinition.name, "EditorMarkup");
|
||||||
|
|
||||||
|
const UISchemaElementDefinition* viewElement =
|
||||||
|
compileResult.document.schemaDefinition.FindElement("View");
|
||||||
|
ASSERT_NE(viewElement, nullptr);
|
||||||
|
EXPECT_TRUE(viewElement->allowUnknownChildren);
|
||||||
|
|
||||||
|
const UISchemaAttributeDefinition* themeAttribute = viewElement->FindAttribute("theme");
|
||||||
|
ASSERT_NE(themeAttribute, nullptr);
|
||||||
|
EXPECT_EQ(themeAttribute->valueType, UISchemaValueType::Document);
|
||||||
|
EXPECT_TRUE(themeAttribute->restrictDocumentKind);
|
||||||
|
EXPECT_EQ(themeAttribute->documentKind, UIDocumentKind::Theme);
|
||||||
|
|
||||||
|
const UISchemaAttributeDefinition* modeAttribute = viewElement->FindAttribute("mode");
|
||||||
|
ASSERT_NE(modeAttribute, nullptr);
|
||||||
|
EXPECT_EQ(modeAttribute->valueType, UISchemaValueType::Enum);
|
||||||
|
ASSERT_EQ(modeAttribute->allowedValues.Size(), 2u);
|
||||||
|
EXPECT_EQ(modeAttribute->allowedValues[0], "compact");
|
||||||
|
EXPECT_EQ(modeAttribute->allowedValues[1], "cozy");
|
||||||
|
|
||||||
|
LoadResult loadResult = loader.Load(schemaPath.string().c_str());
|
||||||
|
ASSERT_TRUE(loadResult);
|
||||||
|
ASSERT_NE(loadResult.resource, nullptr);
|
||||||
|
auto* schemaResource = static_cast<UISchema*>(loadResult.resource);
|
||||||
|
ASSERT_NE(schemaResource, nullptr);
|
||||||
|
ASSERT_TRUE(schemaResource->GetSchemaDefinition().valid);
|
||||||
|
ASSERT_NE(schemaResource->GetSchemaDefinition().FindElement("View"), nullptr);
|
||||||
|
delete schemaResource;
|
||||||
|
|
||||||
|
XCEngine::Containers::String artifactWriteError;
|
||||||
|
ASSERT_TRUE(
|
||||||
|
WriteUIDocumentArtifact(artifactPath.string().c_str(), compileResult, &artifactWriteError))
|
||||||
|
<< artifactWriteError.CStr();
|
||||||
|
|
||||||
|
UIDocumentCompileResult artifactResult = {};
|
||||||
|
ASSERT_TRUE(LoadUIDocumentArtifact(
|
||||||
|
artifactPath.string().c_str(),
|
||||||
|
UIDocumentKind::Schema,
|
||||||
|
artifactResult));
|
||||||
|
ASSERT_TRUE(artifactResult.succeeded);
|
||||||
|
ASSERT_TRUE(artifactResult.document.valid);
|
||||||
|
ASSERT_TRUE(artifactResult.document.schemaDefinition.valid);
|
||||||
|
|
||||||
|
const UISchemaElementDefinition* artifactViewElement =
|
||||||
|
artifactResult.document.schemaDefinition.FindElement("View");
|
||||||
|
ASSERT_NE(artifactViewElement, nullptr);
|
||||||
|
ASSERT_NE(artifactViewElement->FindChild("Column"), nullptr);
|
||||||
|
|
||||||
|
fs::remove_all(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UISchemaDocument, CompileRejectsInvalidSchemaFlags) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
const fs::path root = fs::temp_directory_path() / "xc_ui_schema_invalid_flag_test";
|
||||||
|
const fs::path schemaPath = root / "broken.xcschema";
|
||||||
|
fs::remove_all(root);
|
||||||
|
|
||||||
|
WriteTextFile(
|
||||||
|
schemaPath,
|
||||||
|
"<Schema>\n"
|
||||||
|
" <Element tag=\"View\" allowUnknownChildren=\"sometimes\" />\n"
|
||||||
|
"</Schema>\n");
|
||||||
|
|
||||||
|
UISchemaLoader loader;
|
||||||
|
UIDocumentCompileResult compileResult = {};
|
||||||
|
EXPECT_FALSE(loader.CompileDocument(schemaPath.string().c_str(), compileResult));
|
||||||
|
EXPECT_FALSE(compileResult.succeeded);
|
||||||
|
EXPECT_FALSE(compileResult.errorMessage.Empty());
|
||||||
|
ASSERT_FALSE(compileResult.document.diagnostics.Empty());
|
||||||
|
|
||||||
|
const std::string errorMessage = compileResult.errorMessage.CStr();
|
||||||
|
EXPECT_NE(errorMessage.find("allowUnknownChildren"), std::string::npos);
|
||||||
|
|
||||||
|
fs::remove_all(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UISchemaDocument, CompileRejectsDuplicateAttributeDefinitions) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
const fs::path root = fs::temp_directory_path() / "xc_ui_schema_duplicate_attribute_test";
|
||||||
|
const fs::path schemaPath = root / "duplicate.xcschema";
|
||||||
|
fs::remove_all(root);
|
||||||
|
|
||||||
|
WriteTextFile(
|
||||||
|
schemaPath,
|
||||||
|
"<Schema>\n"
|
||||||
|
" <Element tag=\"View\">\n"
|
||||||
|
" <Attribute name=\"id\" type=\"string\" />\n"
|
||||||
|
" <Attribute name=\"id\" type=\"string\" />\n"
|
||||||
|
" </Element>\n"
|
||||||
|
"</Schema>\n");
|
||||||
|
|
||||||
|
UISchemaLoader loader;
|
||||||
|
UIDocumentCompileResult compileResult = {};
|
||||||
|
EXPECT_FALSE(loader.CompileDocument(schemaPath.string().c_str(), compileResult));
|
||||||
|
EXPECT_FALSE(compileResult.succeeded);
|
||||||
|
EXPECT_FALSE(compileResult.errorMessage.Empty());
|
||||||
|
ASSERT_FALSE(compileResult.document.diagnostics.Empty());
|
||||||
|
|
||||||
|
const std::string errorMessage = compileResult.errorMessage.CStr();
|
||||||
|
EXPECT_NE(errorMessage.find("Duplicate schema attribute definition"), std::string::npos);
|
||||||
|
|
||||||
|
fs::remove_all(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
Reference in New Issue
Block a user