Merge branch 'master' into currency
[kivitendo-erp.git] / t / db_helper / acts_as_list.t
1 use Test::More;
2 use Test::Exception;
3
4 use strict;
5
6 use lib 't';
7 use utf8;
8
9 use Data::Dumper;
10 use Support::TestSetup;
11
12 eval {
13   require 'SL::DB::RequirementSpec';
14   require 'SL::DB::RequirementSpecItem';
15   require 'SL::DB::RequirementSpecTextBlock';
16   1;
17 } or my $skip = 'RequirementSpec is not available for this test';
18
19 if ($skip) {
20   plan skip_all => $skip;
21 } else {
22   plan tests => 48;
23 }
24
25 sub reset_state {
26   "SL::DB::Manager::${_}"->delete_all(all => 1) for qw(RequirementSpecTextBlock RequirementSpecItem RequirementSpec);
27
28   SL::DB::RequirementSpec->new(id => 2, type_id => 1, status_id => 1, customer_id => 12395, hourly_rate => 42.24,  title => "Azumbo")->save;
29
30   SL::DB::RequirementSpecItem->new(requirement_spec_id => 2, parent_id => undef, id => 1, position => 1, fb_number => "A01",    title => "Mühköh",   description => "The Kuh.")->save;
31   SL::DB::RequirementSpecItem->new(requirement_spec_id => 2, parent_id => undef, id => 2, position => 2, fb_number => "A02",    title => "Geheim",   description => "Kofferkombination")->save;
32   SL::DB::RequirementSpecItem->new(requirement_spec_id => 2, parent_id => 1,     id => 3, position => 1, fb_number => "FB0001", title => "Yäääh",    description => "Und so")->save;
33   SL::DB::RequirementSpecItem->new(requirement_spec_id => 2, parent_id => 1,     id => 4, position => 2, fb_number => "FB0012", title => "Blubb",    description => "blabb")->save;
34   SL::DB::RequirementSpecItem->new(requirement_spec_id => 2, parent_id => 1,     id => 5, position => 3, fb_number => "FB0022", title => "Fingo",    description => "fungo")->save;
35   SL::DB::RequirementSpecItem->new(requirement_spec_id => 2, parent_id => 4,     id => 6, position => 1, fb_number => "UFB002", title => "Suppi",    description => "Suppa")->save;
36   SL::DB::RequirementSpecItem->new(requirement_spec_id => 2, parent_id => 4,     id => 7, position => 2, fb_number => "UFB000", title => "Suppa",    description => "Suppi")->save;
37   SL::DB::RequirementSpecItem->new(requirement_spec_id => 2, parent_id => 2,     id => 8, position => 1, fb_number => "FB0018", title => "Neuneins", description => "Eins")->save;
38
39   SL::DB::RequirementSpec->new->db->dbh->do(qq|SELECT pg_catalog.setval('| . $_->[0] . qq|', | . $_->[1] . qq|, true)|) for (['requirement_spec_items_id_seq', 8], [ 'requirement_specs_id_seq', 2 ]);
40 }
41
42 sub values_eq {
43   my ($val1, $val2) = @_;
44   return (defined($val1) == defined($val2))
45       && (!defined($val1) || ($val1 == $val2));
46 }
47
48 sub test_positions {
49   my ($message, @positions) = @_;
50
51   my $failures =
52     join ' ',
53     map  { join ':', map { $_ // 'undef' } @{ $_ } }
54     grep { !values_eq($_->[1], $_->[3]) || !values_eq($_->[2], $_->[4]) }
55     map  { my $item = SL::DB::RequirementSpecItem->new(id => $_->[0])->load; [ @{ $_ }, $item->parent_id, $item->position ] }
56     @positions;
57
58   is($failures, '', $message);
59 }
60
61 sub new_item {
62   return SL::DB::RequirementSpecItem->new(requirement_spec_id => 2, fb_number => 'dummy', title => 'dummy', @_);
63 }
64
65 sub get_item {
66   return SL::DB::RequirementSpecItem->new(id => $_[0])->load;
67 }
68
69 Support::TestSetup::login();
70 my $item;
71
72 # 1
73 # `+- 3
74 #  +- 4
75 #  |  `+- 6
76 #  |   `- 7
77 #  `- 5
78 # 2
79 # `- 8
80
81 reset_state();
82 test_positions "reset_state", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
83
84 # Einfügen neuer Objekte: "set_position"
85 new_item(parent_id => 1)->save;
86 test_positions "set_position via new with parent_id NOT NULL", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ], [ 9, 1, 4 ];
87
88 reset_state();
89 new_item()->save;
90 test_positions "set_position via new with parent_id IS NULL",  [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ], [ 9, undef, 3 ];
91
92 # Löschen von Objekten: "remove_position"
93 reset_state();
94 get_item(3)->delete;
95 test_positions "remove_position via delete with parent_id NOT NULL",  [ 1, undef, 1 ], [ 2, undef, 2 ], [ 4, 1, 1 ], [ 5, 1, 2 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
96
97 reset_state();
98 get_item(1)->delete;
99 test_positions "remove_position via delete with parent_id IS NULL 1",  [ 2, undef, 1 ], [ 8, 2, 1 ];
100
101 reset_state();
102 get_item(2)->delete;
103 test_positions "remove_position via delete with parent_id IS NULL 2",  [ 1, undef, 1 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ];
104
105 # Hoch schieben
106 reset_state();
107 get_item(3)->move_position_up;
108 test_positions "move_position_up when at top of sub-list", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
109
110 reset_state();
111 get_item(4)->move_position_up;
112 test_positions "move_position_up when in middle of sub-list", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 4, 1, 1 ], [ 3, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
113
114 reset_state();
115 get_item(5)->move_position_up;
116 test_positions "move_position_up when at end of sub-list", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 5, 1, 2 ], [ 4, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
117
118 reset_state();
119 get_item(8)->move_position_up;
120 test_positions "move_position_up when only element in sub-list", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
121
122 # Herunter schieben
123 reset_state();
124 get_item(3)->move_position_down;
125 test_positions "move_position_down when at top of sub-list", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 4, 1, 1 ], [ 3, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
126
127 reset_state();
128 get_item(4)->move_position_down;
129 test_positions "move_position_down when in middle of sub-list", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 5, 1, 2 ], [ 4, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
130
131 reset_state();
132 get_item(5)->move_position_down;
133 test_positions "move_position_down when at bottom of sub-list", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
134
135 reset_state();
136 get_item(8)->move_position_down;
137 test_positions "move_position_down when only element in sub-list", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
138
139 # Listen neu anordnen
140 reset_state();
141 get_item(8)->reorder_list(4, 5, 3);
142 test_positions "reoder_list called as instance method", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 4, 1, 1 ], [ 5, 1, 2 ], [ 3, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
143
144 reset_state();
145 SL::DB::RequirementSpecItem->reorder_list(4, 5, 3);
146 test_positions "reoder_list called as class method", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 4, 1, 1 ], [ 5, 1, 2 ], [ 3, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
147
148 # Aus Liste entfernen
149 reset_state();
150 get_item(3)->remove_from_list;
151 test_positions "remove_from_list on top", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, -1 ], [ 4, 1, 1 ], [ 5, 1, 2 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
152
153 reset_state();
154 get_item(4)->remove_from_list;
155 test_positions "remove_from_list on middle", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, -1 ], [ 5, 1, 2 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
156
157 reset_state();
158 get_item(5)->remove_from_list;
159 test_positions "remove_from_list on bottom", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, -1 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
160
161 reset_state();
162 get_item(8)->remove_from_list;
163 test_positions "remove_from_list on only item in list", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, -1 ];
164
165 reset_state();
166 $item = get_item(3); $item->remove_from_list; $item->delete;
167 test_positions "remove_from_list and delete afterwards", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 4, 1, 1 ], [ 5, 1, 2 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
168
169 # Zu Liste hinzufügen
170 reset_state();
171 $item = get_item(8); $item->remove_from_list; $item->parent_id(1); $item->add_to_list(position => 'last');
172 test_positions "add_to_list position 'last'", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 1, 4 ];
173
174 reset_state();
175 $item = get_item(8); $item->remove_from_list; $item->parent_id(1); $item->add_to_list(position => 'first');
176 test_positions "add_to_list position 'first'", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 2 ], [ 4, 1, 3 ], [ 5, 1, 4 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 1, 1 ];
177
178 reset_state();
179 $item = get_item(8); $item->remove_from_list; $item->parent_id(1); $item->add_to_list(position => 'before', reference => 3);
180 test_positions "add_to_list position 'before' first by ID", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 2 ], [ 4, 1, 3 ], [ 5, 1, 4 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 1, 1 ];
181
182 reset_state();
183 $item = get_item(8); $item->remove_from_list; $item->parent_id(1); $item->add_to_list(position => 'before', reference => get_item(3));
184 test_positions "add_to_list position 'before' first by reference", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 2 ], [ 4, 1, 3 ], [ 5, 1, 4 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 1, 1 ];
185
186 reset_state();
187 $item = get_item(8); $item->remove_from_list; $item->parent_id(1); $item->add_to_list(position => 'before', reference => 4);
188 test_positions "add_to_list position 'before' middle by ID", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 3 ], [ 5, 1, 4 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 1, 2 ];
189
190 reset_state();
191 $item = get_item(8); $item->remove_from_list; $item->parent_id(1); $item->add_to_list(position => 'before', reference => get_item(4));
192 test_positions "add_to_list position 'before' middle by reference", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 3 ], [ 5, 1, 4 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 1, 2 ];
193
194 reset_state();
195 $item = get_item(8); $item->remove_from_list; $item->parent_id(1); $item->add_to_list(position => 'after', reference => 5);
196 test_positions "add_to_list position 'after' last by ID", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 1, 4 ];
197
198 reset_state();
199 $item = get_item(8); $item->remove_from_list; $item->parent_id(1); $item->add_to_list(position => 'after', reference => get_item(5));
200 test_positions "add_to_list position 'after' last by reference", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 1, 4 ];
201
202 reset_state();
203 $item = get_item(8); $item->remove_from_list; $item->parent_id(1); $item->add_to_list(position => 'after', reference => 4);
204 test_positions "add_to_list position 'after' middle by ID", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 4 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 1, 3 ];
205
206 reset_state();
207 $item = get_item(8); $item->remove_from_list; $item->parent_id(1); $item->add_to_list(position => 'after', reference => get_item(4));
208 test_positions "add_to_list position 'after' middle by reference", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 4 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 1, 3 ];
209
210 reset_state();
211 $item = get_item(8); $item->remove_from_list; $item->parent_id(3); $item->add_to_list(position => 'last');
212 test_positions "add_to_list position 'last' in empty", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 3, 1 ];
213
214 reset_state();
215 $item = get_item(8); $item->remove_from_list; $item->parent_id(3); $item->add_to_list(position => 'first');
216 test_positions "add_to_list position 'first' in empty", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 4, 1, 2 ], [ 5, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 3, 1 ];
217
218 reset_state();
219 $item = get_item(5); $item->add_to_list(position => 'after', reference => 3);
220 test_positions "add_to_list without prior remove_from_list", [ 1, undef, 1 ], [ 2, undef, 2 ], [ 3, 1, 1 ], [ 5, 1, 2 ], [ 4, 1, 3 ], [ 6, 4, 1 ], [ 7, 4, 2 ], [ 8, 2, 1 ];
221
222 reset_state();
223 $item = get_item(4);
224 is($item->get_next_in_list->id,                           5, 'Next of 4 is 5');
225 is($item->get_previous_in_list->id,                       3, 'Previous of 4 is 5');
226 is($item->get_next_in_list->get_previous_in_list->id,     4, 'Previous of Next of 4 is 4');
227 is($item->get_previous_in_list->get_next_in_list->id,     4, 'Next of Previous of 4 is 4');
228 is($item->get_next_in_list->get_next_in_list,         undef, 'Next of Next of 4 is undef');
229 is($item->get_previous_in_list->get_previous_in_list, undef, 'Previous of Previous of 4 is undef');
230
231 # Parametervalidierung
232 throws_ok { new_item()->move_position_up   } qr/not.*been.*saved/i, 'move up not saved yet';
233 throws_ok { new_item()->move_position_down } qr/not.*been.*saved/i, 'move down not saved yet';
234
235 throws_ok { $item = get_item(8); $item->position(undef); $item->move_position_up   } qr/no.*position.*set.*yet/i, 'move up no position set';
236 throws_ok { $item = get_item(8); $item->position(undef); $item->move_position_down } qr/no.*position.*set.*yet/i, 'move down no position set';
237
238 throws_ok { get_item(8)->add_to_list;                      } qr/invalid.*parameter.*position/i,  'missing position';
239 throws_ok { get_item(8)->add_to_list(position => 'gonzo')  } qr/invalid.*parameter.*position/i,  'invalid position';
240 throws_ok { get_item(8)->add_to_list(position => 'before') } qr/missing.*parameter.*reference/i, 'missing reference for position "before"';
241 throws_ok { get_item(8)->add_to_list(position => 'after')  } qr/missing.*parameter.*reference/i, 'missing reference for position "after"';
242
243 done_testing();