blob: c63362b78b49910b2ae6e260c73562c6d91fd764 [file]
/*
* Copyright 2026 Rive
*/
// Shared helpers for the C++ semantic test suite. Groups the fixture-load +
// bind + settle boilerplate, the tiny diff-query utilities, and the
// per-test constants that were previously duplicated across the five
// semantic_*_test.cpp files.
#ifndef _RIVE_SEMANTIC_TEST_HELPERS_HPP_
#define _RIVE_SEMANTIC_TEST_HELPERS_HPP_
#include <rive/animation/state_machine_instance.hpp>
#include <rive/artboard.hpp>
#include <rive/file.hpp>
#include <rive/math/vec2d.hpp>
#include <rive/semantic/semantic_manager.hpp>
#include <rive/semantic/semantic_node.hpp>
#include <rive/semantic/semantic_role.hpp>
#include <rive/semantic/semantic_snapshot.hpp>
#include <rive/semantic/semantic_state.hpp>
#include <rive/viewmodel/runtime/viewmodel_instance_runtime.hpp>
#include "rive_file_reader.hpp"
#include <cstdint>
#include <memory>
#include <string>
namespace rive
{
namespace semantic_test
{
// Default settle duration: 10 frames at 0.1s each = 1s of simulated time.
// Long enough for layout, transitions, and data-binding-driven list-item
// spawns to reach steady state in the current fixtures.
constexpr int kSettleFrames = 10;
constexpr float kSettleDt = 0.1f;
// Shared fixture labels (authored into .riv; drift here fails tests in
// multiple files at once).
constexpr const char* kDropdownLabel = "Select a fandom";
inline void advance(StateMachineInstance* sm,
int frames = kSettleFrames,
float dt = kSettleDt)
{
for (int i = 0; i < frames; ++i)
{
sm->advanceAndApply(dt);
}
}
inline SemanticsDiff settleAndDrainDiff(StateMachineInstance* sm,
int frames = kSettleFrames,
float dt = kSettleDt)
{
advance(sm, frames, dt);
auto* mgr = sm->semanticManager();
return mgr != nullptr ? mgr->drainDiff() : SemanticsDiff{};
}
inline const SemanticsDiffNode* findAddedByLabel(const SemanticsDiff& diff,
const std::string& label)
{
for (const auto& n : diff.added)
{
if (n.label == label)
{
return &n;
}
}
return nullptr;
}
inline const SemanticsDiffNode* findAddedByRole(const SemanticsDiff& diff,
SemanticRole role)
{
const uint32_t asU = static_cast<uint32_t>(role);
for (const auto& n : diff.added)
{
if (n.role == asU)
{
return &n;
}
}
return nullptr;
}
inline Vec2D centerOf(const SemanticsDiffNode& node)
{
return Vec2D((node.minX + node.maxX) * 0.5f,
(node.minY + node.maxY) * 0.5f);
}
inline bool isSelected(const SemanticNode* node)
{
return node != nullptr &&
hasSemanticState(node->stateFlags(), SemanticState::Selected);
}
// Holds a loaded .riv + its default artboard, state machine, and bound view
// model instance, with semantics enabled and the tree already settled.
struct Fixture
{
rcp<File> file;
std::unique_ptr<ArtboardInstance> artboard;
std::unique_ptr<StateMachineInstance> sm;
rcp<ViewModelInstance> vmi;
};
// Load a fixture, enable semantics, bind the default VMI (if any), advance
// to settle. Every fixture-driven test in the suite uses this.
inline Fixture loadFixture(const char* path)
{
Fixture f;
f.file = ReadRiveFile(path);
f.artboard = f.file->artboardDefault();
f.sm = f.artboard->stateMachineAt(0);
if (f.sm != nullptr)
{
f.sm->enableSemantics();
}
f.vmi = f.file->createDefaultViewModelInstance(f.artboard.get());
if (f.vmi != nullptr)
{
f.artboard->bindViewModelInstance(f.vmi);
if (f.sm != nullptr)
{
f.sm->bindViewModelInstance(f.vmi);
}
}
if (f.sm != nullptr)
{
advance(f.sm.get());
}
return f;
}
} // namespace semantic_test
} // namespace rive
#endif