use Data::Dumper;
use AnyEvent::I3;
use Time::HiRes qw(sleep);
+use i3test::Test;
__
$tester->BAIL_OUT("$@") if $@;
feature->import(":5.10");
--- /dev/null
+package i3test::Test;
+
+use base 'Test::Builder::Module';
+
+our @EXPORT = qw(is_num_children);
+
+my $CLASS = __PACKAGE__;
+
+=head1 NAME
+
+i3test::Test - Additional test instructions for use in i3 testcases
+
+=head1 SYNOPSIS
+
+ use i3test;
+
+ my $ws = fresh_workspace;
+ is_num_children($ws, 0, 'no containers on this workspace yet');
+ cmd 'open';
+ is_num_children($ws, 1, 'one container after "open"');
+
+ done_testing;
+
+=head1 DESCRIPTION
+
+This module provides convenience methods for i3 testcases. If you notice that a
+certain pattern is present in 5 or more test cases, it should most likely be
+moved into this module.
+
+=head1 EXPORT
+
+=head2 is_num_children($workspace, $expected, $test_name)
+
+Gets the number of children on the given workspace and verifies that they match
+the expected amount of children.
+
+ is_num_children('1', 0, 'no containers on workspace 1 at startup');
+
+=cut
+
+sub is_num_children {
+ my ($workspace, $num_children, $name) = @_;
+ my $tb = $CLASS->builder;
+
+ my $con = i3test::get_ws($workspace);
+ $tb->ok(defined($con), "Workspace $workspace exists");
+ if (!defined($con)) {
+ $tb->skip("Workspace does not exist, skipping is_num_children");
+ return;
+ }
+
+ my $got_num_children = scalar @{$con->{nodes}};
+
+ $tb->is_num($got_num_children, $num_children, $name);
+}
+
+=head1 AUTHOR
+
+Michael Stapelberg <michael@i3wm.org>
+
+=cut
+
+1
cmd q|[class=".*"] kill|;
cmd q|[con_id="99999"] kill|;
-$content = get_ws_content($tmp);
-ok(@{$content} == 1, 'window still there');
+is_num_children($tmp, 1, 'window still there');
# now kill the window
cmd 'nop now killing the window';
wait_for_unmap $window;
cmd 'nop checking if its gone';
-$content = get_ws_content($tmp);
-ok(@{$content} == 0, 'window killed');
+is_num_children($tmp, 0, 'window killed');
# TODO: same test, but with pcre expressions
ok($right->mapped, 'right window mapped');
# two windows should be here
-$content = get_ws_content($tmp);
-ok(@{$content} == 2, 'two windows opened');
+is_num_children($tmp, 2, 'two windows opened');
cmd '[class="special" title="left"] kill';
sync_with_i3;
-$content = get_ws_content($tmp);
-is(@{$content}, 1, 'one window still there');
+is_num_children($tmp, 1, 'one window still there');
######################################################################
# check that regular expressions work
$left = open_special(name => 'left', wm_class => 'special7');
ok($left->mapped, 'left window mapped');
-
-# two windows should be here
-$content = get_ws_content($tmp);
-ok(@{$content} == 1, 'window opened');
+is_num_children($tmp, 1, 'window opened');
cmd '[class="^special[0-9]$"] kill';
-
wait_for_unmap $left;
-
-$content = get_ws_content($tmp);
-is(@{$content}, 0, 'window killed');
+is_num_children($tmp, 0, 'window killed');
######################################################################
# check that UTF-8 works when matching
$left = open_special(name => 'รค 3', wm_class => 'special7');
ok($left->mapped, 'left window mapped');
-
-# two windows should be here
-$content = get_ws_content($tmp);
-ok(@{$content} == 1, 'window opened');
+is_num_children($tmp, 1, 'window opened');
cmd '[title="^\w [3]$"] kill';
-
wait_for_unmap $left;
-
-$content = get_ws_content($tmp);
-is(@{$content}, 0, 'window killed');
+is_num_children($tmp, 0, 'window killed');
done_testing;
# move it outside again
cmd 'move left';
-$content = get_ws_content($tmp);
-is(@{$content}, 3, 'three nodes on this workspace');
+is_num_children($tmp, 3, 'three containers after moving left');
# due to automatic flattening/cleanup, the remaining split container
# will be replaced by the con itself, so we will still have 3 nodes
cmd 'move right';
-$content = get_ws_content($tmp);
-is(@{$content}, 2, 'two nodes on this workspace');
+is_num_children($tmp, 2, 'two containers after moving right (flattening)');
######################################################################
# 4) We create two v-split containers on the workspace, then we move
cmd 'focus left';
cmd "move right";
-$content = get_ws_content($otmp);
-is(@{$content}, 1, 'only one nodes on this workspace');
+is_num_children($otmp, 1, 'only one node on this workspace');
######################################################################
# 5) test moving floating containers.
my $tmp2 = get_unused_workspace();
cmd "workspace $tmp";
- ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+ is_num_children($tmp, 0, 'no containers yet');
my $first = open_empty_con($i3);
my $second = open_empty_con($i3);
- ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws');
+ is_num_children($tmp, 2, 'two containers on first ws');
cmd "workspace $tmp2";
- ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws yet');
+ is_num_children($tmp2, 0, 'no containers on second ws yet');
cmd "workspace $tmp";
cmd "$movecmd $tmp2";
- ok(@{get_ws_content($tmp)} == 1, 'one container on first ws anymore');
- ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
+ is_num_children($tmp, 1, 'one container on first ws anymore');
+ is_num_children($tmp2, 1, 'one container on second ws');
my ($nodes, $focus) = get_ws_content($tmp2);
is($focus->[0], $second, 'same container on different ws');
cmd 'workspace 13: meh';
cmd 'open';
-ok(@{get_ws_content('13: meh')} == 1, 'one container on 13: meh');
+is_num_children('13: meh', 1, 'one container on 13: meh');
ok(!workspace_exists('13'), 'workspace 13 does not exist yet');
cmd 'open';
cmd 'move to workspace number 13';
-ok(@{get_ws_content('13: meh')} == 2, 'two containers on 13: meh');
-ok(@{get_ws_content('12')} == 0, 'no container on 12 anymore');
+is_num_children('13: meh', 2, 'one container on 13: meh');
+is_num_children('12', 0, 'no container on 12 anymore');
ok(!workspace_exists('13'), 'workspace 13 does still not exist');
my $tmp = get_unused_workspace();
my $tmp2 = get_unused_workspace();
cmd "workspace $tmp";
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+is_num_children($tmp, 0, 'no containers yet');
my $first = open_empty_con($i3);
my $second = open_empty_con($i3);
-ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws');
+is_num_children($tmp, 2, 'two containers');
cmd "workspace $tmp2";
-ok(@{get_ws_content($tmp2)} == 0, 'no containers yet');
+is_num_children($tmp2, 0, 'no containers yet');
my $third = open_empty_con($i3);
-ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
+is_num_children($tmp2, 1, 'one container on second ws');
# go back to the first workspace, move one of the containers to the next one
cmd "workspace $tmp";
cmd 'move workspace next';
-ok(@{get_ws_content($tmp)} == 1, 'one container on first ws');
-ok(@{get_ws_content($tmp2)} == 2, 'two containers on second ws');
+is_num_children($tmp, 1, 'one container on first ws');
+is_num_children($tmp2, 2, 'two containers on second ws');
# go to the second workspace and move two containers to the first one
cmd "workspace $tmp2";
cmd 'move workspace prev';
cmd 'move workspace prev';
-ok(@{get_ws_content($tmp)} == 3, 'three containers on first ws');
-ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws');
+is_num_children($tmp, 3, 'three containers on first ws');
+is_num_children($tmp2, 0, 'no containers on second ws');
###################################################################
# check if 'move workspace current' works
cmd "workspace $tmp";
$first = open_window(name => 'win-name');
-ok(@{get_ws_content($tmp)} == 1, 'one container on first ws');
+is_num_children($tmp, 1, 'one container on first ws');
cmd "workspace $tmp2";
-ok(@{get_ws_content($tmp2)} == 0, 'no containers yet');
+is_num_children($tmp2, 0, 'no containers yet');
cmd qq|[title="win-name"] move workspace $tmp2|;
-ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws');
+is_num_children($tmp2, 1, 'one container on second ws');
cmd qq|[title="win-name"] move workspace $tmp|;
-ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws');
+is_num_children($tmp2, 0, 'no containers on second ws');
###################################################################
# check if floating cons are moved to new workspaces properly
cmd 'workspace 16';
cmd 'open';
-is(@{get_ws('16')->{nodes}}, 1, 'one node on ws 16');
+is_num_children('16', 1, 'one node on ws 16');
cmd "workspace $tmp";
cmd 'open';
cmd 'move workspace number 16';
-is(@{get_ws('16')->{nodes}}, 2, 'two nodes on ws 16');
+is_num_children('16', 2, 'two nodes on ws 16');
ok(!workspace_exists('17'), 'workspace 17 does not exist yet');
cmd 'open';
my $first_ws = fresh_workspace;
-is(@{get_ws_content($first_ws)}, 0, 'no containers on this workspace yet');
+is_num_children($first_ws, 0, 'no containers on this workspace yet');
######################################################################
# 1) initiate startup, switch workspace, create window
my $second_ws = fresh_workspace;
-is(@{get_ws_content($second_ws)}, 0, 'no containers on the second workspace yet');
+is_num_children($second_ws, 0, 'no containers on the second workspace yet');
my $win = open_window({ dont_map => 1 });
mark_window($win->id);
# We sync with i3 here to make sure $x->input_focus is updated.
sync_with_i3;
-is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
-is(@{get_ws_content($first_ws)}, 1, 'one container on the first workspace');
+is_num_children($second_ws, 0, 'still no containers on the second workspace');
+is_num_children($first_ws, 1, 'one container on the first workspace');
######################################################################
# same thing, but with _NET_STARTUP_ID set on the leader
$win->map;
sync_with_i3;
-is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
-is(@{get_ws_content($first_ws)}, 2, 'two containers on the first workspace');
+is_num_children($second_ws, 0, 'still no containers on the second workspace');
+is_num_children($first_ws, 2, 'two containers on the first workspace');
######################################################################
# 2) open another window after the startup process is completed
sync_with_i3;
my $otherwin = open_window;
-is(@{get_ws_content($second_ws)}, 1, 'one container on the second workspace');
+is_num_children($second_ws, 1, 'one container on the second workspace');
######################################################################
# 3) test that the --no-startup-id flag for exec leads to no DESKTOP_STARTUP_ID
is($startup_id, '', 'startup_id empty');
-
done_testing;
my $tmp = fresh_workspace;
my $con = open_empty_con($i3);
-is(@{get_ws_content($tmp)}, 1, 'one container');
-is(@{get_ws_content($old)}, 1, 'one container on old ws');
+is_num_children($tmp, 1, 'one container');
+is_num_children($old, 1, 'one container on old ws');
cmd 'move workspace prev; workspace prev';
-is(@{get_ws_content($old)}, 2, 'container moved away');
+is_num_children($old, 2, 'container moved away');
done_testing;
cmd 'workspace targetws';
open_window(name => "testcase");
-
-my $nodes = get_ws_content('targetws');
-is(scalar @$nodes, 1, 'precisely one window');
+is_num_children('targetws', 1, 'precisely one window');
open_window(name => "testcase");
-
-$nodes = get_ws_content('targetws');
-is(scalar @$nodes, 2, 'precisely two windows');
+is_num_children('targetws', 2, 'precisely two windows');
cmd 'split v';
open_window(name => "testcase");
-
-$nodes = get_ws_content('targetws');
-is(scalar @$nodes, 2, 'still two windows');
+is_num_children('targetws', 2, 'still two windows');
# focus parent. the new window should now be opened right next to the last one.
cmd 'focus parent';
open_window(name => "testcase");
-
-$nodes = get_ws_content('targetws');
-is(scalar @$nodes, 3, 'new window opened next to last one');
+is_num_children('targetws', 3, 'new window opened next to last one');
exit_gracefully($pid);
my $scratch = open_special;
cmd '[class="special"] move scratchpad';
-my ($nodes, $focus) = get_ws_content($tmp);
-is(scalar @$nodes, 1, 'one window on current ws');
+is_num_children($tmp, 1, 'one window on current ws');
my $otmp = fresh_workspace;
cmd 'scratchpad show';
sub verify_scratchpad_on_same_ws {
my ($ws) = @_;
- is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws');
+ is_num_children($ws, 0, 'no nodes on this ws');
my $window = open_window;
- is(scalar @{get_ws($ws)->{nodes}}, 1, 'one nodes on this ws');
+ is_num_children($ws, 1, 'one nodes on this ws');
cmd 'move scratchpad';
- is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws');
+ is_num_children($ws, 0, 'no nodes on this ws');
cmd 'scratchpad show';
- is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws');
+ is_num_children($ws, 0, 'no nodes on this ws');
is(scalar @{get_ws($ws)->{floating_nodes}}, 1, 'one floating node on this ws');
}
cmd "workspace $first";
- is(scalar @{get_ws($first)->{nodes}}, 0, 'no nodes on this ws');
+ is_num_children($first, 0, 'no nodes on this ws');
my $window = open_window;
- is(scalar @{get_ws($first)->{nodes}}, 1, 'one nodes on this ws');
+ is_num_children($first, 1, 'one nodes on this ws');
cmd 'move scratchpad';
- is(scalar @{get_ws($first)->{nodes}}, 0, 'no nodes on this ws');
+ is_num_children($first, 0, 'no nodes on this ws');
cmd "workspace $second";
cmd 'scratchpad show';
my $ws = get_ws($second);
- is(scalar @{$ws->{nodes}}, 0, 'no nodes on this ws');
+ is_num_children($second, 0, 'no nodes on this ws');
is(scalar @{$ws->{floating_nodes}}, 1, 'one floating node on this ws');
# Verify that the coordinates are within bounds.
sub verify_scratchpad_doesnt_move {
my ($ws) = @_;
- is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws');
+ is_num_children($ws, 0, 'no nodes on this ws');
my $window = open_window;
-
- is(scalar @{get_ws($ws)->{nodes}}, 1, 'one nodes on this ws');
+ is_num_children($ws, 1, 'one node on this ws');
cmd 'move scratchpad';
-
- is(scalar @{get_ws($ws)->{nodes}}, 0, 'no nodes on this ws');
+ is_num_children($ws, 0, 'no nodes on this ws');
my $last_x = -1;
for (1 .. 20) {