tests: Add extensive tests for the existing notification features.
This new test suit add a rather extensive coverage to the "legacy" notification popups. A few minor bugs have been found and fixed and we can rest assured that the new spec 1.2 support and extended manipulation API wont regress existing configs.
This commit is contained in:
parent
7c96a98a0d
commit
db591b50a4
|
@ -0,0 +1,669 @@
|
|||
-- 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 cairo = require("lgi" ).cairo
|
||||
local beautiful = require("beautiful")
|
||||
|
||||
-- This module test deprecated APIs
|
||||
require("gears.debug").deprecate = function() end
|
||||
|
||||
local steps = {}
|
||||
|
||||
local has_cmd_notify, has_cmd_send = 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
|
||||
|
||||
f = io.open(p.."dbus-send")
|
||||
if f then
|
||||
f:close()
|
||||
has_cmd_send = true
|
||||
end
|
||||
|
||||
if has_cmd_notify and has_cmd_send then return end
|
||||
end
|
||||
end
|
||||
|
||||
check_cmd()
|
||||
|
||||
-- Can't test anything of value the documentation example tests don't already
|
||||
-- hit.
|
||||
if not has_cmd_send then require("_runner").run_steps {}; return end
|
||||
|
||||
local active, destroyed, reasons, counter = {}, {}, {}, 0
|
||||
|
||||
local default_width, default_height = 0, 0
|
||||
|
||||
naughty.connect_signal("added", function(n)
|
||||
table.insert(active, n)
|
||||
counter = counter + 1
|
||||
end)
|
||||
|
||||
naughty.connect_signal("destroyed", function(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)
|
||||
|
||||
table.insert(steps, function()
|
||||
if not has_cmd_notify then return true end
|
||||
|
||||
spawn('notify-send title message -t 25000')
|
||||
|
||||
return true
|
||||
end)
|
||||
|
||||
table.insert(steps, function()
|
||||
if not has_cmd_notify then return true end
|
||||
if #active ~= 1 then return end
|
||||
|
||||
local n = active[1]
|
||||
|
||||
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)
|
||||
|
||||
-- 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 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 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 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)
|
||||
|
||||
-- Test vertical overlapping
|
||||
local function test_overlap()
|
||||
local wa = mouse.screen.workarea
|
||||
|
||||
for _, n1 in ipairs(active) do
|
||||
assert(n1.box)
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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 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 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)
|
||||
|
||||
local positions = {
|
||||
"top_left" , "top_middle" , "top_right" ,
|
||||
"bottom_left" , "bottom_middle" , "bottom_right" ,
|
||||
}
|
||||
|
||||
-- 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.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.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)
|
||||
|
||||
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.
|
||||
if has_cmd_send then
|
||||
|
||||
local cmd = [[dbus-send \
|
||||
--type=method_call \
|
||||
--print-reply=literal \
|
||||
--dest=org.freedesktop.Notifications \
|
||||
/org/freedesktop/Notifications \
|
||||
org.freedesktop.Notifications.Notify \
|
||||
string:"Awesome test" \
|
||||
uint32:0 \
|
||||
string:"" \
|
||||
string:"title" \
|
||||
string:"message body" \
|
||||
array:string:1,"one",2,"two",3,"three" \
|
||||
dict:string:string:"","" \
|
||||
int32:25000]]
|
||||
|
||||
-- Test the actions.
|
||||
table.insert(steps, function()
|
||||
|
||||
assert(#active == 0)
|
||||
|
||||
spawn(cmd)
|
||||
|
||||
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()
|
||||
spawn.easy_async(cmd, function(out)
|
||||
nid = tonumber(out:match(" [0-9]+"):match("[0-9]+"))
|
||||
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() name_u = true end)
|
||||
n:connect_signal("property::message", function() message_u = true end)
|
||||
n:connect_signal("property::actions", function() actions_u = true end)
|
||||
|
||||
local update = [[dbus-send \
|
||||
--type=method_call \
|
||||
--print-reply=literal \
|
||||
--dest=org.freedesktop.Notifications \
|
||||
/org/freedesktop/Notifications \
|
||||
org.freedesktop.Notifications.Notify \
|
||||
string:"Awesome test" \
|
||||
uint32:]].. nid ..[[ \
|
||||
string:"" \
|
||||
string:"updated title" \
|
||||
string:"updated message body" \
|
||||
array:string:1,"four",2,"five",3,"six" \
|
||||
dict:string:string:"","" \
|
||||
int32:25000]]
|
||||
|
||||
spawn(update)
|
||||
|
||||
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" )
|
||||
|
||||
return true
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
-- Test many screens.
|
||||
|
||||
require("_runner").run_steps(steps)
|
Loading…
Reference in New Issue