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());
+}