Skip to content

Commit 42913b0

Browse files
authored
Increase library tests (#80)
1 parent d844258 commit 42913b0

36 files changed

+11729
-146
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ Generate the build system files with CMake.
177177
For a standard desktop build with tests and examples enabled, run:
178178

179179
```bash
180-
cmake . -B build -DYUP_ENABLE_TESTS=ON -DYUP_ENABLE_EXAMPLES=ON
180+
cmake . -B build -DYUP_BUILD_TESTS=ON -DYUP_BUILD_EXAMPLES=ON
181181
cmake --build build --config Release --parallel 4
182182
```
183183

@@ -186,7 +186,7 @@ cmake --build build --config Release --parallel 4
186186
Android will rely on cmake for configuration and gradlew will again call into cmake to build the native part of yup:
187187

188188
```bash
189-
cmake -G "Ninja Multi-Config" . -B build -DYUP_TARGET_ANDROID=ON -DYUP_ENABLE_TESTS=ON -DYUP_ENABLE_EXAMPLES=ON
189+
cmake -G "Ninja Multi-Config" . -B build -DYUP_TARGET_ANDROID=ON -DYUP_BUILD_TESTS=ON -DYUP_BUILD_EXAMPLES=ON
190190
cd build/examples/render
191191
./gradlew assembleRelease
192192
# ./gradlew assembleDebug

cmake/yup_standalone.cmake

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function (yup_standalone_app)
8585

8686
# ==== Enable profiling
8787
if (YUP_ENABLE_PROFILING AND NOT "${target_name}" STREQUAL "yup_tests")
88-
list (APPEND additional_definitions YUP_ENABLE_PROFILING=1 YUP_ENABLE_PROFILING=1)
88+
list (APPEND additional_definitions YUP_ENABLE_PROFILING=1)
8989
list (APPEND additional_libraries perfetto::perfetto)
9090
endif()
9191

@@ -184,11 +184,11 @@ function (yup_standalone_app)
184184
-sFETCH=1
185185
#-sASYNCIFY=1
186186
-sEXPORTED_RUNTIME_METHODS=ccall,cwrap
187-
-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='$dynCall','$stackTrace'
188-
--shell-file "${YUP_ARG_CUSTOM_SHELL}")
187+
-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='$dynCall'
188+
--shell-file=${YUP_ARG_CUSTOM_SHELL})
189189

190-
foreach (preload_file ${YUP_ARG_PRELOAD_FILES})
191-
list (APPEND additional_link_options --preload-file ${preload_file})
190+
foreach (preload_file IN ITEMS ${YUP_ARG_PRELOAD_FILES})
191+
list (APPEND additional_link_options "--preload-file=${preload_file}")
192192
endforeach()
193193

194194
set (target_copy_dest "$<TARGET_FILE_DIR:${target_name}>")

modules/yup_core/system/yup_SystemStats.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ String SystemStats::getStackBacktrace()
252252

253253
#elif YUP_EMSCRIPTEN
254254
std::string temporaryStack;
255-
temporaryStack.resize (10 * EM_ASM_INT_V ({ return (lengthBytesUTF8 || Module.lengthBytesUTF8) (stackTrace()); }));
256-
EM_ASM_ARGS ({ (stringToUTF8 || Module.stringToUTF8) (stackTrace(), $0, $1); }, temporaryStack.data(), temporaryStack.size());
255+
temporaryStack.resize (emscripten_get_callstack (EM_LOG_C_STACK, nullptr, 0));
256+
emscripten_get_callstack (EM_LOG_C_STACK, temporaryStack.data(), static_cast<int> (temporaryStack.size()));
257257
result << temporaryStack.c_str();
258258

259259
#elif YUP_WINDOWS

modules/yup_data_model/tree/yup_DataTree.cpp

Lines changed: 155 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,103 @@ namespace yup
2424

2525
//==============================================================================
2626

27+
namespace
28+
{
29+
var coerceAttributeValue (const Identifier& nodeType,
30+
const Identifier& propertyName,
31+
const String& rawValue,
32+
const ReferenceCountedObjectPtr<DataTreeSchema>& schema)
33+
{
34+
if (schema == nullptr)
35+
return var (rawValue);
36+
37+
auto info = schema->getPropertyInfo (nodeType, propertyName);
38+
if (info.type.isEmpty())
39+
return var (rawValue);
40+
41+
const auto trimmed = rawValue.trim();
42+
43+
const auto looksLikeInteger = [] (const String& text)
44+
{
45+
if (text.isEmpty())
46+
return false;
47+
48+
int start = 0;
49+
if (text.startsWithChar ('-') || text.startsWithChar ('+'))
50+
start = 1;
51+
52+
if (start == text.length())
53+
return false;
54+
55+
for (int i = start; i < text.length(); ++i)
56+
{
57+
if (! CharacterFunctions::isDigit (text[i]))
58+
return false;
59+
}
60+
61+
return true;
62+
};
63+
64+
const auto looksLikeNumber = [] (const String& text)
65+
{
66+
bool hasDigit = false;
67+
68+
for (int i = 0; i < text.length(); ++i)
69+
{
70+
const auto c = text[i];
71+
if (CharacterFunctions::isDigit (c))
72+
{
73+
hasDigit = true;
74+
continue;
75+
}
76+
77+
if (c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')
78+
continue;
79+
80+
return false;
81+
}
82+
83+
return hasDigit;
84+
};
85+
86+
if (info.type == "boolean")
87+
{
88+
if (trimmed.equalsIgnoreCase ("true") || trimmed == "1" || trimmed.equalsIgnoreCase ("yes"))
89+
return var (true);
90+
91+
if (trimmed.equalsIgnoreCase ("false") || trimmed == "0" || trimmed.equalsIgnoreCase ("no"))
92+
return var (false);
93+
94+
return var (rawValue);
95+
}
96+
97+
if (info.type == "number" && looksLikeNumber (trimmed))
98+
{
99+
if (looksLikeInteger (trimmed))
100+
return var (trimmed.getLargeIntValue());
101+
102+
return var (trimmed.getDoubleValue());
103+
}
104+
105+
if ((info.type == "array" || info.type == "object") && trimmed.isNotEmpty())
106+
{
107+
var parsed;
108+
if (JSON::parse (trimmed, parsed))
109+
{
110+
if (info.type == "array" && parsed.isArray())
111+
return parsed;
112+
113+
if (info.type == "object" && parsed.isObject())
114+
return parsed;
115+
}
116+
}
117+
118+
return var (rawValue);
119+
}
120+
} // namespace
121+
122+
//==============================================================================
123+
27124
class PropertySetAction : public UndoableAction
28125
{
29126
public:
@@ -911,13 +1008,7 @@ std::unique_ptr<XmlElement> DataTree::createXml() const
9111008
auto element = std::make_unique<XmlElement> (object->type.toString());
9121009

9131010
// Add properties as attributes
914-
for (int i = 0; i < object->properties.size(); ++i)
915-
{
916-
const auto name = object->properties.getName (i);
917-
const auto value = object->properties.getValueAt (i);
918-
919-
element->setAttribute (name.toString(), value.toString());
920-
}
1011+
object->properties.copyToXmlAttributes (*element);
9211012

9221013
// Add children as child elements
9231014
for (const auto& child : object->children)
@@ -930,21 +1021,28 @@ std::unique_ptr<XmlElement> DataTree::createXml() const
9301021
}
9311022

9321023
DataTree DataTree::fromXml (const XmlElement& xml)
1024+
{
1025+
return fromXml (xml, nullptr);
1026+
}
1027+
1028+
DataTree DataTree::fromXml (const XmlElement& xml, ReferenceCountedObjectPtr<DataTreeSchema> schema)
9331029
{
9341030
DataTree tree (xml.getTagName());
1031+
const auto nodeType = tree.getType();
9351032

9361033
// Load properties from attributes
9371034
for (int i = 0; i < xml.getNumAttributes(); ++i)
9381035
{
939-
const auto name = xml.getAttributeName (i);
940-
const auto value = xml.getAttributeValue (i);
941-
tree.setProperty (name, value);
1036+
auto name = xml.getAttributeName (i);
1037+
auto value = xml.getAttributeValue (i);
1038+
1039+
tree.setProperty (name, coerceAttributeValue (nodeType, name, value, schema));
9421040
}
9431041

9441042
// Load children from child elements
9451043
for (const auto* childXml : xml.getChildIterator())
9461044
{
947-
auto child = fromXml (*childXml);
1045+
auto child = fromXml (*childXml, schema);
9481046
tree.addChild (child);
9491047
}
9501048

@@ -1475,6 +1573,38 @@ void DataTree::Transaction::moveChild (int currentIndex, int newIndex)
14751573
childChanges.push_back (change);
14761574
}
14771575

1576+
int DataTree::Transaction::getEffectiveChildCount() const
1577+
{
1578+
if (dataTree.object == nullptr)
1579+
return 0;
1580+
1581+
int count = dataTree.getNumChildren();
1582+
1583+
for (const auto& change : childChanges)
1584+
{
1585+
switch (change.type)
1586+
{
1587+
case ChildChange::Add:
1588+
++count;
1589+
break;
1590+
1591+
case ChildChange::Remove:
1592+
if (count > 0)
1593+
--count;
1594+
break;
1595+
1596+
case ChildChange::RemoveAll:
1597+
count = 0;
1598+
break;
1599+
1600+
case ChildChange::Move:
1601+
break; // No change in count
1602+
}
1603+
}
1604+
1605+
return std::max (0, count);
1606+
}
1607+
14781608
//==============================================================================
14791609

14801610
DataTree::ValidatedTransaction::ValidatedTransaction (DataTree& tree, ReferenceCountedObjectPtr<DataTreeSchema> schema, UndoManager* undoManager)
@@ -1555,8 +1685,8 @@ Result DataTree::ValidatedTransaction::addChild (const DataTree& child, int inde
15551685
if (! child.isValid())
15561686
return Result::fail ("Cannot add invalid child");
15571687

1558-
// TODO: Get current child count from the transaction's target tree
1559-
auto validationResult = schema->validateChildAddition (nodeType, child.getType(), 0);
1688+
const int effectiveChildCount = transaction->getEffectiveChildCount();
1689+
auto validationResult = schema->validateChildAddition (nodeType, child.getType(), effectiveChildCount);
15601690
if (validationResult.failed())
15611691
{
15621692
hasValidationErrors = true;
@@ -1588,7 +1718,18 @@ Result DataTree::ValidatedTransaction::removeChild (const DataTree& child)
15881718
if (! transaction || ! transaction->isActive() || ! schema)
15891719
return Result::fail ("Transaction is not active");
15901720

1591-
// TODO: Check minimum child count constraints
1721+
if (! schema->hasNodeType (nodeType))
1722+
return Result::fail ("Unknown node type: " + nodeType.toString());
1723+
1724+
const auto constraints = schema->getChildConstraints (nodeType);
1725+
const int currentCount = transaction->getEffectiveChildCount();
1726+
const int resultingCount = std::max (0, currentCount - 1);
1727+
1728+
if (resultingCount < constraints.minCount)
1729+
{
1730+
hasValidationErrors = true;
1731+
return Result::fail ("Cannot remove child: would violate minimum child count (" + String (constraints.minCount) + ")");
1732+
}
15921733

15931734
transaction->removeChild (child);
15941735
return Result::ok();

modules/yup_data_model/tree/yup_DataTree.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,21 @@ class YUP_API DataTree
570570
*/
571571
static DataTree fromXml (const XmlElement& xml);
572572

573+
/**
574+
Recreates a DataTree from an XmlElement using a schema to recover types.
575+
576+
Behaves like fromXml(), but uses the provided schema to coerce property values
577+
back to their declared types (e.g., numbers, booleans) during deserialization.
578+
When no schema is provided, properties are imported as strings, matching JUCE
579+
ValueTree behaviour.
580+
581+
@param xml The XmlElement to deserialize from
582+
@param schema Optional schema describing node/property types for coercion
583+
@return A new DataTree representing the XML content, or invalid DataTree on failure
584+
*/
585+
static DataTree fromXml (const XmlElement& xml,
586+
ReferenceCountedObjectPtr<DataTreeSchema> schema);
587+
573588
/**
574589
Writes this DataTree to a binary stream in a compact format.
575590
@@ -955,6 +970,11 @@ class YUP_API DataTree
955970
*/
956971
void moveChild (int currentIndex, int newIndex);
957972

973+
/**
974+
Returns the effective number of children taking pending operations into account.
975+
*/
976+
int getEffectiveChildCount() const;
977+
958978
private:
959979
friend class TransactionAction;
960980

0 commit comments

Comments
 (0)