Files
vinihcrosaandClaude Sonnet 4.6 6295d540a4 feat: migrate to monorepo with WASM core, Fastify API, and React frontend
- Move C++ sources to core/ subdirectory (T05)
- Add pnpm workspaces + Turborepo pipeline (T06)
- Add Emscripten CMake target and build-wasm.sh (T07)
- Implement Embind bindings via YmirBindings.cpp (T08)
- Add packages/types with TypeBox schemas for VesselDTO, ScenarioDTO, SimulationStateDTO (T09)
- Create apps/api: Fastify v4 + Drizzle ORM + SQLite with vessels/scenarios CRUD (T10-T13)
- Create apps/web: Vite + React 18 + COOP/COEP headers (T14)
- Add simulation.worker.ts for WASM loop in Web Worker (T15)
- Add Zustand simulationStore (T16)
- Add TelemetryPanel component (T17)
- Verified: pnpm build 3/3 tasks, 21ms cached; ctest 211/211 pass (T18-T19)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 00:08:28 -03:00

106 lines
3.0 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <ymir/world/IDomain.h>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
namespace {
// Minimal stub that satisfies all IDomain pure virtuals.
class StubDomain final : public ymir::IDomain {
public:
void onAddedToWorld(ymir::Environment&, ymir::CouplingRegistry&) override {}
void step(double) override {}
std::vector<ymir::BodyPosition> allBodyPositions() const override
{
return {{0, 1.0, 2.0, 3.0}};
}
ymir::BodyState bodyState(int) const override { return {}; }
std::string name() const override { return "stub"; }
};
} // namespace
// --- Compile-time invariants -------------------------------------------------
static_assert(!std::is_default_constructible_v<ymir::IDomain>,
"IDomain is abstract — must not be default-constructible");
static_assert(!std::is_copy_constructible_v<ymir::IDomain>,
"IDomain copy constructor is deleted");
static_assert(!std::is_copy_assignable_v<ymir::IDomain>,
"IDomain copy assignment is deleted");
// --- Runtime tests -----------------------------------------------------------
TEST_CASE("StubDomain implements all IDomain virtuals and compiles")
{
StubDomain d;
REQUIRE(d.name() == "stub");
}
TEST_CASE("BodyPosition aggregate-initializes correctly")
{
ymir::BodyPosition bp{1, 1.0, 2.0, 3.0};
REQUIRE(bp.id == 1);
REQUIRE(bp.x == 1.0);
REQUIRE(bp.y == 2.0);
REQUIRE(bp.z == 3.0);
}
TEST_CASE("StubDomain allBodyPositions returns expected position")
{
StubDomain d;
auto positions = d.allBodyPositions();
REQUIRE(positions.size() == 1);
REQUIRE(positions[0].id == 0);
REQUIRE(positions[0].x == 1.0);
REQUIRE(positions[0].y == 2.0);
REQUIRE(positions[0].z == 3.0);
}
TEST_CASE("unique_ptr<IDomain> calls virtual destructor on StubDomain")
{
bool destroyed = false;
struct TrackingDomain final : ymir::IDomain {
bool& flag;
explicit TrackingDomain(bool& f) : flag(f) {}
~TrackingDomain() override { flag = true; }
void onAddedToWorld(ymir::Environment&, ymir::CouplingRegistry&) override {}
void step(double) override {}
std::vector<ymir::BodyPosition> allBodyPositions() const override { return {}; }
ymir::BodyState bodyState(int) const override { return {}; }
std::string name() const override { return "tracking"; }
};
{
std::unique_ptr<ymir::IDomain> domain = std::make_unique<TrackingDomain>(destroyed);
REQUIRE_FALSE(destroyed);
} // destructor fires here
REQUIRE(destroyed);
}
TEST_CASE("StubDomain is not copy-constructible (type trait)")
{
REQUIRE_FALSE(std::is_copy_constructible_v<ymir::IDomain>);
}
TEST_CASE("StubDomain step and bodyState are callable")
{
StubDomain d;
d.step(1.0);
auto state = d.bodyState(0);
// BodyState default-constructs — no assertion needed beyond compile
(void)state;
SUCCEED();
}