Skip to content

Commit 676b30c

Browse files
authored
Add previous and next sibling functions (#815)
* Add previous and next sibling functions * Remove unused import * Style fixes * Use snake case for method names * Fix method names
1 parent 69ba271 commit 676b30c

File tree

2 files changed

+68
-0
lines changed

2 files changed

+68
-0
lines changed

src/mantle/support/class-html.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,36 @@ public function first_by_selector( string $selector ): static {
152152
return $this->filter( $selector )->first();
153153
}
154154

155+
/**
156+
* Get the nearest previous sibling element.
157+
*
158+
* @param string|null $selector Optional CSS selector to filter the previous siblings.
159+
* @throws \InvalidArgumentException If the current node list is empty.
160+
*/
161+
public function previous_sibling( ?string $selector = null ): static {
162+
if ( ! $this->has_nodes() ) {
163+
throw new \InvalidArgumentException( 'The current node list is empty.' );
164+
}
165+
166+
$previous = $this->previousAll();
167+
return (bool) $selector ? $previous->filter( $selector )->first() : $previous->first();
168+
}
169+
170+
/**
171+
* Get the nearest next sibling element.
172+
*
173+
* @param string|null $selector Optional CSS selector to filter the next siblings.
174+
* @throws \InvalidArgumentException If the current node list is empty.
175+
*/
176+
public function next_sibling( ?string $selector = null ): static {
177+
if ( ! $this->has_nodes() ) {
178+
throw new \InvalidArgumentException( 'The current node list is empty.' );
179+
}
180+
181+
$next = $this->nextAll();
182+
return (bool) $selector ? $next->filter( $selector )->first() : $next->first();
183+
}
184+
155185
/**
156186
* Retrieve all elements matching an XPath expression.
157187
*

tests/Support/HtmlTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,4 +569,42 @@ public function test_element_assertions(): void {
569569
->assertElementExistsByTestId( 'test-item' )
570570
->assertQuerySelectorMissing( '.non-existent-class' );
571571
}
572+
573+
public function test_it_can_get_the_nearest_previous_sibling(): void {
574+
$html = new HTML(self::TEST_CONTENT);
575+
$previous = $html->filter('#test-id')->previous_sibling();
576+
$this->assertInstanceOf(HTML::class, $previous);
577+
$this->assertEquals('div', $previous->nodeName());
578+
$this->assertEquals('test-class', $previous->attr('class'));
579+
$this->assertEquals('Example Div By Class', $previous->text());
580+
}
581+
582+
public function test_it_can_get_the_nearest_next_sibling(): void {
583+
$html = new HTML(self::TEST_CONTENT);
584+
$next = $html->filter('#test-id')->next_sibling();
585+
$this->assertInstanceOf(HTML::class, $next);
586+
$this->assertEquals('ul', $next->nodeName());
587+
}
588+
589+
public function test_it_returns_empty_crawler_for_missing_next_sibling(): void {
590+
$html = new HTML(self::TEST_CONTENT);
591+
$next = $html->filter('ul')->next_sibling();
592+
$this->assertInstanceOf(HTML::class, $next);
593+
$this->assertEquals(0, $next->count());
594+
}
595+
596+
public function test_it_can_get_the_nearest_previous_filtered_sibling(): void {
597+
$html = new HTML(self::TEST_CONTENT);
598+
$previous = $html->filter('#test-id')->previous_sibling('section');
599+
$this->assertInstanceOf(HTML::class, $previous);
600+
$this->assertEquals('section', $previous->nodeName());
601+
}
602+
603+
public function test_it_can_get_the_nearest_next_filtered_sibling(): void {
604+
$html = new HTML(self::TEST_CONTENT);
605+
$next = $html->filter('section')->next_sibling('ul');
606+
$this->assertInstanceOf(HTML::class, $next);
607+
$this->assertEquals('ul', $next->nodeName());
608+
}
609+
572610
}

0 commit comments

Comments
 (0)