#include #include #include #include #include #include #include #include using namespace lt; using std::ignore; using surface::SurfaceComponent; using surface::System; using test::Case; using test::expect_eq; using test::expect_ne; using test::expect_not_nullptr; using test::expect_throw; using test::expect_true; using test::Suite; [[nodiscard]] auto tick_info() -> app::TickInfo { return { .delta_time = std::chrono::milliseconds { 16 }, .budget = std::chrono::milliseconds { 10 }, .start_time = std::chrono::steady_clock::now(), }; } constexpr auto title = "TestWindow"; constexpr auto width = 800u; constexpr auto height = 600u; constexpr auto vsync = true; constexpr auto visible = false; template struct overloads: Ts... { using Ts::operator()...; }; class Fixture { public: [[nodiscard]] auto registry() -> memory::Ref { return m_registry; } auto create_component( SurfaceComponent::CreateInfo info = SurfaceComponent::CreateInfo { .title = title, .resolution = { width, height }, .vsync = vsync, .visible = visible, } ) -> std::optional { auto entity = m_registry->create_entity(); m_system.create_surface_component(entity, info); return &m_registry->get(entity); } void check_values(SurfaceComponent *component) { #ifdef LIGHT_PLATFORM_LINUX expect_not_nullptr(component->get_native_data().display); expect_ne(component->get_native_data().window, 0); #endif expect_eq(component->get_resolution().x, width); expect_eq(component->get_resolution().y, height); expect_eq(component->get_title(), title); expect_eq(component->is_vsync(), vsync); expect_eq(component->is_visible(), visible); } private: memory::Ref m_registry = memory::create_ref(); System m_system { m_registry }; }; Suite raii = "raii"_suite = [] { Case { "happy path won't throw" } = [] { auto fixture = Fixture {}; ignore = System { fixture.registry() }; }; Case { "many won't freeze/throw" } = [] { auto fixture = Fixture {}; for (auto idx : std::views::iota(0, 250)) { ignore = System { fixture.registry() }; } }; Case { "unhappy path throws" } = [] { expect_throw([] { ignore = System { {} }; }); }; Case { "post construct has correct state" } = [] { auto fixture = Fixture {}; auto system = System { fixture.registry() }; expect_eq(fixture.registry()->view().get_size(), 0); }; Case { "post destruct has correct state" } = [] { auto fixture = Fixture {}; auto system = memory::create_scope(fixture.registry()); fixture.create_component(); expect_eq(fixture.registry()->view().get_size(), 1); system.reset(); expect_eq(fixture.registry()->view().get_size(), 0); }; }; Suite system_events = "system_events"_suite = [] { Case { "on_register won't throw" } = [] { auto fixture = Fixture {}; auto system = System { fixture.registry() }; system.on_register(); expect_eq(fixture.registry()->view().get_size(), 0); }; Case { "on_unregister won't throw" } = [] { auto fixture = Fixture {}; auto system = System { fixture.registry() }; system.on_register(); system.on_unregister(); expect_eq(fixture.registry()->view().get_size(), 0); }; }; Suite registry_events = "registry_events"_suite = [] { Case { "on_construct initializes component" } = [] { auto fixture = Fixture {}; const auto &component = fixture.create_component(); expect_eq(fixture.registry()->view().get_size(), 1); fixture.check_values(*component); }; Case { "unhappy on_construct throws" } = [] { auto fixture = Fixture {}; auto system = System { fixture.registry() }; expect_throw([&] { fixture.create_component({ .resolution = { width, 0 } }); }); expect_throw([&] { fixture.create_component({ .resolution = { 0, height } }); }); expect_throw([&] { fixture.create_component( { .title = "", .resolution = { SurfaceComponent::max_dimension + 1, height } } ); }); expect_throw([&] { fixture.create_component( { .title = "", .resolution = { width, SurfaceComponent::max_dimension + 1 } } ); }); auto big_str = std::string {}; big_str.resize(SurfaceComponent::max_title_length + 1); expect_throw([&] { fixture.create_component({ .title = big_str, .resolution = { width, height } }); }); }; Case { "unhappy on_construct removes component" } = [] { auto fixture = Fixture {}; auto system = System { fixture.registry() }; expect_throw([&] { fixture.create_component({ .resolution = { width, 0 } }); }); expect_eq(fixture.registry()->view().get_size(), 0); }; Case { "on_destrroy cleans up component" } = [] { auto fixture = Fixture {}; auto system = memory::create_scope(fixture.registry()); const auto &component = fixture.create_component(); expect_eq(fixture.registry()->view().get_size(), 1); fixture.check_values(*component); system.reset(); expect_eq(fixture.registry()->view().get_size(), 0); }; }; Suite tick = "tick"_suite = [] { Case { "ticking on empty registry won't throw" } = [] { auto fixture = Fixture {}; System { fixture.registry() }.tick(tick_info()); }; Case { "ticking on non-empty registry won't throw" } = [] { auto fixture = Fixture {}; auto system = System { fixture.registry() }; fixture.create_component(); system.tick(tick_info()); }; }; Suite tick_handles_events = "tick_handles_events"_suite = [] { Case { "ticking clears previous tick's events" } = [] { auto fixture = Fixture {}; auto system = System { fixture.registry() }; auto &surface = **fixture.create_component(); // flush window-creation events system.tick(tick_info()); expect_eq(surface.peek_events().size(), 0); surface.push_event(surface::MovedEvent({}, {})); expect_eq(surface.peek_events().size(), 1); surface.push_event(surface::ButtonPressedEvent({})); expect_eq(surface.peek_events().size(), 2); system.tick(tick_info()); expect_eq(surface.peek_events().size(), 0); }; }; Suite tick_handles_requests = "tick_handles_requests"_suite = [] { Case { "ticking clears requests" } = [] { auto fixture = Fixture {}; auto system = System { fixture.registry() }; auto &surface = **fixture.create_component(); constexpr auto title = "ABC"; constexpr auto position = math::ivec2 { 50, 50 }; constexpr auto resolution = math::uvec2 { 50, 50 }; expect_eq(surface.peek_requests().size(), 0); surface.push_request(surface::ModifyVisibilityRequest(true)); expect_eq(surface.peek_requests().size(), 1); system.tick(tick_info()); expect_eq(surface.peek_requests().size(), 0); surface.push_request(surface::ModifyTitleRequest(title)); expect_eq(surface.peek_requests().size(), 1); surface.push_request(surface::ModifyResolutionRequest(resolution)); surface.push_request(surface::ModifyPositionRequest(position)); expect_eq(surface.peek_requests().size(), 1 + 2); surface.push_request(surface::ModifyVisibilityRequest(false)); surface.push_request(surface::ModifyVisibilityRequest(true)); surface.push_request(surface::ModifyVisibilityRequest(false)); expect_eq(surface.peek_requests().size(), 1 + 2 + 3); system.tick(tick_info()); expect_eq(surface.peek_requests().size(), 0); expect_eq(surface.get_title(), title); expect_eq(surface.get_position(), position); expect_eq(surface.get_resolution(), resolution); log_dbg("EVENT COUNT: {}", surface.peek_events().size()); for (const auto &event : surface.peek_events()) { const auto visitor = overloads { [&](auto event) { log_dbg("event: {}", event.to_string()); }, }; std::visit(visitor, event); } }; };