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