--------------------------------------------------------------------------- -- @author Alex Belykh -- @copyright 2021 Alex Belykh --------------------------------------------------------------------------- -- Require test_utils for the assert.widget_fit() helper. require("wibox.test_utils") -- Set emit_signal so as to not die on deprecation warnings. _G.awesome.emit_signal = function() end -- Silence debug warnings. require("gears.debug").print_warning = function() end local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1) local color = require("gears.color") local graph = require("wibox.widget.graph") local deprecated_properties = { height = true, width = true, stack_colors = true, } local property_defaults = { baseline_value = 0, clamp_bars = true, nan_indication = true, step_width = 1, step_spacing = 0, } local redrawless_properties = { capacity = true, height = true, width = true, stack_colors = true, } local data = {45.5, -44.5, -7.5, 1.5, 47.5, -38.5, 0.5, 38.0, 47.0, 23.5} local data2 = {-26.5, 25.0, -19.0, 38.0, 12.0 -19.0, -35.0, 16.5} local function push_data(widget, d, group_idx) -- Add in reverse, so that d could be compared with -- the backing value array directly. for i = #d,1,-1 do widget:add_value(d[i], group_idx) end end describe("wibox.widget.graph", function() local widget local redraw_needed, layout_changed before_each(function() widget = graph() widget:connect_signal("widget::redraw_needed", function() redraw_needed = redraw_needed + 1 end) widget:connect_signal("widget::layout_changed", function() layout_changed = layout_changed + 1 end) redraw_needed, layout_changed = 0, 0 end) -- Check the trivial properties of all getters and setters. -- -- There shouldn't be a field with a "get_/set_*" name, that isn't one. -- Iteration is over module fields, because fields of the instance can be -- polluted by inheritance and whatnot with something, that doesn't -- hold itself to the standard I'm setting here. for field, _ in pairs(graph) do if string.sub(field, 1, 4) == "get_" then -- A field with a getter-like name. Let's ensure that it is one. local prop_name = string.sub(field, 5) describe("field " .. field, function() it("has a corresponding set_" .. prop_name .. " counterpart", function() assert.is_function(widget["set_" .. prop_name]) end) it("is a property getter", function() assert.is_function(widget[field]) stub(widget, field, 3456) -- Access the property through metatable magic local result_value = widget[prop_name] assert.is.equal(3456, result_value) assert.stub(widget[field]).was_called_with(widget) end) end) elseif string.sub(field, 1, 4) == "set_" then -- A field with a setter-like name. Let's ensure that it is one. local prop_name = string.sub(field, 5) describe("field " .. field, function() local property_signal_emitted, property_signal_emitted_with before_each(function() widget:connect_signal("property::" .. prop_name, function(...) property_signal_emitted = property_signal_emitted + 1 property_signal_emitted_with = {...} end) property_signal_emitted, property_signal_emitted_with = 0, nil end) it("has a corresponding get_" .. prop_name .. " counterpart", function() assert.is_function(widget["get_" .. prop_name]) end) it("is a property setter", function() assert.is_function(widget[field]) assert.is.equal(0, redraw_needed) assert.is.equal(0, layout_changed) assert.is.equal(0, property_signal_emitted) local s = spy.on(widget, field) -- An access through metatable magic widget[prop_name] = 3456 -- should have caused the call of the method assert.spy(s).was_called_with(match.is_ref(widget), 3456) -- and maybe a property:: signal assert.is.equal(deprecated_properties[prop_name] and 0 or 1, property_signal_emitted) -- and maybe a redraw assert.is.equal(redrawless_properties[prop_name] and 0 or 1, redraw_needed) -- but never a layout change. assert.is.equal(0, layout_changed) if not deprecated_properties[prop_name] then -- The property signal contains the new value. assert.is.equal(widget, property_signal_emitted_with[1]) assert.is.equal(3456, property_signal_emitted_with[2]) -- What is set, can be gotten back through the prop_name field. assert.is.equal(3456, widget[prop_name]) -- The setter returns the widget itself for call chaining. assert.spy(s).returned_with(match.is_ref(widget)) end s:clear() assert.spy(s).was_not.called() -- A repeated setting of the same value widget[prop_name] = 3456 -- should have caused the call of the method again assert.spy(s).was_called_with(match.is_ref(widget), 3456) -- but none of the signals. assert.is.equal(deprecated_properties[prop_name] and 0 or 1, property_signal_emitted) assert.is.equal(redrawless_properties[prop_name] and 0 or 1, redraw_needed) assert.is.equal(0, layout_changed) end) it("has a specific default value", function() assert.is_equal(property_defaults[prop_name], widget[prop_name]) end) it("is not magical on nil-s", function() -- When set to nil widget[prop_name] = nil -- it should stay nil and not fall back to some "default". assert.is.equal(nil, widget[prop_name]) end) end) -- end describe(field) end -- end if end -- end field loop -- Now let's check some nontrivial behavior describe("values", function() it("are empty in a fresh instance", function() assert.is.same({}, widget._private.values) end) describe("method add_value()", function() it("adds values", function() push_data(widget, data) -- Adds into the first datagroup by default. assert.is.same({data}, widget._private.values) end) it("defaults to NaN when no/falsy value is supplied", function() local amount = 15 for _ = 1, amount do widget:add_value(false) widget:add_value() widget:add_value(false, 3) widget:add_value(nil, 3) end assert.array(widget._private.values).has.no.holes() assert.is.equal(3, #widget._private.values) assert.is.equal(2*amount, #widget._private.values[1]) assert.is.equal(0, #widget._private.values[2]) assert.is.equal(2*amount, #widget._private.values[3]) for i = 1, 2*amount do local tmp = widget._private.values[1][i] assert.is_not.equal(tmp, tmp) tmp = widget._private.values[3][i] assert.is_not.equal(tmp, tmp) end end) it("adds values into specific data group", function() push_data(widget, data, 15) assert.is.same(data, widget._private.values[15]) -- Smaller datagroups are present too, but empty. assert.array(widget._private.values).has.no.holes() assert.is.equal(15, #widget._private.values) for i, data_group in ipairs(widget._private.values) do assert.array(data_group).has.no.holes() assert.is.equal(i ~= 15 and 0 or #data, #data_group) end -- Adding again to a different group push_data(widget, data2, 30) -- works assert.is.same(data2, widget._private.values[30]) -- and doesn't affect the other group. assert.is.same(data, widget._private.values[15]) -- Smaller-index datagroups are present but empty. assert.array(widget._private.values).has.no.holes() assert.is.equal(30, #widget._private.values) for i, data_group in ipairs(widget._private.values) do assert.array(data_group).has.no.holes() if i ~= 15 and i ~= 30 then assert.is.same({}, data_group) end end end) it("doesn't care about group order", function() for i = math.max(#data,#data2),1,-1 do if i % 2 == 0 and data[i] then widget:add_value(data[i], 1) end if data2[i] then widget:add_value(data2[i], 2) end if i % 2 == 1 and data[i] then widget:add_value(data[i], 1) end end assert.is.same({data, data2}, widget._private.values) end) it("doesn't work with non-natural datagroups", function() for i = #data,1,-1 do assert.has.errors(function() widget:add_value(data[i], 14.5) end) end for i = #data,1,-1 do assert.has.errors(function() widget:add_value(data[i], 0) end) end for i = #data,1,-1 do assert.has.errors(function() widget:add_value(data[i], -4) end) end for i = #data,1,-1 do assert.has.errors(function() widget:add_value(data[i], "index") end) end for i = #data,1,-1 do assert.has.errors(function() widget:add_value(data[i], {1, 2, 3}) end) end for i = #data,1,-1 do assert.has.errors(function() widget:add_value(data[i], function() end) end) end -- No values were added after all this, -- but some empty datagroups were. This is a bit suboptimal, -- but is not worth fixing. Adding an assert here -- so that one would be reminded to change it to -- a #values == 0 check, if one fixes it. assert.is.equal(14, #widget._private.values) assert.array(widget._private.values).has.no.holes() for _, data_group in ipairs(widget._private.values) do assert.is.same({}, data_group) end end) it("honors the capacity property", function() local function check_cap(cap, expected_len) expected_len = expected_len or math.max(0, math.ceil(cap)) widget.capacity = cap push_data(widget, data, 1) push_data(widget, data2, 2) -- Only the last inserted elements are kept. assert.is.same( { {unpack(data, 1, expected_len)}, {unpack(data2, 1, expected_len)} }, widget._private.values ) end for i = 0, math.min(#data, #data2) do check_cap(i) end -- It handles negatives and NaNs like zeros check_cap(-100, 0) check_cap(-100.5, 0) check_cap(0/0) -- And fractional numbers are rounded up check_cap(2.5, 3) -- But setting the capacity property by itself doesn't do anything, -- if add_value() wasn't called. widget.capacity = 1 assert.is.equal(3, #widget._private.values[1]) assert.is.equal(3, #widget._private.values[2]) end) it("stores up to 8192 values when no usage stats are available", function() assert.is_nil(widget.capacity) assert.is_nil(widget._private.last_drawn_values_num) for i = 1, 9000 do widget:add_value(i) widget:add_value(i, 3) end assert.is.equal(8192, #widget._private.values[1]) assert.is.equal(8192, #widget._private.values[3]) end) it("relies on usage stats, when capacity is unset", function() assert.is_nil(widget.capacity) widget._private.last_drawn_values_num = 100 for i = 1, 400 do widget:add_value(i) widget:add_value(i, 3) end -- The smallest multiple of 64 that is >= last_drawn_values_num + 64 assert.is.equal(192, #widget._private.values[1]) assert.is.equal(192, #widget._private.values[3]) -- so 192 elements will be kept with this too. widget._private.last_drawn_values_num = 128 widget:add_value(0) widget:add_value(0, 3) assert.is.equal(192, #widget._private.values[1]) assert.is.equal(192, #widget._private.values[3]) -- But this is one is already one too many. widget._private.last_drawn_values_num = 129 for i = 1, 400 do widget:add_value(i) widget:add_value(i, 3) end assert.is.equal(256, #widget._private.values[1]) assert.is.equal(256, #widget._private.values[3]) -- Setting it back and calling add_value() once is enough -- to purge overflowing elements, widget._private.last_drawn_values_num = 128 widget:add_value(0) assert.is.equal(192, #widget._private.values[1]) -- but only in the group, for which add_value() happened to be -- called during the time last_drawn_values_num was small enough, -- even though it's probably not a very fair behavior and could -- lead to weird visual artefacts. assert.is.equal(256, #widget._private.values[3]) -- Calling add_value for the other group puts it in line too. widget:add_value(0, 3) assert.is.equal(192, #widget._private.values[3]) end) end) -- end describe(add_value) describe("method clear()", function() it("clears values", function() local function check_clear(i) assert.is.same({}, widget._private.values) push_data(widget, data) assert.is.same({data}, widget._private.values) widget:clear() assert.is.same({}, widget._private.values) push_data(widget, data2, 3*i) assert.is.same(data2, widget._private.values[3*i]) widget:clear() assert.is.same({}, widget._private.values) end for i = 1, 3 do check_clear(i) end end) end) -- end describe(clear) end) -- end describe(values) describe("method fit()", function() it("requests all available space", function() assert.widget_fit(widget, {120, 80}, {120, 80}) assert.widget_fit(widget, {1300, 700}, {1300, 700}) end) end) describe("drawing-related", function() local dimensions local context local cr before_each(function() dimensions = {120, 80} context = { fake_context = "fake context" } cr = setmetatable({"fake cairo"}, {__index = function() return function() end end}) -- assert.widget_fit(widget, dimensions, dimensions) end) describe("property step_shape()", function() it("is not called when there are no values", function() widget.step_shape = spy.new() widget:draw(context, cr, unpack(dimensions)) widget:draw(context, cr, unpack(dimensions)) -- No values to draw, so no shapes drawn assert.spy(widget.step_shape).was_not_called() end) it("is called to draw values", function() widget.step_shape = spy.new() -- It is called once for each value. push_data(widget, data) widget:draw(context, cr, unpack(dimensions)) assert.spy(widget.step_shape).was_called(#data) -- All data is drawn despite the gap between data groups push_data(widget, data2, 3) widget:draw(context, cr, unpack(dimensions)) assert.spy(widget.step_shape).was_called(2*#data + #data2) widget:draw(context, cr, unpack(dimensions)) assert.spy(widget.step_shape).was_called(3*#data + 2*#data2) end) it("receives proper arguments from draw()", function() widget.step_shape = spy.new() push_data(widget, data) widget:draw(context, cr, unpack(dimensions)) assert.spy(widget.step_shape).was_called_with( cr, -- cairo match.is_number(), -- width match.is_number() -- height ) end) it("doesn't receive NaNs from draw()", function() widget.step_shape = spy.new(function(_, width, height) -- These are never NaNs. assert.is_equal(width, width) assert.is_equal(height, height) end) -- When there are no NaNs in data, there are no NaNs. push_data(widget, data) push_data(widget, data2, 3) widget:draw(context, cr, unpack(dimensions)) -- Write out arguments in full once so that one doesn't -- forget to update this test if arguments get changed. assert.spy(widget.step_shape).was_called_with( cr, match.is_number(), match.is_number() ) widget.step_shape:clear() -- No matter where the NaNs are, widget:add_value(0/0, 1) widget:add_value(0/0, 2) widget:add_value(0/0, 3) widget:add_value(0/0, 4) widget:draw(context, cr, unpack(dimensions)) assert.spy(widget.step_shape).was_called() widget.step_shape:clear() -- even if there's nothing but NaNs, we get them not, widget:clear() for _ = 1, 100 do widget:add_value(0/0, 1) widget:add_value(0/0, 2) widget:add_value(0/0, 3) end widget:draw(context, cr, unpack(dimensions)) -- in fact we don't even get called. assert.spy(widget.step_shape).was_not_called() widget.step_shape:clear() end) it("'s errors are not silenced by draw()", function() widget.step_shape = spy.new(function() assert(false) end) push_data(widget, data) assert.has.errors(function() widget:draw(context, cr, unpack(dimensions)) end) assert.spy(widget.step_shape).was_called(1) end) it("is disabled by falsy group_colors", function() -- TODO: if should_draw_data_group() gets decided on, -- this should be rewritten in terms of it and -- should_draw_data_group() should be tested by colors in turn. widget.step_shape = spy.new() widget.group_colors = { "#feedf00d", "#deadbeef", "#0badcafe" } push_data(widget, data, 1) push_data(widget, data, 2) push_data(widget, data2, 3) widget:draw(context, cr, unpack(dimensions)) assert.spy(widget.step_shape).was_called(2*#data + #data2) widget.step_shape:clear() widget.group_colors = { nil, false, "#feedf00d" } widget:draw(context, cr, unpack(dimensions)) assert.spy(widget.step_shape).was_called(#data2) end) end) -- end describe(step_shape) describe("method compute_drawn_values_num()", function() it("'s default implementation computes things correctly", function() local function cdvn(param) return widget:compute_drawn_values_num(param) end -- Negative and zero values don't make it return negatives. -- Default settings, border_width=step_spacing=0, step_width=1. assert.is.equal(0, cdvn(-5)) assert.is.equal(0, cdvn(0)) assert.is.equal(10, cdvn(10)) assert.is.equal(11, cdvn(10.5)) -- border_width doesn't affect anything, because -- the function assumes that it was subtracted already. widget.border_width = 2 assert.is.equal(0, cdvn(-50.5)) assert.is.equal(0, cdvn(0)) assert.is.equal(9, cdvn(9)) assert.is.equal(10, cdvn(10)) assert.is.equal(11, cdvn(10.5)) -- All of this is just how you'd expect it to be for -- math.ceil(width/(step_width + step_spacing), which it is. widget.step_width = 2 assert.is.equal(0, cdvn(-100)) assert.is.equal(0, cdvn(0)) assert.is.equal(4, cdvn(8)) assert.is.equal(5, cdvn(9)) assert.is.equal(5, cdvn(10)) assert.is.equal(6, cdvn(11)) assert.is.equal(6, cdvn(12)) widget.step_spacing = 2 assert.is.equal(0, cdvn(-100)) assert.is.equal(0, cdvn(0)) assert.is.equal(2, cdvn(8)) assert.is.equal(3, cdvn(9)) assert.is.equal(3, cdvn(10)) assert.is.equal(3, cdvn(11)) assert.is.equal(3, cdvn(12)) assert.is.equal(4, cdvn(13)) end) it("is called by draw() once", function() local s = spy.on(widget, "compute_drawn_values_num") widget:draw(context, cr, unpack(dimensions)) assert.spy(s).was_called(1) assert.spy(s).was_called_with(match.is_ref(widget), dimensions[1]) end) it("'s result is stored by draw() in last_drawn_values_num", function() local our_value = 10 local function cdvn() return our_value end widget.compute_drawn_values_num = cdvn -- The usage stats variable is uninitialized before first call. assert.is_nil(widget._private.last_drawn_values_num) widget:draw(context, cr, unpack(dimensions)) -- Now it should be initialized. assert.is.equal(our_value, widget._private.last_drawn_values_num) -- Repeated calls with increasing values should set it instantly. our_value = 20 widget:draw(context, cr, unpack(dimensions)) assert.is.equal(our_value, widget._private.last_drawn_values_num) our_value = 35 widget:draw(context, cr, unpack(dimensions)) assert.is.equal(our_value, widget._private.last_drawn_values_num) our_value = 50 widget:draw(context, cr, unpack(dimensions)) assert.is.equal(our_value, widget._private.last_drawn_values_num) -- Same values keep it on the same level too. widget:draw(context, cr, unpack(dimensions)) assert.is.equal(our_value, widget._private.last_drawn_values_num) -- Calls with smaller values should only decrement it by one. our_value = 30 widget:draw(context, cr, unpack(dimensions)) assert.is.equal(49, widget._private.last_drawn_values_num) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(48, widget._private.last_drawn_values_num) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(47, widget._private.last_drawn_values_num) -- No matter how small the values are. our_value = 0 widget:draw(context, cr, unpack(dimensions)) assert.is.equal(46, widget._private.last_drawn_values_num) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(45, widget._private.last_drawn_values_num) -- Negatives and NaNs are outright ignored. our_value = -100 widget:draw(context, cr, unpack(dimensions)) assert.is.equal(45, widget._private.last_drawn_values_num) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(45, widget._private.last_drawn_values_num) our_value = 0/0 widget:draw(context, cr, unpack(dimensions)) assert.is.equal(45, widget._private.last_drawn_values_num) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(45, widget._private.last_drawn_values_num) -- Positive infinity will fry the widget forever though. our_value = math.huge widget:draw(context, cr, unpack(dimensions)) assert.is.equal(math.huge, widget._private.last_drawn_values_num) our_value = 15 widget:draw(context, cr, unpack(dimensions)) assert.is.equal(math.huge, widget._private.last_drawn_values_num) end) end) -- end describe(computed_drawn_values_num) describe("colors", function() local magic_color local magic_color_used local current_cr_source before_each(function() cr.set_source = function(_, src) if src == magic_color then magic_color_used = magic_color_used + 1 end current_cr_source = src end magic_color, magic_color_used = color("#deadc0de"), 0 current_cr_source = nil end) it("aren't used, when unset", function() -- Let's check that our magic color isn't some default color -- in a fresh widget assert.is.equal(0, magic_color_used) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) -- even after pushing data. push_data(widget, data, 4) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) end) describe("property color", function() -- These tests overlap with pick_data_group_color() tests, -- but are added for completeness. it("is used by draw() when set", function() assert.is.equal(0, magic_color_used) widget.color = magic_color -- Not used, if there are no values to draw. widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) -- Used, if there are, once for each data group. push_data(widget, data, 4) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(4, magic_color_used) end) it("is overridden by group_colors property", function() assert.is.equal(0, magic_color_used) widget.color = magic_color widget.group_colors = { "#feedf00d" } push_data(widget, data, 1) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) end) end) -- end describe(color) describe("property group_colors", function() -- These tests overlap with pick_data_group_color() tests, -- but are added for completeness. it("is used by draw() when set", function() assert.is.equal(0, magic_color_used) widget.group_colors = { "#feedfood", false, magic_color, magic_color } -- Not used, if there are no values to draw. widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) -- Not used, if there are no values in its group. push_data(widget, data, 1) push_data(widget, data2, 2) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) -- Used, if there are, once for each data group. push_data(widget, data, 3) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(1, magic_color_used) push_data(widget, data2, 4) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(3, magic_color_used) end) end) -- end describe(group_colors) describe("property background_color", function() it("is used by draw() when set", function() assert.is.equal(0, magic_color_used) widget.background_color = magic_color widget:draw(context, cr, unpack(dimensions)) assert.is.equal(1, magic_color_used) end) end) -- end describe(background_color) describe("property border_color", function() it("is used by draw() when set", function() assert.is.equal(0, magic_color_used) widget.border_color = magic_color -- Not used, when border_width <= 0, so not by default either. widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) widget.border_width = 0 widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) widget.border_width = -1 widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) -- Anything positive enables the border. widget.border_width = 0.000001 widget:draw(context, cr, unpack(dimensions)) assert.is.equal(1, magic_color_used) end) end) -- end describe(border_color) describe("property nan_color", function() it("is good to go by default", function() assert.is.truthy(widget.nan_indication) -- TODO: maybe expose default_nan_color and check that it's used. end) it("is used by draw() when set", function() assert.is.equal(0, magic_color_used) widget.nan_color = magic_color push_data(widget, data, 4) -- Not used, when there are no NaNs. widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) -- Is used once when a NaN is found, no matter how many -- NaNs and groups there are. widget:add_value(0/0, 4) widget:add_value(0/0, 4) widget:add_value(0/0, 5) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(1, magic_color_used) -- But not when nan_indication is disabled. widget.nan_indication = false widget:draw(context, cr, unpack(dimensions)) assert.is.equal(1, magic_color_used) end) end) -- end describe(nan_color) describe("method pick_data_group_color()", function() it("is called by draw() for each data group", function() widget.pick_data_group_color = spy.new() -- Not used, when there are no data groups. widget:draw(context, cr, unpack(dimensions)) assert.spy(widget.pick_data_group_color).was_not.called() -- Used once for each data group (even empty ones, as it -- happens, though that is not really necessary). -- But I'm testing for it here too, so that possible -- future behavior change can be detected, and it can -- be decided, if it's ok to break. push_data(widget, data, 1) push_data(widget, data2, 3) widget:draw(context, cr, unpack(dimensions)) assert.spy(widget.pick_data_group_color).was_called(3) assert.spy(widget.pick_data_group_color).was_called_with(match.is_ref(widget), 1) assert.spy(widget.pick_data_group_color).was_called_with(match.is_ref(widget), 2) assert.spy(widget.pick_data_group_color).was_called_with(match.is_ref(widget), 3) end) it("is used by draw() to pick colors", function() local s = spy.new(function(_, data_group) return data_group > 2 and magic_color or "#feedf00d" end) widget.pick_data_group_color = s -- Not used, if there are no values to draw. assert.is.equal(0, magic_color_used) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) assert.spy(s).was_not_called() -- Colors it returns are used for respective data groups, -- once for each data group. push_data(widget, data, 1) push_data(widget, data2, 2) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(0, magic_color_used) assert.spy(s).was_called(2) push_data(widget, data, 3) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(1, magic_color_used) assert.spy(s).was_called(5) push_data(widget, data2, 4) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(3, magic_color_used) assert.spy(s).was_called(9) s:clear() end) it("'s returned color is set early, so as to be available in the corresponding step_shape()", function() local current_data_group widget.pick_data_group_color = function(_, data_group) current_data_group = data_group return (data_group % 2 == 1) and magic_color or "#feedf00d" end widget.step_shape = function() if current_data_group % 2 == 1 then assert.is.equal(magic_color, current_cr_source) else assert.is_not.equal(magic_color, current_cr_source) end end assert.is.equal(0, magic_color_used) assert.is_nil(current_cr_source) push_data(widget, data, 1) push_data(widget, data2, 2) push_data(widget, data2, 3) push_data(widget, data, 4) widget:draw(context, cr, unpack(dimensions)) assert.is.equal(2, magic_color_used) end) it("returns some valid color even in a fresh widget", function() local groups = {1, 2, 50, 0, -1} local colors = {} for i, group in ipairs(groups) do colors[i] = widget:pick_data_group_color(group) assert.is.truthy(colors[i]) end -- Regardless of data presence. push_data(widget, data, 2) local colors2 = {} for i, group in ipairs(groups) do colors2[i] = widget:pick_data_group_color(group) assert.is.truthy(colors2[i]) end assert.is.same(colors, colors2) -- And it's actually the same default color in all cases. for i = 2, #colors do assert.is.equal(colors[i-1], colors[i]) end -- And gears.color() doesn't choke on it. assert.is.truthy(color(colors[1])) -- Because it's red, tbh. assert.is.equal("#ff0000", colors[1]) end) it("uses color and group_colors properties", function() local groups = {1, 2, 5, 3, 4, 0, 6, -1} for _, group in ipairs(groups) do assert.is_not.equal(magic_color, widget:pick_data_group_color(group)) end widget.color = magic_color for _, group in ipairs(groups) do assert.is.equal(magic_color, widget:pick_data_group_color(group)) end -- But group_colors override color. local group_colors = { "#feedf00d", "#deadbeef", false, nil, "#0badcafe" } widget.group_colors = {unpack(group_colors, 1, 5)} for _, group in ipairs(groups) do if group_colors[group] then assert.is.equal(group_colors[group], widget:pick_data_group_color(group)) else -- But not when the color is falsy in the table group_colors. assert.is.equal(magic_color, widget:pick_data_group_color(group)) end end end) end) -- end describe(pick_data_group_color) end) -- end describe(colors) end) -- end describe(drawing-related) end) -- end describe(graph) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80