Skip to content

Commit 26a085d

Browse files
committed
Tighten up the constraints and take advantage of strict(er) types
1 parent e6d298d commit 26a085d

File tree

6 files changed

+206
-38
lines changed

6 files changed

+206
-38
lines changed

src/Constraints/ContainsSelector.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
use SteveGrunwell\PHPUnit_Markup_Assertions\DOM;
77
use SteveGrunwell\PHPUnit_Markup_Assertions\Selector;
88

9+
/**
10+
* Evaluate whether or not markup contains at least one instance of the given selector.
11+
*/
912
class ContainsSelector extends Constraint
1013
{
1114
/**
@@ -32,16 +35,15 @@ public function toString(): string
3235
}
3336

3437
/**
35-
* Evaluates the constraint for parameter $other. Returns true if the
36-
* constraint is met, false otherwise.
38+
* {@inheritDoc}
3739
*
38-
* @param mixed $other value or object to evaluate
40+
* @param mixed $html The HTML to evaluate.
3941
*
4042
* @return bool
4143
*/
42-
protected function matches($value): bool
44+
protected function matches($html): bool
4345
{
44-
$dom = new DOM($value);
46+
$dom = new DOM($html);
4547

4648
return $dom->countInstancesOfSelector($this->selector) > 0;
4749
}

src/Constraints/ElementContainsString.php

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,23 @@
66
use SteveGrunwell\PHPUnit_Markup_Assertions\DOM;
77
use SteveGrunwell\PHPUnit_Markup_Assertions\Selector;
88

9+
/**
10+
* Evaluate whether or not the element(s) matching the given selector contain a given string.
11+
*/
912
class ElementContainsString extends Constraint
1013
{
14+
/**
15+
* A cache of matches that we have checked against.
16+
*
17+
* @var array<string>
18+
*/
19+
protected $matchingElements = [];
20+
21+
/**
22+
* @var Selector
23+
*/
24+
protected $selector;
25+
1126
/**
1227
* @var bool
1328
*/
@@ -18,11 +33,6 @@ class ElementContainsString extends Constraint
1833
*/
1934
private $needle;
2035

21-
/**
22-
* @var Selector
23-
*/
24-
private $selector;
25-
2636
/**
2737
* @param Selector $selector The query selector.
2838
* @param string $needle The string to search for within the matching element(s).
@@ -44,39 +54,80 @@ public function __construct(Selector $selector, $needle, $ignore_case = false)
4454
public function toString(): string
4555
{
4656
return sprintf(
47-
'contains string %s',
57+
'%s string %s',
58+
count($this->matchingElements) >= 2 ? 'contain' : 'contains',
4859
$this->exporter()->export($this->needle)
4960
);
5061
}
5162

5263
/**
53-
* {@inheritDoc}
64+
* Return additional failure description where needed.
65+
*
66+
* The function can be overridden to provide additional failure
67+
* information like a diff
5468
*
5569
* @param mixed $other evaluated value or object
70+
*/
71+
protected function additionalFailureDescription($other): string
72+
{
73+
if (empty($this->matchingElements)) {
74+
return '';
75+
}
76+
77+
return sprintf(
78+
"%s\n%s",
79+
count($this->matchingElements) >= 2 ? 'Matching elements:' : 'Matching element:',
80+
$this->exportMatchesArray($this->matchingElements)
81+
);
82+
}
83+
84+
/**
85+
* Export an array of DOM matches for a selector.
86+
*
87+
* @param array<string> $matches
5688
*
5789
* @return string
5890
*/
59-
protected function failureDescription($other): string
91+
protected function exportMatchesArray(array $matches): string
6092
{
93+
return '[' . PHP_EOL . ' ' . implode(PHP_EOL . ' ', $matches) . PHP_EOL . ']';
94+
}
95+
96+
/**
97+
* {@inheritDoc}
98+
*
99+
* @param mixed $html The evaluated markup. Will not actually be used, instead replaced with
100+
* {@see $this->matches}.
101+
*
102+
* @return string
103+
*/
104+
protected function failureDescription($html): string
105+
{
106+
if (empty($this->matchingElements)) {
107+
return "any elements match selector '{$this->selector->getValue()}'";
108+
}
109+
110+
$label = count($this->matchingElements) >= 2
111+
? 'any elements matching selector %s %s'
112+
: 'element matching selector %s %s';
113+
61114
return sprintf(
62-
'element with selector %s in %s %s',
115+
$label,
63116
$this->exporter()->export($this->selector->getValue()),
64-
$this->exporter()->export($other),
65117
$this->toString()
66118
);
67119
}
68120

69121
/**
70-
* Evaluates the constraint for parameter $other. Returns true if the
71-
* constraint is met, false otherwise.
122+
* {@inheritDoc}
72123
*
73-
* @param mixed $other value or object to evaluate
124+
* @param mixed $html The HTML to match against.
74125
*
75126
* @return bool
76127
*/
77-
protected function matches($value): bool
128+
protected function matches($html): bool
78129
{
79-
$dom = new DOM($value);
130+
$dom = new DOM($html);
80131
$fn = $this->ignore_case ? 'stripos' : 'strpos';
81132

82133
// Iterate through each matching element and look for the text.
@@ -86,6 +137,9 @@ protected function matches($value): bool
86137
}
87138
}
88139

140+
// Query again to get the outer elements for error reporting.
141+
$this->matchingElements = $dom->getOuterHtml($this->selector);
142+
89143
return false;
90144
}
91145
}

src/Constraints/SelectorCount.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
use SteveGrunwell\PHPUnit_Markup_Assertions\DOM;
77
use SteveGrunwell\PHPUnit_Markup_Assertions\Selector;
88

9+
/**
10+
* Evaluate how many times a selector appears within some markup.
11+
*/
912
class SelectorCount extends Constraint
1013
{
1114
/**
@@ -22,7 +25,7 @@ class SelectorCount extends Constraint
2225
* @param Selector $selector The query selector.
2326
* @param int $count The expected number of matches.
2427
*/
25-
public function __construct(Selector $selector, $count)
28+
public function __construct(Selector $selector, int $count)
2629
{
2730
$this->selector = $selector;
2831
$this->count = $count;
@@ -50,9 +53,9 @@ public function toString(): string
5053
*
5154
* @return bool
5255
*/
53-
protected function matches($value): bool
56+
protected function matches($html): bool
5457
{
55-
$dom = new DOM($value);
58+
$dom = new DOM($html);
5659

5760
return $dom->countInstancesOfSelector($this->selector) === $this->count;
5861
}

tests/Unit/Constraints/ContainsSelectorTest.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
* @testdox Constraints\ContainsSelector
1111
*
1212
* @covers SteveGrunwell\PHPUnit_Markup_Assertions\Constraints\ContainsSelector
13+
*
14+
* @group Constraints
1315
*/
1416
class ContainsSelectorTest extends TestCase
1517
{
@@ -42,12 +44,12 @@ public function it_should_not_find_unmatched_selectors_in_content($selector)
4244
*/
4345
public function it_should_fail_with_a_useful_error_message()
4446
{
45-
$selector = new Selector('p.body');
47+
$selector = new Selector('p');
4648
$html = '<h1>Some Title</h1>';
4749

4850
try {
4951
(new ContainsSelector($selector))->evaluate($html);
50-
} catch (\Exception $e) {
52+
} catch (\Throwable $e) {
5153
$this->assertSame(
5254
"Failed asserting that '{$html}' contains selector '{$selector}'.",
5355
$e->getMessage()
@@ -61,7 +63,7 @@ public function it_should_fail_with_a_useful_error_message()
6163
/**
6264
* Data provider for testAssertContainsSelector().
6365
*
64-
* @return Iterable<string,array<string>>
66+
* @return iterable<string,array<string>>
6567
*/
6668
public function provideSelectorVariants()
6769
{

tests/Unit/Constraints/ElementContainsStringTest.php

Lines changed: 101 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
* @testdox Constraints\ElementContainsString
1111
*
1212
* @covers SteveGrunwell\PHPUnit_Markup_Assertions\Constraints\ElementContainsString
13+
*
14+
* @group Constraints
1315
*/
1416
class ElementContainsStringTest extends TestCase
1517
{
@@ -63,24 +65,116 @@ public function it_should_fail_if_no_match_is_found()
6365
$this->assertFalse($constraint->evaluate($html, '', true));
6466
}
6567

68+
/**
69+
* @test
70+
*
71+
* @dataProvider provideGreetingsInDifferentLanguages
72+
*
73+
* @ticket https://github.com/stevegrunwell/phpunit-markup-assertions/issues/31
74+
*/
75+
public function it_should_be_able_to_handle_various_character_sets($greeting)
76+
{
77+
$constraint = new ElementContainsString(new Selector('h1'), $greeting);
78+
$html = sprintf('<div><h1>%s</h1></div>', $greeting);
79+
80+
$this->assertTrue($constraint->evaluate($html, '', true));
81+
}
82+
83+
/**
84+
* @test
85+
*/
86+
public function it_should_test_against_the_inner_contents_of_the_found_nodes()
87+
{
88+
$constraint = new ElementContainsString(new Selector('p'), 'class');
89+
$html = '<p class="first">First</p><p class="second">Second</p>';
90+
91+
$this->assertFalse(
92+
$constraint->evaluate($html, '', true),
93+
'The string "class" does not appear in either paragraph, and thus should not be matched.'
94+
);
95+
}
96+
6697
/**
6798
* @test
6899
*/
69100
public function it_should_fail_with_a_useful_error_message()
70101
{
71-
$selector = new Selector('p.body');
72102
$html = '<p>Some other string</p>';
103+
$expected = <<<'MSG'
104+
Failed asserting that element matching selector 'p' contains string 'some string'.
105+
Matching element:
106+
[
107+
<p>Some other string</p>
108+
]
109+
MSG;
110+
111+
try {
112+
(new ElementContainsString(new Selector('p'), 'some string'))->evaluate($html);
113+
} catch (\Throwable $e) {
114+
$this->assertSame($expected, $e->getMessage());
115+
return;
116+
}
117+
118+
$this->fail('Did not receive the expected error message.');
119+
}
120+
121+
/**
122+
* @test
123+
*/
124+
public function it_should_include_all_relevant_matches_in_error_messages()
125+
{
126+
$html = '<p>Some other string</p><p>Yet another string</p>';
127+
$expected = <<<'MSG'
128+
Failed asserting that any elements matching selector 'p' contain string 'some string'.
129+
Matching elements:
130+
[
131+
<p>Some other string</p>
132+
<p>Yet another string</p>
133+
]
134+
MSG;
135+
136+
try {
137+
(new ElementContainsString(new Selector('p'), 'some string'))->evaluate($html);
138+
} catch (\Throwable $e) {
139+
$this->assertSame($expected, $e->getMessage());
140+
return;
141+
}
142+
143+
$this->fail('Did not receive the expected error message.');
144+
}
145+
146+
/**
147+
* @test
148+
*/
149+
public function it_should_provide_a_simple_error_message_if_no_selector_matches_are_found()
150+
{
151+
$html = '<p>Some other string</p><p>Yet another string</p>';
152+
$expected = "Failed asserting that any elements match selector 'h1'.";
73153

74154
try {
75-
(new ElementContainsString($selector, 'some string'))->evaluate($html);
76-
} catch (\Exception $e) {
77-
$this->assertSame(
78-
"Failed asserting that element with selector '{$selector}' in '{$html}' contains string 'some string'.",
79-
$e->getMessage()
80-
);
155+
(new ElementContainsString(new Selector('h1'), 'some string'))->evaluate($html);
156+
} catch (\Throwable $e) {
157+
$this->assertSame($expected, $e->getMessage());
81158
return;
82159
}
83160

84161
$this->fail('Did not receive the expected error message.');
85162
}
163+
164+
/**
165+
* Provide a list of strings in various language.
166+
*
167+
* @return Iterable<string,array<string>>
168+
*/
169+
public function provideGreetingsInDifferentLanguages()
170+
{
171+
yield 'Arabic' => ['مرحبا!'];
172+
yield 'Chinese' => ['你好'];
173+
yield 'English' => ['Hello'];
174+
yield 'Hebrew' => ['שלום'];
175+
yield 'Japanese' => ['こんにちは'];
176+
yield 'Korean' => ['안녕하십니까'];
177+
yield 'Punjabi' => ['ਸਤ ਸ੍ਰੀ ਅਕਾਲ'];
178+
yield 'Ukrainian' => ['Привіт'];
179+
}
86180
}

0 commit comments

Comments
 (0)