Completed
Push — correct-classname-values ( f9b487 )
by Sam
08:29
created

testMarkChildrenDoesntUnmarkPreviouslyMarked()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 43
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 13
c 1
b 1
f 0
nc 1
nop 0
dl 0
loc 43
rs 8.8571
1
<?php
2
3
use SilverStripe\ORM\ValidationException;
4
use SilverStripe\ORM\Versioning\Versioned;
5
use SilverStripe\ORM\DataObject;
6
7
class HierarchyTest extends SapphireTest {
8
9
	protected static $fixture_file = 'HierarchyTest.yml';
10
11
	protected $requiredExtensions = array(
12
		'HierarchyTest_Object' => array('SilverStripe\\ORM\\Hierarchy\\Hierarchy', 'SilverStripe\\ORM\\Versioning\\Versioned'),
13
		'HierarchyHideTest_Object' => array('SilverStripe\\ORM\\Hierarchy\\Hierarchy', 'SilverStripe\\ORM\\Versioning\\Versioned'),
14
	);
15
16
	protected $extraDataObjects = array(
17
		'HierarchyTest_Object',
18
		'HierarchyHideTest_Object'
19
	);
20
21
	/**
22
	 * Test the Hierarchy prevents infinite loops.
23
	 */
24
	public function testPreventLoop() {
25
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
26
		$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
27
28
		$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...
29
		try {
30
			$obj2->write();
31
		}
32
		catch (ValidationException $e) {
33
			$this->assertContains('Infinite loop found within the "HierarchyTest_Object" hierarchy', $e->getMessage());
34
			return;
35
		}
36
37
		$this->fail('Failed to prevent infinite loop in hierarchy.');
38
	}
39
40
	/**
41
	 * Test Hierarchy::AllHistoricalChildren().
42
	 */
43
	public function testAllHistoricalChildren() {
44
		// Delete some objs
45
		$this->objFromFixture('HierarchyTest_Object', 'obj2b')->delete();
46
		$this->objFromFixture('HierarchyTest_Object', 'obj3a')->delete();
47
		$this->objFromFixture('HierarchyTest_Object', 'obj3')->delete();
48
49
		// Check that obj1-3 appear at the top level of the AllHistoricalChildren tree
50
		$this->assertEquals(array("Obj 1", "Obj 2", "Obj 3"),
51
			singleton('HierarchyTest_Object')->AllHistoricalChildren()->column('Title'));
52
53
		// Check numHistoricalChildren
54
		$this->assertEquals(3, singleton('HierarchyTest_Object')->numHistoricalChildren());
55
56
		// Check that both obj 2 children are returned
57
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
58
		$this->assertEquals(array("Obj 2a", "Obj 2b"),
59
			$obj2->AllHistoricalChildren()->column('Title'));
60
61
		// Check numHistoricalChildren
62
		$this->assertEquals(2, $obj2->numHistoricalChildren());
63
64
65
		// Obj 3 has been deleted; let's bring it back from the grave
66
		$obj3 = Versioned::get_including_deleted("HierarchyTest_Object", "\"Title\" = 'Obj 3'")->First();
67
68
		// Check that all obj 3 children are returned
69
		$this->assertEquals(array("Obj 3a", "Obj 3b", "Obj 3c", "Obj 3d"),
70
			$obj3->AllHistoricalChildren()->column('Title'));
71
72
		// Check numHistoricalChildren
73
		$this->assertEquals(4, $obj3->numHistoricalChildren());
74
75
	}
76
77
	/**
78
	 * Test that you can call Hierarchy::markExpanded/Unexpanded/Open() on a obj, and that
79
	 * calling Hierarchy::isMarked() on a different instance of that object will return true.
80
	 */
81
	public function testItemMarkingIsntRestrictedToSpecificInstance() {
82
		// Mark a few objs
83
		$this->objFromFixture('HierarchyTest_Object', 'obj2')->markExpanded();
84
		$this->objFromFixture('HierarchyTest_Object', 'obj2a')->markExpanded();
85
		$this->objFromFixture('HierarchyTest_Object', 'obj2b')->markExpanded();
86
		$this->objFromFixture('HierarchyTest_Object', 'obj3')->markUnexpanded();
87
88
		// Query some objs in a different context and check their m
89
		$objs = DataObject::get("HierarchyTest_Object", '', '"ID" ASC');
90
		$marked = $expanded = array();
91
		foreach($objs as $obj) {
92
			if($obj->isMarked()) $marked[] = $obj->Title;
93
			if($obj->isExpanded()) $expanded[] = $obj->Title;
94
		}
95
96
		$this->assertEquals(array('Obj 2', 'Obj 3', 'Obj 2a', 'Obj 2b'), $marked);
97
		$this->assertEquals(array('Obj 2', 'Obj 2a', 'Obj 2b'), $expanded);
98
	}
99
100
	public function testNumChildren() {
101
		$this->assertEquals($this->objFromFixture('HierarchyTest_Object', 'obj1')->numChildren(), 0);
102
		$this->assertEquals($this->objFromFixture('HierarchyTest_Object', 'obj2')->numChildren(), 2);
103
		$this->assertEquals($this->objFromFixture('HierarchyTest_Object', 'obj3')->numChildren(), 4);
104
		$this->assertEquals($this->objFromFixture('HierarchyTest_Object', 'obj2a')->numChildren(), 2);
105
		$this->assertEquals($this->objFromFixture('HierarchyTest_Object', 'obj2b')->numChildren(), 0);
106
		$this->assertEquals($this->objFromFixture('HierarchyTest_Object', 'obj3a')->numChildren(), 2);
107
		$this->assertEquals($this->objFromFixture('HierarchyTest_Object', 'obj3d')->numChildren(), 0);
108
109
		$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
110
		$this->assertEquals($obj1->numChildren(), 0);
111
		$obj1Child1 = new HierarchyTest_Object();
112
		$obj1Child1->ParentID = $obj1->ID;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<HierarchyTest_Object>. 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...
113
		$obj1Child1->write();
114
		$this->assertEquals($obj1->numChildren(false), 1,
115
			'numChildren() caching can be disabled through method parameter'
116
		);
117
		$obj1Child2 = new HierarchyTest_Object();
118
		$obj1Child2->ParentID = $obj1->ID;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<HierarchyTest_Object>. 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...
119
		$obj1Child2->write();
120
		$obj1->flushCache();
121
		$this->assertEquals($obj1->numChildren(), 2,
122
			'numChildren() caching can be disabled by flushCache()'
123
		);
124
	}
125
126
	public function testLoadDescendantIDListIntoArray() {
127
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
128
		$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
129
		$obj2b = $this->objFromFixture('HierarchyTest_Object', 'obj2b');
130
		$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
131
		$obj2ab = $this->objFromFixture('HierarchyTest_Object', 'obj2ab');
132
133
		$obj2IdList = $obj2->getDescendantIDList();
134
		$obj2aIdList = $obj2a->getDescendantIDList();
135
136
		$this->assertContains($obj2a->ID, $obj2IdList);
137
		$this->assertContains($obj2b->ID, $obj2IdList);
138
		$this->assertContains($obj2aa->ID, $obj2IdList);
139
		$this->assertContains($obj2ab->ID, $obj2IdList);
140
		$this->assertEquals(4, count($obj2IdList));
141
142
		$this->assertContains($obj2aa->ID, $obj2aIdList);
143
		$this->assertContains($obj2ab->ID, $obj2aIdList);
144
		$this->assertEquals(2, count($obj2aIdList));
145
	}
146
147
	/**
148
	 * The "only deleted from stage" argument to liveChildren() should exclude
149
	 * any page that has been moved to another location on the stage site
150
	 */
151
	public function testLiveChildrenOnlyDeletedFromStage() {
152
		$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
153
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
154
		$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
155
		$obj2b = $this->objFromFixture('HierarchyTest_Object', 'obj2b');
156
157
		// Get a published set of objects for our fixture
158
		$obj1->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
159
		$obj2->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
160
		$obj2a->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
161
		$obj2b->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
162
163
		// Then delete 2a from stage and move 2b to a sub-node of 1.
164
		$obj2a->delete();
165
		$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...
166
		$obj2b->write();
167
168
		// Get live children, excluding pages that have been moved on the stage site
169
		$children = $obj2->liveChildren(true, true)->column("Title");
170
171
		// 2a has been deleted from stage and should be shown
172
		$this->assertContains("Obj 2a", $children);
173
174
		// 2b has merely been moved to a different parent and so shouldn't be shown
175
		$this->assertNotContains("Obj 2b", $children);
176
	}
177
178
	public function testBreadcrumbs() {
179
		$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
180
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
0 ignored issues
show
Unused Code introduced by
$obj2 is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
181
		$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
182
		$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
183
184
		$this->assertEquals('Obj 1', $obj1->getBreadcrumbs());
185
		$this->assertEquals('Obj 2 &raquo; Obj 2a', $obj2a->getBreadcrumbs());
186
		$this->assertEquals('Obj 2 &raquo; Obj 2a &raquo; Obj 2aa', $obj2aa->getBreadcrumbs());
187
	}
188
189
	/**
190
	 * @covers Hierarchy::markChildren()
191
	 */
192
	public function testMarkChildrenDoesntUnmarkPreviouslyMarked() {
193
		$obj3 = $this->objFromFixture('HierarchyTest_Object', 'obj3');
194
		$obj3aa = $this->objFromFixture('HierarchyTest_Object', 'obj3aa');
195
		$obj3ba = $this->objFromFixture('HierarchyTest_Object', 'obj3ba');
196
		$obj3ca = $this->objFromFixture('HierarchyTest_Object', 'obj3ca');
197
198
		$obj3->markPartialTree();
199
		$obj3->markToExpose($obj3aa);
200
		$obj3->markToExpose($obj3ba);
201
		$obj3->markToExpose($obj3ca);
202
203
		$expected = <<<EOT
204
<ul>
205
<li>Obj 3a
206
<ul>
207
<li>Obj 3aa
208
</li>
209
<li>Obj 3ab
210
</li>
211
</ul>
212
</li>
213
<li>Obj 3b
214
<ul>
215
<li>Obj 3ba
216
</li>
217
<li>Obj 3bb
218
</li>
219
</ul>
220
</li>
221
<li>Obj 3c
222
<ul>
223
<li>Obj 3c
224
</li>
225
</ul>
226
</li>
227
<li>Obj 3d
228
</li>
229
</ul>
230
231
EOT;
232
233
		$this->assertSame($expected, $obj3->getChildrenAsUL());
234
	}
235
236
	public function testGetChildrenAsUL() {
237
		$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
0 ignored issues
show
Unused Code introduced by
$obj1 is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
238
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
239
		$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
240
		$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
241
242
		$nodeCountThreshold = 30;
243
244
		$root = new HierarchyTest_Object();
245
		$root->markPartialTree($nodeCountThreshold);
246
		$html = $root->getChildrenAsUL(
247
			"",
248
			'"<li id=\"" . $child->ID . "\">" . $child->Title',
249
			null,
250
			false,
251
			"AllChildrenIncludingDeleted",
252
			"numChildren",
253
			true,  // rootCall
254
			$nodeCountThreshold
255
		);
256
		$this->assertTreeContains($html, array($obj2),
257
			'Contains root elements'
258
		);
259
		$this->assertTreeContains($html, array($obj2, $obj2a),
260
			'Contains child elements (in correct nesting)'
261
		);
262
		$this->assertTreeContains($html, array($obj2, $obj2a, $obj2aa),
263
			'Contains grandchild elements (in correct nesting)'
264
		);
265
	}
266
267 View Code Duplication
	public function testGetChildrenAsULMinNodeCount() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
268
		$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
269
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
270
		$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
271
272
		// Set low enough that it should be fulfilled by root only elements
273
		$nodeCountThreshold = 3;
274
275
		$root = new HierarchyTest_Object();
276
		$root->markPartialTree($nodeCountThreshold);
277
		$html = $root->getChildrenAsUL(
278
			"",
279
			'"<li id=\"" . $child->ID . "\">" . $child->Title',
280
			null,
281
			false,
282
			"AllChildrenIncludingDeleted",
283
			"numChildren",
284
			true,
285
			$nodeCountThreshold
286
		);
287
		$this->assertTreeContains($html, array($obj1),
288
			'Contains root elements'
289
		);
290
		$this->assertTreeContains($html, array($obj2),
291
			'Contains root elements'
292
		);
293
		$this->assertTreeNotContains($html, array($obj2, $obj2a),
294
			'Does not contains child elements because they exceed minNodeCount'
295
		);
296
	}
297
298 View Code Duplication
	public function testGetChildrenAsULMinNodeCountWithMarkToExpose() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
299
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
300
		$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
301
		$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
302
303
		// Set low enough that it should be fulfilled by root only elements
304
		$nodeCountThreshold = 3;
305
306
		$root = new HierarchyTest_Object();
307
		$root->markPartialTree($nodeCountThreshold);
308
309
		// Mark certain node which should be included regardless of minNodeCount restrictions
310
		$root->markToExpose($obj2aa);
311
312
		$html = $root->getChildrenAsUL(
313
			"",
314
			'"<li id=\"" . $child->ID . "\">" . $child->Title',
315
			null,
316
			false,
317
			"AllChildrenIncludingDeleted",
318
			"numChildren",
319
			true,
320
			$nodeCountThreshold
321
		);
322
		$this->assertTreeContains($html, array($obj2),
323
			'Contains root elements'
324
		);
325
		$this->assertTreeContains($html, array($obj2, $obj2a, $obj2aa),
326
			'Does contain marked children nodes regardless of configured threshold'
327
		);
328
	}
329
330 View Code Duplication
	public function testGetChildrenAsULMinNodeCountWithFilters() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
331
		$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
332
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
333
		$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
334
		$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
335
336
		// Set low enough that it should fit all search matches without lazy loading
337
		$nodeCountThreshold = 3;
338
339
		$root = new HierarchyTest_Object();
340
341
		// Includes nodes by filter regardless of minNodeCount restrictions
342
		$root->setMarkingFilterFunction(function($record) use($obj2, $obj2a, $obj2aa) {
343
			// Results need to include parent hierarchy, even if we just want to
344
			// match the innermost node.
345
			return in_array($record->ID, array($obj2->ID, $obj2a->ID, $obj2aa->ID));
346
		});
347
		$root->markPartialTree($nodeCountThreshold);
348
349
		$html = $root->getChildrenAsUL(
350
			"",
351
			'"<li id=\"" . $child->ID . "\">" . $child->Title',
352
			null,
353
			true, // limit to marked
354
			"AllChildrenIncludingDeleted",
355
			"numChildren",
356
			true,
357
			$nodeCountThreshold
358
		);
359
		$this->assertTreeNotContains($html, array($obj1),
360
			'Does not contain root elements which dont match the filter'
361
		);
362
		$this->assertTreeContains($html, array($obj2, $obj2a, $obj2aa),
363
			'Contains non-root elements which match the filter'
364
		);
365
	}
366
367 View Code Duplication
	public function testGetChildrenAsULHardLimitsNodes() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
368
		$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
369
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
370
		$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
371
		$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
372
373
		// Set low enough that it should fit all search matches without lazy loading
374
		$nodeCountThreshold = 3;
375
376
		$root = new HierarchyTest_Object();
377
378
		// Includes nodes by filter regardless of minNodeCount restrictions
379
		$root->setMarkingFilterFunction(function($record) use($obj2, $obj2a, $obj2aa) {
380
			// Results need to include parent hierarchy, even if we just want to
381
			// match the innermost node.
382
			return in_array($record->ID, array($obj2->ID, $obj2a->ID, $obj2aa->ID));
383
		});
384
		$root->markPartialTree($nodeCountThreshold);
385
386
		$html = $root->getChildrenAsUL(
387
			"",
388
			'"<li id=\"" . $child->ID . "\">" . $child->Title',
389
			null,
390
			true, // limit to marked
391
			"AllChildrenIncludingDeleted",
392
			"numChildren",
393
			true,
394
			$nodeCountThreshold
395
		);
396
		$this->assertTreeNotContains($html, array($obj1),
397
			'Does not contain root elements which dont match the filter'
398
		);
399
		$this->assertTreeContains($html, array($obj2, $obj2a, $obj2aa),
400
			'Contains non-root elements which match the filter'
401
		);
402
	}
403
404
	public function testGetChildrenAsULNodeThresholdLeaf() {
405
		$obj1 = $this->objFromFixture('HierarchyTest_Object', 'obj1');
406
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
407
		$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
408
		$obj3 = $this->objFromFixture('HierarchyTest_Object', 'obj3');
409
		$obj3a = $this->objFromFixture('HierarchyTest_Object', 'obj3a');
410
411
		$nodeCountThreshold = 99;
412
413
		$root = new HierarchyTest_Object();
414
		$root->markPartialTree($nodeCountThreshold);
415
		$nodeCountCallback = function($parent, $numChildren) {
416
			// Set low enough that it the fixture structure should exceed it
417
			if($parent->ID && $numChildren > 2) {
418
				return '<span class="exceeded">Exceeded!</span>';
419
			}
420
		};
421
422
		$html = $root->getChildrenAsUL(
423
			"",
424
			'"<li id=\"" . $child->ID . "\">" . $child->Title',
425
			null,
426
			true, // limit to marked
427
			"AllChildrenIncludingDeleted",
428
			"numChildren",
429
			true,
430
			$nodeCountThreshold,
431
			$nodeCountCallback
432
		);
433
		$this->assertTreeContains($html, array($obj1),
434
			'Does contain root elements regardless of count'
435
		);
436
		$this->assertTreeContains($html, array($obj3),
437
			'Does contain root elements regardless of count'
438
		);
439
		$this->assertTreeContains($html, array($obj2, $obj2a),
440
			'Contains children which do not exceed threshold'
441
		);
442
		$this->assertTreeNotContains($html, array($obj3, $obj3a),
443
			'Does not contain children which exceed threshold'
444
		);
445
	}
446
447
	/**
448
	 * This test checks that deleted ('archived') child pages don't set a css class on the parent
449
	 * node that makes it look like it has children
450
	 */
451 View Code Duplication
	public function testGetChildrenAsULNodeDeletedOnLive() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
452
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
453
		$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
454
		$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
455
		$obj2ab = $this->objFromFixture('HierarchyTest_Object', 'obj2b');
456
457
		// delete all children under obj2
458
		$obj2a->delete();
459
		$obj2aa->delete();
460
		$obj2ab->delete();
461
		// Don't pre-load all children
462
		$nodeCountThreshold = 1;
463
464
		$childrenMethod = 'AllChildren';
465
		$numChildrenMethod = 'numChildren';
466
467
		$root = new HierarchyTest_Object();
468
		$root->markPartialTree($nodeCountThreshold, null, $childrenMethod, $numChildrenMethod);
469
470
		// As in LeftAndMain::getSiteTreeFor() but simpler and more to the point for testing purposes
471
		$titleFn = function(&$child, $numChildrenMethod="") {
472
			return '<li class="' . $child->markingClasses($numChildrenMethod).
473
				'" id="' . $child->ID . '">"' . $child->Title;
474
		};
475
476
		$html = $root->getChildrenAsUL(
477
			"",
478
			$titleFn,
479
			null,
480
			true, // limit to marked
481
			$childrenMethod,
482
			$numChildrenMethod,
483
			true,
484
			$nodeCountThreshold
485
		);
486
487
		// Get the class attribute from the $obj2 node in the sitetree, class 'jstree-leaf' means it's a leaf node
488
		$nodeClass = $this->getNodeClassFromTree($html, $obj2);
489
		$this->assertEquals('jstree-leaf closed', $nodeClass, 'object2 should not have children in the sitetree');
490
	}
491
492
	/**
493
	 * This test checks that deleted ('archived') child pages _do_ set a css class on the parent
494
	 * node that makes it look like it has children when getting all children including deleted
495
	 */
496 View Code Duplication
	public function testGetChildrenAsULNodeDeletedOnStage() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
497
		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
498
		$obj2a = $this->objFromFixture('HierarchyTest_Object', 'obj2a');
499
		$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
500
		$obj2ab = $this->objFromFixture('HierarchyTest_Object', 'obj2b');
501
502
		// delete all children under obj2
503
		$obj2a->delete();
504
		$obj2aa->delete();
505
		$obj2ab->delete();
506
		// Don't pre-load all children
507
		$nodeCountThreshold = 1;
508
509
		$childrenMethod = 'AllChildrenIncludingDeleted';
510
		$numChildrenMethod = 'numHistoricalChildren';
511
512
		$root = new HierarchyTest_Object();
513
		$root->markPartialTree($nodeCountThreshold, null, $childrenMethod, $numChildrenMethod);
514
515
		// As in LeftAndMain::getSiteTreeFor() but simpler and more to the point for testing purposes
516
		$titleFn = function(&$child, $numChildrenMethod="") {
517
			return '<li class="' . $child->markingClasses($numChildrenMethod).
518
				'" id="' . $child->ID . '">"' . $child->Title;
519
		};
520
521
		$html = $root->getChildrenAsUL(
522
			"",
523
			$titleFn,
524
			null,
525
			true, // limit to marked
526
			$childrenMethod,
527
			$numChildrenMethod,
528
			true,
529
			$nodeCountThreshold
530
		);
531
532
		// Get the class attribute from the $obj2 node in the sitetree
533
		$nodeClass = $this->getNodeClassFromTree($html, $obj2);
534
		// Object2 can now be expanded
535
		$this->assertEquals('unexpanded jstree-closed closed', $nodeClass, 'obj2 should have children in the sitetree');
536
	}
537
538
	public function testNoHideFromHeirarchy() {
539
		$obj4 = $this->objFromFixture('HierarchyHideTest_Object', 'obj4');
540
		$obj4->publish("Stage", "Live");
541
542
		foreach($obj4->stageChildren() as $child) {
543
			$child->publish("Stage", "Live");
544
		}
545
		$this->assertEquals($obj4->stageChildren()->Count(), 2);
546
		$this->assertEquals($obj4->liveChildren()->Count(), 2);
547
	}
548
549
	public function testHideFromHeirarchy() {
550
		HierarchyHideTest_Object::config()->hide_from_hierarchy = array('HierarchyHideTest_SubObject');
0 ignored issues
show
Documentation introduced by
The property hide_from_hierarchy does not exist on object<Config_ForClass>. 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...
551
		$obj4 = $this->objFromFixture('HierarchyHideTest_Object', 'obj4');
552
		$obj4->publish("Stage", "Live");
553
554
		// load without using stage children otherwise it'll bbe filtered before it's publish
555
		// we need to publish all of them, and expect liveChildren to return some.
556
		$children = HierarchyHideTest_Object::get()
557
			->filter('ParentID', (int)$obj4->ID)
558
			->exclude('ID', (int)$obj4->ID);
559
560
		foreach($children as $child) {
561
			$child->publish("Stage", "Live");
562
		}
563
		$this->assertEquals($obj4->stageChildren()->Count(), 1);
564
		$this->assertEquals($obj4->liveChildren()->Count(), 1);
565
	}
566
567
	/**
568
	 * @param String $html  [description]
569
	 * @param array $nodes Breadcrumb path as array
570
	 * @param String $message
571
	 */
572 View Code Duplication
	protected function assertTreeContains($html, $nodes, $message = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
573
		$parser = new CSSContentParser($html);
574
		$xpath = '/';
575
		foreach($nodes as $node) $xpath .= '/ul/li[@id="' . $node->ID . '"]';
576
		$match = $parser->getByXpath($xpath);
577
		self::assertThat((bool)$match, self::isTrue(), $message);
578
	}
579
580
	/**
581
	 * @param String $html  [description]
582
	 * @param array $nodes Breadcrumb path as array
583
	 * @param String $message
584
	 */
585 View Code Duplication
	protected function assertTreeNotContains($html, $nodes, $message = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
586
		$parser = new CSSContentParser($html);
587
		$xpath = '/';
588
		foreach($nodes as $node) $xpath .= '/ul/li[@id="' . $node->ID . '"]';
589
		$match = $parser->getByXpath($xpath);
590
		self::assertThat((bool)$match, self::isFalse(), $message);
591
	}
592
593
	/**
594
	 * Get the HTML class attribute from a node in the sitetree
595
	 *
596
	 * @param $html
597
	 * @param $node
598
	 * @return string
599
	 */
600
	protected function getNodeClassFromTree($html, $node) {
601
		$parser = new CSSContentParser($html);
602
		$xpath = '//ul/li[@id="' . $node->ID . '"]';
603
		$object = $parser->getByXpath($xpath);
604
605
		foreach($object[0]->attributes() as $key => $attr) {
606
			if($key == 'class') {
607
				return (string)$attr;
608
			}
609
		}
610
		return '';
611
	}
612
}
613
614 View Code Duplication
class HierarchyTest_Object extends DataObject implements TestOnly {
0 ignored issues
show
Duplication introduced by
This class seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
615
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
616
		'Title' => 'Varchar'
617
	);
618
619
	private static $extensions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
620
		'SilverStripe\\ORM\\Hierarchy\\Hierarchy',
621
		'SilverStripe\\ORM\\Versioning\\Versioned',
622
	);
623
624
	private static $default_sort = 'Title ASC';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
625
626
	public function cmstreeclasses() {
627
		return $this->markingClasses();
628
	}
629
}
630
631 View Code Duplication
class HierarchyHideTest_Object extends DataObject implements TestOnly {
0 ignored issues
show
Duplication introduced by
This class seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
632
	private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
633
		'Title' => 'Varchar'
634
	);
635
636
	private static $extensions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
637
		'SilverStripe\\ORM\\Hierarchy\\Hierarchy',
638
		"SilverStripe\\ORM\\Versioning\\Versioned('Stage', 'Live')",
639
	);
640
641
	public function cmstreeclasses() {
642
		return $this->markingClasses();
643
	}
644
}
645
646
class HierarchyHideTest_SubObject extends HierarchyHideTest_Object {
647
648
}
649