|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace SilverStripe\ORM\Tests; |
|
4
|
|
|
|
|
5
|
|
|
use SilverStripe\ORM\ValidationException; |
|
6
|
|
|
use SilverStripe\Versioned\Versioned; |
|
7
|
|
|
use SilverStripe\ORM\DataObject; |
|
8
|
|
|
use SilverStripe\Dev\CSSContentParser; |
|
9
|
|
|
use SilverStripe\Dev\SapphireTest; |
|
10
|
|
|
|
|
11
|
|
|
class HierarchyTest extends SapphireTest |
|
12
|
|
|
{ |
|
13
|
|
|
protected static $fixture_file = 'HierarchyTest.yml'; |
|
14
|
|
|
|
|
15
|
|
|
protected $extraDataObjects = array( |
|
16
|
|
|
HierarchyTest\TestObject::class, |
|
17
|
|
|
HierarchyTest\HideTestObject::class, |
|
18
|
|
|
HierarchyTest\HideTestSubObject::class, |
|
19
|
|
|
); |
|
20
|
|
|
|
|
21
|
|
|
protected function getExtraDataObjects() |
|
22
|
|
|
{ |
|
23
|
|
|
// Prevent setup breaking if versioned module absent |
|
24
|
|
|
if (class_exists(Versioned::class)) { |
|
25
|
|
|
return parent::getExtraDataObjects(); |
|
26
|
|
|
} |
|
27
|
|
|
return []; |
|
28
|
|
|
} |
|
29
|
|
|
|
|
30
|
|
|
public function setUp() |
|
31
|
|
|
{ |
|
32
|
|
|
parent::setUp(); |
|
33
|
|
|
|
|
34
|
|
|
// Note: Soft support for versioned module optionality |
|
35
|
|
|
if (!class_exists(Versioned::class)) { |
|
36
|
|
|
$this->markTestSkipped('HierarchyTest requires the Versioned extension'); |
|
37
|
|
|
} |
|
38
|
|
|
} |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* Test the Hierarchy prevents infinite loops. |
|
42
|
|
|
*/ |
|
43
|
|
|
public function testPreventLoop() |
|
44
|
|
|
{ |
|
45
|
|
|
$this->setExpectedException( |
|
|
|
|
|
|
46
|
|
|
ValidationException::class, |
|
47
|
|
|
sprintf('Infinite loop found within the "%s" hierarchy', HierarchyTest\TestObject::class) |
|
48
|
|
|
); |
|
49
|
|
|
|
|
50
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
51
|
|
|
$obj2aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2aa'); |
|
52
|
|
|
|
|
53
|
|
|
$obj2->ParentID = $obj2aa->ID; |
|
|
|
|
|
|
54
|
|
|
$obj2->write(); |
|
55
|
|
|
} |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* Test Hierarchy::AllHistoricalChildren(). |
|
59
|
|
|
*/ |
|
60
|
|
|
public function testAllHistoricalChildren() |
|
61
|
|
|
{ |
|
62
|
|
|
// Delete some objs |
|
63
|
|
|
$this->objFromFixture(HierarchyTest\TestObject::class, 'obj2b')->delete(); |
|
64
|
|
|
$this->objFromFixture(HierarchyTest\TestObject::class, 'obj3a')->delete(); |
|
65
|
|
|
$this->objFromFixture(HierarchyTest\TestObject::class, 'obj3')->delete(); |
|
66
|
|
|
|
|
67
|
|
|
// Check that obj1-3 appear at the top level of the AllHistoricalChildren tree |
|
68
|
|
|
$this->assertEquals( |
|
69
|
|
|
array("Obj 1", "Obj 2", "Obj 3"), |
|
70
|
|
|
singleton(HierarchyTest\TestObject::class)->AllHistoricalChildren()->column('Title') |
|
71
|
|
|
); |
|
72
|
|
|
|
|
73
|
|
|
// Check numHistoricalChildren |
|
74
|
|
|
$this->assertEquals(3, singleton(HierarchyTest\TestObject::class)->numHistoricalChildren()); |
|
75
|
|
|
|
|
76
|
|
|
// Check that both obj 2 children are returned |
|
77
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
78
|
|
|
$this->assertEquals( |
|
79
|
|
|
array("Obj 2a", "Obj 2b"), |
|
80
|
|
|
$obj2->AllHistoricalChildren()->column('Title') |
|
81
|
|
|
); |
|
82
|
|
|
|
|
83
|
|
|
// Check numHistoricalChildren |
|
84
|
|
|
$this->assertEquals(2, $obj2->numHistoricalChildren()); |
|
85
|
|
|
|
|
86
|
|
|
|
|
87
|
|
|
// Obj 3 has been deleted; let's bring it back from the grave |
|
88
|
|
|
$obj3 = Versioned::get_including_deleted(HierarchyTest\TestObject::class, "\"Title\" = 'Obj 3'")->First(); |
|
89
|
|
|
|
|
90
|
|
|
// Check that all obj 3 children are returned |
|
91
|
|
|
$this->assertEquals( |
|
92
|
|
|
array("Obj 3a", "Obj 3b", "Obj 3c", "Obj 3d"), |
|
93
|
|
|
$obj3->AllHistoricalChildren()->column('Title') |
|
94
|
|
|
); |
|
95
|
|
|
|
|
96
|
|
|
// Check numHistoricalChildren |
|
97
|
|
|
$this->assertEquals(4, $obj3->numHistoricalChildren()); |
|
98
|
|
|
} |
|
99
|
|
|
|
|
100
|
|
|
/** |
|
101
|
|
|
* Test that you can call Hierarchy::markExpanded/Unexpanded/Open() on a obj, and that |
|
102
|
|
|
* calling Hierarchy::isMarked() on a different instance of that object will return true. |
|
103
|
|
|
*/ |
|
104
|
|
|
public function testItemMarkingIsntRestrictedToSpecificInstance() |
|
105
|
|
|
{ |
|
106
|
|
|
// Mark a few objs |
|
107
|
|
|
$this->objFromFixture(HierarchyTest\TestObject::class, 'obj2')->markExpanded(); |
|
108
|
|
|
$this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a')->markExpanded(); |
|
109
|
|
|
$this->objFromFixture(HierarchyTest\TestObject::class, 'obj2b')->markExpanded(); |
|
110
|
|
|
$this->objFromFixture(HierarchyTest\TestObject::class, 'obj3')->markUnexpanded(); |
|
111
|
|
|
|
|
112
|
|
|
// Query some objs in a different context and check their m |
|
113
|
|
|
$objs = DataObject::get(HierarchyTest\TestObject::class, '', '"ID" ASC'); |
|
114
|
|
|
$marked = $expanded = array(); |
|
115
|
|
|
foreach ($objs as $obj) { |
|
116
|
|
|
if ($obj->isMarked()) { |
|
117
|
|
|
$marked[] = $obj->Title; |
|
118
|
|
|
} |
|
119
|
|
|
if ($obj->isExpanded()) { |
|
120
|
|
|
$expanded[] = $obj->Title; |
|
121
|
|
|
} |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
$this->assertEquals(array('Obj 2', 'Obj 3', 'Obj 2a', 'Obj 2b'), $marked); |
|
125
|
|
|
$this->assertEquals(array('Obj 2', 'Obj 2a', 'Obj 2b'), $expanded); |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
public function testNumChildren() |
|
129
|
|
|
{ |
|
130
|
|
|
$this->assertEquals($this->objFromFixture(HierarchyTest\TestObject::class, 'obj1')->numChildren(), 0); |
|
131
|
|
|
$this->assertEquals($this->objFromFixture(HierarchyTest\TestObject::class, 'obj2')->numChildren(), 2); |
|
132
|
|
|
$this->assertEquals($this->objFromFixture(HierarchyTest\TestObject::class, 'obj3')->numChildren(), 4); |
|
133
|
|
|
$this->assertEquals($this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a')->numChildren(), 2); |
|
134
|
|
|
$this->assertEquals($this->objFromFixture(HierarchyTest\TestObject::class, 'obj2b')->numChildren(), 0); |
|
135
|
|
|
$this->assertEquals($this->objFromFixture(HierarchyTest\TestObject::class, 'obj3a')->numChildren(), 2); |
|
136
|
|
|
$this->assertEquals($this->objFromFixture(HierarchyTest\TestObject::class, 'obj3d')->numChildren(), 0); |
|
137
|
|
|
|
|
138
|
|
|
$obj1 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj1'); |
|
139
|
|
|
$this->assertEquals($obj1->numChildren(), 0); |
|
140
|
|
|
$obj1Child1 = new HierarchyTest\TestObject(); |
|
141
|
|
|
$obj1Child1->ParentID = $obj1->ID; |
|
|
|
|
|
|
142
|
|
|
$obj1Child1->write(); |
|
143
|
|
|
$this->assertEquals( |
|
144
|
|
|
$obj1->numChildren(false), |
|
145
|
|
|
1, |
|
146
|
|
|
'numChildren() caching can be disabled through method parameter' |
|
147
|
|
|
); |
|
148
|
|
|
$obj1Child2 = new HierarchyTest\TestObject(); |
|
149
|
|
|
$obj1Child2->ParentID = $obj1->ID; |
|
|
|
|
|
|
150
|
|
|
$obj1Child2->write(); |
|
151
|
|
|
$obj1->flushCache(); |
|
152
|
|
|
$this->assertEquals( |
|
153
|
|
|
$obj1->numChildren(), |
|
154
|
|
|
2, |
|
155
|
|
|
'numChildren() caching can be disabled by flushCache()' |
|
156
|
|
|
); |
|
157
|
|
|
} |
|
158
|
|
|
|
|
159
|
|
|
public function testLoadDescendantIDListIntoArray() |
|
160
|
|
|
{ |
|
161
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
162
|
|
|
$obj2a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a'); |
|
163
|
|
|
$obj2b = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2b'); |
|
164
|
|
|
$obj2aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2aa'); |
|
165
|
|
|
$obj2ab = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2ab'); |
|
166
|
|
|
|
|
167
|
|
|
$obj2IdList = $obj2->getDescendantIDList(); |
|
168
|
|
|
$obj2aIdList = $obj2a->getDescendantIDList(); |
|
169
|
|
|
|
|
170
|
|
|
$this->assertContains($obj2a->ID, $obj2IdList); |
|
171
|
|
|
$this->assertContains($obj2b->ID, $obj2IdList); |
|
172
|
|
|
$this->assertContains($obj2aa->ID, $obj2IdList); |
|
173
|
|
|
$this->assertContains($obj2ab->ID, $obj2IdList); |
|
174
|
|
|
$this->assertEquals(4, count($obj2IdList)); |
|
175
|
|
|
|
|
176
|
|
|
$this->assertContains($obj2aa->ID, $obj2aIdList); |
|
177
|
|
|
$this->assertContains($obj2ab->ID, $obj2aIdList); |
|
178
|
|
|
$this->assertEquals(2, count($obj2aIdList)); |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
/** |
|
182
|
|
|
* The "only deleted from stage" argument to liveChildren() should exclude |
|
183
|
|
|
* any page that has been moved to another location on the stage site |
|
184
|
|
|
*/ |
|
185
|
|
|
public function testLiveChildrenOnlyDeletedFromStage() |
|
186
|
|
|
{ |
|
187
|
|
|
$obj1 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj1'); |
|
188
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
189
|
|
|
$obj2a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a'); |
|
190
|
|
|
$obj2b = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2b'); |
|
191
|
|
|
|
|
192
|
|
|
// Get a published set of objects for our fixture |
|
193
|
|
|
$obj1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); |
|
194
|
|
|
$obj2->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); |
|
195
|
|
|
$obj2a->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); |
|
196
|
|
|
$obj2b->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); |
|
197
|
|
|
|
|
198
|
|
|
// Then delete 2a from stage and move 2b to a sub-node of 1. |
|
199
|
|
|
$obj2a->delete(); |
|
200
|
|
|
$obj2b->ParentID = $obj1->ID; |
|
|
|
|
|
|
201
|
|
|
$obj2b->write(); |
|
202
|
|
|
|
|
203
|
|
|
// Get live children, excluding pages that have been moved on the stage site |
|
204
|
|
|
$children = $obj2->liveChildren(true, true)->column("Title"); |
|
205
|
|
|
|
|
206
|
|
|
// 2a has been deleted from stage and should be shown |
|
207
|
|
|
$this->assertContains("Obj 2a", $children); |
|
208
|
|
|
|
|
209
|
|
|
// 2b has merely been moved to a different parent and so shouldn't be shown |
|
210
|
|
|
$this->assertNotContains("Obj 2b", $children); |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
public function testBreadcrumbs() |
|
214
|
|
|
{ |
|
215
|
|
|
$obj1 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj1'); |
|
216
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
217
|
|
|
$obj2a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a'); |
|
218
|
|
|
$obj2aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2aa'); |
|
219
|
|
|
|
|
220
|
|
|
$this->assertEquals('Obj 1', $obj1->getBreadcrumbs()); |
|
221
|
|
|
$this->assertEquals('Obj 2 » Obj 2a', $obj2a->getBreadcrumbs()); |
|
222
|
|
|
$this->assertEquals('Obj 2 » Obj 2a » Obj 2aa', $obj2aa->getBreadcrumbs()); |
|
223
|
|
|
} |
|
224
|
|
|
|
|
225
|
|
|
/** |
|
226
|
|
|
* @covers \SilverStripe\ORM\Hierarchy\Hierarchy::markChildren() |
|
227
|
|
|
*/ |
|
228
|
|
|
public function testMarkChildrenDoesntUnmarkPreviouslyMarked() |
|
229
|
|
|
{ |
|
230
|
|
|
$obj3 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj3'); |
|
231
|
|
|
$obj3aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj3aa'); |
|
232
|
|
|
$obj3ba = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj3ba'); |
|
233
|
|
|
$obj3ca = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj3ca'); |
|
234
|
|
|
|
|
235
|
|
|
$obj3->markPartialTree(); |
|
236
|
|
|
$obj3->markToExpose($obj3aa); |
|
237
|
|
|
$obj3->markToExpose($obj3ba); |
|
238
|
|
|
$obj3->markToExpose($obj3ca); |
|
239
|
|
|
|
|
240
|
|
|
$expected = <<<EOT |
|
241
|
|
|
<ul> |
|
242
|
|
|
<li>Obj 3a |
|
243
|
|
|
<ul> |
|
244
|
|
|
<li>Obj 3aa |
|
245
|
|
|
</li> |
|
246
|
|
|
<li>Obj 3ab |
|
247
|
|
|
</li> |
|
248
|
|
|
</ul> |
|
249
|
|
|
</li> |
|
250
|
|
|
<li>Obj 3b |
|
251
|
|
|
<ul> |
|
252
|
|
|
<li>Obj 3ba |
|
253
|
|
|
</li> |
|
254
|
|
|
<li>Obj 3bb |
|
255
|
|
|
</li> |
|
256
|
|
|
</ul> |
|
257
|
|
|
</li> |
|
258
|
|
|
<li>Obj 3c |
|
259
|
|
|
<ul> |
|
260
|
|
|
<li>Obj 3c |
|
261
|
|
|
</li> |
|
262
|
|
|
</ul> |
|
263
|
|
|
</li> |
|
264
|
|
|
<li>Obj 3d |
|
265
|
|
|
</li> |
|
266
|
|
|
</ul> |
|
267
|
|
|
|
|
268
|
|
|
EOT; |
|
269
|
|
|
|
|
270
|
|
|
$this->assertSame($expected, $obj3->getChildrenAsUL()); |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
public function testGetChildrenAsUL() |
|
274
|
|
|
{ |
|
275
|
|
|
$obj1 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj1'); |
|
276
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
277
|
|
|
$obj2a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a'); |
|
278
|
|
|
$obj2aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2aa'); |
|
279
|
|
|
|
|
280
|
|
|
$nodeCountThreshold = 30; |
|
281
|
|
|
|
|
282
|
|
|
$root = new HierarchyTest\TestObject(); |
|
283
|
|
|
$root->markPartialTree($nodeCountThreshold); |
|
284
|
|
|
$html = $root->getChildrenAsUL( |
|
285
|
|
|
"", |
|
286
|
|
|
'"<li id=\"" . $child->ID . "\">" . $child->Title', |
|
287
|
|
|
null, |
|
288
|
|
|
false, |
|
289
|
|
|
"AllChildrenIncludingDeleted", |
|
290
|
|
|
"numChildren", |
|
291
|
|
|
true, // rootCall |
|
292
|
|
|
$nodeCountThreshold |
|
293
|
|
|
); |
|
294
|
|
|
$this->assertTreeContains( |
|
295
|
|
|
$html, |
|
296
|
|
|
array($obj2), |
|
297
|
|
|
'Contains root elements' |
|
298
|
|
|
); |
|
299
|
|
|
$this->assertTreeContains( |
|
300
|
|
|
$html, |
|
301
|
|
|
array($obj2, $obj2a), |
|
302
|
|
|
'Contains child elements (in correct nesting)' |
|
303
|
|
|
); |
|
304
|
|
|
$this->assertTreeContains( |
|
305
|
|
|
$html, |
|
306
|
|
|
array($obj2, $obj2a, $obj2aa), |
|
307
|
|
|
'Contains grandchild elements (in correct nesting)' |
|
308
|
|
|
); |
|
309
|
|
|
} |
|
310
|
|
|
|
|
311
|
|
|
public function testGetChildrenAsULMinNodeCount() |
|
312
|
|
|
{ |
|
313
|
|
|
$obj1 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj1'); |
|
314
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
315
|
|
|
$obj2a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a'); |
|
316
|
|
|
|
|
317
|
|
|
// Set low enough that it should be fulfilled by root only elements |
|
318
|
|
|
$nodeCountThreshold = 3; |
|
319
|
|
|
|
|
320
|
|
|
$root = new HierarchyTest\TestObject(); |
|
321
|
|
|
$root->markPartialTree($nodeCountThreshold); |
|
322
|
|
|
$html = $root->getChildrenAsUL( |
|
323
|
|
|
"", |
|
324
|
|
|
'"<li id=\"" . $child->ID . "\">" . $child->Title', |
|
325
|
|
|
null, |
|
326
|
|
|
false, |
|
327
|
|
|
"AllChildrenIncludingDeleted", |
|
328
|
|
|
"numChildren", |
|
329
|
|
|
true, |
|
330
|
|
|
$nodeCountThreshold |
|
331
|
|
|
); |
|
332
|
|
|
$this->assertTreeContains( |
|
333
|
|
|
$html, |
|
334
|
|
|
array($obj1), |
|
335
|
|
|
'Contains root elements' |
|
336
|
|
|
); |
|
337
|
|
|
$this->assertTreeContains( |
|
338
|
|
|
$html, |
|
339
|
|
|
array($obj2), |
|
340
|
|
|
'Contains root elements' |
|
341
|
|
|
); |
|
342
|
|
|
$this->assertTreeNotContains( |
|
343
|
|
|
$html, |
|
344
|
|
|
array($obj2, $obj2a), |
|
345
|
|
|
'Does not contains child elements because they exceed minNodeCount' |
|
346
|
|
|
); |
|
347
|
|
|
} |
|
348
|
|
|
|
|
349
|
|
|
public function testGetChildrenAsULMinNodeCountWithMarkToExpose() |
|
350
|
|
|
{ |
|
351
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
352
|
|
|
$obj2a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a'); |
|
353
|
|
|
$obj2aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2aa'); |
|
354
|
|
|
|
|
355
|
|
|
// Set low enough that it should be fulfilled by root only elements |
|
356
|
|
|
$nodeCountThreshold = 3; |
|
357
|
|
|
|
|
358
|
|
|
$root = new HierarchyTest\TestObject(); |
|
359
|
|
|
$root->markPartialTree($nodeCountThreshold); |
|
360
|
|
|
|
|
361
|
|
|
// Mark certain node which should be included regardless of minNodeCount restrictions |
|
362
|
|
|
$root->markToExpose($obj2aa); |
|
363
|
|
|
|
|
364
|
|
|
$html = $root->getChildrenAsUL( |
|
365
|
|
|
"", |
|
366
|
|
|
'"<li id=\"" . $child->ID . "\">" . $child->Title', |
|
367
|
|
|
null, |
|
368
|
|
|
false, |
|
369
|
|
|
"AllChildrenIncludingDeleted", |
|
370
|
|
|
"numChildren", |
|
371
|
|
|
true, |
|
372
|
|
|
$nodeCountThreshold |
|
373
|
|
|
); |
|
374
|
|
|
$this->assertTreeContains( |
|
375
|
|
|
$html, |
|
376
|
|
|
array($obj2), |
|
377
|
|
|
'Contains root elements' |
|
378
|
|
|
); |
|
379
|
|
|
$this->assertTreeContains( |
|
380
|
|
|
$html, |
|
381
|
|
|
array($obj2, $obj2a, $obj2aa), |
|
382
|
|
|
'Does contain marked children nodes regardless of configured threshold' |
|
383
|
|
|
); |
|
384
|
|
|
} |
|
385
|
|
|
|
|
386
|
|
|
public function testGetChildrenAsULMinNodeCountWithFilters() |
|
387
|
|
|
{ |
|
388
|
|
|
$obj1 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj1'); |
|
389
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
390
|
|
|
$obj2a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a'); |
|
391
|
|
|
$obj2aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2aa'); |
|
392
|
|
|
|
|
393
|
|
|
// Set low enough that it should fit all search matches without lazy loading |
|
394
|
|
|
$nodeCountThreshold = 3; |
|
395
|
|
|
|
|
396
|
|
|
$root = new HierarchyTest\TestObject(); |
|
397
|
|
|
|
|
398
|
|
|
// Includes nodes by filter regardless of minNodeCount restrictions |
|
399
|
|
|
$root->setMarkingFilterFunction( |
|
400
|
|
|
function ($record) use ($obj2, $obj2a, $obj2aa) { |
|
401
|
|
|
// Results need to include parent hierarchy, even if we just want to |
|
402
|
|
|
// match the innermost node. |
|
403
|
|
|
return in_array($record->ID, array($obj2->ID, $obj2a->ID, $obj2aa->ID)); |
|
404
|
|
|
} |
|
405
|
|
|
); |
|
406
|
|
|
$root->markPartialTree($nodeCountThreshold); |
|
407
|
|
|
|
|
408
|
|
|
$html = $root->getChildrenAsUL( |
|
409
|
|
|
"", |
|
410
|
|
|
'"<li id=\"" . $child->ID . "\">" . $child->Title', |
|
411
|
|
|
null, |
|
412
|
|
|
true, // limit to marked |
|
413
|
|
|
"AllChildrenIncludingDeleted", |
|
414
|
|
|
"numChildren", |
|
415
|
|
|
true, |
|
416
|
|
|
$nodeCountThreshold |
|
417
|
|
|
); |
|
418
|
|
|
$this->assertTreeNotContains( |
|
419
|
|
|
$html, |
|
420
|
|
|
array($obj1), |
|
421
|
|
|
'Does not contain root elements which dont match the filter' |
|
422
|
|
|
); |
|
423
|
|
|
$this->assertTreeContains( |
|
424
|
|
|
$html, |
|
425
|
|
|
array($obj2, $obj2a, $obj2aa), |
|
426
|
|
|
'Contains non-root elements which match the filter' |
|
427
|
|
|
); |
|
428
|
|
|
} |
|
429
|
|
|
|
|
430
|
|
|
public function testGetChildrenAsULHardLimitsNodes() |
|
431
|
|
|
{ |
|
432
|
|
|
$obj1 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj1'); |
|
433
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
434
|
|
|
$obj2a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a'); |
|
435
|
|
|
$obj2aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2aa'); |
|
436
|
|
|
|
|
437
|
|
|
// Set low enough that it should fit all search matches without lazy loading |
|
438
|
|
|
$nodeCountThreshold = 3; |
|
439
|
|
|
|
|
440
|
|
|
$root = new HierarchyTest\TestObject(); |
|
441
|
|
|
|
|
442
|
|
|
// Includes nodes by filter regardless of minNodeCount restrictions |
|
443
|
|
|
$root->setMarkingFilterFunction( |
|
444
|
|
|
function ($record) use ($obj2, $obj2a, $obj2aa) { |
|
445
|
|
|
// Results need to include parent hierarchy, even if we just want to |
|
446
|
|
|
// match the innermost node. |
|
447
|
|
|
return in_array($record->ID, array($obj2->ID, $obj2a->ID, $obj2aa->ID)); |
|
448
|
|
|
} |
|
449
|
|
|
); |
|
450
|
|
|
$root->markPartialTree($nodeCountThreshold); |
|
451
|
|
|
|
|
452
|
|
|
$html = $root->getChildrenAsUL( |
|
453
|
|
|
"", |
|
454
|
|
|
'"<li id=\"" . $child->ID . "\">" . $child->Title', |
|
455
|
|
|
null, |
|
456
|
|
|
true, // limit to marked |
|
457
|
|
|
"AllChildrenIncludingDeleted", |
|
458
|
|
|
"numChildren", |
|
459
|
|
|
true, |
|
460
|
|
|
$nodeCountThreshold |
|
461
|
|
|
); |
|
462
|
|
|
$this->assertTreeNotContains( |
|
463
|
|
|
$html, |
|
464
|
|
|
array($obj1), |
|
465
|
|
|
'Does not contain root elements which dont match the filter' |
|
466
|
|
|
); |
|
467
|
|
|
$this->assertTreeContains( |
|
468
|
|
|
$html, |
|
469
|
|
|
array($obj2, $obj2a, $obj2aa), |
|
470
|
|
|
'Contains non-root elements which match the filter' |
|
471
|
|
|
); |
|
472
|
|
|
} |
|
473
|
|
|
|
|
474
|
|
|
public function testGetChildrenAsULNodeThresholdLeaf() |
|
475
|
|
|
{ |
|
476
|
|
|
$obj1 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj1'); |
|
477
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
478
|
|
|
$obj2a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a'); |
|
479
|
|
|
$obj3 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj3'); |
|
480
|
|
|
$obj3a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj3a'); |
|
481
|
|
|
|
|
482
|
|
|
$nodeCountThreshold = 99; |
|
483
|
|
|
|
|
484
|
|
|
$root = new HierarchyTest\TestObject(); |
|
485
|
|
|
$root->markPartialTree($nodeCountThreshold); |
|
486
|
|
|
$nodeCountCallback = function ($parent, $numChildren) { |
|
487
|
|
|
// Set low enough that it the fixture structure should exceed it |
|
488
|
|
|
if ($parent->ID && $numChildren > 2) { |
|
489
|
|
|
return '<span class="exceeded">Exceeded!</span>'; |
|
490
|
|
|
} |
|
491
|
|
|
}; |
|
492
|
|
|
|
|
493
|
|
|
$html = $root->getChildrenAsUL( |
|
494
|
|
|
"", |
|
495
|
|
|
'"<li id=\"" . $child->ID . "\">" . $child->Title', |
|
496
|
|
|
null, |
|
497
|
|
|
true, // limit to marked |
|
498
|
|
|
"AllChildrenIncludingDeleted", |
|
499
|
|
|
"numChildren", |
|
500
|
|
|
true, |
|
501
|
|
|
$nodeCountThreshold, |
|
502
|
|
|
$nodeCountCallback |
|
503
|
|
|
); |
|
504
|
|
|
$this->assertTreeContains( |
|
505
|
|
|
$html, |
|
506
|
|
|
array($obj1), |
|
507
|
|
|
'Does contain root elements regardless of count' |
|
508
|
|
|
); |
|
509
|
|
|
$this->assertTreeContains( |
|
510
|
|
|
$html, |
|
511
|
|
|
array($obj3), |
|
512
|
|
|
'Does contain root elements regardless of count' |
|
513
|
|
|
); |
|
514
|
|
|
$this->assertTreeContains( |
|
515
|
|
|
$html, |
|
516
|
|
|
array($obj2, $obj2a), |
|
517
|
|
|
'Contains children which do not exceed threshold' |
|
518
|
|
|
); |
|
519
|
|
|
$this->assertTreeNotContains( |
|
520
|
|
|
$html, |
|
521
|
|
|
array($obj3, $obj3a), |
|
522
|
|
|
'Does not contain children which exceed threshold' |
|
523
|
|
|
); |
|
524
|
|
|
} |
|
525
|
|
|
|
|
526
|
|
|
/** |
|
527
|
|
|
* This test checks that deleted ('archived') child pages don't set a css class on the parent |
|
528
|
|
|
* node that makes it look like it has children |
|
529
|
|
|
*/ |
|
530
|
|
|
public function testGetChildrenAsULNodeDeletedOnLive() |
|
531
|
|
|
{ |
|
532
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
533
|
|
|
$obj2a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a'); |
|
534
|
|
|
$obj2aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2aa'); |
|
535
|
|
|
$obj2ab = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2b'); |
|
536
|
|
|
|
|
537
|
|
|
// delete all children under obj2 |
|
538
|
|
|
$obj2a->delete(); |
|
539
|
|
|
$obj2aa->delete(); |
|
540
|
|
|
$obj2ab->delete(); |
|
541
|
|
|
// Don't pre-load all children |
|
542
|
|
|
$nodeCountThreshold = 1; |
|
543
|
|
|
|
|
544
|
|
|
$childrenMethod = 'AllChildren'; |
|
545
|
|
|
$numChildrenMethod = 'numChildren'; |
|
546
|
|
|
|
|
547
|
|
|
$root = new HierarchyTest\TestObject(); |
|
548
|
|
|
$root->markPartialTree($nodeCountThreshold, null, $childrenMethod, $numChildrenMethod); |
|
549
|
|
|
|
|
550
|
|
|
// As in LeftAndMain::getSiteTreeFor() but simpler and more to the point for testing purposes |
|
551
|
|
|
$titleFn = function (&$child, $numChildrenMethod = "") { |
|
552
|
|
|
return '<li class="' . $child->markingClasses($numChildrenMethod). |
|
553
|
|
|
'" id="' . $child->ID . '">"' . $child->Title; |
|
554
|
|
|
}; |
|
555
|
|
|
|
|
556
|
|
|
$html = $root->getChildrenAsUL( |
|
557
|
|
|
"", |
|
558
|
|
|
$titleFn, |
|
559
|
|
|
null, |
|
560
|
|
|
true, // limit to marked |
|
561
|
|
|
$childrenMethod, |
|
562
|
|
|
$numChildrenMethod, |
|
563
|
|
|
true, |
|
564
|
|
|
$nodeCountThreshold |
|
565
|
|
|
); |
|
566
|
|
|
|
|
567
|
|
|
// Get the class attribute from the $obj2 node in the sitetree, class 'jstree-leaf' means it's a leaf node |
|
568
|
|
|
$nodeClass = $this->getNodeClassFromTree($html, $obj2); |
|
569
|
|
|
$this->assertEquals('jstree-leaf closed', $nodeClass, 'object2 should not have children in the sitetree'); |
|
570
|
|
|
} |
|
571
|
|
|
|
|
572
|
|
|
/** |
|
573
|
|
|
* This test checks that deleted ('archived') child pages _do_ set a css class on the parent |
|
574
|
|
|
* node that makes it look like it has children when getting all children including deleted |
|
575
|
|
|
*/ |
|
576
|
|
|
public function testGetChildrenAsULNodeDeletedOnStage() |
|
577
|
|
|
{ |
|
578
|
|
|
$obj2 = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2'); |
|
579
|
|
|
$obj2a = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2a'); |
|
580
|
|
|
$obj2aa = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2aa'); |
|
581
|
|
|
$obj2ab = $this->objFromFixture(HierarchyTest\TestObject::class, 'obj2b'); |
|
582
|
|
|
|
|
583
|
|
|
// delete all children under obj2 |
|
584
|
|
|
$obj2a->delete(); |
|
585
|
|
|
$obj2aa->delete(); |
|
586
|
|
|
$obj2ab->delete(); |
|
587
|
|
|
// Don't pre-load all children |
|
588
|
|
|
$nodeCountThreshold = 1; |
|
589
|
|
|
|
|
590
|
|
|
$childrenMethod = 'AllChildrenIncludingDeleted'; |
|
591
|
|
|
$numChildrenMethod = 'numHistoricalChildren'; |
|
592
|
|
|
|
|
593
|
|
|
$root = new HierarchyTest\TestObject(); |
|
594
|
|
|
$root->markPartialTree($nodeCountThreshold, null, $childrenMethod, $numChildrenMethod); |
|
595
|
|
|
|
|
596
|
|
|
// As in LeftAndMain::getSiteTreeFor() but simpler and more to the point for testing purposes |
|
597
|
|
|
$titleFn = function (&$child, $numChildrenMethod = "") { |
|
598
|
|
|
return '<li class="' . $child->markingClasses($numChildrenMethod). |
|
599
|
|
|
'" id="' . $child->ID . '">"' . $child->Title; |
|
600
|
|
|
}; |
|
601
|
|
|
|
|
602
|
|
|
$html = $root->getChildrenAsUL( |
|
603
|
|
|
"", |
|
604
|
|
|
$titleFn, |
|
605
|
|
|
null, |
|
606
|
|
|
true, // limit to marked |
|
607
|
|
|
$childrenMethod, |
|
608
|
|
|
$numChildrenMethod, |
|
609
|
|
|
true, |
|
610
|
|
|
$nodeCountThreshold |
|
611
|
|
|
); |
|
612
|
|
|
|
|
613
|
|
|
// Get the class attribute from the $obj2 node in the sitetree |
|
614
|
|
|
$nodeClass = $this->getNodeClassFromTree($html, $obj2); |
|
615
|
|
|
// Object2 can now be expanded |
|
616
|
|
|
$this->assertEquals('unexpanded jstree-closed closed', $nodeClass, 'obj2 should have children in the sitetree'); |
|
617
|
|
|
} |
|
618
|
|
|
|
|
619
|
|
|
public function testNoHideFromHeirarchy() |
|
620
|
|
|
{ |
|
621
|
|
|
$obj4 = $this->objFromFixture(HierarchyTest\HideTestObject::class, 'obj4'); |
|
622
|
|
|
$obj4->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); |
|
623
|
|
|
|
|
624
|
|
|
foreach ($obj4->stageChildren() as $child) { |
|
625
|
|
|
$child->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); |
|
626
|
|
|
} |
|
627
|
|
|
$this->assertEquals($obj4->stageChildren()->Count(), 2); |
|
628
|
|
|
$this->assertEquals($obj4->liveChildren()->Count(), 2); |
|
629
|
|
|
} |
|
630
|
|
|
|
|
631
|
|
|
public function testHideFromHeirarchy() |
|
632
|
|
|
{ |
|
633
|
|
|
HierarchyTest\HideTestObject::config()->update( |
|
634
|
|
|
'hide_from_hierarchy', |
|
635
|
|
|
[ |
|
636
|
|
|
HierarchyTest\HideTestSubObject::class, |
|
637
|
|
|
] |
|
638
|
|
|
); |
|
639
|
|
|
$obj4 = $this->objFromFixture(HierarchyTest\HideTestObject::class, 'obj4'); |
|
640
|
|
|
$obj4->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); |
|
641
|
|
|
|
|
642
|
|
|
// load without using stage children otherwise it'll bbe filtered before it's publish |
|
643
|
|
|
// we need to publish all of them, and expect liveChildren to return some. |
|
644
|
|
|
$children = HierarchyTest\HideTestObject::get() |
|
645
|
|
|
->filter('ParentID', (int)$obj4->ID) |
|
646
|
|
|
->exclude('ID', (int)$obj4->ID); |
|
647
|
|
|
|
|
648
|
|
|
foreach ($children as $child) { |
|
649
|
|
|
$child->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); |
|
650
|
|
|
} |
|
651
|
|
|
$this->assertEquals($obj4->stageChildren()->Count(), 1); |
|
652
|
|
|
$this->assertEquals($obj4->liveChildren()->Count(), 1); |
|
653
|
|
|
} |
|
654
|
|
|
|
|
655
|
|
|
/** |
|
656
|
|
|
* @param String $html [description] |
|
657
|
|
|
* @param array $nodes Breadcrumb path as array |
|
658
|
|
|
* @param String $message |
|
659
|
|
|
*/ |
|
660
|
|
|
protected function assertTreeContains($html, $nodes, $message = null) |
|
661
|
|
|
{ |
|
662
|
|
|
$parser = new CSSContentParser($html); |
|
663
|
|
|
$xpath = '/'; |
|
664
|
|
|
foreach ($nodes as $node) { |
|
665
|
|
|
$xpath .= '/ul/li[@id="' . $node->ID . '"]'; |
|
666
|
|
|
} |
|
667
|
|
|
$match = $parser->getByXpath($xpath); |
|
668
|
|
|
self::assertThat((bool)$match, self::isTrue(), $message); |
|
669
|
|
|
} |
|
670
|
|
|
|
|
671
|
|
|
/** |
|
672
|
|
|
* @param String $html [description] |
|
673
|
|
|
* @param array $nodes Breadcrumb path as array |
|
674
|
|
|
* @param String $message |
|
675
|
|
|
*/ |
|
676
|
|
|
protected function assertTreeNotContains($html, $nodes, $message = null) |
|
677
|
|
|
{ |
|
678
|
|
|
$parser = new CSSContentParser($html); |
|
679
|
|
|
$xpath = '/'; |
|
680
|
|
|
foreach ($nodes as $node) { |
|
681
|
|
|
$xpath .= '/ul/li[@id="' . $node->ID . '"]'; |
|
682
|
|
|
} |
|
683
|
|
|
$match = $parser->getByXpath($xpath); |
|
684
|
|
|
self::assertThat((bool)$match, self::isFalse(), $message); |
|
685
|
|
|
} |
|
686
|
|
|
|
|
687
|
|
|
/** |
|
688
|
|
|
* Get the HTML class attribute from a node in the sitetree |
|
689
|
|
|
* |
|
690
|
|
|
* @param $html |
|
691
|
|
|
* @param $node |
|
692
|
|
|
* @return string |
|
693
|
|
|
*/ |
|
694
|
|
|
protected function getNodeClassFromTree($html, $node) |
|
695
|
|
|
{ |
|
696
|
|
|
$parser = new CSSContentParser($html); |
|
697
|
|
|
$xpath = '//ul/li[@id="' . $node->ID . '"]'; |
|
698
|
|
|
$object = $parser->getByXpath($xpath); |
|
699
|
|
|
|
|
700
|
|
|
foreach ($object[0]->attributes() as $key => $attr) { |
|
701
|
|
|
if ($key == 'class') { |
|
702
|
|
|
return (string)$attr; |
|
703
|
|
|
} |
|
704
|
|
|
} |
|
705
|
|
|
return ''; |
|
706
|
|
|
} |
|
707
|
|
|
} |
|
708
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.