awesome/tests/test-naughty-legacy.lua

1278 lines
35 KiB
Lua

-- This test suite tries to prevent the legacy notification popups from
-- regressing as the new notification API is improving.
local spawn = require("awful.spawn")
local naughty = require("naughty" )
local gdebug = require("gears.debug")
local gtable = require("gears.table")
local gfs = require("gears.filesystem")
local cairo = require("lgi" ).cairo
local beautiful = require("beautiful")
local Gio = require("lgi" ).Gio
local GLib = require("lgi" ).GLib
local gpcall = require("gears.protected_call")
local dwidget = require("naughty.widget._default")
-- Bypass the new rc.lua and force the legacy mode again.
naughty._reset_display_handlers()
-- This module test deprecated APIs
require("gears.debug").deprecate = function() end
local dbus_connection = assert(Gio.bus_get_sync(Gio.BusType.SESSION))
local function send_notify(app, id, icon, summary, body, actions, hints, timeout, callback)
local parameters = GLib.Variant("(susssasa{sv}i)", {
app, id, icon, summary, body, actions, hints, timeout
})
local reply_type = GLib.VariantType.new("(u)")
local function invoke_callback(conn, result)
local new_id = conn:call_finish(result).value[1]
if callback then
callback(new_id)
end
end
dbus_connection:call("org.freedesktop.Notifications",
"/org/freedesktop/Notifications", "org.freedesktop.Notifications",
"Notify", parameters, reply_type, Gio.DBusCallFlags.NO_AUTO_START, -1,
nil, invoke_callback)
end
local steps = {}
local has_cmd_notify = false
-- Use `notify-send` instead of the shimmed version to better test the dbus
-- to notification code.
local function check_cmd()
local path = os.getenv("PATH")
local pos = 1
while path:find(":", pos) do
local np = path:find(":", pos)
local p = path:sub(pos, np-1).."/"
pos = np+1
local f = io.open(p.."notify-send")
if f then
f:close()
has_cmd_notify = true
end
if has_cmd_notify then return end
end
end
check_cmd()
if not has_cmd_notify then
require("gears.debug").print_warning("Did not find notify-send, skipping some tests")
end
local active, destroyed, reasons, added_counter = {}, {}, {}, 0
local default_width, default_height = 0, 0
local function added_callback(n)
table.insert(active, n)
added_counter = added_counter + 1
end
naughty.connect_signal("added", added_callback)
local function destroyed_callback(n, reason)
local found = false
for k, n2 in ipairs(active) do
if n2 == n then
found = true
table.remove(active, k)
end
end
assert(found)
if reason then
reasons[reason] = reasons[reason] and reasons[reason] + 1 or 1
end
table.insert(destroyed, n)
end
naughty.connect_signal("destroyed", destroyed_callback)
-- Test vertical overlapping
local function test_overlap()
local wa = mouse.screen.workarea
for _, n1 in ipairs(active) do
assert(n1.box)
assert(n1.id)
-- Check for overlapping the workarea
assert(n1.box.y+default_height < wa.y+wa.height)
assert(n1.box.y >= wa.y)
-- Check for overlapping each other
for _, n2 in ipairs(active) do
assert(n2.box)
if n1 ~= n2 then
local geo1, geo2 = n1.box:geometry(), n2.box:geometry()
assert(geo1.height == geo2.height)
assert(geo1.height + 2*n1.box.border_width + naughty.config.spacing
== default_height)
if n1.position == n2.position then
assert(
geo1.y >= geo2.y+default_height or
geo2.y >= geo1.y+default_height
)
end
end
end
end
end
-- Test default size, and title/message.
table.insert(steps, function()
local n = naughty.notification {
title = "title",
message = "message"
}
assert(n.box)
local offset = 2*n.box.border_width
default_width = n.box.width+offset
default_height = n.box.height + offset + naughty.config.spacing
assert(default_width > 0)
assert(default_height > 0)
-- Test title/message behavior.
assert(n.textbox:get_markup() == "<b>title</b>\nmessage", n.textbox:get_markup())
assert(n.textbox:get_text() == "title\nmessage", n.textbox:get_text())
n.title = nil
assert(n.textbox:get_markup() == "message", n.textbox:get_markup())
assert(n.textbox:get_text() == "message", n.textbox:get_text())
n.title = ""
assert(n.textbox:get_markup() == "message", n.textbox:get_markup())
assert(n.textbox:get_text() == "message", n.textbox:get_text())
n.message = "<i>only</i> message"
assert(n.textbox:get_markup() == "<i>only</i> message", n.textbox:get_markup())
assert(n.textbox:get_text() == "only message", n.textbox:get_text())
n.message = nil
assert(n.textbox:get_markup() == "", n.textbox:get_markup())
assert(n.textbox:get_text() == "", n.textbox:get_text())
n.message = ""
assert(n.textbox:get_markup() == "", n.textbox:get_markup())
assert(n.textbox:get_text() == "", n.textbox:get_text())
-- Only title (with markup).
n.title = "<i>only</i> title"
assert(n.textbox:get_markup() == "<b>&lt;i&gt;only&lt;/i&gt; title</b>", n.textbox:get_markup())
assert(n.textbox:get_text() == "<i>only</i> title", n.textbox:get_text())
-- Markup with only interpreting "<br>".
n.title = ""
n.message = "<3"
assert(n.textbox:get_markup() == "&lt;3", n.textbox:get_markup())
assert(n.textbox:get_text() == "<3", n.textbox:get_text())
n.title = ">3"
assert(n.textbox:get_markup() == "<b>&gt;3</b>\n&lt;3", n.textbox:get_markup())
assert(n.textbox:get_text() == ">3\n<3", n.textbox:get_text())
n.message = nil
assert(n.textbox:get_markup() == "<b>&gt;3</b>", n.textbox:get_markup())
assert(n.textbox:get_text() == ">3", n.textbox:get_text())
-- Invalid utf8.
local invalid_title = "title" .. string.char(0x80) .. "X"
local invalid_message = "message" .. string.char(0x80) .. "X"
n.title = invalid_title
n.message = invalid_message
assert(n.textbox:get_markup() == nil, n.textbox:get_markup())
local expected_text = invalid_title .. "\n" .. invalid_message
expected_text = expected_text:gsub(string.char(0x80), string.char(0xff))
assert(n.textbox:get_text() == expected_text, n.textbox:get_text())
n:destroy()
assert(added_counter == 1)
added_counter = 0
return true
end)
if has_cmd_notify then
table.insert(steps, function()
spawn{ 'notify-send', 'title', 'message', '-t', '25000' }
return true
end)
table.insert(steps, function()
if #active ~= 1 then return end
local n = active[1]
assert(n.box)
-- Make sure the expiration timer is started
assert(n.timer)
assert(n.timer.started)
assert(n.is_expired == false)
n:destroy()
assert(#active == 0)
return true
end)
-- Test pausing incoming notifications.
table.insert(steps, function()
assert(not naughty.suspended)
naughty.suspended = true
-- There is some magic behind this, check it works
assert(naughty.suspended)
spawn{ 'notify-send', 'title', 'message', '-t', '25000' }
return true
end)
-- Test resuming incoming notifications.
table.insert(steps, function(count)
if count ~= 4 then return end
assert(#active == 0)
assert(#naughty.notifications.suspended == 1)
assert(naughty.notifications.suspended[1]:get_suspended())
naughty.resume()
assert(not naughty.suspended)
assert(#naughty.notifications.suspended == 0)
assert(#active == 1)
active[1]:destroy()
assert(#active == 0)
spawn{ 'notify-send', 'title', 'message', '-t', '1' }
return true
end)
-- Test automatic expiration.
table.insert(steps, function()
if added_counter ~= 3 then return end
return true
end)
table.insert(steps, function()
if #active > 0 then return end
-- It expired after one milliseconds, so it should be gone as soon as
-- it is registered.
assert(#active == 0)
assert(not naughty.expiration_paused)
naughty.expiration_paused = true
-- There is some magic behind this, make sure it works
assert(naughty.expiration_paused)
spawn{ 'notify-send', 'title', 'message', '-t', '1' }
return true
end)
-- Test disabling automatic expiration.
table.insert(steps, function()
if added_counter ~= 4 then return end
-- It should not expire by itself, so that should always be true
assert(#active == 1)
return true
end)
-- Wait long enough to avoid races.
table.insert(steps, function(count)
if count ~= 4 then return end
assert(#active == 1)
assert(active[1].is_expired)
naughty.expiration_paused = false
assert(not naughty.expiration_paused)
return true
end)
-- Make sure enabling expiration process the expired queue.
table.insert(steps, function()
-- Right now this doesn't require a step for itself, but this could change
-- so better not "document" the instantaneous clearing of the queue.
if #active > 0 then return end
spawn{ 'notify-send', 'low', 'message', '-t', '25000', '-u', 'low' }
spawn{ 'notify-send', 'normal', 'message', '-t', '25000', '-u', 'normal' }
spawn{ 'notify-send', 'critical', 'message', '-t', '25000', '-u', 'critical' }
return true
end)
-- Test the urgency level and default preset.
table.insert(steps, function()
if added_counter ~= 7 then return end
while #active > 0 do
active[1]:destroy()
end
return true
end)
-- Test what happens when the screen has the maximum number of notification it
-- can display at one.
table.insert(steps, function()
local wa = mouse.screen.workarea
local max_notif = math.floor(wa.height/default_height)
-- Everything should fit, otherwise the math is wrong in
-- `neughty.layout.legacy` and its a regression.
for i=1, max_notif do
spawn{ 'notify-send', 'notif '..i, 'message', '-t', '25000', '-u', 'low' }
end
return true
end)
-- Check the lack of overlapping and the presence of the expected content.
table.insert(steps, function()
local wa = mouse.screen.workarea
local max_notif = math.floor(wa.height/default_height)
if added_counter ~= 7 + max_notif then return end
assert(#active == max_notif)
test_overlap()
-- Now add even more!
for i=1, 5 do
spawn{ 'notify-send', 'notif '..i, 'message', '-t', '25000', '-u', 'low' }
end
return true
end)
-- Test the code to hide the older notifications when there is too many for the
-- screen.
table.insert(steps, function()
local wa = mouse.screen.workarea
local max_notif = math.floor(wa.height/default_height)
if added_counter ~= 7 + max_notif + 5 then return end
-- The other should have been hidden
assert(#active == max_notif)
assert(reasons[naughty.notification_closed_reason.too_many_on_screen] == 5)
test_overlap()
while #active > 0 do
active[1]:destroy()
end
return true
end)
end
local positions = {
"top_left" , "top_middle" , "top_right" ,
"bottom_left" , "bottom_middle" , "bottom_right" ,
"middle" ,
}
-- Test each corners.
table.insert(steps, function()
for _, pos in ipairs(positions) do
for i=1, 3 do
-- Skip dbus for this one.
naughty.notification {
position = pos,
title = "At "..pos.." "..i,
message = "some message",
timeout = 25000,
}
end
end
return true
end)
table.insert(steps, function()
if #active ~= #positions*3 then return end
test_overlap()
while #active > 0 do
active[1]:destroy()
end
return true
end)
local big_icon = cairo.ImageSurface(cairo.Format.ARGB32, 256, 256)
local cr = cairo.Context(big_icon)
local small_icon = cairo.ImageSurface(cairo.Format.ARGB32, 32 , 32 )
local cr2 = cairo.Context(small_icon)
local wierd_ratio1 = cairo.ImageSurface(cairo.Format.ARGB32, 256, 128)
local cr3 = cairo.Context(wierd_ratio1)
local wierd_ratio2 = cairo.ImageSurface(cairo.Format.ARGB32, 128, 256)
local cr4 = cairo.Context(wierd_ratio2)
-- Checkboard shirt pattern icon!
for i=1, 5 do
for j=1, 5 do
cr:set_source_rgb(
i%2 == 1 and 1 or 0, j%2 == 1 and 1 or 0, i%2 == 0 and 0 or 1
)
cr:rectangle( (i-1)*48, (j-1)*48, 48, 48 )
cr:fill()
cr2:set_source_rgb(
i%2 == 1 and 1 or 0, j%2 == 1 and 1 or 0, i%2 == 0 and 0 or 1
)
cr2:rectangle( (i-1)*6, (j-1)*6, 6, 6 )
cr2:fill()
cr3:set_source_rgb(
i%2 == 1 and 1 or 0, j%2 == 1 and 1 or 0, i%2 == 0 and 0 or 1
)
cr3:rectangle( (i-1)*48, (j-1)*24, 48, 24 )
cr3:fill()
cr4:set_source_rgb(
i%2 == 1 and 1 or 0, j%2 == 1 and 1 or 0, i%2 == 0 and 0 or 1
)
cr4:rectangle( (i-1)*24, (j-1)*48, 24, 48 )
cr4:fill()
end
end
-- Test the icon size constraints.
table.insert(steps, function()
beautiful.notification_icon_size = 64
-- Icons that are too large (they should be downscaled)
local n1 = naughty.notification {
icon = big_icon,
title = "Has a nice icon!",
message = "big",
timeout = 25000,
}
assert(n1.id)
assert(n1.iconbox)
assert(n1.iconbox._private.image:get_width () == beautiful.notification_icon_size)
assert(n1.iconbox._private.image:get_height() == beautiful.notification_icon_size)
assert(n1.iconbox._private.image:get_width () == n1.size_info.icon_w)
assert(n1.iconbox._private.image:get_height() == n1.size_info.icon_h)
assert(n1.size_info.icon_scale_factor == 1/4)
-- Icons that are too small (they should not be upscaled)
local n2 = naughty.notification {
icon = small_icon,
title = "Has a nice icon!",
message = "small",
timeout = 25000,
}
assert(n2.id)
assert(n2.iconbox)
assert(n2.iconbox._private.image:get_width () == 32)
assert(n2.iconbox._private.image:get_height() == 32)
assert(n2.iconbox._private.image:get_width () == n2.size_info.icon_w)
assert(n2.iconbox._private.image:get_height() == n2.size_info.icon_h)
assert(not n2.size_info.icon_scale_factor)
-- Downscaled non square icons (aspect ratio should be kept).
local n3 = naughty.notification {
icon = wierd_ratio1,
title = "Has a nice icon!",
message = "big",
timeout = 25000,
}
local n4 = naughty.notification {
icon = wierd_ratio2,
title = "Has a nice icon!",
message = "big",
timeout = 25000,
}
assert(n3.iconbox)
assert(n3.iconbox._private.image:get_width () == beautiful.notification_icon_size)
assert(n3.iconbox._private.image:get_height() == beautiful.notification_icon_size/2)
assert(n3.iconbox._private.image:get_width () == n3.size_info.icon_w)
assert(n3.iconbox._private.image:get_height() == n3.size_info.icon_h)
assert(n3.size_info.icon_scale_factor == 1/4)
assert(n4.iconbox)
assert(n4.iconbox._private.image:get_width () == beautiful.notification_icon_size/2)
assert(n4.iconbox._private.image:get_height() == beautiful.notification_icon_size)
assert(n4.iconbox._private.image:get_width () == n4.size_info.icon_w)
assert(n4.iconbox._private.image:get_height() == n4.size_info.icon_h)
assert(n4.size_info.icon_scale_factor == 1/4)
-- The notification size should change proportionally to the icon size.
assert(n1.box.width == n2.box.width + 32)
assert(n1.box.height == n2.box.height + 32)
assert(n1.box.height == n3.box.height + 32)
assert(n1.box.width == n4.box.width + 32)
assert(n1.box.height == n4.box.height)
assert(n1.box.width == n3.box.width )
-- Make sure unconstrained icons work as expected.
beautiful.notification_icon_size = nil
local n5 = naughty.notification {
icon = big_icon,
title = "Has a nice icon!",
message = "big",
timeout = 25000,
}
assert(n5.iconbox)
assert(n5.iconbox._private.image:get_width () == 256)
assert(n5.iconbox._private.image:get_height() == 256)
assert(n5.iconbox._private.image:get_width () == n5.size_info.icon_w)
assert(n5.iconbox._private.image:get_height() == n5.size_info.icon_h)
assert(not n5.size_info.icon_scale_factor)
-- Make sure invalid icons don't prevent the message from being shown.
local n6 = naughty.notification {
icon = "this/is/an/invlid/path",
title = "Has a nice icon!",
message = "Very important life saving advice",
timeout = 25000,
}
n1:destroy()
n2:destroy()
n3:destroy()
n4:destroy()
n5:destroy()
n6:destroy()
assert(#active == 0)
return true
end)
-- Test notifications with size constraints.
table.insert(steps, function()
local str = "foobar! "
assert(#active == 0)
-- 2^9 foobars is a lot of foobars.
for _=1, 10 do
str = str .. str
end
-- First, see what happen without any constraint and enormous messages.
-- This also test notifications larger than the workarea.
local n1 = naughty.notification {
title = str,
message = str,
timeout = 25000,
}
-- Same, but with an icon and larger borders.
local n2 = naughty.notification {
icon = big_icon,
title = str,
message = str,
timeout = 25000,
border_width = 40,
}
local wa = mouse.screen.workarea
assert(n1.box.width +2*n1.box.border_width <= wa.width )
assert(n1.box.height+2*n1.box.border_width <= wa.height)
assert(n2.box.width +2*n2.box.border_width <= wa.width )
assert(n2.box.height+2*n2.box.border_width <= wa.height)
-- Check with client icons.
assert(not n1.icon)
n1._private.clients = {{icon= big_icon, type = "normal"}}
assert(n1.icon == big_icon)
assert(n1.box.width +2*n1.box.border_width <= wa.width )
assert(n1.box.height+2*n1.box.border_width <= wa.height)
assert(n2.box.width +2*n2.box.border_width <= wa.width )
assert(n2.box.height+2*n2.box.border_width <= wa.height)
n1:destroy()
n2:destroy()
-- Now set a maximum size and try again.
beautiful.notification_max_width = 256
beautiful.notification_max_height = 96
local n3 = naughty.notification {
title = str,
message = str,
timeout = 25000,
}
assert(n3.box.width <= 256)
assert(n3.box.height <= 96 )
-- Now test when the icon is larger than the maximum.
local n4 = naughty.notification {
icon = big_icon,
title = str,
message = str,
timeout = 25000,
}
assert(n4.box.width <= 256)
assert(n4.box.height <= 96 )
assert(n4.iconbox._private.image:get_width () == n4.size_info.icon_w)
assert(n4.iconbox._private.image:get_height() == n4.size_info.icon_h)
assert(n4.size_info.icon_w <= 256)
assert(n4.size_info.icon_h <= 96 )
n3:destroy()
n4:destroy()
assert(#active == 0)
return true
end)
-- Test more advanced features than what notify-send can provide.
-- Test the actions.
table.insert(steps, function()
assert(#active == 0)
send_notify("Awesome test", 0, "", "title", "message body",
{ "1", "one", "2", "two", "3", "three" }, {}, 25000)
return true
end)
table.insert(steps, function()
if #active == 0 then return end
assert(#active == 1)
local n = active[1]
assert(n.box)
assert(#n.actions == 3)
assert(n.actions[1].name == "one" )
assert(n.actions[2].name == "two" )
assert(n.actions[3].name == "three")
n:destroy()
return true
end)
--TODO Test too many actions.
--TODO Test action with long names.
local nid, name_u, message_u, actions_u = nil
-- Test updating a notification.
table.insert(steps, function()
send_notify("Awesome test", 0, "", "title", "message body",
{ "1", "one", "2", "two", "3", "three" }, {}, 25000, function(id)
nid = id
end)
return true
end)
table.insert(steps, function()
if #active == 0 or not nid then return end
local n = active[1]
n:connect_signal("property::title" , function(_, val) name_u = val end)
n:connect_signal("property::message", function(_, val) message_u = val end)
n:connect_signal("property::actions", function(_, val) actions_u = val end)
send_notify("Awesome test", nid, "", "updated title", "updated message body",
{ "1", "four", "2", "five", "3", "six" }, {}, 25000)
return true
end)
-- Test if all properties have been updated.
table.insert(steps, function()
if not name_u then return end
if not message_u then return end
if not actions_u then return end
-- No new notification should have been created.
assert(#active == 1)
local n = active[1]
assert(n.title == "updated title" )
assert(n.message == "updated message body")
assert(#n.actions == 3)
assert(n.actions[1].name == "four" )
assert(n.actions[2].name == "five" )
assert(n.actions[3].name == "six" )
n:destroy()
assert(#active == 0)
assert(name_u == "updated title", name_u)
assert(message_u == "updated message body", name_u)
assert(#actions_u == 3)
return true
end)
local int_n = nil
-- Test replace_id with internally generated notifications.
-- Even if it makes little sense, it should work and is used in the wild.
table.insert(steps, function()
int_n = naughty.notification {
title = "foo",
message = "bar",
timeout = 25000,
}
local nid2 = int_n.id
assert(int_n.id)
local res, err = pcall(function()
int_n.id = 1337
end)
assert(err:match("Notification identifier can only be set once"))
assert(not res)
assert(int_n.id == nid2)
-- watch out, capi.dbus signals are not normal object signals
send_notify("Awesome test", nid2, "", "title2", "text2",
{ "the one action" }, {}, 123)
return true
end)
-- Test that the values were replaced
table.insert(steps, function()
if int_n.title ~= "title2" then return end
assert(int_n.title == "title2")
assert(int_n.message == "text2" )
--assert(int_n.timeout == 123 ) --FIXME
int_n:destroy()
--TODO test what happens when updating a destroyed notification
-- There is currently no API to resurrect notifications.
return true
end)
-- Test adding actions, resident mode and action sharing.
table.insert(steps, function()
local n1 = naughty.notification {
title = "foo",
message = "bar",
timeout = 25000,
resident = true,
actions = { naughty.action { name = "a1" } }
}
local n2 = naughty.notification {
title = "foo",
message = "bar",
resident = true,
timeout = 25000,
actions = { naughty.action { name = "a2" } }
}
local is_called = {}
n1:connect_signal("invoked", function() is_called[1] = true end)
n2:connect_signal("invoked", function() is_called[2] = true end)
n1.actions[1]:invoke(n1)
n2.actions[1]:invoke(n2)
assert(is_called[1])
assert(is_called[2])
assert(not n1._private.is_destroyed)
assert(not n2._private.is_destroyed)
local shared_a = naughty.action { name = "a3" }
n1:append_actions {shared_a}
n2:append_actions {shared_a}
n1:connect_signal("invoked", function() is_called[3] = true end)
n2:connect_signal("invoked", function() is_called[4] = true end)
assert(n1.actions[2] == shared_a)
assert(n2.actions[2] == shared_a)
shared_a:invoke(n1)
assert(is_called[3])
assert(not is_called[4])
assert(not n1._private.is_destroyed)
assert(not n2._private.is_destroyed)
n1.resident = false
n2.resident = false
shared_a:invoke(n1)
assert(n1._private.is_destroyed)
assert(not n2._private.is_destroyed)
shared_a:invoke(n2)
assert(n2._private.is_destroyed)
return true
end)
-- Test that exposing support for animations and persistence are exposed to DBus.
table.insert(steps, function()
assert(not naughty.persistence_enabled)
assert(not naughty.image_animations_enabled)
assert(gtable.hasitem(naughty.dbus._capabilities, "icon-static"))
assert(not gtable.hasitem(naughty.dbus._capabilities, "icon-multi"))
assert(not gtable.hasitem(naughty.dbus._capabilities, "persistence"))
naughty.persistence_enabled = true
naughty.image_animations_enabled = true
assert(naughty.persistence_enabled)
assert(naughty.image_animations_enabled)
assert(gtable.hasitem(naughty.dbus._capabilities, "icon-multi"))
assert(gtable.hasitem(naughty.dbus._capabilities, "persistence"))
assert(not gtable.hasitem(naughty.dbus._capabilities, "icon-static"))
naughty.persistence_enabled = false
naughty.image_animations_enabled = false
assert(not naughty.persistence_enabled)
assert(not naughty.image_animations_enabled)
assert( gtable.hasitem(naughty.dbus._capabilities, "icon-static"))
assert(not gtable.hasitem(naughty.dbus._capabilities, "icon-multi" ))
assert(not gtable.hasitem(naughty.dbus._capabilities, "persistence"))
return true
end)
-- Check that request::icon stops when an icon is found.
table.insert(steps, function()
local called = 0
local function set_icon1()
called = called + 1
end
local function set_icon2(n)
called = called + 1
n.icon = big_icon
end
local function set_icon3()
called = called + 1
end
naughty.connect_signal("request::icon", set_icon1)
naughty.connect_signal("request::icon", set_icon2)
naughty.connect_signal("request::icon", set_icon3)
assert(called == 0)
local n1 = naughty.notification {
title = "foo",
message = "bar",
app_icon = "baz"
}
assert(called == 2)
n1:destroy()
naughty.disconnect_signal("request::icon", set_icon1)
naughty.disconnect_signal("request::icon", set_icon2)
naughty.disconnect_signal("request::icon", set_icon3)
-- Check if `disconnect_signal` works.
n1 = naughty.notification {
title = "foo",
message = "bar",
app_icon = "baz"
}
assert(called == 2)
n1:destroy()
return true
end)
local icon_requests = {}
-- Check if the action icon support is detected.
table.insert(steps, function()
assert(#active == 0)
naughty.connect_signal("request::action_icon", function(a, _, hints)
icon_requests[hints.id] = a
a.icon = hints.id == "list-add" and small_icon or big_icon
end)
naughty.connect_signal("request::icon", function(n, _)
icon_requests[n] = true
end)
local hints = {
["action-icons"] = GLib.Variant("b", true),
}
send_notify("Awesome test", 0, "", "title", "message body",
{ "list-add", "add", "list-remove", "remove" }, hints, 25000)
return true
end)
table.insert(steps, function()
if #active ~= 1 then return end
local n = active[1]
assert(icon_requests[n])
assert(n._private.freedesktop_hints)
assert(n._private.freedesktop_hints["action-icons"] == true)
assert(icon_requests["list-add" ] == n.actions[1])
assert(icon_requests["list-remove"] == n.actions[2])
assert(n.actions[1].icon == small_icon)
assert(n.actions[2].icon == big_icon )
assert(type(n.actions[1].position) == "number")
assert(type(n.actions[2].position) == "number")
assert(n.actions[1].position == 1)
assert(n.actions[2].position == 2)
n:destroy()
return true
end)
-- Test the error popup.
table.insert(steps, function()
local got = nil
naughty.connect_signal("request::display_error", function(err)
got = err
end)
awesome.emit_signal("debug::error", "foo")
assert(got == "foo")
return true
end)
-- Now check if the old deprecated (but still supported) APIs don't have errors.
table.insert(steps, function()
-- Tests are (by default) not allowed to call deprecated APIs
gdebug.deprecate = function() end
local n = naughty.notification {
title = "foo",
message = "bar",
timeout = 25000,
app_icon = "baz"
}
-- Make sure the suspension don't cause errors
assert(not naughty.is_suspended())
assert(not naughty.suspended)
naughty.suspend()
assert(naughty.is_suspended())
assert(naughty.suspended)
naughty.resume()
assert(not naughty.is_suspended())
assert(not naughty.suspended)
naughty.toggle()
assert(naughty.is_suspended())
assert(naughty.suspended)
naughty.toggle()
assert(not naughty.is_suspended())
assert(not naughty.suspended)
naughty.suspended = not naughty.suspended
assert(naughty.is_suspended())
assert(naughty.suspended)
naughty.suspended = not naughty.suspended
assert(not naughty.is_suspended())
assert(not naughty.suspended)
-- Replace the text
assert(icon_requests[n])
assert(n.title == "foo")
assert(n.message == "bar")
assert(n.text == "bar")
local width, height = n.width, n.height
assert(width)
assert(height)
naughty.replace_text(n, "foobar", "baz")
assert(n.title == "foobar")
assert(n.message == "baz")
assert(n.text == "baz")
assert(n.width > width)
assert(n.height == height)
width, height = n.width, n.height
naughty.replace_text(n, "foo", "bar\nbar")
assert(n.title == "foo")
assert(n.message == "bar\nbar")
assert(n.text == "bar\nbar")
assert(n.width < width)
assert(n.height > height)
width, height = n.width, n.height
naughty.replace_text(n, "foo", "bar")
assert(n.title == "foo")
assert(n.message == "bar")
assert(n.text == "bar")
assert(n.width == width)
assert(n.height < height)
-- Test the ID system
local id = n.id
assert(naughty.getById(id) == n)
assert(naughty.get_by_id(id) == n)
assert(naughty.getById(42) ~= n)
assert(naughty.get_by_id(42) ~= n)
-- The timeout
local real, called = n.reset_timeout, false
n.reset_timeout = function(...)
called = true
return real(...)
end
naughty.reset_timeout(n, 1337)
assert(called)
assert(n.timer and n.timer.started)
-- Destroy using the old API
local old_count = #destroyed
naughty.destroy(n)
assert(old_count == #destroyed - 1)
-- Destroy using the old API, while suspended
local n2 = naughty.notification {
title = "foo",
message = "bar",
timeout = 25000,
}
naughty.suspended = true
naughty.destroy(n2)
assert(old_count == #destroyed - 2)
naughty.suspended = false
-- The old notify function and "text" instead of "message"
n = naughty.notify { text = "foo" }
assert(n.message == "foo")
assert(n.text == "foo")
-- Calling `naughty.notify` with replace_id.
n2 = naughty.notify {
replaces_id = n.id,
message = "bar",
title = "foo",
}
assert(n == n2 )
assert(n.message == "bar")
assert(n.text == "bar")
assert(n.title == "foo")
-- Finish by testing disconnect_signal
naughty.disconnect_signal("destroyed", destroyed_callback)
naughty.disconnect_signal("added", added_callback)
return true
end)
-- Add a "new API" handler.
local current_template, had_error, handler_called = nil
table.insert(steps, function()
assert(naughty.has_display_handler == false)
naughty.connect_signal("request::display", function(n)
handler_called = true
naughty.layout.box {
notification = n,
widget_template = current_template
}
end)
return true
end)
-- Check that the various request::icon work.
table.insert(steps, function()
local gsurface = require("gears.surface")
local ls = gsurface.load_uncached_silently
function gsurface.load_uncached_silently(input)
return {
input = input,
get_height = function() return 1 end,
get_width = function() return 1 end,
}
end
local fr, gc = gfs.file_readable, naughty.notification.get_clients
local mocked_client = {
type = "normal",
icon = "42",
}
function naughty.notification.get_clients()
return {mocked_client}
end
function gfs.file_readable() return true end
local n = naughty.notification {
app_icon = "file:///one%20two"
}
assert(type(n.icon) == "table" and n.icon.input == "/one two")
n:destroy()
local n2 = naughty.notification {
title = "foo"
}
assert(type(n2.icon) == "table" and n2.icon.input == "42")
n2:destroy()
-- Restore the real methods.
gsurface.load_uncached_silently = ls
naughty.notification.get_clients = gc
gfs.file_readable = fr
return true
end)
-- Make sure the legacy popup is used when the new APIs fails.
table.insert(steps, function()
assert(naughty.has_display_handler == true)
function gpcall._error_handler()
had_error = true
end
local n = naughty.notification {
title = nil,
message = nil,
timeout = 25000,
}
assert(handler_called)
assert(not had_error)
assert(not n._private.widget_template_failed)
assert(not n.box)
-- Check adding messages later
n.title = "foo"
n.message = "bar"
n.icon = big_icon
n:destroy()
handler_called = false
-- Try with a broken template.
current_template = {widget = function() assert(false) end}
n = naughty.notification {
title = "foo",
message = "bar",
timeout = 25000,
}
assert(handler_called)
assert(had_error)
assert(not n.box)
-- Check changing existing content.
n.title = "bar"
n.message = "foo"
n.icon = big_icon
n.image = small_icon
handler_called = false
had_error = false
-- Break the default template
assert(dwidget.widget)
dwidget.widget = nil
dwidget.layout = function() assert(false) end
table.remove(dwidget, 1)
n = naughty.notification {
title = "foo",
message = "bar",
timeout = 25000,
}
assert(handler_called)
assert(n._private.widget_template_failed)
assert(had_error)
assert(n.box)
return true
end)
-- Make sure it isn't possible to remove default variables (#3145).
table.insert(steps, function()
naughty.config.defaults = {fake_variable = 24}
naughty.config.text = 1337
assert(naughty.config.defaults.fake_variable == 24)
assert(naughty.config.defaults.timeout == 5)
assert(naughty.config.text == 1337)
return true
end)
require("_runner").run_steps(steps)