From e904b408d1e3ec84808d95eafea8691a062290c1 Mon Sep 17 00:00:00 2001 From: patrick96 Date: Mon, 14 Mar 2022 22:11:46 +0100 Subject: [PATCH] fix(builder): ignored offsets The builder::offset function returned immediately if a valid extent was passed instead of when an invalid one was passed. --- include/adapters/pulseaudio.hpp | 82 +++++++------- include/components/bar.hpp | 2 +- include/components/builder.hpp | 8 +- include/modules/meta/base.hpp | 4 +- include/modules/meta/base.inl | 2 +- include/utils/units.hpp | 2 + src/adapters/pulseaudio.cpp | 60 +++++----- src/components/bar.cpp | 2 +- src/components/builder.cpp | 49 +++----- src/modules/meta/base.cpp | 6 +- src/modules/pulseaudio.cpp | 3 +- src/utils/units.cpp | 17 +++ tests/CMakeLists.txt | 1 + tests/unit_tests/components/builder.cpp | 142 ++++++++++++++++++++++++ 14 files changed, 266 insertions(+), 114 deletions(-) create mode 100644 tests/unit_tests/components/builder.cpp diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp index 09398ac1..a5d23426 100644 --- a/include/adapters/pulseaudio.hpp +++ b/include/adapters/pulseaudio.hpp @@ -1,12 +1,12 @@ #pragma once #include + #include #include "common.hpp" -#include "settings.hpp" #include "errors.hpp" - +#include "settings.hpp" #include "utils/math.hpp" // fwd struct pa_context; @@ -25,57 +25,57 @@ class pulseaudio { enum class evtype { NEW = 0, CHANGE, REMOVE, SERVER }; using queue = std::queue; - public: - explicit pulseaudio(const logger& logger, string&& sink_name, bool m_max_volume); - ~pulseaudio(); + public: + explicit pulseaudio(const logger& logger, string&& sink_name, bool m_max_volume); + ~pulseaudio(); - pulseaudio(const pulseaudio& o) = delete; - pulseaudio& operator=(const pulseaudio& o) = delete; + pulseaudio(const pulseaudio& o) = delete; + pulseaudio& operator=(const pulseaudio& o) = delete; - const string& get_name(); + const string& get_name(); - bool wait(); - int process_events(); + bool wait(); + int process_events(); - int get_volume(); - double get_decibels(); - void set_volume(float percentage); - void inc_volume(int delta_perc); - void set_mute(bool mode); - void toggle_mute(); - bool is_muted(); + int get_volume(); + double get_decibels(); + void set_volume(float percentage); + void inc_volume(int delta_perc); + void set_mute(bool mode); + void toggle_mute(); + bool is_muted(); - private: - void update_volume(pa_operation *o); - static void check_mute_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata); - static void get_sink_volume_callback(pa_context *context, const pa_sink_info *info, int is_last, void *userdata); - static void subscribe_callback(pa_context* context, pa_subscription_event_type_t t, uint32_t idx, void* userdata); - static void simple_callback(pa_context *context, int success, void *userdata); - static void sink_info_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata); - static void context_state_callback(pa_context *context, void *userdata); + private: + void update_volume(pa_operation* o); + static void check_mute_callback(pa_context* context, const pa_sink_info* info, int eol, void* userdata); + static void get_sink_volume_callback(pa_context* context, const pa_sink_info* info, int is_last, void* userdata); + static void subscribe_callback(pa_context* context, pa_subscription_event_type_t t, uint32_t idx, void* userdata); + static void simple_callback(pa_context* context, int success, void* userdata); + static void sink_info_callback(pa_context* context, const pa_sink_info* info, int eol, void* userdata); + static void context_state_callback(pa_context* context, void* userdata); - inline void wait_loop(pa_operation *op, pa_threaded_mainloop *loop); + inline void wait_loop(pa_operation* op, pa_threaded_mainloop* loop); - const logger& m_log; + const logger& m_log; - // used for temporary callback results - int success{0}; - pa_cvolume cv; - bool muted{false}; - // default sink name - static constexpr auto DEFAULT_SINK = "@DEFAULT_SINK@"; + // used for temporary callback results + int success{0}; + pa_cvolume cv{}; + bool muted{false}; + // default sink name + static constexpr auto DEFAULT_SINK = "@DEFAULT_SINK@"; - pa_context* m_context{nullptr}; - pa_threaded_mainloop* m_mainloop{nullptr}; + pa_context* m_context{nullptr}; + pa_threaded_mainloop* m_mainloop{nullptr}; - queue m_events; + queue m_events; - // specified sink name - string spec_s_name; - string s_name; - uint32_t m_index{0}; + // specified sink name + string spec_s_name; + string s_name; + uint32_t m_index{0}; - pa_volume_t m_max_volume{PA_VOLUME_UI_MAX}; + pa_volume_t m_max_volume{PA_VOLUME_UI_MAX}; }; POLYBAR_NS_END diff --git a/include/components/bar.hpp b/include/components/bar.hpp index 43a444dc..e16f9563 100644 --- a/include/components/bar.hpp +++ b/include/components/bar.hpp @@ -46,7 +46,7 @@ class bar : public xpp::event::sink m_tags{}; diff --git a/include/modules/meta/base.hpp b/include/modules/meta/base.hpp index b7de9ef0..78b4fc17 100644 --- a/include/modules/meta/base.hpp +++ b/include/modules/meta/base.hpp @@ -142,7 +142,7 @@ namespace modules { template class module : public module_interface { public: - module(const bar_settings bar, string name); + module(const bar_settings& bar, string name); ~module() noexcept; static constexpr auto EVENT_MODULE_TOGGLE = "module_toggle"; @@ -197,7 +197,7 @@ namespace modules { protected: signal_emitter& m_sig; - const bar_settings m_bar; + const bar_settings& m_bar; const logger& m_log; const config& m_conf; diff --git a/include/modules/meta/base.inl b/include/modules/meta/base.inl index 8ea403d2..9b6899ad 100644 --- a/include/modules/meta/base.inl +++ b/include/modules/meta/base.inl @@ -15,7 +15,7 @@ namespace modules { // module public {{{ template - module::module(const bar_settings bar, string name) + module::module(const bar_settings& bar, string name) : m_sig(signal_emitter::make()) , m_bar(bar) , m_log(logger::make()) diff --git a/include/utils/units.hpp b/include/utils/units.hpp index 9e2ee414..27e52c50 100644 --- a/include/utils/units.hpp +++ b/include/utils/units.hpp @@ -27,6 +27,8 @@ namespace units_utils { spacing_type parse_spacing_unit(const string& str); spacing_val parse_spacing(const string& str); + extent_val spacing_to_extent(spacing_val spacing); + } // namespace units_utils POLYBAR_NS_END diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp index 9f0c1b3c..1a6a15b8 100644 --- a/src/adapters/pulseaudio.cpp +++ b/src/adapters/pulseaudio.cpp @@ -1,4 +1,5 @@ #include "adapters/pulseaudio.hpp" + #include "components/logger.hpp" POLYBAR_NS @@ -6,7 +7,8 @@ POLYBAR_NS /** * Construct pulseaudio object */ -pulseaudio::pulseaudio(const logger& logger, string&& sink_name, bool max_volume) : m_log(logger), spec_s_name(sink_name) { +pulseaudio::pulseaudio(const logger& logger, string&& sink_name, bool max_volume) + : m_log(logger), spec_s_name(sink_name) { m_mainloop = pa_threaded_mainloop_new(); if (!m_mainloop) { throw pulseaudio_error("Could not create pulseaudio threaded mainloop."); @@ -50,7 +52,7 @@ pulseaudio::pulseaudio(const logger& logger, string&& sink_name, bool max_volume throw pulseaudio_error("Could not connect to pulseaudio server."); } - pa_operation *op{nullptr}; + pa_operation* op{nullptr}; if (!sink_name.empty()) { op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this); wait_loop(op, m_mainloop); @@ -69,14 +71,14 @@ pulseaudio::pulseaudio(const logger& logger, string&& sink_name, bool max_volume auto event_types = static_cast(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SERVER); op = pa_context_subscribe(m_context, event_types, simple_callback, this); wait_loop(op, m_mainloop); - if (!success) + if (!success) { throw pulseaudio_error("Failed to subscribe to sink."); + } pa_context_set_subscribe_callback(m_context, subscribe_callback, this); update_volume(op); pa_threaded_mainloop_unlock(m_mainloop); - } /** @@ -87,7 +89,6 @@ pulseaudio::~pulseaudio() { pa_context_disconnect(m_context); pa_context_unref(m_context); pa_threaded_mainloop_free(m_mainloop); - } /** @@ -110,7 +111,7 @@ bool pulseaudio::wait() { int pulseaudio::process_events() { int ret = m_events.size(); pa_threaded_mainloop_lock(m_mainloop); - pa_operation *o{nullptr}; + pa_operation* o{nullptr}; // clear the queue while (!m_events.empty()) { switch (m_events.front()) { @@ -133,8 +134,9 @@ int pulseaudio::process_events() { case evtype::REMOVE: o = pa_context_get_sink_info_by_name(m_context, DEFAULT_SINK, sink_info_callback, this); wait_loop(o, m_mainloop); - if (spec_s_name != s_name) + if (spec_s_name != s_name) { m_log.notice("pulseaudio: using default sink %s", s_name); + } break; default: break; @@ -168,7 +170,7 @@ void pulseaudio::set_volume(float percentage) { pa_threaded_mainloop_lock(m_mainloop); pa_volume_t vol = math_util::percentage_to_value(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM); pa_cvolume_scale(&cv, vol); - pa_operation *op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); + pa_operation* op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); wait_loop(op, m_mainloop); if (!success) throw pulseaudio_error("Failed to set sink volume."); @@ -193,7 +195,7 @@ void pulseaudio::inc_volume(int delta_perc) { } } else pa_cvolume_dec(&cv, vol); - pa_operation *op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); + pa_operation* op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this); wait_loop(op, m_mainloop); if (!success) throw pulseaudio_error("Failed to set sink volume."); @@ -205,7 +207,7 @@ void pulseaudio::inc_volume(int delta_perc) { */ void pulseaudio::set_mute(bool mode) { pa_threaded_mainloop_lock(m_mainloop); - pa_operation *op = pa_context_set_sink_mute_by_index(m_context, m_index, mode, simple_callback, this); + pa_operation* op = pa_context_set_sink_mute_by_index(m_context, m_index, mode, simple_callback, this); wait_loop(op, m_mainloop); if (!success) throw pulseaudio_error("Failed to mute sink."); @@ -229,7 +231,7 @@ bool pulseaudio::is_muted() { /** * Update local volume cache */ -void pulseaudio::update_volume(pa_operation *o) { +void pulseaudio::update_volume(pa_operation* o) { o = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this); wait_loop(o, m_mainloop); } @@ -237,8 +239,8 @@ void pulseaudio::update_volume(pa_operation *o) { /** * Callback when getting volume */ -void pulseaudio::get_sink_volume_callback(pa_context *, const pa_sink_info *info, int, void *userdata) { - pulseaudio* This = static_cast(userdata); +void pulseaudio::get_sink_volume_callback(pa_context*, const pa_sink_info* info, int, void* userdata) { + pulseaudio* This = static_cast(userdata); if (info) { This->cv = info->volume; This->muted = info->mute; @@ -249,20 +251,20 @@ void pulseaudio::get_sink_volume_callback(pa_context *, const pa_sink_info *info /** * Callback when subscribing to changes */ -void pulseaudio::subscribe_callback(pa_context *, pa_subscription_event_type_t t, uint32_t idx, void* userdata) { - pulseaudio *This = static_cast(userdata); - switch(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { +void pulseaudio::subscribe_callback(pa_context*, pa_subscription_event_type_t t, uint32_t idx, void* userdata) { + pulseaudio* This = static_cast(userdata); + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { case PA_SUBSCRIPTION_EVENT_SERVER: - switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { + switch (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { case PA_SUBSCRIPTION_EVENT_CHANGE: This->m_events.emplace(evtype::SERVER); - break; + break; } break; case PA_SUBSCRIPTION_EVENT_SINK: - switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { + switch (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { case PA_SUBSCRIPTION_EVENT_NEW: - This->m_events.emplace(evtype::NEW); + This->m_events.emplace(evtype::NEW); break; case PA_SUBSCRIPTION_EVENT_CHANGE: if (idx == This->m_index) @@ -281,18 +283,17 @@ void pulseaudio::subscribe_callback(pa_context *, pa_subscription_event_type_t t /** * Simple callback to check for success */ -void pulseaudio::simple_callback(pa_context *, int success, void *userdata) { - pulseaudio *This = static_cast(userdata); +void pulseaudio::simple_callback(pa_context*, int success, void* userdata) { + pulseaudio* This = static_cast(userdata); This->success = success; pa_threaded_mainloop_signal(This->m_mainloop, 0); } - /** * Callback when getting sink info & existence */ -void pulseaudio::sink_info_callback(pa_context *, const pa_sink_info *info, int eol, void *userdata) { - pulseaudio *This = static_cast(userdata); +void pulseaudio::sink_info_callback(pa_context*, const pa_sink_info* info, int eol, void* userdata) { + pulseaudio* This = static_cast(userdata); if (!eol && info) { This->m_index = info->index; This->s_name = info->name; @@ -303,8 +304,8 @@ void pulseaudio::sink_info_callback(pa_context *, const pa_sink_info *info, int /** * Callback when context state changes */ -void pulseaudio::context_state_callback(pa_context *context, void *userdata) { - pulseaudio* This = static_cast(userdata); +void pulseaudio::context_state_callback(pa_context* context, void* userdata) { + pulseaudio* This = static_cast(userdata); switch (pa_context_get_state(context)) { case PA_CONTEXT_READY: case PA_CONTEXT_TERMINATED: @@ -320,9 +321,10 @@ void pulseaudio::context_state_callback(pa_context *context, void *userdata) { } } -inline void pulseaudio::wait_loop(pa_operation *op, pa_threaded_mainloop *loop) { - while (pa_operation_get_state(op) != PA_OPERATION_DONE) +inline void pulseaudio::wait_loop(pa_operation* op, pa_threaded_mainloop* loop) { + while (pa_operation_get_state(op) != PA_OPERATION_DONE) { pa_threaded_mainloop_wait(loop); + } pa_operation_unref(op); } diff --git a/src/components/bar.cpp b/src/components/bar.cpp index b1f63bb4..187a6eeb 100644 --- a/src/components/bar.cpp +++ b/src/components/bar.cpp @@ -361,7 +361,7 @@ bar::~bar() { /** * Get the bar settings container */ -const bar_settings bar::settings() const { +const bar_settings& bar::settings() const { return m_opts; } diff --git a/src/components/builder.cpp b/src/components/builder.cpp index 54e0294e..ec153d2d 100644 --- a/src/components/builder.cpp +++ b/src/components/builder.cpp @@ -46,7 +46,7 @@ void builder::reset() { */ string builder::flush() { background_close(); - color_close(); + foreground_close(); font_close(); overline_color_close(); underline_color_close(); @@ -122,7 +122,7 @@ void builder::node(const label_t& label) { background(label->m_background); } if (label->m_foreground.has_color()) { - color(label->m_foreground); + foreground(label->m_foreground); } if (label->m_padding.left) { @@ -139,7 +139,7 @@ void builder::node(const label_t& label) { background_close(); } if (label->m_foreground.has_color()) { - color_close(); + foreground_close(); } if (label->m_underline.has_color()) { @@ -164,6 +164,7 @@ void builder::node_repeat(const label_t& label, size_t n) { while (n--) { text += label_text; } + label_t tmp{new label_t::element_type{text}}; tmp->replace_defined_values(label); node(tmp); @@ -173,7 +174,7 @@ void builder::node_repeat(const label_t& label, size_t n) { * Insert tag that will offset the contents by the given extent */ void builder::offset(extent_val extent) { - if (extent) { + if (!extent) { return; } tag_open(syntaxtag::O, units_utils::extent_to_string(extent)); @@ -231,7 +232,7 @@ void builder::background_close() { /** * Insert tag to alter the current foreground color */ -void builder::color(rgba color) { +void builder::foreground(rgba color) { color = color.try_apply_alpha_to(m_bar.foreground); auto hex = color_util::simplify_hex(color); @@ -241,7 +242,7 @@ void builder::color(rgba color) { /** * Insert tag to reset the foreground color */ -void builder::color_close() { +void builder::foreground_close() { tag_close(syntaxtag::F); } @@ -305,7 +306,7 @@ void builder::control(controltag tag) { str = "R"; break; default: - break; + throw runtime_error("Invalid controltag: " + to_string(to_integral(tag))); } if (!str.empty()) { @@ -321,7 +322,7 @@ void builder::control(controltag tag) { void builder::action(mousebtn index, string action) { if (!action.empty()) { action = string_util::replace_all(action, ":", "\\:"); - tag_open(syntaxtag::A, to_string(static_cast(index)) + ":" + action + ":"); + tag_open(syntaxtag::A, to_string(to_integral(index)) + ":" + action + ":"); } } @@ -401,6 +402,8 @@ void builder::tag_open(syntaxtag tag, const string& value) { case syntaxtag::r: append("%{r}"); break; + default: + throw runtime_error("Invalid tag: " + to_string(to_integral(tag))); } } @@ -415,14 +418,14 @@ void builder::tag_open(attribute attr) { m_attrs[attr] = true; switch (attr) { - case attribute::NONE: - break; case attribute::UNDERLINE: append("%{+u}"); break; case attribute::OVERLINE: append("%{+o}"); break; + default: + throw runtime_error("Invalid attribute: " + to_string(to_integral(attr))); } } @@ -471,44 +474,28 @@ void builder::tag_close(attribute attr) { m_attrs[attr] = false; switch (attr) { - case attribute::NONE: - break; case attribute::UNDERLINE: append("%{-u}"); break; case attribute::OVERLINE: append("%{-o}"); break; + default: + throw runtime_error("Invalid attribute: " + to_string(to_integral(attr))); } } -string builder::get_spacing_format_string(const spacing_val& space) { +string builder::get_spacing_format_string(spacing_val space) { float value = space.value; if (value == 0) { return ""; } - string out; if (space.type == spacing_type::SPACE) { - out += string(value, ' '); + return string(value, ' '); } else { - out += "%{O"; - - switch (space.type) { - case spacing_type::POINT: - out += to_string(value) + "pt"; - break; - case spacing_type::PIXEL: - out += to_string(static_cast(value)) + "px"; - break; - default: - break; - } - - out += '}'; + return "%{O" + units_utils::extent_to_string(units_utils::spacing_to_extent(space)) + "}"; } - - return out; } POLYBAR_NS_END diff --git a/src/modules/meta/base.cpp b/src/modules/meta/base.cpp index 56d2e3b4..db5d8447 100644 --- a/src/modules/meta/base.cpp +++ b/src/modules/meta/base.cpp @@ -25,7 +25,7 @@ namespace modules { builder->background(bg); } if (fg.has_color()) { - builder->color(fg); + builder->foreground(fg); } if (ul.has_color()) { builder->underline(ul); @@ -46,7 +46,7 @@ namespace modules { builder->background(bg); } if (fg.has_color()) { - builder->color(fg); + builder->foreground(fg); } if (ul.has_color()) { builder->underline(ul); @@ -71,7 +71,7 @@ namespace modules { builder->underline_close(); } if (fg.has_color()) { - builder->color_close(); + builder->foreground_close(); } if (bg.has_color()) { builder->background_close(); diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index cbe829e5..f4d0dfde 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -58,8 +58,9 @@ namespace modules { bool pulseaudio_module::has_event() { // Poll for mixer and control events try { - if (m_pulseaudio->wait()) + if (m_pulseaudio->wait()) { return true; + } } catch (const pulseaudio_error& e) { m_log.err("%s: %s", name(), e.what()); } diff --git a/src/utils/units.cpp b/src/utils/units.cpp index 5030be8a..2c7a1237 100644 --- a/src/utils/units.cpp +++ b/src/utils/units.cpp @@ -135,6 +135,23 @@ namespace units_utils { return {type, size_value}; } + extent_val spacing_to_extent(spacing_val spacing) { + extent_type t; + + switch (spacing.type) { + case spacing_type::POINT: + t = extent_type::POINT; + break; + case spacing_type::PIXEL: + t = extent_type::PIXEL; + break; + default: + throw std::runtime_error("spacing_to_extent: Illegal type: " + to_string(to_integral(spacing.type))); + } + + return {t, spacing.value}; + } + } // namespace units_utils POLYBAR_NS_END diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 08deddb1..7c9149f2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -58,6 +58,7 @@ add_unit_test(utils/string) add_unit_test(utils/file) add_unit_test(utils/process) add_unit_test(utils/units) +add_unit_test(components/builder) add_unit_test(components/command_line) add_unit_test(components/config_parser) add_unit_test(drawtypes/label) diff --git a/tests/unit_tests/components/builder.cpp b/tests/unit_tests/components/builder.cpp new file mode 100644 index 00000000..66178f0b --- /dev/null +++ b/tests/unit_tests/components/builder.cpp @@ -0,0 +1,142 @@ +#include "components/builder.hpp" + +#include "common/test.hpp" + +using namespace polybar; + +static const rgba FG = rgba("#00FF00"); +static const rgba BG = rgba("#FF0000"); + +class BuilderTest : public ::testing::Test { + protected: + virtual void SetUp() override { + opts.foreground = FG; + opts.background = BG; + opts.spacing = ZERO_SPACE; + } + + bar_settings opts{}; + builder b{opts}; +}; + +TEST_F(BuilderTest, empty) { + EXPECT_EQ("", b.flush()); +} + +TEST_F(BuilderTest, text) { + b.node("foo"); + EXPECT_EQ("foo", b.flush()); +} + +TEST_F(BuilderTest, textFont) { + b.node("foo", 12); + EXPECT_EQ("%{T12}foo%{T-}", b.flush()); +} + +using offset_test = std::pair; +class OffsetTest : public BuilderTest, public ::testing::WithParamInterface {}; + +vector offset_test_list = { + {ZERO_PX_EXTENT, ""}, + {{extent_type::POINT, 0}, ""}, + {{extent_type::PIXEL, 10}, "%{O10px}"}, + {{extent_type::PIXEL, -10}, "%{O-10px}"}, + {{extent_type::POINT, 23}, "%{O23pt}"}, + {{extent_type::POINT, -1}, "%{O-1pt}"}, +}; + +INSTANTIATE_TEST_SUITE_P(Inst, OffsetTest, ::testing::ValuesIn(offset_test_list)); + +TEST_P(OffsetTest, correctness) { + b.offset(GetParam().first); + EXPECT_EQ(GetParam().second, b.flush()); +} + +using spacing_test = std::pair; +class SpacingTest : public BuilderTest, public ::testing::WithParamInterface {}; + +vector spacing_test_list = { + {ZERO_SPACE, ""}, + {{spacing_type::SPACE, 2}, " "}, + {{spacing_type::PIXEL, 3}, "%{O3px}"}, + {{spacing_type::POINT, 4}, "%{O4pt}"}, +}; + +INSTANTIATE_TEST_SUITE_P(Inst, SpacingTest, ::testing::ValuesIn(spacing_test_list)); + +TEST_P(SpacingTest, correctness) { + b.spacing(GetParam().first); + EXPECT_EQ(GetParam().second, b.flush()); +} + +TEST_P(SpacingTest, get_spacing_format_string) { + b.spacing(GetParam().first); + EXPECT_EQ(GetParam().second, b.flush()); +} + +TEST_F(BuilderTest, tags) { + b.font(10); + b.font_close(); + EXPECT_EQ("%{T10}%{T-}", b.flush()); + + b.background(rgba("#f0f0f0")); + b.background_close(); + EXPECT_EQ("%{B#f0f0f0}%{B-}", b.flush()); + + b.foreground(rgba("#0f0f0f")); + b.foreground_close(); + EXPECT_EQ("%{F#0f0f0f}%{F-}", b.flush()); + + b.overline(rgba("#0e0e0e")); + b.overline_close(); + // Last tag is from auto-closing during flush + EXPECT_EQ("%{o#0e0e0e}%{+o}%{-o}%{o-}", b.flush()); + + b.underline(rgba("#0d0d0d")); + b.underline_close(); + // Last tag is from auto-closing during flush + EXPECT_EQ("%{u#0d0d0d}%{+u}%{-u}%{u-}", b.flush()); + + b.control(tags::controltag::R); + EXPECT_EQ("%{PR}", b.flush()); + + b.action(mousebtn::LEFT, "cmd"); + b.action_close(); + EXPECT_EQ("%{A1:cmd:}%{A}", b.flush()); +} + +TEST_F(BuilderTest, tagsAutoClose) { + b.font(20); + EXPECT_EQ("%{T20}%{T-}", b.flush()); + + b.background(rgba("#f0f0f0")); + EXPECT_EQ("%{B#f0f0f0}%{B-}", b.flush()); + + b.foreground(rgba("#0f0f0f")); + EXPECT_EQ("%{F#0f0f0f}%{F-}", b.flush()); + + b.overline(rgba("#0e0e0e")); + EXPECT_EQ("%{o#0e0e0e}%{+o}%{o-}%{-o}", b.flush()); + + b.underline(rgba("#0d0d0d")); + EXPECT_EQ("%{u#0d0d0d}%{+u}%{u-}%{-u}", b.flush()); + + b.action(mousebtn::LEFT, "cmd"); + EXPECT_EQ("%{A1:cmd:}%{A}", b.flush()); +} + +TEST_F(BuilderTest, invalidTags) { + EXPECT_THROW(b.control(tags::controltag::NONE), std::runtime_error); +} + +TEST_F(BuilderTest, colorBlending) { + b.background(rgba(0x12000000, rgba::type::ALPHA_ONLY)); + b.background_close(); + + EXPECT_EQ("%{B#12ff0000}%{B-}", b.flush()); + + b.foreground(rgba(0x34000000, rgba::type::ALPHA_ONLY)); + b.foreground_close(); + + EXPECT_EQ("%{F#3400ff00}%{F-}", b.flush()); +}