Completed
Push — master ( a9e1ce...416b3d )
by Sam
22s
created

HierarchyTest::getNodeClassFromTree()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 2
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
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(
0 ignored issues
show
Deprecated Code introduced by
The method PHPUnit_Framework_TestCase::setExpectedException() has been deprecated with message: Method deprecated since Release 5.2.0; use expectException() instead

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.

Loading history...
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;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
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;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\ORM\...erarchyTest\TestObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
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;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\ORM\...erarchyTest\TestObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
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;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
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 &raquo; Obj 2a', $obj2a->getBreadcrumbs());
222
        $this->assertEquals('Obj 2 &raquo; Obj 2a &raquo; 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